##// END OF EJS Templates
prevent transient leaks of file handle by using new helper functions...
Dan Villiom Podlaski Christiansen -
r14168:135e2447 default
parent child Browse files
Show More

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

@@ -1,200 +1,200 b''
1 1 # darcs.py - darcs support for the convert extension
2 2 #
3 3 # Copyright 2007-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 common import NoRepo, checktool, commandline, commit, converter_source
9 9 from mercurial.i18n import _
10 10 from mercurial import util
11 11 import os, shutil, tempfile, re
12 12
13 13 # The naming drift of ElementTree is fun!
14 14
15 15 try:
16 16 from xml.etree.cElementTree import ElementTree, XMLParser
17 17 except ImportError:
18 18 try:
19 19 from xml.etree.ElementTree import ElementTree, XMLParser
20 20 except ImportError:
21 21 try:
22 22 from elementtree.cElementTree import ElementTree, XMLParser
23 23 except ImportError:
24 24 try:
25 25 from elementtree.ElementTree import ElementTree, XMLParser
26 26 except ImportError:
27 27 ElementTree = None
28 28
29 29 class darcs_source(converter_source, commandline):
30 30 def __init__(self, ui, path, rev=None):
31 31 converter_source.__init__(self, ui, path, rev=rev)
32 32 commandline.__init__(self, ui, 'darcs')
33 33
34 34 # check for _darcs, ElementTree so that we can easily skip
35 35 # test-convert-darcs if ElementTree is not around
36 36 if not os.path.exists(os.path.join(path, '_darcs')):
37 37 raise NoRepo(_("%s does not look like a darcs repository") % path)
38 38
39 39 checktool('darcs')
40 40 version = self.run0('--version').splitlines()[0].strip()
41 41 if version < '2.1':
42 42 raise util.Abort(_('darcs version 2.1 or newer needed (found %r)') %
43 43 version)
44 44
45 45 if ElementTree is None:
46 46 raise util.Abort(_("Python ElementTree module is not available"))
47 47
48 48 self.path = os.path.realpath(path)
49 49
50 50 self.lastrev = None
51 51 self.changes = {}
52 52 self.parents = {}
53 53 self.tags = {}
54 54
55 55 # Check darcs repository format
56 56 format = self.format()
57 57 if format:
58 58 if format in ('darcs-1.0', 'hashed'):
59 59 raise NoRepo(_("%s repository format is unsupported, "
60 60 "please upgrade") % format)
61 61 else:
62 62 self.ui.warn(_('failed to detect repository format!'))
63 63
64 64 def before(self):
65 65 self.tmppath = tempfile.mkdtemp(
66 66 prefix='convert-' + os.path.basename(self.path) + '-')
67 67 output, status = self.run('init', repodir=self.tmppath)
68 68 self.checkexit(status)
69 69
70 70 tree = self.xml('changes', xml_output=True, summary=True,
71 71 repodir=self.path)
72 72 tagname = None
73 73 child = None
74 74 for elt in tree.findall('patch'):
75 75 node = elt.get('hash')
76 76 name = elt.findtext('name', '')
77 77 if name.startswith('TAG '):
78 78 tagname = name[4:].strip()
79 79 elif tagname is not None:
80 80 self.tags[tagname] = node
81 81 tagname = None
82 82 self.changes[node] = elt
83 83 self.parents[child] = [node]
84 84 child = node
85 85 self.parents[child] = []
86 86
87 87 def after(self):
88 88 self.ui.debug('cleaning up %s\n' % self.tmppath)
89 89 shutil.rmtree(self.tmppath, ignore_errors=True)
90 90
91 91 def recode(self, s, encoding=None):
92 92 if isinstance(s, unicode):
93 93 # XMLParser returns unicode objects for anything it can't
94 94 # encode into ASCII. We convert them back to str to get
95 95 # recode's normal conversion behavior.
96 96 s = s.encode('latin-1')
97 97 return super(darcs_source, self).recode(s, encoding)
98 98
99 99 def xml(self, cmd, **kwargs):
100 100 # NOTE: darcs is currently encoding agnostic and will print
101 101 # patch metadata byte-for-byte, even in the XML changelog.
102 102 etree = ElementTree()
103 103 # While we are decoding the XML as latin-1 to be as liberal as
104 104 # possible, etree will still raise an exception if any
105 105 # non-printable characters are in the XML changelog.
106 106 parser = XMLParser(encoding='latin-1')
107 107 fp = self._run(cmd, **kwargs)
108 108 etree.parse(fp, parser=parser)
109 109 self.checkexit(fp.close())
110 110 return etree.getroot()
111 111
112 112 def format(self):
113 113 output, status = self.run('show', 'repo', no_files=True,
114 114 repodir=self.path)
115 115 self.checkexit(status)
116 116 m = re.search(r'^\s*Format:\s*(.*)$', output, re.MULTILINE)
117 117 if not m:
118 118 return None
119 119 return ','.join(sorted(f.strip() for f in m.group(1).split(',')))
120 120
121 121 def manifest(self):
122 122 man = []
123 123 output, status = self.run('show', 'files', no_directories=True,
124 124 repodir=self.tmppath)
125 125 self.checkexit(status)
126 126 for line in output.split('\n'):
127 127 path = line[2:]
128 128 if path:
129 129 man.append(path)
130 130 return man
131 131
132 132 def getheads(self):
133 133 return self.parents[None]
134 134
135 135 def getcommit(self, rev):
136 136 elt = self.changes[rev]
137 137 date = util.strdate(elt.get('local_date'), '%a %b %d %H:%M:%S %Z %Y')
138 138 desc = elt.findtext('name') + '\n' + elt.findtext('comment', '')
139 139 # etree can return unicode objects for name, comment, and author,
140 140 # so recode() is used to ensure str objects are emitted.
141 141 return commit(author=self.recode(elt.get('author')),
142 142 date=util.datestr(date),
143 143 desc=self.recode(desc).strip(),
144 144 parents=self.parents[rev])
145 145
146 146 def pull(self, rev):
147 147 output, status = self.run('pull', self.path, all=True,
148 148 match='hash %s' % rev,
149 149 no_test=True, no_posthook=True,
150 150 external_merge='/bin/false',
151 151 repodir=self.tmppath)
152 152 if status:
153 153 if output.find('We have conflicts in') == -1:
154 154 self.checkexit(status, output)
155 155 output, status = self.run('revert', all=True, repodir=self.tmppath)
156 156 self.checkexit(status, output)
157 157
158 158 def getchanges(self, rev):
159 159 copies = {}
160 160 changes = []
161 161 man = None
162 162 for elt in self.changes[rev].find('summary').getchildren():
163 163 if elt.tag in ('add_directory', 'remove_directory'):
164 164 continue
165 165 if elt.tag == 'move':
166 166 if man is None:
167 167 man = self.manifest()
168 168 source, dest = elt.get('from'), elt.get('to')
169 169 if source in man:
170 170 # File move
171 171 changes.append((source, rev))
172 172 changes.append((dest, rev))
173 173 copies[dest] = source
174 174 else:
175 175 # Directory move, deduce file moves from manifest
176 176 source = source + '/'
177 177 for f in man:
178 178 if not f.startswith(source):
179 179 continue
180 180 fdest = dest + '/' + f[len(source):]
181 181 changes.append((f, rev))
182 182 changes.append((fdest, rev))
183 183 copies[fdest] = f
184 184 else:
185 185 changes.append((elt.text.strip(), rev))
186 186 self.pull(rev)
187 187 self.lastrev = rev
188 188 return sorted(changes), copies
189 189
190 190 def getfile(self, name, rev):
191 191 if rev != self.lastrev:
192 192 raise util.Abort(_('internal calling inconsistency'))
193 193 path = os.path.join(self.tmppath, name)
194 data = open(path, 'rb').read()
194 data = util.readfile(path)
195 195 mode = os.lstat(path).st_mode
196 196 mode = (mode & 0111) and 'x' or ''
197 197 return data, mode
198 198
199 199 def gettags(self):
200 200 return self.tags
@@ -1,1175 +1,1175 b''
1 1 # Subversion 1.4/1.5 Python API backend
2 2 #
3 3 # Copyright(C) 2007 Daniel Holth et al
4 4
5 5 import os
6 6 import re
7 7 import sys
8 8 import cPickle as pickle
9 9 import tempfile
10 10 import urllib
11 11 import urllib2
12 12
13 13 from mercurial import strutil, scmutil, util, encoding
14 14 from mercurial.i18n import _
15 15
16 16 # Subversion stuff. Works best with very recent Python SVN bindings
17 17 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
18 18 # these bindings.
19 19
20 20 from cStringIO import StringIO
21 21
22 22 from common import NoRepo, MissingTool, commit, encodeargs, decodeargs
23 23 from common import commandline, converter_source, converter_sink, mapfile
24 24
25 25 try:
26 26 from svn.core import SubversionException, Pool
27 27 import svn
28 28 import svn.client
29 29 import svn.core
30 30 import svn.ra
31 31 import svn.delta
32 32 import transport
33 33 import warnings
34 34 warnings.filterwarnings('ignore',
35 35 module='svn.core',
36 36 category=DeprecationWarning)
37 37
38 38 except ImportError:
39 39 svn = None
40 40
41 41 class SvnPathNotFound(Exception):
42 42 pass
43 43
44 44 def revsplit(rev):
45 45 """Parse a revision string and return (uuid, path, revnum)."""
46 46 url, revnum = rev.rsplit('@', 1)
47 47 parts = url.split('/', 1)
48 48 mod = ''
49 49 if len(parts) > 1:
50 50 mod = '/' + parts[1]
51 51 return parts[0][4:], mod, int(revnum)
52 52
53 53 def geturl(path):
54 54 try:
55 55 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
56 56 except SubversionException:
57 57 pass
58 58 if os.path.isdir(path):
59 59 path = os.path.normpath(os.path.abspath(path))
60 60 if os.name == 'nt':
61 61 path = '/' + util.normpath(path)
62 62 # Module URL is later compared with the repository URL returned
63 63 # by svn API, which is UTF-8.
64 64 path = encoding.tolocal(path)
65 65 return 'file://%s' % urllib.quote(path)
66 66 return path
67 67
68 68 def optrev(number):
69 69 optrev = svn.core.svn_opt_revision_t()
70 70 optrev.kind = svn.core.svn_opt_revision_number
71 71 optrev.value.number = number
72 72 return optrev
73 73
74 74 class changedpath(object):
75 75 def __init__(self, p):
76 76 self.copyfrom_path = p.copyfrom_path
77 77 self.copyfrom_rev = p.copyfrom_rev
78 78 self.action = p.action
79 79
80 80 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
81 81 strict_node_history=False):
82 82 protocol = -1
83 83 def receiver(orig_paths, revnum, author, date, message, pool):
84 84 if orig_paths is not None:
85 85 for k, v in orig_paths.iteritems():
86 86 orig_paths[k] = changedpath(v)
87 87 pickle.dump((orig_paths, revnum, author, date, message),
88 88 fp, protocol)
89 89
90 90 try:
91 91 # Use an ra of our own so that our parent can consume
92 92 # our results without confusing the server.
93 93 t = transport.SvnRaTransport(url=url)
94 94 svn.ra.get_log(t.ra, paths, start, end, limit,
95 95 discover_changed_paths,
96 96 strict_node_history,
97 97 receiver)
98 98 except SubversionException, (inst, num):
99 99 pickle.dump(num, fp, protocol)
100 100 except IOError:
101 101 # Caller may interrupt the iteration
102 102 pickle.dump(None, fp, protocol)
103 103 else:
104 104 pickle.dump(None, fp, protocol)
105 105 fp.close()
106 106 # With large history, cleanup process goes crazy and suddenly
107 107 # consumes *huge* amount of memory. The output file being closed,
108 108 # there is no need for clean termination.
109 109 os._exit(0)
110 110
111 111 def debugsvnlog(ui, **opts):
112 112 """Fetch SVN log in a subprocess and channel them back to parent to
113 113 avoid memory collection issues.
114 114 """
115 115 util.set_binary(sys.stdin)
116 116 util.set_binary(sys.stdout)
117 117 args = decodeargs(sys.stdin.read())
118 118 get_log_child(sys.stdout, *args)
119 119
120 120 class logstream(object):
121 121 """Interruptible revision log iterator."""
122 122 def __init__(self, stdout):
123 123 self._stdout = stdout
124 124
125 125 def __iter__(self):
126 126 while True:
127 127 try:
128 128 entry = pickle.load(self._stdout)
129 129 except EOFError:
130 130 raise util.Abort(_('Mercurial failed to run itself, check'
131 131 ' hg executable is in PATH'))
132 132 try:
133 133 orig_paths, revnum, author, date, message = entry
134 134 except:
135 135 if entry is None:
136 136 break
137 137 raise SubversionException("child raised exception", entry)
138 138 yield entry
139 139
140 140 def close(self):
141 141 if self._stdout:
142 142 self._stdout.close()
143 143 self._stdout = None
144 144
145 145
146 146 # Check to see if the given path is a local Subversion repo. Verify this by
147 147 # looking for several svn-specific files and directories in the given
148 148 # directory.
149 149 def filecheck(ui, path, proto):
150 150 for x in ('locks', 'hooks', 'format', 'db'):
151 151 if not os.path.exists(os.path.join(path, x)):
152 152 return False
153 153 return True
154 154
155 155 # Check to see if a given path is the root of an svn repo over http. We verify
156 156 # this by requesting a version-controlled URL we know can't exist and looking
157 157 # for the svn-specific "not found" XML.
158 158 def httpcheck(ui, path, proto):
159 159 try:
160 160 opener = urllib2.build_opener()
161 161 rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path))
162 162 data = rsp.read()
163 163 except urllib2.HTTPError, inst:
164 164 if inst.code != 404:
165 165 # Except for 404 we cannot know for sure this is not an svn repo
166 166 ui.warn(_('svn: cannot probe remote repository, assume it could '
167 167 'be a subversion repository. Use --source-type if you '
168 168 'know better.\n'))
169 169 return True
170 170 data = inst.fp.read()
171 171 except:
172 172 # Could be urllib2.URLError if the URL is invalid or anything else.
173 173 return False
174 174 return '<m:human-readable errcode="160013">' in data
175 175
176 176 protomap = {'http': httpcheck,
177 177 'https': httpcheck,
178 178 'file': filecheck,
179 179 }
180 180 def issvnurl(ui, url):
181 181 try:
182 182 proto, path = url.split('://', 1)
183 183 if proto == 'file':
184 184 path = urllib.url2pathname(path)
185 185 except ValueError:
186 186 proto = 'file'
187 187 path = os.path.abspath(url)
188 188 if proto == 'file':
189 189 path = path.replace(os.sep, '/')
190 190 check = protomap.get(proto, lambda *args: False)
191 191 while '/' in path:
192 192 if check(ui, path, proto):
193 193 return True
194 194 path = path.rsplit('/', 1)[0]
195 195 return False
196 196
197 197 # SVN conversion code stolen from bzr-svn and tailor
198 198 #
199 199 # Subversion looks like a versioned filesystem, branches structures
200 200 # are defined by conventions and not enforced by the tool. First,
201 201 # we define the potential branches (modules) as "trunk" and "branches"
202 202 # children directories. Revisions are then identified by their
203 203 # module and revision number (and a repository identifier).
204 204 #
205 205 # The revision graph is really a tree (or a forest). By default, a
206 206 # revision parent is the previous revision in the same module. If the
207 207 # module directory is copied/moved from another module then the
208 208 # revision is the module root and its parent the source revision in
209 209 # the parent module. A revision has at most one parent.
210 210 #
211 211 class svn_source(converter_source):
212 212 def __init__(self, ui, url, rev=None):
213 213 super(svn_source, self).__init__(ui, url, rev=rev)
214 214
215 215 if not (url.startswith('svn://') or url.startswith('svn+ssh://') or
216 216 (os.path.exists(url) and
217 217 os.path.exists(os.path.join(url, '.svn'))) or
218 218 issvnurl(ui, url)):
219 219 raise NoRepo(_("%s does not look like a Subversion repository")
220 220 % url)
221 221 if svn is None:
222 222 raise MissingTool(_('Could not load Subversion python bindings'))
223 223
224 224 try:
225 225 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
226 226 if version < (1, 4):
227 227 raise MissingTool(_('Subversion python bindings %d.%d found, '
228 228 '1.4 or later required') % version)
229 229 except AttributeError:
230 230 raise MissingTool(_('Subversion python bindings are too old, 1.4 '
231 231 'or later required'))
232 232
233 233 self.lastrevs = {}
234 234
235 235 latest = None
236 236 try:
237 237 # Support file://path@rev syntax. Useful e.g. to convert
238 238 # deleted branches.
239 239 at = url.rfind('@')
240 240 if at >= 0:
241 241 latest = int(url[at + 1:])
242 242 url = url[:at]
243 243 except ValueError:
244 244 pass
245 245 self.url = geturl(url)
246 246 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
247 247 try:
248 248 self.transport = transport.SvnRaTransport(url=self.url)
249 249 self.ra = self.transport.ra
250 250 self.ctx = self.transport.client
251 251 self.baseurl = svn.ra.get_repos_root(self.ra)
252 252 # Module is either empty or a repository path starting with
253 253 # a slash and not ending with a slash.
254 254 self.module = urllib.unquote(self.url[len(self.baseurl):])
255 255 self.prevmodule = None
256 256 self.rootmodule = self.module
257 257 self.commits = {}
258 258 self.paths = {}
259 259 self.uuid = svn.ra.get_uuid(self.ra)
260 260 except SubversionException:
261 261 ui.traceback()
262 262 raise NoRepo(_("%s does not look like a Subversion repository")
263 263 % self.url)
264 264
265 265 if rev:
266 266 try:
267 267 latest = int(rev)
268 268 except ValueError:
269 269 raise util.Abort(_('svn: revision %s is not an integer') % rev)
270 270
271 271 self.trunkname = self.ui.config('convert', 'svn.trunk', 'trunk').strip('/')
272 272 self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
273 273 try:
274 274 self.startrev = int(self.startrev)
275 275 if self.startrev < 0:
276 276 self.startrev = 0
277 277 except ValueError:
278 278 raise util.Abort(_('svn: start revision %s is not an integer')
279 279 % self.startrev)
280 280
281 281 try:
282 282 self.head = self.latest(self.module, latest)
283 283 except SvnPathNotFound:
284 284 self.head = None
285 285 if not self.head:
286 286 raise util.Abort(_('no revision found in module %s')
287 287 % self.module)
288 288 self.last_changed = self.revnum(self.head)
289 289
290 290 self._changescache = None
291 291
292 292 if os.path.exists(os.path.join(url, '.svn/entries')):
293 293 self.wc = url
294 294 else:
295 295 self.wc = None
296 296 self.convertfp = None
297 297
298 298 def setrevmap(self, revmap):
299 299 lastrevs = {}
300 300 for revid in revmap.iterkeys():
301 301 uuid, module, revnum = revsplit(revid)
302 302 lastrevnum = lastrevs.setdefault(module, revnum)
303 303 if revnum > lastrevnum:
304 304 lastrevs[module] = revnum
305 305 self.lastrevs = lastrevs
306 306
307 307 def exists(self, path, optrev):
308 308 try:
309 309 svn.client.ls(self.url.rstrip('/') + '/' + urllib.quote(path),
310 310 optrev, False, self.ctx)
311 311 return True
312 312 except SubversionException:
313 313 return False
314 314
315 315 def getheads(self):
316 316
317 317 def isdir(path, revnum):
318 318 kind = self._checkpath(path, revnum)
319 319 return kind == svn.core.svn_node_dir
320 320
321 321 def getcfgpath(name, rev):
322 322 cfgpath = self.ui.config('convert', 'svn.' + name)
323 323 if cfgpath is not None and cfgpath.strip() == '':
324 324 return None
325 325 path = (cfgpath or name).strip('/')
326 326 if not self.exists(path, rev):
327 327 if self.module.endswith(path) and name == 'trunk':
328 328 # we are converting from inside this directory
329 329 return None
330 330 if cfgpath:
331 331 raise util.Abort(_('expected %s to be at %r, but not found')
332 332 % (name, path))
333 333 return None
334 334 self.ui.note(_('found %s at %r\n') % (name, path))
335 335 return path
336 336
337 337 rev = optrev(self.last_changed)
338 338 oldmodule = ''
339 339 trunk = getcfgpath('trunk', rev)
340 340 self.tags = getcfgpath('tags', rev)
341 341 branches = getcfgpath('branches', rev)
342 342
343 343 # If the project has a trunk or branches, we will extract heads
344 344 # from them. We keep the project root otherwise.
345 345 if trunk:
346 346 oldmodule = self.module or ''
347 347 self.module += '/' + trunk
348 348 self.head = self.latest(self.module, self.last_changed)
349 349 if not self.head:
350 350 raise util.Abort(_('no revision found in module %s')
351 351 % self.module)
352 352
353 353 # First head in the list is the module's head
354 354 self.heads = [self.head]
355 355 if self.tags is not None:
356 356 self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags'))
357 357
358 358 # Check if branches bring a few more heads to the list
359 359 if branches:
360 360 rpath = self.url.strip('/')
361 361 branchnames = svn.client.ls(rpath + '/' + urllib.quote(branches),
362 362 rev, False, self.ctx)
363 363 for branch in branchnames.keys():
364 364 module = '%s/%s/%s' % (oldmodule, branches, branch)
365 365 if not isdir(module, self.last_changed):
366 366 continue
367 367 brevid = self.latest(module, self.last_changed)
368 368 if not brevid:
369 369 self.ui.note(_('ignoring empty branch %s\n') % branch)
370 370 continue
371 371 self.ui.note(_('found branch %s at %d\n') %
372 372 (branch, self.revnum(brevid)))
373 373 self.heads.append(brevid)
374 374
375 375 if self.startrev and self.heads:
376 376 if len(self.heads) > 1:
377 377 raise util.Abort(_('svn: start revision is not supported '
378 378 'with more than one branch'))
379 379 revnum = self.revnum(self.heads[0])
380 380 if revnum < self.startrev:
381 381 raise util.Abort(
382 382 _('svn: no revision found after start revision %d')
383 383 % self.startrev)
384 384
385 385 return self.heads
386 386
387 387 def getchanges(self, rev):
388 388 if self._changescache and self._changescache[0] == rev:
389 389 return self._changescache[1]
390 390 self._changescache = None
391 391 (paths, parents) = self.paths[rev]
392 392 if parents:
393 393 files, self.removed, copies = self.expandpaths(rev, paths, parents)
394 394 else:
395 395 # Perform a full checkout on roots
396 396 uuid, module, revnum = revsplit(rev)
397 397 entries = svn.client.ls(self.baseurl + urllib.quote(module),
398 398 optrev(revnum), True, self.ctx)
399 399 files = [n for n, e in entries.iteritems()
400 400 if e.kind == svn.core.svn_node_file]
401 401 copies = {}
402 402 self.removed = set()
403 403
404 404 files.sort()
405 405 files = zip(files, [rev] * len(files))
406 406
407 407 # caller caches the result, so free it here to release memory
408 408 del self.paths[rev]
409 409 return (files, copies)
410 410
411 411 def getchangedfiles(self, rev, i):
412 412 changes = self.getchanges(rev)
413 413 self._changescache = (rev, changes)
414 414 return [f[0] for f in changes[0]]
415 415
416 416 def getcommit(self, rev):
417 417 if rev not in self.commits:
418 418 uuid, module, revnum = revsplit(rev)
419 419 self.module = module
420 420 self.reparent(module)
421 421 # We assume that:
422 422 # - requests for revisions after "stop" come from the
423 423 # revision graph backward traversal. Cache all of them
424 424 # down to stop, they will be used eventually.
425 425 # - requests for revisions before "stop" come to get
426 426 # isolated branches parents. Just fetch what is needed.
427 427 stop = self.lastrevs.get(module, 0)
428 428 if revnum < stop:
429 429 stop = revnum + 1
430 430 self._fetch_revisions(revnum, stop)
431 431 commit = self.commits[rev]
432 432 # caller caches the result, so free it here to release memory
433 433 del self.commits[rev]
434 434 return commit
435 435
436 436 def gettags(self):
437 437 tags = {}
438 438 if self.tags is None:
439 439 return tags
440 440
441 441 # svn tags are just a convention, project branches left in a
442 442 # 'tags' directory. There is no other relationship than
443 443 # ancestry, which is expensive to discover and makes them hard
444 444 # to update incrementally. Worse, past revisions may be
445 445 # referenced by tags far away in the future, requiring a deep
446 446 # history traversal on every calculation. Current code
447 447 # performs a single backward traversal, tracking moves within
448 448 # the tags directory (tag renaming) and recording a new tag
449 449 # everytime a project is copied from outside the tags
450 450 # directory. It also lists deleted tags, this behaviour may
451 451 # change in the future.
452 452 pendings = []
453 453 tagspath = self.tags
454 454 start = svn.ra.get_latest_revnum(self.ra)
455 455 stream = self._getlog([self.tags], start, self.startrev)
456 456 try:
457 457 for entry in stream:
458 458 origpaths, revnum, author, date, message = entry
459 459 copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e
460 460 in origpaths.iteritems() if e.copyfrom_path]
461 461 # Apply moves/copies from more specific to general
462 462 copies.sort(reverse=True)
463 463
464 464 srctagspath = tagspath
465 465 if copies and copies[-1][2] == tagspath:
466 466 # Track tags directory moves
467 467 srctagspath = copies.pop()[0]
468 468
469 469 for source, sourcerev, dest in copies:
470 470 if not dest.startswith(tagspath + '/'):
471 471 continue
472 472 for tag in pendings:
473 473 if tag[0].startswith(dest):
474 474 tagpath = source + tag[0][len(dest):]
475 475 tag[:2] = [tagpath, sourcerev]
476 476 break
477 477 else:
478 478 pendings.append([source, sourcerev, dest])
479 479
480 480 # Filter out tags with children coming from different
481 481 # parts of the repository like:
482 482 # /tags/tag.1 (from /trunk:10)
483 483 # /tags/tag.1/foo (from /branches/foo:12)
484 484 # Here/tags/tag.1 discarded as well as its children.
485 485 # It happens with tools like cvs2svn. Such tags cannot
486 486 # be represented in mercurial.
487 487 addeds = dict((p, e.copyfrom_path) for p, e
488 488 in origpaths.iteritems()
489 489 if e.action == 'A' and e.copyfrom_path)
490 490 badroots = set()
491 491 for destroot in addeds:
492 492 for source, sourcerev, dest in pendings:
493 493 if (not dest.startswith(destroot + '/')
494 494 or source.startswith(addeds[destroot] + '/')):
495 495 continue
496 496 badroots.add(destroot)
497 497 break
498 498
499 499 for badroot in badroots:
500 500 pendings = [p for p in pendings if p[2] != badroot
501 501 and not p[2].startswith(badroot + '/')]
502 502
503 503 # Tell tag renamings from tag creations
504 504 remainings = []
505 505 for source, sourcerev, dest in pendings:
506 506 tagname = dest.split('/')[-1]
507 507 if source.startswith(srctagspath):
508 508 remainings.append([source, sourcerev, tagname])
509 509 continue
510 510 if tagname in tags:
511 511 # Keep the latest tag value
512 512 continue
513 513 # From revision may be fake, get one with changes
514 514 try:
515 515 tagid = self.latest(source, sourcerev)
516 516 if tagid and tagname not in tags:
517 517 tags[tagname] = tagid
518 518 except SvnPathNotFound:
519 519 # It happens when we are following directories
520 520 # we assumed were copied with their parents
521 521 # but were really created in the tag
522 522 # directory.
523 523 pass
524 524 pendings = remainings
525 525 tagspath = srctagspath
526 526 finally:
527 527 stream.close()
528 528 return tags
529 529
530 530 def converted(self, rev, destrev):
531 531 if not self.wc:
532 532 return
533 533 if self.convertfp is None:
534 534 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
535 535 'a')
536 536 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
537 537 self.convertfp.flush()
538 538
539 539 def revid(self, revnum, module=None):
540 540 return 'svn:%s%s@%s' % (self.uuid, module or self.module, revnum)
541 541
542 542 def revnum(self, rev):
543 543 return int(rev.split('@')[-1])
544 544
545 545 def latest(self, path, stop=0):
546 546 """Find the latest revid affecting path, up to stop. It may return
547 547 a revision in a different module, since a branch may be moved without
548 548 a change being reported. Return None if computed module does not
549 549 belong to rootmodule subtree.
550 550 """
551 551 if not path.startswith(self.rootmodule):
552 552 # Requests on foreign branches may be forbidden at server level
553 553 self.ui.debug('ignoring foreign branch %r\n' % path)
554 554 return None
555 555
556 556 if not stop:
557 557 stop = svn.ra.get_latest_revnum(self.ra)
558 558 try:
559 559 prevmodule = self.reparent('')
560 560 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
561 561 self.reparent(prevmodule)
562 562 except SubversionException:
563 563 dirent = None
564 564 if not dirent:
565 565 raise SvnPathNotFound(_('%s not found up to revision %d')
566 566 % (path, stop))
567 567
568 568 # stat() gives us the previous revision on this line of
569 569 # development, but it might be in *another module*. Fetch the
570 570 # log and detect renames down to the latest revision.
571 571 stream = self._getlog([path], stop, dirent.created_rev)
572 572 try:
573 573 for entry in stream:
574 574 paths, revnum, author, date, message = entry
575 575 if revnum <= dirent.created_rev:
576 576 break
577 577
578 578 for p in paths:
579 579 if not path.startswith(p) or not paths[p].copyfrom_path:
580 580 continue
581 581 newpath = paths[p].copyfrom_path + path[len(p):]
582 582 self.ui.debug("branch renamed from %s to %s at %d\n" %
583 583 (path, newpath, revnum))
584 584 path = newpath
585 585 break
586 586 finally:
587 587 stream.close()
588 588
589 589 if not path.startswith(self.rootmodule):
590 590 self.ui.debug('ignoring foreign branch %r\n' % path)
591 591 return None
592 592 return self.revid(dirent.created_rev, path)
593 593
594 594 def reparent(self, module):
595 595 """Reparent the svn transport and return the previous parent."""
596 596 if self.prevmodule == module:
597 597 return module
598 598 svnurl = self.baseurl + urllib.quote(module)
599 599 prevmodule = self.prevmodule
600 600 if prevmodule is None:
601 601 prevmodule = ''
602 602 self.ui.debug("reparent to %s\n" % svnurl)
603 603 svn.ra.reparent(self.ra, svnurl)
604 604 self.prevmodule = module
605 605 return prevmodule
606 606
607 607 def expandpaths(self, rev, paths, parents):
608 608 changed, removed = set(), set()
609 609 copies = {}
610 610
611 611 new_module, revnum = revsplit(rev)[1:]
612 612 if new_module != self.module:
613 613 self.module = new_module
614 614 self.reparent(self.module)
615 615
616 616 for i, (path, ent) in enumerate(paths):
617 617 self.ui.progress(_('scanning paths'), i, item=path,
618 618 total=len(paths))
619 619 entrypath = self.getrelpath(path)
620 620
621 621 kind = self._checkpath(entrypath, revnum)
622 622 if kind == svn.core.svn_node_file:
623 623 changed.add(self.recode(entrypath))
624 624 if not ent.copyfrom_path or not parents:
625 625 continue
626 626 # Copy sources not in parent revisions cannot be
627 627 # represented, ignore their origin for now
628 628 pmodule, prevnum = revsplit(parents[0])[1:]
629 629 if ent.copyfrom_rev < prevnum:
630 630 continue
631 631 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
632 632 if not copyfrom_path:
633 633 continue
634 634 self.ui.debug("copied to %s from %s@%s\n" %
635 635 (entrypath, copyfrom_path, ent.copyfrom_rev))
636 636 copies[self.recode(entrypath)] = self.recode(copyfrom_path)
637 637 elif kind == 0: # gone, but had better be a deleted *file*
638 638 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
639 639 pmodule, prevnum = revsplit(parents[0])[1:]
640 640 parentpath = pmodule + "/" + entrypath
641 641 fromkind = self._checkpath(entrypath, prevnum, pmodule)
642 642
643 643 if fromkind == svn.core.svn_node_file:
644 644 removed.add(self.recode(entrypath))
645 645 elif fromkind == svn.core.svn_node_dir:
646 646 oroot = parentpath.strip('/')
647 647 nroot = path.strip('/')
648 648 children = self._iterfiles(oroot, prevnum)
649 649 for childpath in children:
650 650 childpath = childpath.replace(oroot, nroot)
651 651 childpath = self.getrelpath("/" + childpath, pmodule)
652 652 if childpath:
653 653 removed.add(self.recode(childpath))
654 654 else:
655 655 self.ui.debug('unknown path in revision %d: %s\n' % \
656 656 (revnum, path))
657 657 elif kind == svn.core.svn_node_dir:
658 658 if ent.action == 'M':
659 659 # If the directory just had a prop change,
660 660 # then we shouldn't need to look for its children.
661 661 continue
662 662 if ent.action == 'R' and parents:
663 663 # If a directory is replacing a file, mark the previous
664 664 # file as deleted
665 665 pmodule, prevnum = revsplit(parents[0])[1:]
666 666 pkind = self._checkpath(entrypath, prevnum, pmodule)
667 667 if pkind == svn.core.svn_node_file:
668 668 removed.add(self.recode(entrypath))
669 669 elif pkind == svn.core.svn_node_dir:
670 670 # We do not know what files were kept or removed,
671 671 # mark them all as changed.
672 672 for childpath in self._iterfiles(pmodule, prevnum):
673 673 childpath = self.getrelpath("/" + childpath)
674 674 if childpath:
675 675 changed.add(self.recode(childpath))
676 676
677 677 for childpath in self._iterfiles(path, revnum):
678 678 childpath = self.getrelpath("/" + childpath)
679 679 if childpath:
680 680 changed.add(self.recode(childpath))
681 681
682 682 # Handle directory copies
683 683 if not ent.copyfrom_path or not parents:
684 684 continue
685 685 # Copy sources not in parent revisions cannot be
686 686 # represented, ignore their origin for now
687 687 pmodule, prevnum = revsplit(parents[0])[1:]
688 688 if ent.copyfrom_rev < prevnum:
689 689 continue
690 690 copyfrompath = self.getrelpath(ent.copyfrom_path, pmodule)
691 691 if not copyfrompath:
692 692 continue
693 693 self.ui.debug("mark %s came from %s:%d\n"
694 694 % (path, copyfrompath, ent.copyfrom_rev))
695 695 children = self._iterfiles(ent.copyfrom_path, ent.copyfrom_rev)
696 696 for childpath in children:
697 697 childpath = self.getrelpath("/" + childpath, pmodule)
698 698 if not childpath:
699 699 continue
700 700 copytopath = path + childpath[len(copyfrompath):]
701 701 copytopath = self.getrelpath(copytopath)
702 702 copies[self.recode(copytopath)] = self.recode(childpath)
703 703
704 704 self.ui.progress(_('scanning paths'), None)
705 705 changed.update(removed)
706 706 return (list(changed), removed, copies)
707 707
708 708 def _fetch_revisions(self, from_revnum, to_revnum):
709 709 if from_revnum < to_revnum:
710 710 from_revnum, to_revnum = to_revnum, from_revnum
711 711
712 712 self.child_cset = None
713 713
714 714 def parselogentry(orig_paths, revnum, author, date, message):
715 715 """Return the parsed commit object or None, and True if
716 716 the revision is a branch root.
717 717 """
718 718 self.ui.debug("parsing revision %d (%d changes)\n" %
719 719 (revnum, len(orig_paths)))
720 720
721 721 branched = False
722 722 rev = self.revid(revnum)
723 723 # branch log might return entries for a parent we already have
724 724
725 725 if rev in self.commits or revnum < to_revnum:
726 726 return None, branched
727 727
728 728 parents = []
729 729 # check whether this revision is the start of a branch or part
730 730 # of a branch renaming
731 731 orig_paths = sorted(orig_paths.iteritems())
732 732 root_paths = [(p, e) for p, e in orig_paths
733 733 if self.module.startswith(p)]
734 734 if root_paths:
735 735 path, ent = root_paths[-1]
736 736 if ent.copyfrom_path:
737 737 branched = True
738 738 newpath = ent.copyfrom_path + self.module[len(path):]
739 739 # ent.copyfrom_rev may not be the actual last revision
740 740 previd = self.latest(newpath, ent.copyfrom_rev)
741 741 if previd is not None:
742 742 prevmodule, prevnum = revsplit(previd)[1:]
743 743 if prevnum >= self.startrev:
744 744 parents = [previd]
745 745 self.ui.note(
746 746 _('found parent of branch %s at %d: %s\n') %
747 747 (self.module, prevnum, prevmodule))
748 748 else:
749 749 self.ui.debug("no copyfrom path, don't know what to do.\n")
750 750
751 751 paths = []
752 752 # filter out unrelated paths
753 753 for path, ent in orig_paths:
754 754 if self.getrelpath(path) is None:
755 755 continue
756 756 paths.append((path, ent))
757 757
758 758 # Example SVN datetime. Includes microseconds.
759 759 # ISO-8601 conformant
760 760 # '2007-01-04T17:35:00.902377Z'
761 761 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
762 762
763 763 log = message and self.recode(message) or ''
764 764 author = author and self.recode(author) or ''
765 765 try:
766 766 branch = self.module.split("/")[-1]
767 767 if branch == self.trunkname:
768 768 branch = None
769 769 except IndexError:
770 770 branch = None
771 771
772 772 cset = commit(author=author,
773 773 date=util.datestr(date),
774 774 desc=log,
775 775 parents=parents,
776 776 branch=branch,
777 777 rev=rev)
778 778
779 779 self.commits[rev] = cset
780 780 # The parents list is *shared* among self.paths and the
781 781 # commit object. Both will be updated below.
782 782 self.paths[rev] = (paths, cset.parents)
783 783 if self.child_cset and not self.child_cset.parents:
784 784 self.child_cset.parents[:] = [rev]
785 785 self.child_cset = cset
786 786 return cset, branched
787 787
788 788 self.ui.note(_('fetching revision log for "%s" from %d to %d\n') %
789 789 (self.module, from_revnum, to_revnum))
790 790
791 791 try:
792 792 firstcset = None
793 793 lastonbranch = False
794 794 stream = self._getlog([self.module], from_revnum, to_revnum)
795 795 try:
796 796 for entry in stream:
797 797 paths, revnum, author, date, message = entry
798 798 if revnum < self.startrev:
799 799 lastonbranch = True
800 800 break
801 801 if not paths:
802 802 self.ui.debug('revision %d has no entries\n' % revnum)
803 803 # If we ever leave the loop on an empty
804 804 # revision, do not try to get a parent branch
805 805 lastonbranch = lastonbranch or revnum == 0
806 806 continue
807 807 cset, lastonbranch = parselogentry(paths, revnum, author,
808 808 date, message)
809 809 if cset:
810 810 firstcset = cset
811 811 if lastonbranch:
812 812 break
813 813 finally:
814 814 stream.close()
815 815
816 816 if not lastonbranch and firstcset and not firstcset.parents:
817 817 # The first revision of the sequence (the last fetched one)
818 818 # has invalid parents if not a branch root. Find the parent
819 819 # revision now, if any.
820 820 try:
821 821 firstrevnum = self.revnum(firstcset.rev)
822 822 if firstrevnum > 1:
823 823 latest = self.latest(self.module, firstrevnum - 1)
824 824 if latest:
825 825 firstcset.parents.append(latest)
826 826 except SvnPathNotFound:
827 827 pass
828 828 except SubversionException, (inst, num):
829 829 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
830 830 raise util.Abort(_('svn: branch has no revision %s') % to_revnum)
831 831 raise
832 832
833 833 def getfile(self, file, rev):
834 834 # TODO: ra.get_file transmits the whole file instead of diffs.
835 835 if file in self.removed:
836 836 raise IOError()
837 837 mode = ''
838 838 try:
839 839 new_module, revnum = revsplit(rev)[1:]
840 840 if self.module != new_module:
841 841 self.module = new_module
842 842 self.reparent(self.module)
843 843 io = StringIO()
844 844 info = svn.ra.get_file(self.ra, file, revnum, io)
845 845 data = io.getvalue()
846 846 # ra.get_files() seems to keep a reference on the input buffer
847 847 # preventing collection. Release it explicitely.
848 848 io.close()
849 849 if isinstance(info, list):
850 850 info = info[-1]
851 851 mode = ("svn:executable" in info) and 'x' or ''
852 852 mode = ("svn:special" in info) and 'l' or mode
853 853 except SubversionException, e:
854 854 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
855 855 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
856 856 if e.apr_err in notfound: # File not found
857 857 raise IOError()
858 858 raise
859 859 if mode == 'l':
860 860 link_prefix = "link "
861 861 if data.startswith(link_prefix):
862 862 data = data[len(link_prefix):]
863 863 return data, mode
864 864
865 865 def _iterfiles(self, path, revnum):
866 866 """Enumerate all files in path at revnum, recursively."""
867 867 path = path.strip('/')
868 868 pool = Pool()
869 869 rpath = '/'.join([self.baseurl, urllib.quote(path)]).strip('/')
870 870 entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool)
871 871 if path:
872 872 path += '/'
873 873 return ((path + p) for p, e in entries.iteritems()
874 874 if e.kind == svn.core.svn_node_file)
875 875
876 876 def getrelpath(self, path, module=None):
877 877 if module is None:
878 878 module = self.module
879 879 # Given the repository url of this wc, say
880 880 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
881 881 # extract the "entry" portion (a relative path) from what
882 882 # svn log --xml says, ie
883 883 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
884 884 # that is to say "tests/PloneTestCase.py"
885 885 if path.startswith(module):
886 886 relative = path.rstrip('/')[len(module):]
887 887 if relative.startswith('/'):
888 888 return relative[1:]
889 889 elif relative == '':
890 890 return relative
891 891
892 892 # The path is outside our tracked tree...
893 893 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
894 894 return None
895 895
896 896 def _checkpath(self, path, revnum, module=None):
897 897 if module is not None:
898 898 prevmodule = self.reparent('')
899 899 path = module + '/' + path
900 900 try:
901 901 # ra.check_path does not like leading slashes very much, it leads
902 902 # to PROPFIND subversion errors
903 903 return svn.ra.check_path(self.ra, path.strip('/'), revnum)
904 904 finally:
905 905 if module is not None:
906 906 self.reparent(prevmodule)
907 907
908 908 def _getlog(self, paths, start, end, limit=0, discover_changed_paths=True,
909 909 strict_node_history=False):
910 910 # Normalize path names, svn >= 1.5 only wants paths relative to
911 911 # supplied URL
912 912 relpaths = []
913 913 for p in paths:
914 914 if not p.startswith('/'):
915 915 p = self.module + '/' + p
916 916 relpaths.append(p.strip('/'))
917 917 args = [self.baseurl, relpaths, start, end, limit, discover_changed_paths,
918 918 strict_node_history]
919 919 arg = encodeargs(args)
920 920 hgexe = util.hgexecutable()
921 921 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
922 922 stdin, stdout = util.popen2(util.quotecommand(cmd))
923 923 stdin.write(arg)
924 924 try:
925 925 stdin.close()
926 926 except IOError:
927 927 raise util.Abort(_('Mercurial failed to run itself, check'
928 928 ' hg executable is in PATH'))
929 929 return logstream(stdout)
930 930
931 931 pre_revprop_change = '''#!/bin/sh
932 932
933 933 REPOS="$1"
934 934 REV="$2"
935 935 USER="$3"
936 936 PROPNAME="$4"
937 937 ACTION="$5"
938 938
939 939 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
940 940 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
941 941 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
942 942
943 943 echo "Changing prohibited revision property" >&2
944 944 exit 1
945 945 '''
946 946
947 947 class svn_sink(converter_sink, commandline):
948 948 commit_re = re.compile(r'Committed revision (\d+).', re.M)
949 949 uuid_re = re.compile(r'Repository UUID:\s*(\S+)', re.M)
950 950
951 951 def prerun(self):
952 952 if self.wc:
953 953 os.chdir(self.wc)
954 954
955 955 def postrun(self):
956 956 if self.wc:
957 957 os.chdir(self.cwd)
958 958
959 959 def join(self, name):
960 960 return os.path.join(self.wc, '.svn', name)
961 961
962 962 def revmapfile(self):
963 963 return self.join('hg-shamap')
964 964
965 965 def authorfile(self):
966 966 return self.join('hg-authormap')
967 967
968 968 def __init__(self, ui, path):
969 969
970 970 converter_sink.__init__(self, ui, path)
971 971 commandline.__init__(self, ui, 'svn')
972 972 self.delete = []
973 973 self.setexec = []
974 974 self.delexec = []
975 975 self.copies = []
976 976 self.wc = None
977 977 self.cwd = os.getcwd()
978 978
979 979 path = os.path.realpath(path)
980 980
981 981 created = False
982 982 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
983 983 self.wc = path
984 984 self.run0('update')
985 985 else:
986 986 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
987 987
988 988 if os.path.isdir(os.path.dirname(path)):
989 989 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
990 990 ui.status(_('initializing svn repository %r\n') %
991 991 os.path.basename(path))
992 992 commandline(ui, 'svnadmin').run0('create', path)
993 993 created = path
994 994 path = util.normpath(path)
995 995 if not path.startswith('/'):
996 996 path = '/' + path
997 997 path = 'file://' + path
998 998
999 999 ui.status(_('initializing svn working copy %r\n')
1000 1000 % os.path.basename(wcpath))
1001 1001 self.run0('checkout', path, wcpath)
1002 1002
1003 1003 self.wc = wcpath
1004 1004 self.opener = scmutil.opener(self.wc)
1005 1005 self.wopener = scmutil.opener(self.wc)
1006 1006 self.childmap = mapfile(ui, self.join('hg-childmap'))
1007 1007 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
1008 1008
1009 1009 if created:
1010 1010 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
1011 1011 fp = open(hook, 'w')
1012 1012 fp.write(pre_revprop_change)
1013 1013 fp.close()
1014 1014 util.set_flags(hook, False, True)
1015 1015
1016 1016 output = self.run0('info')
1017 1017 self.uuid = self.uuid_re.search(output).group(1).strip()
1018 1018
1019 1019 def wjoin(self, *names):
1020 1020 return os.path.join(self.wc, *names)
1021 1021
1022 1022 def putfile(self, filename, flags, data):
1023 1023 if 'l' in flags:
1024 1024 self.wopener.symlink(data, filename)
1025 1025 else:
1026 1026 try:
1027 1027 if os.path.islink(self.wjoin(filename)):
1028 1028 os.unlink(filename)
1029 1029 except OSError:
1030 1030 pass
1031 self.wopener(filename, 'w').write(data)
1031 self.wopener.write(filename, data)
1032 1032
1033 1033 if self.is_exec:
1034 1034 was_exec = self.is_exec(self.wjoin(filename))
1035 1035 else:
1036 1036 # On filesystems not supporting execute-bit, there is no way
1037 1037 # to know if it is set but asking subversion. Setting it
1038 1038 # systematically is just as expensive and much simpler.
1039 1039 was_exec = 'x' not in flags
1040 1040
1041 1041 util.set_flags(self.wjoin(filename), False, 'x' in flags)
1042 1042 if was_exec:
1043 1043 if 'x' not in flags:
1044 1044 self.delexec.append(filename)
1045 1045 else:
1046 1046 if 'x' in flags:
1047 1047 self.setexec.append(filename)
1048 1048
1049 1049 def _copyfile(self, source, dest):
1050 1050 # SVN's copy command pukes if the destination file exists, but
1051 1051 # our copyfile method expects to record a copy that has
1052 1052 # already occurred. Cross the semantic gap.
1053 1053 wdest = self.wjoin(dest)
1054 1054 exists = os.path.lexists(wdest)
1055 1055 if exists:
1056 1056 fd, tempname = tempfile.mkstemp(
1057 1057 prefix='hg-copy-', dir=os.path.dirname(wdest))
1058 1058 os.close(fd)
1059 1059 os.unlink(tempname)
1060 1060 os.rename(wdest, tempname)
1061 1061 try:
1062 1062 self.run0('copy', source, dest)
1063 1063 finally:
1064 1064 if exists:
1065 1065 try:
1066 1066 os.unlink(wdest)
1067 1067 except OSError:
1068 1068 pass
1069 1069 os.rename(tempname, wdest)
1070 1070
1071 1071 def dirs_of(self, files):
1072 1072 dirs = set()
1073 1073 for f in files:
1074 1074 if os.path.isdir(self.wjoin(f)):
1075 1075 dirs.add(f)
1076 1076 for i in strutil.rfindall(f, '/'):
1077 1077 dirs.add(f[:i])
1078 1078 return dirs
1079 1079
1080 1080 def add_dirs(self, files):
1081 1081 add_dirs = [d for d in sorted(self.dirs_of(files))
1082 1082 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
1083 1083 if add_dirs:
1084 1084 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
1085 1085 return add_dirs
1086 1086
1087 1087 def add_files(self, files):
1088 1088 if files:
1089 1089 self.xargs(files, 'add', quiet=True)
1090 1090 return files
1091 1091
1092 1092 def tidy_dirs(self, names):
1093 1093 deleted = []
1094 1094 for d in sorted(self.dirs_of(names), reverse=True):
1095 1095 wd = self.wjoin(d)
1096 1096 if os.listdir(wd) == '.svn':
1097 1097 self.run0('delete', d)
1098 1098 deleted.append(d)
1099 1099 return deleted
1100 1100
1101 1101 def addchild(self, parent, child):
1102 1102 self.childmap[parent] = child
1103 1103
1104 1104 def revid(self, rev):
1105 1105 return u"svn:%s@%s" % (self.uuid, rev)
1106 1106
1107 1107 def putcommit(self, files, copies, parents, commit, source, revmap):
1108 1108 # Apply changes to working copy
1109 1109 for f, v in files:
1110 1110 try:
1111 1111 data, mode = source.getfile(f, v)
1112 1112 except IOError:
1113 1113 self.delete.append(f)
1114 1114 else:
1115 1115 self.putfile(f, mode, data)
1116 1116 if f in copies:
1117 1117 self.copies.append([copies[f], f])
1118 1118 files = [f[0] for f in files]
1119 1119
1120 1120 for parent in parents:
1121 1121 try:
1122 1122 return self.revid(self.childmap[parent])
1123 1123 except KeyError:
1124 1124 pass
1125 1125 entries = set(self.delete)
1126 1126 files = frozenset(files)
1127 1127 entries.update(self.add_dirs(files.difference(entries)))
1128 1128 if self.copies:
1129 1129 for s, d in self.copies:
1130 1130 self._copyfile(s, d)
1131 1131 self.copies = []
1132 1132 if self.delete:
1133 1133 self.xargs(self.delete, 'delete')
1134 1134 self.delete = []
1135 1135 entries.update(self.add_files(files.difference(entries)))
1136 1136 entries.update(self.tidy_dirs(entries))
1137 1137 if self.delexec:
1138 1138 self.xargs(self.delexec, 'propdel', 'svn:executable')
1139 1139 self.delexec = []
1140 1140 if self.setexec:
1141 1141 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1142 1142 self.setexec = []
1143 1143
1144 1144 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
1145 1145 fp = os.fdopen(fd, 'w')
1146 1146 fp.write(commit.desc)
1147 1147 fp.close()
1148 1148 try:
1149 1149 output = self.run0('commit',
1150 1150 username=util.shortuser(commit.author),
1151 1151 file=messagefile,
1152 1152 encoding='utf-8')
1153 1153 try:
1154 1154 rev = self.commit_re.search(output).group(1)
1155 1155 except AttributeError:
1156 1156 if not files:
1157 1157 return parents[0]
1158 1158 self.ui.warn(_('unexpected svn output:\n'))
1159 1159 self.ui.warn(output)
1160 1160 raise util.Abort(_('unable to cope with svn output'))
1161 1161 if commit.rev:
1162 1162 self.run('propset', 'hg:convert-rev', commit.rev,
1163 1163 revprop=True, revision=rev)
1164 1164 if commit.branch and commit.branch != 'default':
1165 1165 self.run('propset', 'hg:convert-branch', commit.branch,
1166 1166 revprop=True, revision=rev)
1167 1167 for parent in parents:
1168 1168 self.addchild(parent, rev)
1169 1169 return self.revid(rev)
1170 1170 finally:
1171 1171 os.unlink(messagefile)
1172 1172
1173 1173 def puttags(self, tags):
1174 1174 self.ui.warn(_('writing Subversion tags is not yet implemented\n'))
1175 1175 return None, None
@@ -1,328 +1,328 b''
1 1 # extdiff.py - external diff program support for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 '''command to allow external programs to compare revisions
9 9
10 10 The extdiff Mercurial extension allows you to use external programs
11 11 to compare revisions, or revision with working directory. The external
12 12 diff programs are called with a configurable set of options and two
13 13 non-option arguments: paths to directories containing snapshots of
14 14 files to compare.
15 15
16 16 The extdiff extension also allows to configure new diff commands, so
17 17 you do not need to type :hg:`extdiff -p kdiff3` always. ::
18 18
19 19 [extdiff]
20 20 # add new command that runs GNU diff(1) in 'context diff' mode
21 21 cdiff = gdiff -Nprc5
22 22 ## or the old way:
23 23 #cmd.cdiff = gdiff
24 24 #opts.cdiff = -Nprc5
25 25
26 26 # add new command called vdiff, runs kdiff3
27 27 vdiff = kdiff3
28 28
29 29 # add new command called meld, runs meld (no need to name twice)
30 30 meld =
31 31
32 32 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
33 33 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
34 34 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
35 35 # your .vimrc
36 36 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
37 37
38 38 Tool arguments can include variables that are expanded at runtime::
39 39
40 40 $parent1, $plabel1 - filename, descriptive label of first parent
41 41 $child, $clabel - filename, descriptive label of child revision
42 42 $parent2, $plabel2 - filename, descriptive label of second parent
43 43 $root - repository root
44 44 $parent is an alias for $parent1.
45 45
46 46 The extdiff extension will look in your [diff-tools] and [merge-tools]
47 47 sections for diff tool arguments, when none are specified in [extdiff].
48 48
49 49 ::
50 50
51 51 [extdiff]
52 52 kdiff3 =
53 53
54 54 [diff-tools]
55 55 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
56 56
57 57 You can use -I/-X and list of file or directory names like normal
58 58 :hg:`diff` command. The extdiff extension makes snapshots of only
59 59 needed files, so running the external diff program will actually be
60 60 pretty fast (at least faster than having to compare the entire tree).
61 61 '''
62 62
63 63 from mercurial.i18n import _
64 64 from mercurial.node import short, nullid
65 65 from mercurial import cmdutil, scmutil, util, commands, encoding
66 66 import os, shlex, shutil, tempfile, re
67 67
68 68 def snapshot(ui, repo, files, node, tmproot):
69 69 '''snapshot files as of some revision
70 70 if not using snapshot, -I/-X does not work and recursive diff
71 71 in tools like kdiff3 and meld displays too many files.'''
72 72 dirname = os.path.basename(repo.root)
73 73 if dirname == "":
74 74 dirname = "root"
75 75 if node is not None:
76 76 dirname = '%s.%s' % (dirname, short(node))
77 77 base = os.path.join(tmproot, dirname)
78 78 os.mkdir(base)
79 79 if node is not None:
80 80 ui.note(_('making snapshot of %d files from rev %s\n') %
81 81 (len(files), short(node)))
82 82 else:
83 83 ui.note(_('making snapshot of %d files from working directory\n') %
84 84 (len(files)))
85 85 wopener = scmutil.opener(base)
86 86 fns_and_mtime = []
87 87 ctx = repo[node]
88 88 for fn in files:
89 89 wfn = util.pconvert(fn)
90 90 if not wfn in ctx:
91 91 # File doesn't exist; could be a bogus modify
92 92 continue
93 93 ui.note(' %s\n' % wfn)
94 94 dest = os.path.join(base, wfn)
95 95 fctx = ctx[wfn]
96 96 data = repo.wwritedata(wfn, fctx.data())
97 97 if 'l' in fctx.flags():
98 98 wopener.symlink(data, wfn)
99 99 else:
100 wopener(wfn, 'w').write(data)
100 wopener.write(wfn, data)
101 101 if 'x' in fctx.flags():
102 102 util.set_flags(dest, False, True)
103 103 if node is None:
104 104 fns_and_mtime.append((dest, repo.wjoin(fn),
105 105 os.lstat(dest).st_mtime))
106 106 return dirname, fns_and_mtime
107 107
108 108 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
109 109 '''Do the actuall diff:
110 110
111 111 - copy to a temp structure if diffing 2 internal revisions
112 112 - copy to a temp structure if diffing working revision with
113 113 another one and more than 1 file is changed
114 114 - just invoke the diff for a single file in the working dir
115 115 '''
116 116
117 117 revs = opts.get('rev')
118 118 change = opts.get('change')
119 119 args = ' '.join(diffopts)
120 120 do3way = '$parent2' in args
121 121
122 122 if revs and change:
123 123 msg = _('cannot specify --rev and --change at the same time')
124 124 raise util.Abort(msg)
125 125 elif change:
126 126 node2 = cmdutil.revsingle(repo, change, None).node()
127 127 node1a, node1b = repo.changelog.parents(node2)
128 128 else:
129 129 node1a, node2 = cmdutil.revpair(repo, revs)
130 130 if not revs:
131 131 node1b = repo.dirstate.p2()
132 132 else:
133 133 node1b = nullid
134 134
135 135 # Disable 3-way merge if there is only one parent
136 136 if do3way:
137 137 if node1b == nullid:
138 138 do3way = False
139 139
140 140 matcher = cmdutil.match(repo, pats, opts)
141 141 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
142 142 if do3way:
143 143 mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
144 144 else:
145 145 mod_b, add_b, rem_b = set(), set(), set()
146 146 modadd = mod_a | add_a | mod_b | add_b
147 147 common = modadd | rem_a | rem_b
148 148 if not common:
149 149 return 0
150 150
151 151 tmproot = tempfile.mkdtemp(prefix='extdiff.')
152 152 try:
153 153 # Always make a copy of node1a (and node1b, if applicable)
154 154 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
155 155 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
156 156 rev1a = '@%d' % repo[node1a].rev()
157 157 if do3way:
158 158 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
159 159 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
160 160 rev1b = '@%d' % repo[node1b].rev()
161 161 else:
162 162 dir1b = None
163 163 rev1b = ''
164 164
165 165 fns_and_mtime = []
166 166
167 167 # If node2 in not the wc or there is >1 change, copy it
168 168 dir2root = ''
169 169 rev2 = ''
170 170 if node2:
171 171 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
172 172 rev2 = '@%d' % repo[node2].rev()
173 173 elif len(common) > 1:
174 174 #we only actually need to get the files to copy back to
175 175 #the working dir in this case (because the other cases
176 176 #are: diffing 2 revisions or single file -- in which case
177 177 #the file is already directly passed to the diff tool).
178 178 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
179 179 else:
180 180 # This lets the diff tool open the changed file directly
181 181 dir2 = ''
182 182 dir2root = repo.root
183 183
184 184 label1a = rev1a
185 185 label1b = rev1b
186 186 label2 = rev2
187 187
188 188 # If only one change, diff the files instead of the directories
189 189 # Handle bogus modifies correctly by checking if the files exist
190 190 if len(common) == 1:
191 191 common_file = util.localpath(common.pop())
192 192 dir1a = os.path.join(tmproot, dir1a, common_file)
193 193 label1a = common_file + rev1a
194 194 if not os.path.isfile(dir1a):
195 195 dir1a = os.devnull
196 196 if do3way:
197 197 dir1b = os.path.join(tmproot, dir1b, common_file)
198 198 label1b = common_file + rev1b
199 199 if not os.path.isfile(dir1b):
200 200 dir1b = os.devnull
201 201 dir2 = os.path.join(dir2root, dir2, common_file)
202 202 label2 = common_file + rev2
203 203
204 204 # Function to quote file/dir names in the argument string.
205 205 # When not operating in 3-way mode, an empty string is
206 206 # returned for parent2
207 207 replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b,
208 208 plabel1=label1a, plabel2=label1b,
209 209 clabel=label2, child=dir2,
210 210 root=repo.root)
211 211 def quote(match):
212 212 key = match.group()[1:]
213 213 if not do3way and key == 'parent2':
214 214 return ''
215 215 return util.shellquote(replace[key])
216 216
217 217 # Match parent2 first, so 'parent1?' will match both parent1 and parent
218 218 regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)'
219 219 if not do3way and not re.search(regex, args):
220 220 args += ' $parent1 $child'
221 221 args = re.sub(regex, quote, args)
222 222 cmdline = util.shellquote(diffcmd) + ' ' + args
223 223
224 224 ui.debug('running %r in %s\n' % (cmdline, tmproot))
225 225 util.system(cmdline, cwd=tmproot)
226 226
227 227 for copy_fn, working_fn, mtime in fns_and_mtime:
228 228 if os.lstat(copy_fn).st_mtime != mtime:
229 229 ui.debug('file changed while diffing. '
230 230 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
231 231 util.copyfile(copy_fn, working_fn)
232 232
233 233 return 1
234 234 finally:
235 235 ui.note(_('cleaning up temp directory\n'))
236 236 shutil.rmtree(tmproot)
237 237
238 238 def extdiff(ui, repo, *pats, **opts):
239 239 '''use external program to diff repository (or selected files)
240 240
241 241 Show differences between revisions for the specified files, using
242 242 an external program. The default program used is diff, with
243 243 default options "-Npru".
244 244
245 245 To select a different program, use the -p/--program option. The
246 246 program will be passed the names of two directories to compare. To
247 247 pass additional options to the program, use -o/--option. These
248 248 will be passed before the names of the directories to compare.
249 249
250 250 When two revision arguments are given, then changes are shown
251 251 between those revisions. If only one revision is specified then
252 252 that revision is compared to the working directory, and, when no
253 253 revisions are specified, the working directory files are compared
254 254 to its parent.'''
255 255 program = opts.get('program')
256 256 option = opts.get('option')
257 257 if not program:
258 258 program = 'diff'
259 259 option = option or ['-Npru']
260 260 return dodiff(ui, repo, program, option, pats, opts)
261 261
262 262 cmdtable = {
263 263 "extdiff":
264 264 (extdiff,
265 265 [('p', 'program', '',
266 266 _('comparison program to run'), _('CMD')),
267 267 ('o', 'option', [],
268 268 _('pass option to comparison program'), _('OPT')),
269 269 ('r', 'rev', [],
270 270 _('revision'), _('REV')),
271 271 ('c', 'change', '',
272 272 _('change made by revision'), _('REV')),
273 273 ] + commands.walkopts,
274 274 _('hg extdiff [OPT]... [FILE]...')),
275 275 }
276 276
277 277 def uisetup(ui):
278 278 for cmd, path in ui.configitems('extdiff'):
279 279 if cmd.startswith('cmd.'):
280 280 cmd = cmd[4:]
281 281 if not path:
282 282 path = cmd
283 283 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
284 284 diffopts = diffopts and [diffopts] or []
285 285 elif cmd.startswith('opts.'):
286 286 continue
287 287 else:
288 288 # command = path opts
289 289 if path:
290 290 diffopts = shlex.split(path)
291 291 path = diffopts.pop(0)
292 292 else:
293 293 path, diffopts = cmd, []
294 294 # look for diff arguments in [diff-tools] then [merge-tools]
295 295 if diffopts == []:
296 296 args = ui.config('diff-tools', cmd+'.diffargs') or \
297 297 ui.config('merge-tools', cmd+'.diffargs')
298 298 if args:
299 299 diffopts = shlex.split(args)
300 300 def save(cmd, path, diffopts):
301 301 '''use closure to save diff command to use'''
302 302 def mydiff(ui, repo, *pats, **opts):
303 303 return dodiff(ui, repo, path, diffopts + opts['option'],
304 304 pats, opts)
305 305 doc = _('''\
306 306 use %(path)s to diff repository (or selected files)
307 307
308 308 Show differences between revisions for the specified files, using
309 309 the %(path)s program.
310 310
311 311 When two revision arguments are given, then changes are shown
312 312 between those revisions. If only one revision is specified then
313 313 that revision is compared to the working directory, and, when no
314 314 revisions are specified, the working directory files are compared
315 315 to its parent.\
316 316 ''') % dict(path=util.uirepr(path))
317 317
318 318 # We must translate the docstring right away since it is
319 319 # used as a format string. The string will unfortunately
320 320 # be translated again in commands.helpcmd and this will
321 321 # fail when the docstring contains non-ASCII characters.
322 322 # Decoding the string to a Unicode string here (using the
323 323 # right encoding) prevents that.
324 324 mydiff.__doc__ = doc.decode(encoding.encoding)
325 325 return mydiff
326 326 cmdtable[cmd] = (save(cmd, path, diffopts),
327 327 cmdtable['extdiff'][1][1:],
328 328 _('hg %s [OPTION]... [FILE]...') % cmd)
@@ -1,290 +1,290 b''
1 1 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
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 '''commands to sign and verify changesets'''
7 7
8 8 import os, tempfile, binascii
9 9 from mercurial import util, commands, match
10 10 from mercurial import node as hgnode
11 11 from mercurial.i18n import _
12 12
13 13 class gpg(object):
14 14 def __init__(self, path, key=None):
15 15 self.path = path
16 16 self.key = (key and " --local-user \"%s\"" % key) or ""
17 17
18 18 def sign(self, data):
19 19 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
20 20 return util.filter(data, gpgcmd)
21 21
22 22 def verify(self, data, sig):
23 23 """ returns of the good and bad signatures"""
24 24 sigfile = datafile = None
25 25 try:
26 26 # create temporary files
27 27 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
28 28 fp = os.fdopen(fd, 'wb')
29 29 fp.write(sig)
30 30 fp.close()
31 31 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
32 32 fp = os.fdopen(fd, 'wb')
33 33 fp.write(data)
34 34 fp.close()
35 35 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
36 36 "\"%s\" \"%s\"" % (self.path, sigfile, datafile))
37 37 ret = util.filter("", gpgcmd)
38 38 finally:
39 39 for f in (sigfile, datafile):
40 40 try:
41 41 if f:
42 42 os.unlink(f)
43 43 except:
44 44 pass
45 45 keys = []
46 46 key, fingerprint = None, None
47 47 err = ""
48 48 for l in ret.splitlines():
49 49 # see DETAILS in the gnupg documentation
50 50 # filter the logger output
51 51 if not l.startswith("[GNUPG:]"):
52 52 continue
53 53 l = l[9:]
54 54 if l.startswith("ERRSIG"):
55 55 err = _("error while verifying signature")
56 56 break
57 57 elif l.startswith("VALIDSIG"):
58 58 # fingerprint of the primary key
59 59 fingerprint = l.split()[10]
60 60 elif (l.startswith("GOODSIG") or
61 61 l.startswith("EXPSIG") or
62 62 l.startswith("EXPKEYSIG") or
63 63 l.startswith("BADSIG")):
64 64 if key is not None:
65 65 keys.append(key + [fingerprint])
66 66 key = l.split(" ", 2)
67 67 fingerprint = None
68 68 if err:
69 69 return err, []
70 70 if key is not None:
71 71 keys.append(key + [fingerprint])
72 72 return err, keys
73 73
74 74 def newgpg(ui, **opts):
75 75 """create a new gpg instance"""
76 76 gpgpath = ui.config("gpg", "cmd", "gpg")
77 77 gpgkey = opts.get('key')
78 78 if not gpgkey:
79 79 gpgkey = ui.config("gpg", "key", None)
80 80 return gpg(gpgpath, gpgkey)
81 81
82 82 def sigwalk(repo):
83 83 """
84 84 walk over every sigs, yields a couple
85 85 ((node, version, sig), (filename, linenumber))
86 86 """
87 87 def parsefile(fileiter, context):
88 88 ln = 1
89 89 for l in fileiter:
90 90 if not l:
91 91 continue
92 92 yield (l.split(" ", 2), (context, ln))
93 93 ln += 1
94 94
95 95 # read the heads
96 96 fl = repo.file(".hgsigs")
97 97 for r in reversed(fl.heads()):
98 98 fn = ".hgsigs|%s" % hgnode.short(r)
99 99 for item in parsefile(fl.read(r).splitlines(), fn):
100 100 yield item
101 101 try:
102 102 # read local signatures
103 103 fn = "localsigs"
104 104 for item in parsefile(repo.opener(fn), fn):
105 105 yield item
106 106 except IOError:
107 107 pass
108 108
109 109 def getkeys(ui, repo, mygpg, sigdata, context):
110 110 """get the keys who signed a data"""
111 111 fn, ln = context
112 112 node, version, sig = sigdata
113 113 prefix = "%s:%d" % (fn, ln)
114 114 node = hgnode.bin(node)
115 115
116 116 data = node2txt(repo, node, version)
117 117 sig = binascii.a2b_base64(sig)
118 118 err, keys = mygpg.verify(data, sig)
119 119 if err:
120 120 ui.warn("%s:%d %s\n" % (fn, ln , err))
121 121 return None
122 122
123 123 validkeys = []
124 124 # warn for expired key and/or sigs
125 125 for key in keys:
126 126 if key[0] == "BADSIG":
127 127 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
128 128 continue
129 129 if key[0] == "EXPSIG":
130 130 ui.write(_("%s Note: Signature has expired"
131 131 " (signed by: \"%s\")\n") % (prefix, key[2]))
132 132 elif key[0] == "EXPKEYSIG":
133 133 ui.write(_("%s Note: This key has expired"
134 134 " (signed by: \"%s\")\n") % (prefix, key[2]))
135 135 validkeys.append((key[1], key[2], key[3]))
136 136 return validkeys
137 137
138 138 def sigs(ui, repo):
139 139 """list signed changesets"""
140 140 mygpg = newgpg(ui)
141 141 revs = {}
142 142
143 143 for data, context in sigwalk(repo):
144 144 node, version, sig = data
145 145 fn, ln = context
146 146 try:
147 147 n = repo.lookup(node)
148 148 except KeyError:
149 149 ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
150 150 continue
151 151 r = repo.changelog.rev(n)
152 152 keys = getkeys(ui, repo, mygpg, data, context)
153 153 if not keys:
154 154 continue
155 155 revs.setdefault(r, [])
156 156 revs[r].extend(keys)
157 157 for rev in sorted(revs, reverse=True):
158 158 for k in revs[rev]:
159 159 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
160 160 ui.write("%-30s %s\n" % (keystr(ui, k), r))
161 161
162 162 def check(ui, repo, rev):
163 163 """verify all the signatures there may be for a particular revision"""
164 164 mygpg = newgpg(ui)
165 165 rev = repo.lookup(rev)
166 166 hexrev = hgnode.hex(rev)
167 167 keys = []
168 168
169 169 for data, context in sigwalk(repo):
170 170 node, version, sig = data
171 171 if node == hexrev:
172 172 k = getkeys(ui, repo, mygpg, data, context)
173 173 if k:
174 174 keys.extend(k)
175 175
176 176 if not keys:
177 177 ui.write(_("No valid signature for %s\n") % hgnode.short(rev))
178 178 return
179 179
180 180 # print summary
181 181 ui.write("%s is signed by:\n" % hgnode.short(rev))
182 182 for key in keys:
183 183 ui.write(" %s\n" % keystr(ui, key))
184 184
185 185 def keystr(ui, key):
186 186 """associate a string to a key (username, comment)"""
187 187 keyid, user, fingerprint = key
188 188 comment = ui.config("gpg", fingerprint, None)
189 189 if comment:
190 190 return "%s (%s)" % (user, comment)
191 191 else:
192 192 return user
193 193
194 194 def sign(ui, repo, *revs, **opts):
195 195 """add a signature for the current or given revision
196 196
197 197 If no revision is given, the parent of the working directory is used,
198 198 or tip if no revision is checked out.
199 199
200 200 See :hg:`help dates` for a list of formats valid for -d/--date.
201 201 """
202 202
203 203 mygpg = newgpg(ui, **opts)
204 204 sigver = "0"
205 205 sigmessage = ""
206 206
207 207 date = opts.get('date')
208 208 if date:
209 209 opts['date'] = util.parsedate(date)
210 210
211 211 if revs:
212 212 nodes = [repo.lookup(n) for n in revs]
213 213 else:
214 214 nodes = [node for node in repo.dirstate.parents()
215 215 if node != hgnode.nullid]
216 216 if len(nodes) > 1:
217 217 raise util.Abort(_('uncommitted merge - please provide a '
218 218 'specific revision'))
219 219 if not nodes:
220 220 nodes = [repo.changelog.tip()]
221 221
222 222 for n in nodes:
223 223 hexnode = hgnode.hex(n)
224 224 ui.write(_("Signing %d:%s\n") % (repo.changelog.rev(n),
225 225 hgnode.short(n)))
226 226 # build data
227 227 data = node2txt(repo, n, sigver)
228 228 sig = mygpg.sign(data)
229 229 if not sig:
230 230 raise util.Abort(_("error while signing"))
231 231 sig = binascii.b2a_base64(sig)
232 232 sig = sig.replace("\n", "")
233 233 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
234 234
235 235 # write it
236 236 if opts['local']:
237 repo.opener("localsigs", "ab").write(sigmessage)
237 repo.opener.append("localsigs", sigmessage)
238 238 return
239 239
240 240 msigs = match.exact(repo.root, '', ['.hgsigs'])
241 241 s = repo.status(match=msigs, unknown=True, ignored=True)[:6]
242 242 if util.any(s) and not opts["force"]:
243 243 raise util.Abort(_("working copy of .hgsigs is changed "
244 244 "(please commit .hgsigs manually "
245 245 "or use --force)"))
246 246
247 247 sigsfile = repo.wfile(".hgsigs", "ab")
248 248 sigsfile.write(sigmessage)
249 249 sigsfile.close()
250 250
251 251 if '.hgsigs' not in repo.dirstate:
252 252 repo[None].add([".hgsigs"])
253 253
254 254 if opts["no_commit"]:
255 255 return
256 256
257 257 message = opts['message']
258 258 if not message:
259 259 # we don't translate commit messages
260 260 message = "\n".join(["Added signature for changeset %s"
261 261 % hgnode.short(n)
262 262 for n in nodes])
263 263 try:
264 264 repo.commit(message, opts['user'], opts['date'], match=msigs)
265 265 except ValueError, inst:
266 266 raise util.Abort(str(inst))
267 267
268 268 def node2txt(repo, node, ver):
269 269 """map a manifest into some text"""
270 270 if ver == "0":
271 271 return "%s\n" % hgnode.hex(node)
272 272 else:
273 273 raise util.Abort(_("unknown signature version"))
274 274
275 275 cmdtable = {
276 276 "sign":
277 277 (sign,
278 278 [('l', 'local', None, _('make the signature local')),
279 279 ('f', 'force', None, _('sign even if the sigfile is modified')),
280 280 ('', 'no-commit', None, _('do not commit the sigfile after signing')),
281 281 ('k', 'key', '',
282 282 _('the key id to sign with'), _('ID')),
283 283 ('m', 'message', '',
284 284 _('commit message'), _('TEXT')),
285 285 ] + commands.commitopts2,
286 286 _('hg sign [OPTION]... [REVISION]...')),
287 287 "sigcheck": (check, [], _('hg sigcheck REVISION')),
288 288 "sigs": (sigs, [], _('hg sigs')),
289 289 }
290 290
@@ -1,695 +1,695 b''
1 1 # keyword.py - $Keyword$ expansion for Mercurial
2 2 #
3 3 # Copyright 2007-2010 Christian Ebert <blacktrash@gmx.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 # $Id$
9 9 #
10 10 # Keyword expansion hack against the grain of a DSCM
11 11 #
12 12 # There are many good reasons why this is not needed in a distributed
13 13 # SCM, still it may be useful in very small projects based on single
14 14 # files (like LaTeX packages), that are mostly addressed to an
15 15 # audience not running a version control system.
16 16 #
17 17 # For in-depth discussion refer to
18 18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
19 19 #
20 20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 21 #
22 22 # Binary files are not touched.
23 23 #
24 24 # Files to act upon/ignore are specified in the [keyword] section.
25 25 # Customized keyword template mappings in the [keywordmaps] section.
26 26 #
27 27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28 28
29 29 '''expand keywords in tracked files
30 30
31 31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 32 tracked text files selected by your configuration.
33 33
34 34 Keywords are only expanded in local repositories and not stored in the
35 35 change history. The mechanism can be regarded as a convenience for the
36 36 current user or for archive distribution.
37 37
38 38 Keywords expand to the changeset data pertaining to the latest change
39 39 relative to the working directory parent of each file.
40 40
41 41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
42 42 sections of hgrc files.
43 43
44 44 Example::
45 45
46 46 [keyword]
47 47 # expand keywords in every python file except those matching "x*"
48 48 **.py =
49 49 x* = ignore
50 50
51 51 [keywordset]
52 52 # prefer svn- over cvs-like default keywordmaps
53 53 svn = True
54 54
55 55 .. note::
56 56 The more specific you are in your filename patterns the less you
57 57 lose speed in huge repositories.
58 58
59 59 For [keywordmaps] template mapping and expansion demonstration and
60 60 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
61 61 available templates and filters.
62 62
63 63 Three additional date template filters are provided:
64 64
65 65 :``utcdate``: "2006/09/18 15:13:13"
66 66 :``svnutcdate``: "2006-09-18 15:13:13Z"
67 67 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
68 68
69 69 The default template mappings (view with :hg:`kwdemo -d`) can be
70 70 replaced with customized keywords and templates. Again, run
71 71 :hg:`kwdemo` to control the results of your configuration changes.
72 72
73 73 Before changing/disabling active keywords, you must run :hg:`kwshrink`
74 74 to avoid storing expanded keywords in the change history.
75 75
76 76 To force expansion after enabling it, or a configuration change, run
77 77 :hg:`kwexpand`.
78 78
79 79 Expansions spanning more than one line and incremental expansions,
80 80 like CVS' $Log$, are not supported. A keyword template map "Log =
81 81 {desc}" expands to the first line of the changeset description.
82 82 '''
83 83
84 84 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
85 85 from mercurial import localrepo, match, patch, templatefilters, templater, util
86 86 from mercurial import scmutil
87 87 from mercurial.hgweb import webcommands
88 88 from mercurial.i18n import _
89 89 import os, re, shutil, tempfile
90 90
91 91 commands.optionalrepo += ' kwdemo'
92 92
93 93 # hg commands that do not act on keywords
94 94 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
95 95 ' outgoing push tip verify convert email glog')
96 96
97 97 # hg commands that trigger expansion only when writing to working dir,
98 98 # not when reading filelog, and unexpand when reading from working dir
99 99 restricted = 'merge kwexpand kwshrink record qrecord resolve transplant'
100 100
101 101 # names of extensions using dorecord
102 102 recordextensions = 'record'
103 103
104 104 colortable = {
105 105 'kwfiles.enabled': 'green bold',
106 106 'kwfiles.deleted': 'cyan bold underline',
107 107 'kwfiles.enabledunknown': 'green',
108 108 'kwfiles.ignored': 'bold',
109 109 'kwfiles.ignoredunknown': 'none'
110 110 }
111 111
112 112 # date like in cvs' $Date
113 113 def utcdate(text):
114 114 ''':utcdate: Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
115 115 '''
116 116 return util.datestr((text[0], 0), '%Y/%m/%d %H:%M:%S')
117 117 # date like in svn's $Date
118 118 def svnisodate(text):
119 119 ''':svnisodate: Date. Returns a date in this format: "2009-08-18 13:00:13
120 120 +0200 (Tue, 18 Aug 2009)".
121 121 '''
122 122 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
123 123 # date like in svn's $Id
124 124 def svnutcdate(text):
125 125 ''':svnutcdate: Date. Returns a UTC-date in this format: "2009-08-18
126 126 11:00:13Z".
127 127 '''
128 128 return util.datestr((text[0], 0), '%Y-%m-%d %H:%M:%SZ')
129 129
130 130 templatefilters.filters.update({'utcdate': utcdate,
131 131 'svnisodate': svnisodate,
132 132 'svnutcdate': svnutcdate})
133 133
134 134 # make keyword tools accessible
135 135 kwtools = {'templater': None, 'hgcmd': ''}
136 136
137 137 def _defaultkwmaps(ui):
138 138 '''Returns default keywordmaps according to keywordset configuration.'''
139 139 templates = {
140 140 'Revision': '{node|short}',
141 141 'Author': '{author|user}',
142 142 }
143 143 kwsets = ({
144 144 'Date': '{date|utcdate}',
145 145 'RCSfile': '{file|basename},v',
146 146 'RCSFile': '{file|basename},v', # kept for backwards compatibility
147 147 # with hg-keyword
148 148 'Source': '{root}/{file},v',
149 149 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
150 150 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
151 151 }, {
152 152 'Date': '{date|svnisodate}',
153 153 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
154 154 'LastChangedRevision': '{node|short}',
155 155 'LastChangedBy': '{author|user}',
156 156 'LastChangedDate': '{date|svnisodate}',
157 157 })
158 158 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
159 159 return templates
160 160
161 161 def _shrinktext(text, subfunc):
162 162 '''Helper for keyword expansion removal in text.
163 163 Depending on subfunc also returns number of substitutions.'''
164 164 return subfunc(r'$\1$', text)
165 165
166 166 def _preselect(wstatus, changed):
167 167 '''Retrieves modfied and added files from a working directory state
168 168 and returns the subset of each contained in given changed files
169 169 retrieved from a change context.'''
170 170 modified, added = wstatus[:2]
171 171 modified = [f for f in modified if f in changed]
172 172 added = [f for f in added if f in changed]
173 173 return modified, added
174 174
175 175
176 176 class kwtemplater(object):
177 177 '''
178 178 Sets up keyword templates, corresponding keyword regex, and
179 179 provides keyword substitution functions.
180 180 '''
181 181
182 182 def __init__(self, ui, repo, inc, exc):
183 183 self.ui = ui
184 184 self.repo = repo
185 185 self.match = match.match(repo.root, '', [], inc, exc)
186 186 self.restrict = kwtools['hgcmd'] in restricted.split()
187 187 self.record = False
188 188
189 189 kwmaps = self.ui.configitems('keywordmaps')
190 190 if kwmaps: # override default templates
191 191 self.templates = dict((k, templater.parsestring(v, False))
192 192 for k, v in kwmaps)
193 193 else:
194 194 self.templates = _defaultkwmaps(self.ui)
195 195
196 196 @util.propertycache
197 197 def escape(self):
198 198 '''Returns bar-separated and escaped keywords.'''
199 199 return '|'.join(map(re.escape, self.templates.keys()))
200 200
201 201 @util.propertycache
202 202 def rekw(self):
203 203 '''Returns regex for unexpanded keywords.'''
204 204 return re.compile(r'\$(%s)\$' % self.escape)
205 205
206 206 @util.propertycache
207 207 def rekwexp(self):
208 208 '''Returns regex for expanded keywords.'''
209 209 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
210 210
211 211 def substitute(self, data, path, ctx, subfunc):
212 212 '''Replaces keywords in data with expanded template.'''
213 213 def kwsub(mobj):
214 214 kw = mobj.group(1)
215 215 ct = cmdutil.changeset_templater(self.ui, self.repo,
216 216 False, None, '', False)
217 217 ct.use_template(self.templates[kw])
218 218 self.ui.pushbuffer()
219 219 ct.show(ctx, root=self.repo.root, file=path)
220 220 ekw = templatefilters.firstline(self.ui.popbuffer())
221 221 return '$%s: %s $' % (kw, ekw)
222 222 return subfunc(kwsub, data)
223 223
224 224 def linkctx(self, path, fileid):
225 225 '''Similar to filelog.linkrev, but returns a changectx.'''
226 226 return self.repo.filectx(path, fileid=fileid).changectx()
227 227
228 228 def expand(self, path, node, data):
229 229 '''Returns data with keywords expanded.'''
230 230 if not self.restrict and self.match(path) and not util.binary(data):
231 231 ctx = self.linkctx(path, node)
232 232 return self.substitute(data, path, ctx, self.rekw.sub)
233 233 return data
234 234
235 235 def iskwfile(self, cand, ctx):
236 236 '''Returns subset of candidates which are configured for keyword
237 237 expansion are not symbolic links.'''
238 238 return [f for f in cand if self.match(f) and not 'l' in ctx.flags(f)]
239 239
240 240 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
241 241 '''Overwrites selected files expanding/shrinking keywords.'''
242 242 if self.restrict or lookup or self.record: # exclude kw_copy
243 243 candidates = self.iskwfile(candidates, ctx)
244 244 if not candidates:
245 245 return
246 246 kwcmd = self.restrict and lookup # kwexpand/kwshrink
247 247 if self.restrict or expand and lookup:
248 248 mf = ctx.manifest()
249 249 lctx = ctx
250 250 re_kw = (self.restrict or rekw) and self.rekw or self.rekwexp
251 251 msg = (expand and _('overwriting %s expanding keywords\n')
252 252 or _('overwriting %s shrinking keywords\n'))
253 253 for f in candidates:
254 254 if self.restrict:
255 255 data = self.repo.file(f).read(mf[f])
256 256 else:
257 257 data = self.repo.wread(f)
258 258 if util.binary(data):
259 259 continue
260 260 if expand:
261 261 if lookup:
262 262 lctx = self.linkctx(f, mf[f])
263 263 data, found = self.substitute(data, f, lctx, re_kw.subn)
264 264 elif self.restrict:
265 265 found = re_kw.search(data)
266 266 else:
267 267 data, found = _shrinktext(data, re_kw.subn)
268 268 if found:
269 269 self.ui.note(msg % f)
270 270 self.repo.wwrite(f, data, ctx.flags(f))
271 271 if kwcmd:
272 272 self.repo.dirstate.normal(f)
273 273 elif self.record:
274 274 self.repo.dirstate.normallookup(f)
275 275
276 276 def shrink(self, fname, text):
277 277 '''Returns text with all keyword substitutions removed.'''
278 278 if self.match(fname) and not util.binary(text):
279 279 return _shrinktext(text, self.rekwexp.sub)
280 280 return text
281 281
282 282 def shrinklines(self, fname, lines):
283 283 '''Returns lines with keyword substitutions removed.'''
284 284 if self.match(fname):
285 285 text = ''.join(lines)
286 286 if not util.binary(text):
287 287 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
288 288 return lines
289 289
290 290 def wread(self, fname, data):
291 291 '''If in restricted mode returns data read from wdir with
292 292 keyword substitutions removed.'''
293 293 return self.restrict and self.shrink(fname, data) or data
294 294
295 295 class kwfilelog(filelog.filelog):
296 296 '''
297 297 Subclass of filelog to hook into its read, add, cmp methods.
298 298 Keywords are "stored" unexpanded, and processed on reading.
299 299 '''
300 300 def __init__(self, opener, kwt, path):
301 301 super(kwfilelog, self).__init__(opener, path)
302 302 self.kwt = kwt
303 303 self.path = path
304 304
305 305 def read(self, node):
306 306 '''Expands keywords when reading filelog.'''
307 307 data = super(kwfilelog, self).read(node)
308 308 if self.renamed(node):
309 309 return data
310 310 return self.kwt.expand(self.path, node, data)
311 311
312 312 def add(self, text, meta, tr, link, p1=None, p2=None):
313 313 '''Removes keyword substitutions when adding to filelog.'''
314 314 text = self.kwt.shrink(self.path, text)
315 315 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
316 316
317 317 def cmp(self, node, text):
318 318 '''Removes keyword substitutions for comparison.'''
319 319 text = self.kwt.shrink(self.path, text)
320 320 return super(kwfilelog, self).cmp(node, text)
321 321
322 322 def _status(ui, repo, kwt, *pats, **opts):
323 323 '''Bails out if [keyword] configuration is not active.
324 324 Returns status of working directory.'''
325 325 if kwt:
326 326 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
327 327 unknown=opts.get('unknown') or opts.get('all'))
328 328 if ui.configitems('keyword'):
329 329 raise util.Abort(_('[keyword] patterns cannot match'))
330 330 raise util.Abort(_('no [keyword] patterns configured'))
331 331
332 332 def _kwfwrite(ui, repo, expand, *pats, **opts):
333 333 '''Selects files and passes them to kwtemplater.overwrite.'''
334 334 wctx = repo[None]
335 335 if len(wctx.parents()) > 1:
336 336 raise util.Abort(_('outstanding uncommitted merge'))
337 337 kwt = kwtools['templater']
338 338 wlock = repo.wlock()
339 339 try:
340 340 status = _status(ui, repo, kwt, *pats, **opts)
341 341 modified, added, removed, deleted, unknown, ignored, clean = status
342 342 if modified or added or removed or deleted:
343 343 raise util.Abort(_('outstanding uncommitted changes'))
344 344 kwt.overwrite(wctx, clean, True, expand)
345 345 finally:
346 346 wlock.release()
347 347
348 348 def demo(ui, repo, *args, **opts):
349 349 '''print [keywordmaps] configuration and an expansion example
350 350
351 351 Show current, custom, or default keyword template maps and their
352 352 expansions.
353 353
354 354 Extend the current configuration by specifying maps as arguments
355 355 and using -f/--rcfile to source an external hgrc file.
356 356
357 357 Use -d/--default to disable current configuration.
358 358
359 359 See :hg:`help templates` for information on templates and filters.
360 360 '''
361 361 def demoitems(section, items):
362 362 ui.write('[%s]\n' % section)
363 363 for k, v in sorted(items):
364 364 ui.write('%s = %s\n' % (k, v))
365 365
366 366 fn = 'demo.txt'
367 367 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
368 368 ui.note(_('creating temporary repository at %s\n') % tmpdir)
369 369 repo = localrepo.localrepository(ui, tmpdir, True)
370 370 ui.setconfig('keyword', fn, '')
371 371 svn = ui.configbool('keywordset', 'svn')
372 372 # explicitly set keywordset for demo output
373 373 ui.setconfig('keywordset', 'svn', svn)
374 374
375 375 uikwmaps = ui.configitems('keywordmaps')
376 376 if args or opts.get('rcfile'):
377 377 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
378 378 if uikwmaps:
379 379 ui.status(_('\textending current template maps\n'))
380 380 if opts.get('default') or not uikwmaps:
381 381 if svn:
382 382 ui.status(_('\toverriding default svn keywordset\n'))
383 383 else:
384 384 ui.status(_('\toverriding default cvs keywordset\n'))
385 385 if opts.get('rcfile'):
386 386 ui.readconfig(opts.get('rcfile'))
387 387 if args:
388 388 # simulate hgrc parsing
389 389 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
390 390 fp = repo.opener('hgrc', 'w')
391 391 fp.writelines(rcmaps)
392 392 fp.close()
393 393 ui.readconfig(repo.join('hgrc'))
394 394 kwmaps = dict(ui.configitems('keywordmaps'))
395 395 elif opts.get('default'):
396 396 if svn:
397 397 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
398 398 else:
399 399 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
400 400 kwmaps = _defaultkwmaps(ui)
401 401 if uikwmaps:
402 402 ui.status(_('\tdisabling current template maps\n'))
403 403 for k, v in kwmaps.iteritems():
404 404 ui.setconfig('keywordmaps', k, v)
405 405 else:
406 406 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
407 407 kwmaps = dict(uikwmaps) or _defaultkwmaps(ui)
408 408
409 409 uisetup(ui)
410 410 reposetup(ui, repo)
411 411 ui.write('[extensions]\nkeyword =\n')
412 412 demoitems('keyword', ui.configitems('keyword'))
413 413 demoitems('keywordset', ui.configitems('keywordset'))
414 414 demoitems('keywordmaps', kwmaps.iteritems())
415 415 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
416 repo.wopener(fn, 'w').write(keywords)
416 repo.wopener.write(fn, keywords)
417 417 repo[None].add([fn])
418 418 ui.note(_('\nkeywords written to %s:\n') % fn)
419 419 ui.note(keywords)
420 420 repo.dirstate.setbranch('demobranch')
421 421 for name, cmd in ui.configitems('hooks'):
422 422 if name.split('.', 1)[0].find('commit') > -1:
423 423 repo.ui.setconfig('hooks', name, '')
424 424 msg = _('hg keyword configuration and expansion example')
425 425 ui.note("hg ci -m '%s'\n" % msg)
426 426 repo.commit(text=msg)
427 427 ui.status(_('\n\tkeywords expanded\n'))
428 428 ui.write(repo.wread(fn))
429 429 shutil.rmtree(tmpdir, ignore_errors=True)
430 430
431 431 def expand(ui, repo, *pats, **opts):
432 432 '''expand keywords in the working directory
433 433
434 434 Run after (re)enabling keyword expansion.
435 435
436 436 kwexpand refuses to run if given files contain local changes.
437 437 '''
438 438 # 3rd argument sets expansion to True
439 439 _kwfwrite(ui, repo, True, *pats, **opts)
440 440
441 441 def files(ui, repo, *pats, **opts):
442 442 '''show files configured for keyword expansion
443 443
444 444 List which files in the working directory are matched by the
445 445 [keyword] configuration patterns.
446 446
447 447 Useful to prevent inadvertent keyword expansion and to speed up
448 448 execution by including only files that are actual candidates for
449 449 expansion.
450 450
451 451 See :hg:`help keyword` on how to construct patterns both for
452 452 inclusion and exclusion of files.
453 453
454 454 With -A/--all and -v/--verbose the codes used to show the status
455 455 of files are::
456 456
457 457 K = keyword expansion candidate
458 458 k = keyword expansion candidate (not tracked)
459 459 I = ignored
460 460 i = ignored (not tracked)
461 461 '''
462 462 kwt = kwtools['templater']
463 463 status = _status(ui, repo, kwt, *pats, **opts)
464 464 cwd = pats and repo.getcwd() or ''
465 465 modified, added, removed, deleted, unknown, ignored, clean = status
466 466 files = []
467 467 if not opts.get('unknown') or opts.get('all'):
468 468 files = sorted(modified + added + clean)
469 469 wctx = repo[None]
470 470 kwfiles = kwt.iskwfile(files, wctx)
471 471 kwdeleted = kwt.iskwfile(deleted, wctx)
472 472 kwunknown = kwt.iskwfile(unknown, wctx)
473 473 if not opts.get('ignore') or opts.get('all'):
474 474 showfiles = kwfiles, kwdeleted, kwunknown
475 475 else:
476 476 showfiles = [], [], []
477 477 if opts.get('all') or opts.get('ignore'):
478 478 showfiles += ([f for f in files if f not in kwfiles],
479 479 [f for f in unknown if f not in kwunknown])
480 480 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
481 481 kwstates = zip('K!kIi', showfiles, kwlabels)
482 482 for char, filenames, kwstate in kwstates:
483 483 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
484 484 for f in filenames:
485 485 ui.write(fmt % repo.pathto(f, cwd), label='kwfiles.' + kwstate)
486 486
487 487 def shrink(ui, repo, *pats, **opts):
488 488 '''revert expanded keywords in the working directory
489 489
490 490 Must be run before changing/disabling active keywords.
491 491
492 492 kwshrink refuses to run if given files contain local changes.
493 493 '''
494 494 # 3rd argument sets expansion to False
495 495 _kwfwrite(ui, repo, False, *pats, **opts)
496 496
497 497
498 498 def uisetup(ui):
499 499 ''' Monkeypatches dispatch._parse to retrieve user command.'''
500 500
501 501 def kwdispatch_parse(orig, ui, args):
502 502 '''Monkeypatch dispatch._parse to obtain running hg command.'''
503 503 cmd, func, args, options, cmdoptions = orig(ui, args)
504 504 kwtools['hgcmd'] = cmd
505 505 return cmd, func, args, options, cmdoptions
506 506
507 507 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
508 508
509 509 def reposetup(ui, repo):
510 510 '''Sets up repo as kwrepo for keyword substitution.
511 511 Overrides file method to return kwfilelog instead of filelog
512 512 if file matches user configuration.
513 513 Wraps commit to overwrite configured files with updated
514 514 keyword substitutions.
515 515 Monkeypatches patch and webcommands.'''
516 516
517 517 try:
518 518 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
519 519 or '.hg' in util.splitpath(repo.root)
520 520 or repo._url.startswith('bundle:')):
521 521 return
522 522 except AttributeError:
523 523 pass
524 524
525 525 inc, exc = [], ['.hg*']
526 526 for pat, opt in ui.configitems('keyword'):
527 527 if opt != 'ignore':
528 528 inc.append(pat)
529 529 else:
530 530 exc.append(pat)
531 531 if not inc:
532 532 return
533 533
534 534 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
535 535
536 536 class kwrepo(repo.__class__):
537 537 def file(self, f):
538 538 if f[0] == '/':
539 539 f = f[1:]
540 540 return kwfilelog(self.sopener, kwt, f)
541 541
542 542 def wread(self, filename):
543 543 data = super(kwrepo, self).wread(filename)
544 544 return kwt.wread(filename, data)
545 545
546 546 def commit(self, *args, **opts):
547 547 # use custom commitctx for user commands
548 548 # other extensions can still wrap repo.commitctx directly
549 549 self.commitctx = self.kwcommitctx
550 550 try:
551 551 return super(kwrepo, self).commit(*args, **opts)
552 552 finally:
553 553 del self.commitctx
554 554
555 555 def kwcommitctx(self, ctx, error=False):
556 556 n = super(kwrepo, self).commitctx(ctx, error)
557 557 # no lock needed, only called from repo.commit() which already locks
558 558 if not kwt.record:
559 559 restrict = kwt.restrict
560 560 kwt.restrict = True
561 561 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
562 562 False, True)
563 563 kwt.restrict = restrict
564 564 return n
565 565
566 566 def rollback(self, dryrun=False):
567 567 wlock = self.wlock()
568 568 try:
569 569 if not dryrun:
570 570 changed = self['.'].files()
571 571 ret = super(kwrepo, self).rollback(dryrun)
572 572 if not dryrun:
573 573 ctx = self['.']
574 574 modified, added = _preselect(self[None].status(), changed)
575 575 kwt.overwrite(ctx, modified, True, True)
576 576 kwt.overwrite(ctx, added, True, False)
577 577 return ret
578 578 finally:
579 579 wlock.release()
580 580
581 581 # monkeypatches
582 582 def kwpatchfile_init(orig, self, ui, fname, opener,
583 583 missing=False, eolmode=None):
584 584 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
585 585 rejects or conflicts due to expanded keywords in working dir.'''
586 586 orig(self, ui, fname, opener, missing, eolmode)
587 587 # shrink keywords read from working dir
588 588 self.lines = kwt.shrinklines(self.fname, self.lines)
589 589
590 590 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
591 591 opts=None, prefix=''):
592 592 '''Monkeypatch patch.diff to avoid expansion.'''
593 593 kwt.restrict = True
594 594 return orig(repo, node1, node2, match, changes, opts, prefix)
595 595
596 596 def kwweb_skip(orig, web, req, tmpl):
597 597 '''Wraps webcommands.x turning off keyword expansion.'''
598 598 kwt.match = util.never
599 599 return orig(web, req, tmpl)
600 600
601 601 def kw_copy(orig, ui, repo, pats, opts, rename=False):
602 602 '''Wraps cmdutil.copy so that copy/rename destinations do not
603 603 contain expanded keywords.
604 604 Note that the source of a regular file destination may also be a
605 605 symlink:
606 606 hg cp sym x -> x is symlink
607 607 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
608 608 For the latter we have to follow the symlink to find out whether its
609 609 target is configured for expansion and we therefore must unexpand the
610 610 keywords in the destination.'''
611 611 orig(ui, repo, pats, opts, rename)
612 612 if opts.get('dry_run'):
613 613 return
614 614 wctx = repo[None]
615 615 cwd = repo.getcwd()
616 616
617 617 def haskwsource(dest):
618 618 '''Returns true if dest is a regular file and configured for
619 619 expansion or a symlink which points to a file configured for
620 620 expansion. '''
621 621 source = repo.dirstate.copied(dest)
622 622 if 'l' in wctx.flags(source):
623 623 source = scmutil.canonpath(repo.root, cwd,
624 624 os.path.realpath(source))
625 625 return kwt.match(source)
626 626
627 627 candidates = [f for f in repo.dirstate.copies() if
628 628 not 'l' in wctx.flags(f) and haskwsource(f)]
629 629 kwt.overwrite(wctx, candidates, False, False)
630 630
631 631 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
632 632 '''Wraps record.dorecord expanding keywords after recording.'''
633 633 wlock = repo.wlock()
634 634 try:
635 635 # record returns 0 even when nothing has changed
636 636 # therefore compare nodes before and after
637 637 kwt.record = True
638 638 ctx = repo['.']
639 639 wstatus = repo[None].status()
640 640 ret = orig(ui, repo, commitfunc, *pats, **opts)
641 641 recctx = repo['.']
642 642 if ctx != recctx:
643 643 modified, added = _preselect(wstatus, recctx.files())
644 644 kwt.restrict = False
645 645 kwt.overwrite(recctx, modified, False, True)
646 646 kwt.overwrite(recctx, added, False, True, True)
647 647 kwt.restrict = True
648 648 return ret
649 649 finally:
650 650 wlock.release()
651 651
652 652 def kwfilectx_cmp(orig, self, fctx):
653 653 # keyword affects data size, comparing wdir and filelog size does
654 654 # not make sense
655 655 if (fctx._filerev is None and
656 656 (self._repo._encodefilterpats or
657 657 kwt.match(fctx.path()) and not 'l' in fctx.flags()) or
658 658 self.size() == fctx.size()):
659 659 return self._filelog.cmp(self._filenode, fctx.data())
660 660 return True
661 661
662 662 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
663 663 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
664 664 extensions.wrapfunction(patch, 'diff', kw_diff)
665 665 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
666 666 for c in 'annotate changeset rev filediff diff'.split():
667 667 extensions.wrapfunction(webcommands, c, kwweb_skip)
668 668 for name in recordextensions.split():
669 669 try:
670 670 record = extensions.find(name)
671 671 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
672 672 except KeyError:
673 673 pass
674 674
675 675 repo.__class__ = kwrepo
676 676
677 677 cmdtable = {
678 678 'kwdemo':
679 679 (demo,
680 680 [('d', 'default', None, _('show default keyword template maps')),
681 681 ('f', 'rcfile', '',
682 682 _('read maps from rcfile'), _('FILE'))],
683 683 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
684 684 'kwexpand': (expand, commands.walkopts,
685 685 _('hg kwexpand [OPTION]... [FILE]...')),
686 686 'kwfiles':
687 687 (files,
688 688 [('A', 'all', None, _('show keyword status flags of all files')),
689 689 ('i', 'ignore', None, _('show files excluded from expansion')),
690 690 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
691 691 ] + commands.walkopts,
692 692 _('hg kwfiles [OPTION]... [FILE]...')),
693 693 'kwshrink': (shrink, commands.walkopts,
694 694 _('hg kwshrink [OPTION]... [FILE]...')),
695 695 }
@@ -1,3300 +1,3300 b''
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 You will by default be managing a patch queue named "patches". You can
42 42 create other, independent patch queues with the :hg:`qqueue` command.
43 43 '''
44 44
45 45 from mercurial.i18n import _
46 46 from mercurial.node import bin, hex, short, nullid, nullrev
47 47 from mercurial.lock import release
48 48 from mercurial import commands, cmdutil, hg, patch, scmutil, util
49 49 from mercurial import repair, extensions, url, error
50 50 import os, sys, re, errno, shutil
51 51
52 52 commands.norepo += " qclone"
53 53
54 54 # Patch names looks like unix-file names.
55 55 # They must be joinable with queue directory and result in the patch path.
56 56 normname = util.normpath
57 57
58 58 class statusentry(object):
59 59 def __init__(self, node, name):
60 60 self.node, self.name = node, name
61 61 def __repr__(self):
62 62 return hex(self.node) + ':' + self.name
63 63
64 64 class patchheader(object):
65 65 def __init__(self, pf, plainmode=False):
66 66 def eatdiff(lines):
67 67 while lines:
68 68 l = lines[-1]
69 69 if (l.startswith("diff -") or
70 70 l.startswith("Index:") or
71 71 l.startswith("===========")):
72 72 del lines[-1]
73 73 else:
74 74 break
75 75 def eatempty(lines):
76 76 while lines:
77 77 if not lines[-1].strip():
78 78 del lines[-1]
79 79 else:
80 80 break
81 81
82 82 message = []
83 83 comments = []
84 84 user = None
85 85 date = None
86 86 parent = None
87 87 format = None
88 88 subject = None
89 89 branch = None
90 90 nodeid = None
91 91 diffstart = 0
92 92
93 93 for line in file(pf):
94 94 line = line.rstrip()
95 95 if (line.startswith('diff --git')
96 96 or (diffstart and line.startswith('+++ '))):
97 97 diffstart = 2
98 98 break
99 99 diffstart = 0 # reset
100 100 if line.startswith("--- "):
101 101 diffstart = 1
102 102 continue
103 103 elif format == "hgpatch":
104 104 # parse values when importing the result of an hg export
105 105 if line.startswith("# User "):
106 106 user = line[7:]
107 107 elif line.startswith("# Date "):
108 108 date = line[7:]
109 109 elif line.startswith("# Parent "):
110 110 parent = line[9:]
111 111 elif line.startswith("# Branch "):
112 112 branch = line[9:]
113 113 elif line.startswith("# Node ID "):
114 114 nodeid = line[10:]
115 115 elif not line.startswith("# ") and line:
116 116 message.append(line)
117 117 format = None
118 118 elif line == '# HG changeset patch':
119 119 message = []
120 120 format = "hgpatch"
121 121 elif (format != "tagdone" and (line.startswith("Subject: ") or
122 122 line.startswith("subject: "))):
123 123 subject = line[9:]
124 124 format = "tag"
125 125 elif (format != "tagdone" and (line.startswith("From: ") or
126 126 line.startswith("from: "))):
127 127 user = line[6:]
128 128 format = "tag"
129 129 elif (format != "tagdone" and (line.startswith("Date: ") or
130 130 line.startswith("date: "))):
131 131 date = line[6:]
132 132 format = "tag"
133 133 elif format == "tag" and line == "":
134 134 # when looking for tags (subject: from: etc) they
135 135 # end once you find a blank line in the source
136 136 format = "tagdone"
137 137 elif message or line:
138 138 message.append(line)
139 139 comments.append(line)
140 140
141 141 eatdiff(message)
142 142 eatdiff(comments)
143 143 # Remember the exact starting line of the patch diffs before consuming
144 144 # empty lines, for external use by TortoiseHg and others
145 145 self.diffstartline = len(comments)
146 146 eatempty(message)
147 147 eatempty(comments)
148 148
149 149 # make sure message isn't empty
150 150 if format and format.startswith("tag") and subject:
151 151 message.insert(0, "")
152 152 message.insert(0, subject)
153 153
154 154 self.message = message
155 155 self.comments = comments
156 156 self.user = user
157 157 self.date = date
158 158 self.parent = parent
159 159 # nodeid and branch are for external use by TortoiseHg and others
160 160 self.nodeid = nodeid
161 161 self.branch = branch
162 162 self.haspatch = diffstart > 1
163 163 self.plainmode = plainmode
164 164
165 165 def setuser(self, user):
166 166 if not self.updateheader(['From: ', '# User '], user):
167 167 try:
168 168 patchheaderat = self.comments.index('# HG changeset patch')
169 169 self.comments.insert(patchheaderat + 1, '# User ' + user)
170 170 except ValueError:
171 171 if self.plainmode or self._hasheader(['Date: ']):
172 172 self.comments = ['From: ' + user] + self.comments
173 173 else:
174 174 tmp = ['# HG changeset patch', '# User ' + user, '']
175 175 self.comments = tmp + self.comments
176 176 self.user = user
177 177
178 178 def setdate(self, date):
179 179 if not self.updateheader(['Date: ', '# Date '], date):
180 180 try:
181 181 patchheaderat = self.comments.index('# HG changeset patch')
182 182 self.comments.insert(patchheaderat + 1, '# Date ' + date)
183 183 except ValueError:
184 184 if self.plainmode or self._hasheader(['From: ']):
185 185 self.comments = ['Date: ' + date] + self.comments
186 186 else:
187 187 tmp = ['# HG changeset patch', '# Date ' + date, '']
188 188 self.comments = tmp + self.comments
189 189 self.date = date
190 190
191 191 def setparent(self, parent):
192 192 if not self.updateheader(['# Parent '], parent):
193 193 try:
194 194 patchheaderat = self.comments.index('# HG changeset patch')
195 195 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
196 196 except ValueError:
197 197 pass
198 198 self.parent = parent
199 199
200 200 def setmessage(self, message):
201 201 if self.comments:
202 202 self._delmsg()
203 203 self.message = [message]
204 204 self.comments += self.message
205 205
206 206 def updateheader(self, prefixes, new):
207 207 '''Update all references to a field in the patch header.
208 208 Return whether the field is present.'''
209 209 res = False
210 210 for prefix in prefixes:
211 211 for i in xrange(len(self.comments)):
212 212 if self.comments[i].startswith(prefix):
213 213 self.comments[i] = prefix + new
214 214 res = True
215 215 break
216 216 return res
217 217
218 218 def _hasheader(self, prefixes):
219 219 '''Check if a header starts with any of the given prefixes.'''
220 220 for prefix in prefixes:
221 221 for comment in self.comments:
222 222 if comment.startswith(prefix):
223 223 return True
224 224 return False
225 225
226 226 def __str__(self):
227 227 if not self.comments:
228 228 return ''
229 229 return '\n'.join(self.comments) + '\n\n'
230 230
231 231 def _delmsg(self):
232 232 '''Remove existing message, keeping the rest of the comments fields.
233 233 If comments contains 'subject: ', message will prepend
234 234 the field and a blank line.'''
235 235 if self.message:
236 236 subj = 'subject: ' + self.message[0].lower()
237 237 for i in xrange(len(self.comments)):
238 238 if subj == self.comments[i].lower():
239 239 del self.comments[i]
240 240 self.message = self.message[2:]
241 241 break
242 242 ci = 0
243 243 for mi in self.message:
244 244 while mi != self.comments[ci]:
245 245 ci += 1
246 246 del self.comments[ci]
247 247
248 248 class queue(object):
249 249 def __init__(self, ui, path, patchdir=None):
250 250 self.basepath = path
251 251 try:
252 252 fh = open(os.path.join(path, 'patches.queue'))
253 253 cur = fh.read().rstrip()
254 254 fh.close()
255 255 if not cur:
256 256 curpath = os.path.join(path, 'patches')
257 257 else:
258 258 curpath = os.path.join(path, 'patches-' + cur)
259 259 except IOError:
260 260 curpath = os.path.join(path, 'patches')
261 261 self.path = patchdir or curpath
262 262 self.opener = scmutil.opener(self.path)
263 263 self.ui = ui
264 264 self.applied_dirty = 0
265 265 self.series_dirty = 0
266 266 self.added = []
267 267 self.series_path = "series"
268 268 self.status_path = "status"
269 269 self.guards_path = "guards"
270 270 self.active_guards = None
271 271 self.guards_dirty = False
272 272 # Handle mq.git as a bool with extended values
273 273 try:
274 274 gitmode = ui.configbool('mq', 'git', None)
275 275 if gitmode is None:
276 276 raise error.ConfigError()
277 277 self.gitmode = gitmode and 'yes' or 'no'
278 278 except error.ConfigError:
279 279 self.gitmode = ui.config('mq', 'git', 'auto').lower()
280 280 self.plainmode = ui.configbool('mq', 'plain', False)
281 281
282 282 @util.propertycache
283 283 def applied(self):
284 284 if os.path.exists(self.join(self.status_path)):
285 285 def parselines(lines):
286 286 for l in lines:
287 287 entry = l.split(':', 1)
288 288 if len(entry) > 1:
289 289 n, name = entry
290 290 yield statusentry(bin(n), name)
291 291 elif l.strip():
292 292 self.ui.warn(_('malformated mq status line: %s\n') % entry)
293 293 # else we ignore empty lines
294 lines = self.opener(self.status_path).read().splitlines()
294 lines = self.opener.read(self.status_path).splitlines()
295 295 return list(parselines(lines))
296 296 return []
297 297
298 298 @util.propertycache
299 299 def full_series(self):
300 300 if os.path.exists(self.join(self.series_path)):
301 return self.opener(self.series_path).read().splitlines()
301 return self.opener.read(self.series_path).splitlines()
302 302 return []
303 303
304 304 @util.propertycache
305 305 def series(self):
306 306 self.parse_series()
307 307 return self.series
308 308
309 309 @util.propertycache
310 310 def series_guards(self):
311 311 self.parse_series()
312 312 return self.series_guards
313 313
314 314 def invalidate(self):
315 315 for a in 'applied full_series series series_guards'.split():
316 316 if a in self.__dict__:
317 317 delattr(self, a)
318 318 self.applied_dirty = 0
319 319 self.series_dirty = 0
320 320 self.guards_dirty = False
321 321 self.active_guards = None
322 322
323 323 def diffopts(self, opts={}, patchfn=None):
324 324 diffopts = patch.diffopts(self.ui, opts)
325 325 if self.gitmode == 'auto':
326 326 diffopts.upgrade = True
327 327 elif self.gitmode == 'keep':
328 328 pass
329 329 elif self.gitmode in ('yes', 'no'):
330 330 diffopts.git = self.gitmode == 'yes'
331 331 else:
332 332 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
333 333 ' got %s') % self.gitmode)
334 334 if patchfn:
335 335 diffopts = self.patchopts(diffopts, patchfn)
336 336 return diffopts
337 337
338 338 def patchopts(self, diffopts, *patches):
339 339 """Return a copy of input diff options with git set to true if
340 340 referenced patch is a git patch and should be preserved as such.
341 341 """
342 342 diffopts = diffopts.copy()
343 343 if not diffopts.git and self.gitmode == 'keep':
344 344 for patchfn in patches:
345 345 patchf = self.opener(patchfn, 'r')
346 346 # if the patch was a git patch, refresh it as a git patch
347 347 for line in patchf:
348 348 if line.startswith('diff --git'):
349 349 diffopts.git = True
350 350 break
351 351 patchf.close()
352 352 return diffopts
353 353
354 354 def join(self, *p):
355 355 return os.path.join(self.path, *p)
356 356
357 357 def find_series(self, patch):
358 358 def matchpatch(l):
359 359 l = l.split('#', 1)[0]
360 360 return l.strip() == patch
361 361 for index, l in enumerate(self.full_series):
362 362 if matchpatch(l):
363 363 return index
364 364 return None
365 365
366 366 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
367 367
368 368 def parse_series(self):
369 369 self.series = []
370 370 self.series_guards = []
371 371 for l in self.full_series:
372 372 h = l.find('#')
373 373 if h == -1:
374 374 patch = l
375 375 comment = ''
376 376 elif h == 0:
377 377 continue
378 378 else:
379 379 patch = l[:h]
380 380 comment = l[h:]
381 381 patch = patch.strip()
382 382 if patch:
383 383 if patch in self.series:
384 384 raise util.Abort(_('%s appears more than once in %s') %
385 385 (patch, self.join(self.series_path)))
386 386 self.series.append(patch)
387 387 self.series_guards.append(self.guard_re.findall(comment))
388 388
389 389 def check_guard(self, guard):
390 390 if not guard:
391 391 return _('guard cannot be an empty string')
392 392 bad_chars = '# \t\r\n\f'
393 393 first = guard[0]
394 394 if first in '-+':
395 395 return (_('guard %r starts with invalid character: %r') %
396 396 (guard, first))
397 397 for c in bad_chars:
398 398 if c in guard:
399 399 return _('invalid character in guard %r: %r') % (guard, c)
400 400
401 401 def set_active(self, guards):
402 402 for guard in guards:
403 403 bad = self.check_guard(guard)
404 404 if bad:
405 405 raise util.Abort(bad)
406 406 guards = sorted(set(guards))
407 407 self.ui.debug('active guards: %s\n' % ' '.join(guards))
408 408 self.active_guards = guards
409 409 self.guards_dirty = True
410 410
411 411 def active(self):
412 412 if self.active_guards is None:
413 413 self.active_guards = []
414 414 try:
415 guards = self.opener(self.guards_path).read().split()
415 guards = self.opener.read(self.guards_path).split()
416 416 except IOError, err:
417 417 if err.errno != errno.ENOENT:
418 418 raise
419 419 guards = []
420 420 for i, guard in enumerate(guards):
421 421 bad = self.check_guard(guard)
422 422 if bad:
423 423 self.ui.warn('%s:%d: %s\n' %
424 424 (self.join(self.guards_path), i + 1, bad))
425 425 else:
426 426 self.active_guards.append(guard)
427 427 return self.active_guards
428 428
429 429 def set_guards(self, idx, guards):
430 430 for g in guards:
431 431 if len(g) < 2:
432 432 raise util.Abort(_('guard %r too short') % g)
433 433 if g[0] not in '-+':
434 434 raise util.Abort(_('guard %r starts with invalid char') % g)
435 435 bad = self.check_guard(g[1:])
436 436 if bad:
437 437 raise util.Abort(bad)
438 438 drop = self.guard_re.sub('', self.full_series[idx])
439 439 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
440 440 self.parse_series()
441 441 self.series_dirty = True
442 442
443 443 def pushable(self, idx):
444 444 if isinstance(idx, str):
445 445 idx = self.series.index(idx)
446 446 patchguards = self.series_guards[idx]
447 447 if not patchguards:
448 448 return True, None
449 449 guards = self.active()
450 450 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
451 451 if exactneg:
452 452 return False, exactneg[0]
453 453 pos = [g for g in patchguards if g[0] == '+']
454 454 exactpos = [g for g in pos if g[1:] in guards]
455 455 if pos:
456 456 if exactpos:
457 457 return True, exactpos[0]
458 458 return False, pos
459 459 return True, ''
460 460
461 461 def explain_pushable(self, idx, all_patches=False):
462 462 write = all_patches and self.ui.write or self.ui.warn
463 463 if all_patches or self.ui.verbose:
464 464 if isinstance(idx, str):
465 465 idx = self.series.index(idx)
466 466 pushable, why = self.pushable(idx)
467 467 if all_patches and pushable:
468 468 if why is None:
469 469 write(_('allowing %s - no guards in effect\n') %
470 470 self.series[idx])
471 471 else:
472 472 if not why:
473 473 write(_('allowing %s - no matching negative guards\n') %
474 474 self.series[idx])
475 475 else:
476 476 write(_('allowing %s - guarded by %r\n') %
477 477 (self.series[idx], why))
478 478 if not pushable:
479 479 if why:
480 480 write(_('skipping %s - guarded by %r\n') %
481 481 (self.series[idx], why))
482 482 else:
483 483 write(_('skipping %s - no matching guards\n') %
484 484 self.series[idx])
485 485
486 486 def save_dirty(self):
487 487 def write_list(items, path):
488 488 fp = self.opener(path, 'w')
489 489 for i in items:
490 490 fp.write("%s\n" % i)
491 491 fp.close()
492 492 if self.applied_dirty:
493 493 write_list(map(str, self.applied), self.status_path)
494 494 if self.series_dirty:
495 495 write_list(self.full_series, self.series_path)
496 496 if self.guards_dirty:
497 497 write_list(self.active_guards, self.guards_path)
498 498 if self.added:
499 499 qrepo = self.qrepo()
500 500 if qrepo:
501 501 qrepo[None].add(f for f in self.added if f not in qrepo[None])
502 502 self.added = []
503 503
504 504 def removeundo(self, repo):
505 505 undo = repo.sjoin('undo')
506 506 if not os.path.exists(undo):
507 507 return
508 508 try:
509 509 os.unlink(undo)
510 510 except OSError, inst:
511 511 self.ui.warn(_('error removing undo: %s\n') % str(inst))
512 512
513 513 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
514 514 fp=None, changes=None, opts={}):
515 515 stat = opts.get('stat')
516 516 m = cmdutil.match(repo, files, opts)
517 517 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
518 518 changes, stat, fp)
519 519
520 520 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
521 521 # first try just applying the patch
522 522 (err, n) = self.apply(repo, [patch], update_status=False,
523 523 strict=True, merge=rev)
524 524
525 525 if err == 0:
526 526 return (err, n)
527 527
528 528 if n is None:
529 529 raise util.Abort(_("apply failed for patch %s") % patch)
530 530
531 531 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
532 532
533 533 # apply failed, strip away that rev and merge.
534 534 hg.clean(repo, head)
535 535 self.strip(repo, [n], update=False, backup='strip')
536 536
537 537 ctx = repo[rev]
538 538 ret = hg.merge(repo, rev)
539 539 if ret:
540 540 raise util.Abort(_("update returned %d") % ret)
541 541 n = repo.commit(ctx.description(), ctx.user(), force=True)
542 542 if n is None:
543 543 raise util.Abort(_("repo commit failed"))
544 544 try:
545 545 ph = patchheader(mergeq.join(patch), self.plainmode)
546 546 except:
547 547 raise util.Abort(_("unable to read %s") % patch)
548 548
549 549 diffopts = self.patchopts(diffopts, patch)
550 550 patchf = self.opener(patch, "w")
551 551 comments = str(ph)
552 552 if comments:
553 553 patchf.write(comments)
554 554 self.printdiff(repo, diffopts, head, n, fp=patchf)
555 555 patchf.close()
556 556 self.removeundo(repo)
557 557 return (0, n)
558 558
559 559 def qparents(self, repo, rev=None):
560 560 if rev is None:
561 561 (p1, p2) = repo.dirstate.parents()
562 562 if p2 == nullid:
563 563 return p1
564 564 if not self.applied:
565 565 return None
566 566 return self.applied[-1].node
567 567 p1, p2 = repo.changelog.parents(rev)
568 568 if p2 != nullid and p2 in [x.node for x in self.applied]:
569 569 return p2
570 570 return p1
571 571
572 572 def mergepatch(self, repo, mergeq, series, diffopts):
573 573 if not self.applied:
574 574 # each of the patches merged in will have two parents. This
575 575 # can confuse the qrefresh, qdiff, and strip code because it
576 576 # needs to know which parent is actually in the patch queue.
577 577 # so, we insert a merge marker with only one parent. This way
578 578 # the first patch in the queue is never a merge patch
579 579 #
580 580 pname = ".hg.patches.merge.marker"
581 581 n = repo.commit('[mq]: merge marker', force=True)
582 582 self.removeundo(repo)
583 583 self.applied.append(statusentry(n, pname))
584 584 self.applied_dirty = 1
585 585
586 586 head = self.qparents(repo)
587 587
588 588 for patch in series:
589 589 patch = mergeq.lookup(patch, strict=True)
590 590 if not patch:
591 591 self.ui.warn(_("patch %s does not exist\n") % patch)
592 592 return (1, None)
593 593 pushable, reason = self.pushable(patch)
594 594 if not pushable:
595 595 self.explain_pushable(patch, all_patches=True)
596 596 continue
597 597 info = mergeq.isapplied(patch)
598 598 if not info:
599 599 self.ui.warn(_("patch %s is not applied\n") % patch)
600 600 return (1, None)
601 601 rev = info[1]
602 602 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
603 603 if head:
604 604 self.applied.append(statusentry(head, patch))
605 605 self.applied_dirty = 1
606 606 if err:
607 607 return (err, head)
608 608 self.save_dirty()
609 609 return (0, head)
610 610
611 611 def patch(self, repo, patchfile):
612 612 '''Apply patchfile to the working directory.
613 613 patchfile: name of patch file'''
614 614 files = {}
615 615 try:
616 616 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
617 617 files=files, eolmode=None)
618 618 except Exception, inst:
619 619 self.ui.note(str(inst) + '\n')
620 620 if not self.ui.verbose:
621 621 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
622 622 return (False, files, False)
623 623
624 624 return (True, files, fuzz)
625 625
626 626 def apply(self, repo, series, list=False, update_status=True,
627 627 strict=False, patchdir=None, merge=None, all_files=None):
628 628 wlock = lock = tr = None
629 629 try:
630 630 wlock = repo.wlock()
631 631 lock = repo.lock()
632 632 tr = repo.transaction("qpush")
633 633 try:
634 634 ret = self._apply(repo, series, list, update_status,
635 635 strict, patchdir, merge, all_files=all_files)
636 636 tr.close()
637 637 self.save_dirty()
638 638 return ret
639 639 except:
640 640 try:
641 641 tr.abort()
642 642 finally:
643 643 repo.invalidate()
644 644 repo.dirstate.invalidate()
645 645 raise
646 646 finally:
647 647 release(tr, lock, wlock)
648 648 self.removeundo(repo)
649 649
650 650 def _apply(self, repo, series, list=False, update_status=True,
651 651 strict=False, patchdir=None, merge=None, all_files=None):
652 652 '''returns (error, hash)
653 653 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
654 654 # TODO unify with commands.py
655 655 if not patchdir:
656 656 patchdir = self.path
657 657 err = 0
658 658 n = None
659 659 for patchname in series:
660 660 pushable, reason = self.pushable(patchname)
661 661 if not pushable:
662 662 self.explain_pushable(patchname, all_patches=True)
663 663 continue
664 664 self.ui.status(_("applying %s\n") % patchname)
665 665 pf = os.path.join(patchdir, patchname)
666 666
667 667 try:
668 668 ph = patchheader(self.join(patchname), self.plainmode)
669 669 except:
670 670 self.ui.warn(_("unable to read %s\n") % patchname)
671 671 err = 1
672 672 break
673 673
674 674 message = ph.message
675 675 if not message:
676 676 # The commit message should not be translated
677 677 message = "imported patch %s\n" % patchname
678 678 else:
679 679 if list:
680 680 # The commit message should not be translated
681 681 message.append("\nimported patch %s" % patchname)
682 682 message = '\n'.join(message)
683 683
684 684 if ph.haspatch:
685 685 (patcherr, files, fuzz) = self.patch(repo, pf)
686 686 if all_files is not None:
687 687 all_files.update(files)
688 688 patcherr = not patcherr
689 689 else:
690 690 self.ui.warn(_("patch %s is empty\n") % patchname)
691 691 patcherr, files, fuzz = 0, [], 0
692 692
693 693 if merge and files:
694 694 # Mark as removed/merged and update dirstate parent info
695 695 removed = []
696 696 merged = []
697 697 for f in files:
698 698 if os.path.lexists(repo.wjoin(f)):
699 699 merged.append(f)
700 700 else:
701 701 removed.append(f)
702 702 for f in removed:
703 703 repo.dirstate.remove(f)
704 704 for f in merged:
705 705 repo.dirstate.merge(f)
706 706 p1, p2 = repo.dirstate.parents()
707 707 repo.dirstate.setparents(p1, merge)
708 708
709 709 files = cmdutil.updatedir(self.ui, repo, files)
710 710 match = cmdutil.matchfiles(repo, files or [])
711 711 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
712 712
713 713 if n is None:
714 714 raise util.Abort(_("repository commit failed"))
715 715
716 716 if update_status:
717 717 self.applied.append(statusentry(n, patchname))
718 718
719 719 if patcherr:
720 720 self.ui.warn(_("patch failed, rejects left in working dir\n"))
721 721 err = 2
722 722 break
723 723
724 724 if fuzz and strict:
725 725 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
726 726 err = 3
727 727 break
728 728 return (err, n)
729 729
730 730 def _cleanup(self, patches, numrevs, keep=False):
731 731 if not keep:
732 732 r = self.qrepo()
733 733 if r:
734 734 r[None].remove(patches, True)
735 735 else:
736 736 for p in patches:
737 737 os.unlink(self.join(p))
738 738
739 739 if numrevs:
740 740 qfinished = self.applied[:numrevs]
741 741 del self.applied[:numrevs]
742 742 self.applied_dirty = 1
743 743
744 744 unknown = []
745 745
746 746 for (i, p) in sorted([(self.find_series(p), p) for p in patches],
747 747 reverse=True):
748 748 if i is not None:
749 749 del self.full_series[i]
750 750 else:
751 751 unknown.append(p)
752 752
753 753 if unknown:
754 754 if numrevs:
755 755 rev = dict((entry.name, entry.node) for entry in qfinished)
756 756 for p in unknown:
757 757 msg = _('revision %s refers to unknown patches: %s\n')
758 758 self.ui.warn(msg % (short(rev[p]), p))
759 759 else:
760 760 msg = _('unknown patches: %s\n')
761 761 raise util.Abort(''.join(msg % p for p in unknown))
762 762
763 763 self.parse_series()
764 764 self.series_dirty = 1
765 765
766 766 def _revpatches(self, repo, revs):
767 767 firstrev = repo[self.applied[0].node].rev()
768 768 patches = []
769 769 for i, rev in enumerate(revs):
770 770
771 771 if rev < firstrev:
772 772 raise util.Abort(_('revision %d is not managed') % rev)
773 773
774 774 ctx = repo[rev]
775 775 base = self.applied[i].node
776 776 if ctx.node() != base:
777 777 msg = _('cannot delete revision %d above applied patches')
778 778 raise util.Abort(msg % rev)
779 779
780 780 patch = self.applied[i].name
781 781 for fmt in ('[mq]: %s', 'imported patch %s'):
782 782 if ctx.description() == fmt % patch:
783 783 msg = _('patch %s finalized without changeset message\n')
784 784 repo.ui.status(msg % patch)
785 785 break
786 786
787 787 patches.append(patch)
788 788 return patches
789 789
790 790 def finish(self, repo, revs):
791 791 patches = self._revpatches(repo, sorted(revs))
792 792 self._cleanup(patches, len(patches))
793 793
794 794 def delete(self, repo, patches, opts):
795 795 if not patches and not opts.get('rev'):
796 796 raise util.Abort(_('qdelete requires at least one revision or '
797 797 'patch name'))
798 798
799 799 realpatches = []
800 800 for patch in patches:
801 801 patch = self.lookup(patch, strict=True)
802 802 info = self.isapplied(patch)
803 803 if info:
804 804 raise util.Abort(_("cannot delete applied patch %s") % patch)
805 805 if patch not in self.series:
806 806 raise util.Abort(_("patch %s not in series file") % patch)
807 807 if patch not in realpatches:
808 808 realpatches.append(patch)
809 809
810 810 numrevs = 0
811 811 if opts.get('rev'):
812 812 if not self.applied:
813 813 raise util.Abort(_('no patches applied'))
814 814 revs = cmdutil.revrange(repo, opts.get('rev'))
815 815 if len(revs) > 1 and revs[0] > revs[1]:
816 816 revs.reverse()
817 817 revpatches = self._revpatches(repo, revs)
818 818 realpatches += revpatches
819 819 numrevs = len(revpatches)
820 820
821 821 self._cleanup(realpatches, numrevs, opts.get('keep'))
822 822
823 823 def check_toppatch(self, repo):
824 824 if self.applied:
825 825 top = self.applied[-1].node
826 826 patch = self.applied[-1].name
827 827 pp = repo.dirstate.parents()
828 828 if top not in pp:
829 829 raise util.Abort(_("working directory revision is not qtip"))
830 830 return top, patch
831 831 return None, None
832 832
833 833 def check_substate(self, repo):
834 834 '''return list of subrepos at a different revision than substate.
835 835 Abort if any subrepos have uncommitted changes.'''
836 836 inclsubs = []
837 837 wctx = repo[None]
838 838 for s in wctx.substate:
839 839 if wctx.sub(s).dirty(True):
840 840 raise util.Abort(
841 841 _("uncommitted changes in subrepository %s") % s)
842 842 elif wctx.sub(s).dirty():
843 843 inclsubs.append(s)
844 844 return inclsubs
845 845
846 846 def check_localchanges(self, repo, force=False, refresh=True):
847 847 m, a, r, d = repo.status()[:4]
848 848 if (m or a or r or d) and not force:
849 849 if refresh:
850 850 raise util.Abort(_("local changes found, refresh first"))
851 851 else:
852 852 raise util.Abort(_("local changes found"))
853 853 return m, a, r, d
854 854
855 855 _reserved = ('series', 'status', 'guards', '.', '..')
856 856 def check_reserved_name(self, name):
857 857 if name in self._reserved:
858 858 raise util.Abort(_('"%s" cannot be used as the name of a patch')
859 859 % name)
860 860 for prefix in ('.hg', '.mq'):
861 861 if name.startswith(prefix):
862 862 raise util.Abort(_('patch name cannot begin with "%s"')
863 863 % prefix)
864 864 for c in ('#', ':'):
865 865 if c in name:
866 866 raise util.Abort(_('"%s" cannot be used in the name of a patch')
867 867 % c)
868 868
869 869
870 870 def new(self, repo, patchfn, *pats, **opts):
871 871 """options:
872 872 msg: a string or a no-argument function returning a string
873 873 """
874 874 msg = opts.get('msg')
875 875 user = opts.get('user')
876 876 date = opts.get('date')
877 877 if date:
878 878 date = util.parsedate(date)
879 879 diffopts = self.diffopts({'git': opts.get('git')})
880 880 self.check_reserved_name(patchfn)
881 881 if os.path.exists(self.join(patchfn)):
882 882 if os.path.isdir(self.join(patchfn)):
883 883 raise util.Abort(_('"%s" already exists as a directory')
884 884 % patchfn)
885 885 else:
886 886 raise util.Abort(_('patch "%s" already exists') % patchfn)
887 887
888 888 inclsubs = self.check_substate(repo)
889 889 if inclsubs:
890 890 inclsubs.append('.hgsubstate')
891 891 if opts.get('include') or opts.get('exclude') or pats:
892 892 if inclsubs:
893 893 pats = list(pats or []) + inclsubs
894 894 match = cmdutil.match(repo, pats, opts)
895 895 # detect missing files in pats
896 896 def badfn(f, msg):
897 897 if f != '.hgsubstate': # .hgsubstate is auto-created
898 898 raise util.Abort('%s: %s' % (f, msg))
899 899 match.bad = badfn
900 900 m, a, r, d = repo.status(match=match)[:4]
901 901 else:
902 902 m, a, r, d = self.check_localchanges(repo, force=True)
903 903 match = cmdutil.matchfiles(repo, m + a + r + inclsubs)
904 904 if len(repo[None].parents()) > 1:
905 905 raise util.Abort(_('cannot manage merge changesets'))
906 906 commitfiles = m + a + r
907 907 self.check_toppatch(repo)
908 908 insert = self.full_series_end()
909 909 wlock = repo.wlock()
910 910 try:
911 911 try:
912 912 # if patch file write fails, abort early
913 913 p = self.opener(patchfn, "w")
914 914 except IOError, e:
915 915 raise util.Abort(_('cannot write patch "%s": %s')
916 916 % (patchfn, e.strerror))
917 917 try:
918 918 if self.plainmode:
919 919 if user:
920 920 p.write("From: " + user + "\n")
921 921 if not date:
922 922 p.write("\n")
923 923 if date:
924 924 p.write("Date: %d %d\n\n" % date)
925 925 else:
926 926 p.write("# HG changeset patch\n")
927 927 p.write("# Parent "
928 928 + hex(repo[None].p1().node()) + "\n")
929 929 if user:
930 930 p.write("# User " + user + "\n")
931 931 if date:
932 932 p.write("# Date %s %s\n\n" % date)
933 933 if hasattr(msg, '__call__'):
934 934 msg = msg()
935 935 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
936 936 n = repo.commit(commitmsg, user, date, match=match, force=True)
937 937 if n is None:
938 938 raise util.Abort(_("repo commit failed"))
939 939 try:
940 940 self.full_series[insert:insert] = [patchfn]
941 941 self.applied.append(statusentry(n, patchfn))
942 942 self.parse_series()
943 943 self.series_dirty = 1
944 944 self.applied_dirty = 1
945 945 if msg:
946 946 msg = msg + "\n\n"
947 947 p.write(msg)
948 948 if commitfiles:
949 949 parent = self.qparents(repo, n)
950 950 chunks = patch.diff(repo, node1=parent, node2=n,
951 951 match=match, opts=diffopts)
952 952 for chunk in chunks:
953 953 p.write(chunk)
954 954 p.close()
955 955 wlock.release()
956 956 wlock = None
957 957 r = self.qrepo()
958 958 if r:
959 959 r[None].add([patchfn])
960 960 except:
961 961 repo.rollback()
962 962 raise
963 963 except Exception:
964 964 patchpath = self.join(patchfn)
965 965 try:
966 966 os.unlink(patchpath)
967 967 except:
968 968 self.ui.warn(_('error unlinking %s\n') % patchpath)
969 969 raise
970 970 self.removeundo(repo)
971 971 finally:
972 972 release(wlock)
973 973
974 974 def strip(self, repo, revs, update=True, backup="all", force=None):
975 975 wlock = lock = None
976 976 try:
977 977 wlock = repo.wlock()
978 978 lock = repo.lock()
979 979
980 980 if update:
981 981 self.check_localchanges(repo, force=force, refresh=False)
982 982 urev = self.qparents(repo, revs[0])
983 983 hg.clean(repo, urev)
984 984 repo.dirstate.write()
985 985
986 986 self.removeundo(repo)
987 987 for rev in revs:
988 988 repair.strip(self.ui, repo, rev, backup)
989 989 # strip may have unbundled a set of backed up revisions after
990 990 # the actual strip
991 991 self.removeundo(repo)
992 992 finally:
993 993 release(lock, wlock)
994 994
995 995 def isapplied(self, patch):
996 996 """returns (index, rev, patch)"""
997 997 for i, a in enumerate(self.applied):
998 998 if a.name == patch:
999 999 return (i, a.node, a.name)
1000 1000 return None
1001 1001
1002 1002 # if the exact patch name does not exist, we try a few
1003 1003 # variations. If strict is passed, we try only #1
1004 1004 #
1005 1005 # 1) a number to indicate an offset in the series file
1006 1006 # 2) a unique substring of the patch name was given
1007 1007 # 3) patchname[-+]num to indicate an offset in the series file
1008 1008 def lookup(self, patch, strict=False):
1009 1009 patch = patch and str(patch)
1010 1010
1011 1011 def partial_name(s):
1012 1012 if s in self.series:
1013 1013 return s
1014 1014 matches = [x for x in self.series if s in x]
1015 1015 if len(matches) > 1:
1016 1016 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1017 1017 for m in matches:
1018 1018 self.ui.warn(' %s\n' % m)
1019 1019 return None
1020 1020 if matches:
1021 1021 return matches[0]
1022 1022 if self.series and self.applied:
1023 1023 if s == 'qtip':
1024 1024 return self.series[self.series_end(True)-1]
1025 1025 if s == 'qbase':
1026 1026 return self.series[0]
1027 1027 return None
1028 1028
1029 1029 if patch is None:
1030 1030 return None
1031 1031 if patch in self.series:
1032 1032 return patch
1033 1033
1034 1034 if not os.path.isfile(self.join(patch)):
1035 1035 try:
1036 1036 sno = int(patch)
1037 1037 except (ValueError, OverflowError):
1038 1038 pass
1039 1039 else:
1040 1040 if -len(self.series) <= sno < len(self.series):
1041 1041 return self.series[sno]
1042 1042
1043 1043 if not strict:
1044 1044 res = partial_name(patch)
1045 1045 if res:
1046 1046 return res
1047 1047 minus = patch.rfind('-')
1048 1048 if minus >= 0:
1049 1049 res = partial_name(patch[:minus])
1050 1050 if res:
1051 1051 i = self.series.index(res)
1052 1052 try:
1053 1053 off = int(patch[minus + 1:] or 1)
1054 1054 except (ValueError, OverflowError):
1055 1055 pass
1056 1056 else:
1057 1057 if i - off >= 0:
1058 1058 return self.series[i - off]
1059 1059 plus = patch.rfind('+')
1060 1060 if plus >= 0:
1061 1061 res = partial_name(patch[:plus])
1062 1062 if res:
1063 1063 i = self.series.index(res)
1064 1064 try:
1065 1065 off = int(patch[plus + 1:] or 1)
1066 1066 except (ValueError, OverflowError):
1067 1067 pass
1068 1068 else:
1069 1069 if i + off < len(self.series):
1070 1070 return self.series[i + off]
1071 1071 raise util.Abort(_("patch %s not in series") % patch)
1072 1072
1073 1073 def push(self, repo, patch=None, force=False, list=False,
1074 1074 mergeq=None, all=False, move=False, exact=False):
1075 1075 diffopts = self.diffopts()
1076 1076 wlock = repo.wlock()
1077 1077 try:
1078 1078 heads = []
1079 1079 for b, ls in repo.branchmap().iteritems():
1080 1080 heads += ls
1081 1081 if not heads:
1082 1082 heads = [nullid]
1083 1083 if repo.dirstate.p1() not in heads and not exact:
1084 1084 self.ui.status(_("(working directory not at a head)\n"))
1085 1085
1086 1086 if not self.series:
1087 1087 self.ui.warn(_('no patches in series\n'))
1088 1088 return 0
1089 1089
1090 1090 patch = self.lookup(patch)
1091 1091 # Suppose our series file is: A B C and the current 'top'
1092 1092 # patch is B. qpush C should be performed (moving forward)
1093 1093 # qpush B is a NOP (no change) qpush A is an error (can't
1094 1094 # go backwards with qpush)
1095 1095 if patch:
1096 1096 info = self.isapplied(patch)
1097 1097 if info and info[0] >= len(self.applied) - 1:
1098 1098 self.ui.warn(
1099 1099 _('qpush: %s is already at the top\n') % patch)
1100 1100 return 0
1101 1101
1102 1102 pushable, reason = self.pushable(patch)
1103 1103 if pushable:
1104 1104 if self.series.index(patch) < self.series_end():
1105 1105 raise util.Abort(
1106 1106 _("cannot push to a previous patch: %s") % patch)
1107 1107 else:
1108 1108 if reason:
1109 1109 reason = _('guarded by %r') % reason
1110 1110 else:
1111 1111 reason = _('no matching guards')
1112 1112 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1113 1113 return 1
1114 1114 elif all:
1115 1115 patch = self.series[-1]
1116 1116 if self.isapplied(patch):
1117 1117 self.ui.warn(_('all patches are currently applied\n'))
1118 1118 return 0
1119 1119
1120 1120 # Following the above example, starting at 'top' of B:
1121 1121 # qpush should be performed (pushes C), but a subsequent
1122 1122 # qpush without an argument is an error (nothing to
1123 1123 # apply). This allows a loop of "...while hg qpush..." to
1124 1124 # work as it detects an error when done
1125 1125 start = self.series_end()
1126 1126 if start == len(self.series):
1127 1127 self.ui.warn(_('patch series already fully applied\n'))
1128 1128 return 1
1129 1129 if not force:
1130 1130 self.check_localchanges(repo)
1131 1131
1132 1132 if exact:
1133 1133 if move:
1134 1134 raise util.Abort(_("cannot use --exact and --move together"))
1135 1135 if self.applied:
1136 1136 raise util.Abort(_("cannot push --exact with applied patches"))
1137 1137 root = self.series[start]
1138 1138 target = patchheader(self.join(root), self.plainmode).parent
1139 1139 if not target:
1140 1140 raise util.Abort(_("%s does not have a parent recorded" % root))
1141 1141 if not repo[target] == repo['.']:
1142 1142 hg.update(repo, target)
1143 1143
1144 1144 if move:
1145 1145 if not patch:
1146 1146 raise util.Abort(_("please specify the patch to move"))
1147 1147 for i, rpn in enumerate(self.full_series[start:]):
1148 1148 # strip markers for patch guards
1149 1149 if self.guard_re.split(rpn, 1)[0] == patch:
1150 1150 break
1151 1151 index = start + i
1152 1152 assert index < len(self.full_series)
1153 1153 fullpatch = self.full_series[index]
1154 1154 del self.full_series[index]
1155 1155 self.full_series.insert(start, fullpatch)
1156 1156 self.parse_series()
1157 1157 self.series_dirty = 1
1158 1158
1159 1159 self.applied_dirty = 1
1160 1160 if start > 0:
1161 1161 self.check_toppatch(repo)
1162 1162 if not patch:
1163 1163 patch = self.series[start]
1164 1164 end = start + 1
1165 1165 else:
1166 1166 end = self.series.index(patch, start) + 1
1167 1167
1168 1168 s = self.series[start:end]
1169 1169 all_files = set()
1170 1170 try:
1171 1171 if mergeq:
1172 1172 ret = self.mergepatch(repo, mergeq, s, diffopts)
1173 1173 else:
1174 1174 ret = self.apply(repo, s, list, all_files=all_files)
1175 1175 except:
1176 1176 self.ui.warn(_('cleaning up working directory...'))
1177 1177 node = repo.dirstate.p1()
1178 1178 hg.revert(repo, node, None)
1179 1179 # only remove unknown files that we know we touched or
1180 1180 # created while patching
1181 1181 for f in all_files:
1182 1182 if f not in repo.dirstate:
1183 1183 try:
1184 1184 util.unlinkpath(repo.wjoin(f))
1185 1185 except OSError, inst:
1186 1186 if inst.errno != errno.ENOENT:
1187 1187 raise
1188 1188 self.ui.warn(_('done\n'))
1189 1189 raise
1190 1190
1191 1191 if not self.applied:
1192 1192 return ret[0]
1193 1193 top = self.applied[-1].name
1194 1194 if ret[0] and ret[0] > 1:
1195 1195 msg = _("errors during apply, please fix and refresh %s\n")
1196 1196 self.ui.write(msg % top)
1197 1197 else:
1198 1198 self.ui.write(_("now at: %s\n") % top)
1199 1199 return ret[0]
1200 1200
1201 1201 finally:
1202 1202 wlock.release()
1203 1203
1204 1204 def pop(self, repo, patch=None, force=False, update=True, all=False):
1205 1205 wlock = repo.wlock()
1206 1206 try:
1207 1207 if patch:
1208 1208 # index, rev, patch
1209 1209 info = self.isapplied(patch)
1210 1210 if not info:
1211 1211 patch = self.lookup(patch)
1212 1212 info = self.isapplied(patch)
1213 1213 if not info:
1214 1214 raise util.Abort(_("patch %s is not applied") % patch)
1215 1215
1216 1216 if not self.applied:
1217 1217 # Allow qpop -a to work repeatedly,
1218 1218 # but not qpop without an argument
1219 1219 self.ui.warn(_("no patches applied\n"))
1220 1220 return not all
1221 1221
1222 1222 if all:
1223 1223 start = 0
1224 1224 elif patch:
1225 1225 start = info[0] + 1
1226 1226 else:
1227 1227 start = len(self.applied) - 1
1228 1228
1229 1229 if start >= len(self.applied):
1230 1230 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1231 1231 return
1232 1232
1233 1233 if not update:
1234 1234 parents = repo.dirstate.parents()
1235 1235 rr = [x.node for x in self.applied]
1236 1236 for p in parents:
1237 1237 if p in rr:
1238 1238 self.ui.warn(_("qpop: forcing dirstate update\n"))
1239 1239 update = True
1240 1240 else:
1241 1241 parents = [p.node() for p in repo[None].parents()]
1242 1242 needupdate = False
1243 1243 for entry in self.applied[start:]:
1244 1244 if entry.node in parents:
1245 1245 needupdate = True
1246 1246 break
1247 1247 update = needupdate
1248 1248
1249 1249 if not force and update:
1250 1250 self.check_localchanges(repo)
1251 1251
1252 1252 self.applied_dirty = 1
1253 1253 end = len(self.applied)
1254 1254 rev = self.applied[start].node
1255 1255 if update:
1256 1256 top = self.check_toppatch(repo)[0]
1257 1257
1258 1258 try:
1259 1259 heads = repo.changelog.heads(rev)
1260 1260 except error.LookupError:
1261 1261 node = short(rev)
1262 1262 raise util.Abort(_('trying to pop unknown node %s') % node)
1263 1263
1264 1264 if heads != [self.applied[-1].node]:
1265 1265 raise util.Abort(_("popping would remove a revision not "
1266 1266 "managed by this patch queue"))
1267 1267
1268 1268 # we know there are no local changes, so we can make a simplified
1269 1269 # form of hg.update.
1270 1270 if update:
1271 1271 qp = self.qparents(repo, rev)
1272 1272 ctx = repo[qp]
1273 1273 m, a, r, d = repo.status(qp, top)[:4]
1274 1274 if d:
1275 1275 raise util.Abort(_("deletions found between repo revs"))
1276 1276 for f in a:
1277 1277 try:
1278 1278 util.unlinkpath(repo.wjoin(f))
1279 1279 except OSError, e:
1280 1280 if e.errno != errno.ENOENT:
1281 1281 raise
1282 1282 repo.dirstate.forget(f)
1283 1283 for f in m + r:
1284 1284 fctx = ctx[f]
1285 1285 repo.wwrite(f, fctx.data(), fctx.flags())
1286 1286 repo.dirstate.normal(f)
1287 1287 repo.dirstate.setparents(qp, nullid)
1288 1288 for patch in reversed(self.applied[start:end]):
1289 1289 self.ui.status(_("popping %s\n") % patch.name)
1290 1290 del self.applied[start:end]
1291 1291 self.strip(repo, [rev], update=False, backup='strip')
1292 1292 if self.applied:
1293 1293 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1294 1294 else:
1295 1295 self.ui.write(_("patch queue now empty\n"))
1296 1296 finally:
1297 1297 wlock.release()
1298 1298
1299 1299 def diff(self, repo, pats, opts):
1300 1300 top, patch = self.check_toppatch(repo)
1301 1301 if not top:
1302 1302 self.ui.write(_("no patches applied\n"))
1303 1303 return
1304 1304 qp = self.qparents(repo, top)
1305 1305 if opts.get('reverse'):
1306 1306 node1, node2 = None, qp
1307 1307 else:
1308 1308 node1, node2 = qp, None
1309 1309 diffopts = self.diffopts(opts, patch)
1310 1310 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1311 1311
1312 1312 def refresh(self, repo, pats=None, **opts):
1313 1313 if not self.applied:
1314 1314 self.ui.write(_("no patches applied\n"))
1315 1315 return 1
1316 1316 msg = opts.get('msg', '').rstrip()
1317 1317 newuser = opts.get('user')
1318 1318 newdate = opts.get('date')
1319 1319 if newdate:
1320 1320 newdate = '%d %d' % util.parsedate(newdate)
1321 1321 wlock = repo.wlock()
1322 1322
1323 1323 try:
1324 1324 self.check_toppatch(repo)
1325 1325 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1326 1326 if repo.changelog.heads(top) != [top]:
1327 1327 raise util.Abort(_("cannot refresh a revision with children"))
1328 1328
1329 1329 inclsubs = self.check_substate(repo)
1330 1330
1331 1331 cparents = repo.changelog.parents(top)
1332 1332 patchparent = self.qparents(repo, top)
1333 1333 ph = patchheader(self.join(patchfn), self.plainmode)
1334 1334 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1335 1335 if msg:
1336 1336 ph.setmessage(msg)
1337 1337 if newuser:
1338 1338 ph.setuser(newuser)
1339 1339 if newdate:
1340 1340 ph.setdate(newdate)
1341 1341 ph.setparent(hex(patchparent))
1342 1342
1343 1343 # only commit new patch when write is complete
1344 1344 patchf = self.opener(patchfn, 'w', atomictemp=True)
1345 1345
1346 1346 comments = str(ph)
1347 1347 if comments:
1348 1348 patchf.write(comments)
1349 1349
1350 1350 # update the dirstate in place, strip off the qtip commit
1351 1351 # and then commit.
1352 1352 #
1353 1353 # this should really read:
1354 1354 # mm, dd, aa = repo.status(top, patchparent)[:3]
1355 1355 # but we do it backwards to take advantage of manifest/chlog
1356 1356 # caching against the next repo.status call
1357 1357 mm, aa, dd = repo.status(patchparent, top)[:3]
1358 1358 changes = repo.changelog.read(top)
1359 1359 man = repo.manifest.read(changes[0])
1360 1360 aaa = aa[:]
1361 1361 matchfn = cmdutil.match(repo, pats, opts)
1362 1362 # in short mode, we only diff the files included in the
1363 1363 # patch already plus specified files
1364 1364 if opts.get('short'):
1365 1365 # if amending a patch, we start with existing
1366 1366 # files plus specified files - unfiltered
1367 1367 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1368 1368 # filter with inc/exl options
1369 1369 matchfn = cmdutil.match(repo, opts=opts)
1370 1370 else:
1371 1371 match = cmdutil.matchall(repo)
1372 1372 m, a, r, d = repo.status(match=match)[:4]
1373 1373 mm = set(mm)
1374 1374 aa = set(aa)
1375 1375 dd = set(dd)
1376 1376
1377 1377 # we might end up with files that were added between
1378 1378 # qtip and the dirstate parent, but then changed in the
1379 1379 # local dirstate. in this case, we want them to only
1380 1380 # show up in the added section
1381 1381 for x in m:
1382 1382 if x not in aa:
1383 1383 mm.add(x)
1384 1384 # we might end up with files added by the local dirstate that
1385 1385 # were deleted by the patch. In this case, they should only
1386 1386 # show up in the changed section.
1387 1387 for x in a:
1388 1388 if x in dd:
1389 1389 dd.remove(x)
1390 1390 mm.add(x)
1391 1391 else:
1392 1392 aa.add(x)
1393 1393 # make sure any files deleted in the local dirstate
1394 1394 # are not in the add or change column of the patch
1395 1395 forget = []
1396 1396 for x in d + r:
1397 1397 if x in aa:
1398 1398 aa.remove(x)
1399 1399 forget.append(x)
1400 1400 continue
1401 1401 else:
1402 1402 mm.discard(x)
1403 1403 dd.add(x)
1404 1404
1405 1405 m = list(mm)
1406 1406 r = list(dd)
1407 1407 a = list(aa)
1408 1408 c = [filter(matchfn, l) for l in (m, a, r)]
1409 1409 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1410 1410 chunks = patch.diff(repo, patchparent, match=match,
1411 1411 changes=c, opts=diffopts)
1412 1412 for chunk in chunks:
1413 1413 patchf.write(chunk)
1414 1414
1415 1415 try:
1416 1416 if diffopts.git or diffopts.upgrade:
1417 1417 copies = {}
1418 1418 for dst in a:
1419 1419 src = repo.dirstate.copied(dst)
1420 1420 # during qfold, the source file for copies may
1421 1421 # be removed. Treat this as a simple add.
1422 1422 if src is not None and src in repo.dirstate:
1423 1423 copies.setdefault(src, []).append(dst)
1424 1424 repo.dirstate.add(dst)
1425 1425 # remember the copies between patchparent and qtip
1426 1426 for dst in aaa:
1427 1427 f = repo.file(dst)
1428 1428 src = f.renamed(man[dst])
1429 1429 if src:
1430 1430 copies.setdefault(src[0], []).extend(
1431 1431 copies.get(dst, []))
1432 1432 if dst in a:
1433 1433 copies[src[0]].append(dst)
1434 1434 # we can't copy a file created by the patch itself
1435 1435 if dst in copies:
1436 1436 del copies[dst]
1437 1437 for src, dsts in copies.iteritems():
1438 1438 for dst in dsts:
1439 1439 repo.dirstate.copy(src, dst)
1440 1440 else:
1441 1441 for dst in a:
1442 1442 repo.dirstate.add(dst)
1443 1443 # Drop useless copy information
1444 1444 for f in list(repo.dirstate.copies()):
1445 1445 repo.dirstate.copy(None, f)
1446 1446 for f in r:
1447 1447 repo.dirstate.remove(f)
1448 1448 # if the patch excludes a modified file, mark that
1449 1449 # file with mtime=0 so status can see it.
1450 1450 mm = []
1451 1451 for i in xrange(len(m)-1, -1, -1):
1452 1452 if not matchfn(m[i]):
1453 1453 mm.append(m[i])
1454 1454 del m[i]
1455 1455 for f in m:
1456 1456 repo.dirstate.normal(f)
1457 1457 for f in mm:
1458 1458 repo.dirstate.normallookup(f)
1459 1459 for f in forget:
1460 1460 repo.dirstate.forget(f)
1461 1461
1462 1462 if not msg:
1463 1463 if not ph.message:
1464 1464 message = "[mq]: %s\n" % patchfn
1465 1465 else:
1466 1466 message = "\n".join(ph.message)
1467 1467 else:
1468 1468 message = msg
1469 1469
1470 1470 user = ph.user or changes[1]
1471 1471
1472 1472 # assumes strip can roll itself back if interrupted
1473 1473 repo.dirstate.setparents(*cparents)
1474 1474 self.applied.pop()
1475 1475 self.applied_dirty = 1
1476 1476 self.strip(repo, [top], update=False,
1477 1477 backup='strip')
1478 1478 except:
1479 1479 repo.dirstate.invalidate()
1480 1480 raise
1481 1481
1482 1482 try:
1483 1483 # might be nice to attempt to roll back strip after this
1484 1484 n = repo.commit(message, user, ph.date, match=match,
1485 1485 force=True)
1486 1486 # only write patch after a successful commit
1487 1487 patchf.rename()
1488 1488 self.applied.append(statusentry(n, patchfn))
1489 1489 except:
1490 1490 ctx = repo[cparents[0]]
1491 1491 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1492 1492 self.save_dirty()
1493 1493 self.ui.warn(_('refresh interrupted while patch was popped! '
1494 1494 '(revert --all, qpush to recover)\n'))
1495 1495 raise
1496 1496 finally:
1497 1497 wlock.release()
1498 1498 self.removeundo(repo)
1499 1499
1500 1500 def init(self, repo, create=False):
1501 1501 if not create and os.path.isdir(self.path):
1502 1502 raise util.Abort(_("patch queue directory already exists"))
1503 1503 try:
1504 1504 os.mkdir(self.path)
1505 1505 except OSError, inst:
1506 1506 if inst.errno != errno.EEXIST or not create:
1507 1507 raise
1508 1508 if create:
1509 1509 return self.qrepo(create=True)
1510 1510
1511 1511 def unapplied(self, repo, patch=None):
1512 1512 if patch and patch not in self.series:
1513 1513 raise util.Abort(_("patch %s is not in series file") % patch)
1514 1514 if not patch:
1515 1515 start = self.series_end()
1516 1516 else:
1517 1517 start = self.series.index(patch) + 1
1518 1518 unapplied = []
1519 1519 for i in xrange(start, len(self.series)):
1520 1520 pushable, reason = self.pushable(i)
1521 1521 if pushable:
1522 1522 unapplied.append((i, self.series[i]))
1523 1523 self.explain_pushable(i)
1524 1524 return unapplied
1525 1525
1526 1526 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1527 1527 summary=False):
1528 1528 def displayname(pfx, patchname, state):
1529 1529 if pfx:
1530 1530 self.ui.write(pfx)
1531 1531 if summary:
1532 1532 ph = patchheader(self.join(patchname), self.plainmode)
1533 1533 msg = ph.message and ph.message[0] or ''
1534 1534 if self.ui.formatted():
1535 1535 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1536 1536 if width > 0:
1537 1537 msg = util.ellipsis(msg, width)
1538 1538 else:
1539 1539 msg = ''
1540 1540 self.ui.write(patchname, label='qseries.' + state)
1541 1541 self.ui.write(': ')
1542 1542 self.ui.write(msg, label='qseries.message.' + state)
1543 1543 else:
1544 1544 self.ui.write(patchname, label='qseries.' + state)
1545 1545 self.ui.write('\n')
1546 1546
1547 1547 applied = set([p.name for p in self.applied])
1548 1548 if length is None:
1549 1549 length = len(self.series) - start
1550 1550 if not missing:
1551 1551 if self.ui.verbose:
1552 1552 idxwidth = len(str(start + length - 1))
1553 1553 for i in xrange(start, start + length):
1554 1554 patch = self.series[i]
1555 1555 if patch in applied:
1556 1556 char, state = 'A', 'applied'
1557 1557 elif self.pushable(i)[0]:
1558 1558 char, state = 'U', 'unapplied'
1559 1559 else:
1560 1560 char, state = 'G', 'guarded'
1561 1561 pfx = ''
1562 1562 if self.ui.verbose:
1563 1563 pfx = '%*d %s ' % (idxwidth, i, char)
1564 1564 elif status and status != char:
1565 1565 continue
1566 1566 displayname(pfx, patch, state)
1567 1567 else:
1568 1568 msng_list = []
1569 1569 for root, dirs, files in os.walk(self.path):
1570 1570 d = root[len(self.path) + 1:]
1571 1571 for f in files:
1572 1572 fl = os.path.join(d, f)
1573 1573 if (fl not in self.series and
1574 1574 fl not in (self.status_path, self.series_path,
1575 1575 self.guards_path)
1576 1576 and not fl.startswith('.')):
1577 1577 msng_list.append(fl)
1578 1578 for x in sorted(msng_list):
1579 1579 pfx = self.ui.verbose and ('D ') or ''
1580 1580 displayname(pfx, x, 'missing')
1581 1581
1582 1582 def issaveline(self, l):
1583 1583 if l.name == '.hg.patches.save.line':
1584 1584 return True
1585 1585
1586 1586 def qrepo(self, create=False):
1587 1587 ui = self.ui.copy()
1588 1588 ui.setconfig('paths', 'default', '', overlay=False)
1589 1589 ui.setconfig('paths', 'default-push', '', overlay=False)
1590 1590 if create or os.path.isdir(self.join(".hg")):
1591 1591 return hg.repository(ui, path=self.path, create=create)
1592 1592
1593 1593 def restore(self, repo, rev, delete=None, qupdate=None):
1594 1594 desc = repo[rev].description().strip()
1595 1595 lines = desc.splitlines()
1596 1596 i = 0
1597 1597 datastart = None
1598 1598 series = []
1599 1599 applied = []
1600 1600 qpp = None
1601 1601 for i, line in enumerate(lines):
1602 1602 if line == 'Patch Data:':
1603 1603 datastart = i + 1
1604 1604 elif line.startswith('Dirstate:'):
1605 1605 l = line.rstrip()
1606 1606 l = l[10:].split(' ')
1607 1607 qpp = [bin(x) for x in l]
1608 1608 elif datastart is not None:
1609 1609 l = line.rstrip()
1610 1610 n, name = l.split(':', 1)
1611 1611 if n:
1612 1612 applied.append(statusentry(bin(n), name))
1613 1613 else:
1614 1614 series.append(l)
1615 1615 if datastart is None:
1616 1616 self.ui.warn(_("No saved patch data found\n"))
1617 1617 return 1
1618 1618 self.ui.warn(_("restoring status: %s\n") % lines[0])
1619 1619 self.full_series = series
1620 1620 self.applied = applied
1621 1621 self.parse_series()
1622 1622 self.series_dirty = 1
1623 1623 self.applied_dirty = 1
1624 1624 heads = repo.changelog.heads()
1625 1625 if delete:
1626 1626 if rev not in heads:
1627 1627 self.ui.warn(_("save entry has children, leaving it alone\n"))
1628 1628 else:
1629 1629 self.ui.warn(_("removing save entry %s\n") % short(rev))
1630 1630 pp = repo.dirstate.parents()
1631 1631 if rev in pp:
1632 1632 update = True
1633 1633 else:
1634 1634 update = False
1635 1635 self.strip(repo, [rev], update=update, backup='strip')
1636 1636 if qpp:
1637 1637 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1638 1638 (short(qpp[0]), short(qpp[1])))
1639 1639 if qupdate:
1640 1640 self.ui.status(_("updating queue directory\n"))
1641 1641 r = self.qrepo()
1642 1642 if not r:
1643 1643 self.ui.warn(_("Unable to load queue repository\n"))
1644 1644 return 1
1645 1645 hg.clean(r, qpp[0])
1646 1646
1647 1647 def save(self, repo, msg=None):
1648 1648 if not self.applied:
1649 1649 self.ui.warn(_("save: no patches applied, exiting\n"))
1650 1650 return 1
1651 1651 if self.issaveline(self.applied[-1]):
1652 1652 self.ui.warn(_("status is already saved\n"))
1653 1653 return 1
1654 1654
1655 1655 if not msg:
1656 1656 msg = _("hg patches saved state")
1657 1657 else:
1658 1658 msg = "hg patches: " + msg.rstrip('\r\n')
1659 1659 r = self.qrepo()
1660 1660 if r:
1661 1661 pp = r.dirstate.parents()
1662 1662 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1663 1663 msg += "\n\nPatch Data:\n"
1664 1664 msg += ''.join('%s\n' % x for x in self.applied)
1665 1665 msg += ''.join(':%s\n' % x for x in self.full_series)
1666 1666 n = repo.commit(msg, force=True)
1667 1667 if not n:
1668 1668 self.ui.warn(_("repo commit failed\n"))
1669 1669 return 1
1670 1670 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1671 1671 self.applied_dirty = 1
1672 1672 self.removeundo(repo)
1673 1673
1674 1674 def full_series_end(self):
1675 1675 if self.applied:
1676 1676 p = self.applied[-1].name
1677 1677 end = self.find_series(p)
1678 1678 if end is None:
1679 1679 return len(self.full_series)
1680 1680 return end + 1
1681 1681 return 0
1682 1682
1683 1683 def series_end(self, all_patches=False):
1684 1684 """If all_patches is False, return the index of the next pushable patch
1685 1685 in the series, or the series length. If all_patches is True, return the
1686 1686 index of the first patch past the last applied one.
1687 1687 """
1688 1688 end = 0
1689 1689 def next(start):
1690 1690 if all_patches or start >= len(self.series):
1691 1691 return start
1692 1692 for i in xrange(start, len(self.series)):
1693 1693 p, reason = self.pushable(i)
1694 1694 if p:
1695 1695 break
1696 1696 self.explain_pushable(i)
1697 1697 return i
1698 1698 if self.applied:
1699 1699 p = self.applied[-1].name
1700 1700 try:
1701 1701 end = self.series.index(p)
1702 1702 except ValueError:
1703 1703 return 0
1704 1704 return next(end + 1)
1705 1705 return next(end)
1706 1706
1707 1707 def appliedname(self, index):
1708 1708 pname = self.applied[index].name
1709 1709 if not self.ui.verbose:
1710 1710 p = pname
1711 1711 else:
1712 1712 p = str(self.series.index(pname)) + " " + pname
1713 1713 return p
1714 1714
1715 1715 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1716 1716 force=None, git=False):
1717 1717 def checkseries(patchname):
1718 1718 if patchname in self.series:
1719 1719 raise util.Abort(_('patch %s is already in the series file')
1720 1720 % patchname)
1721 1721 def checkfile(patchname):
1722 1722 if not force and os.path.exists(self.join(patchname)):
1723 1723 raise util.Abort(_('patch "%s" already exists')
1724 1724 % patchname)
1725 1725
1726 1726 if rev:
1727 1727 if files:
1728 1728 raise util.Abort(_('option "-r" not valid when importing '
1729 1729 'files'))
1730 1730 rev = cmdutil.revrange(repo, rev)
1731 1731 rev.sort(reverse=True)
1732 1732 if (len(files) > 1 or len(rev) > 1) and patchname:
1733 1733 raise util.Abort(_('option "-n" not valid when importing multiple '
1734 1734 'patches'))
1735 1735 if rev:
1736 1736 # If mq patches are applied, we can only import revisions
1737 1737 # that form a linear path to qbase.
1738 1738 # Otherwise, they should form a linear path to a head.
1739 1739 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1740 1740 if len(heads) > 1:
1741 1741 raise util.Abort(_('revision %d is the root of more than one '
1742 1742 'branch') % rev[-1])
1743 1743 if self.applied:
1744 1744 base = repo.changelog.node(rev[0])
1745 1745 if base in [n.node for n in self.applied]:
1746 1746 raise util.Abort(_('revision %d is already managed')
1747 1747 % rev[0])
1748 1748 if heads != [self.applied[-1].node]:
1749 1749 raise util.Abort(_('revision %d is not the parent of '
1750 1750 'the queue') % rev[0])
1751 1751 base = repo.changelog.rev(self.applied[0].node)
1752 1752 lastparent = repo.changelog.parentrevs(base)[0]
1753 1753 else:
1754 1754 if heads != [repo.changelog.node(rev[0])]:
1755 1755 raise util.Abort(_('revision %d has unmanaged children')
1756 1756 % rev[0])
1757 1757 lastparent = None
1758 1758
1759 1759 diffopts = self.diffopts({'git': git})
1760 1760 for r in rev:
1761 1761 p1, p2 = repo.changelog.parentrevs(r)
1762 1762 n = repo.changelog.node(r)
1763 1763 if p2 != nullrev:
1764 1764 raise util.Abort(_('cannot import merge revision %d') % r)
1765 1765 if lastparent and lastparent != r:
1766 1766 raise util.Abort(_('revision %d is not the parent of %d')
1767 1767 % (r, lastparent))
1768 1768 lastparent = p1
1769 1769
1770 1770 if not patchname:
1771 1771 patchname = normname('%d.diff' % r)
1772 1772 self.check_reserved_name(patchname)
1773 1773 checkseries(patchname)
1774 1774 checkfile(patchname)
1775 1775 self.full_series.insert(0, patchname)
1776 1776
1777 1777 patchf = self.opener(patchname, "w")
1778 1778 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1779 1779 patchf.close()
1780 1780
1781 1781 se = statusentry(n, patchname)
1782 1782 self.applied.insert(0, se)
1783 1783
1784 1784 self.added.append(patchname)
1785 1785 patchname = None
1786 1786 self.parse_series()
1787 1787 self.applied_dirty = 1
1788 1788 self.series_dirty = True
1789 1789
1790 1790 for i, filename in enumerate(files):
1791 1791 if existing:
1792 1792 if filename == '-':
1793 1793 raise util.Abort(_('-e is incompatible with import from -'))
1794 1794 filename = normname(filename)
1795 1795 self.check_reserved_name(filename)
1796 1796 originpath = self.join(filename)
1797 1797 if not os.path.isfile(originpath):
1798 1798 raise util.Abort(_("patch %s does not exist") % filename)
1799 1799
1800 1800 if patchname:
1801 1801 self.check_reserved_name(patchname)
1802 1802 checkfile(patchname)
1803 1803
1804 1804 self.ui.write(_('renaming %s to %s\n')
1805 1805 % (filename, patchname))
1806 1806 util.rename(originpath, self.join(patchname))
1807 1807 else:
1808 1808 patchname = filename
1809 1809
1810 1810 else:
1811 1811 try:
1812 1812 if filename == '-':
1813 1813 if not patchname:
1814 1814 raise util.Abort(
1815 1815 _('need --name to import a patch from -'))
1816 1816 text = sys.stdin.read()
1817 1817 else:
1818 1818 fp = url.open(self.ui, filename)
1819 1819 text = fp.read()
1820 1820 fp.close()
1821 1821 except (OSError, IOError):
1822 1822 raise util.Abort(_("unable to read file %s") % filename)
1823 1823 if not patchname:
1824 1824 patchname = normname(os.path.basename(filename))
1825 1825 self.check_reserved_name(patchname)
1826 1826 checkfile(patchname)
1827 1827 patchf = self.opener(patchname, "w")
1828 1828 patchf.write(text)
1829 1829 patchf.close()
1830 1830 if not force:
1831 1831 checkseries(patchname)
1832 1832 if patchname not in self.series:
1833 1833 index = self.full_series_end() + i
1834 1834 self.full_series[index:index] = [patchname]
1835 1835 self.parse_series()
1836 1836 self.series_dirty = True
1837 1837 self.ui.warn(_("adding %s to series file\n") % patchname)
1838 1838 self.added.append(patchname)
1839 1839 patchname = None
1840 1840
1841 1841 self.removeundo(repo)
1842 1842
1843 1843 def delete(ui, repo, *patches, **opts):
1844 1844 """remove patches from queue
1845 1845
1846 1846 The patches must not be applied, and at least one patch is required. With
1847 1847 -k/--keep, the patch files are preserved in the patch directory.
1848 1848
1849 1849 To stop managing a patch and move it into permanent history,
1850 1850 use the :hg:`qfinish` command."""
1851 1851 q = repo.mq
1852 1852 q.delete(repo, patches, opts)
1853 1853 q.save_dirty()
1854 1854 return 0
1855 1855
1856 1856 def applied(ui, repo, patch=None, **opts):
1857 1857 """print the patches already applied
1858 1858
1859 1859 Returns 0 on success."""
1860 1860
1861 1861 q = repo.mq
1862 1862
1863 1863 if patch:
1864 1864 if patch not in q.series:
1865 1865 raise util.Abort(_("patch %s is not in series file") % patch)
1866 1866 end = q.series.index(patch) + 1
1867 1867 else:
1868 1868 end = q.series_end(True)
1869 1869
1870 1870 if opts.get('last') and not end:
1871 1871 ui.write(_("no patches applied\n"))
1872 1872 return 1
1873 1873 elif opts.get('last') and end == 1:
1874 1874 ui.write(_("only one patch applied\n"))
1875 1875 return 1
1876 1876 elif opts.get('last'):
1877 1877 start = end - 2
1878 1878 end = 1
1879 1879 else:
1880 1880 start = 0
1881 1881
1882 1882 q.qseries(repo, length=end, start=start, status='A',
1883 1883 summary=opts.get('summary'))
1884 1884
1885 1885
1886 1886 def unapplied(ui, repo, patch=None, **opts):
1887 1887 """print the patches not yet applied
1888 1888
1889 1889 Returns 0 on success."""
1890 1890
1891 1891 q = repo.mq
1892 1892 if patch:
1893 1893 if patch not in q.series:
1894 1894 raise util.Abort(_("patch %s is not in series file") % patch)
1895 1895 start = q.series.index(patch) + 1
1896 1896 else:
1897 1897 start = q.series_end(True)
1898 1898
1899 1899 if start == len(q.series) and opts.get('first'):
1900 1900 ui.write(_("all patches applied\n"))
1901 1901 return 1
1902 1902
1903 1903 length = opts.get('first') and 1 or None
1904 1904 q.qseries(repo, start=start, length=length, status='U',
1905 1905 summary=opts.get('summary'))
1906 1906
1907 1907 def qimport(ui, repo, *filename, **opts):
1908 1908 """import a patch
1909 1909
1910 1910 The patch is inserted into the series after the last applied
1911 1911 patch. If no patches have been applied, qimport prepends the patch
1912 1912 to the series.
1913 1913
1914 1914 The patch will have the same name as its source file unless you
1915 1915 give it a new one with -n/--name.
1916 1916
1917 1917 You can register an existing patch inside the patch directory with
1918 1918 the -e/--existing flag.
1919 1919
1920 1920 With -f/--force, an existing patch of the same name will be
1921 1921 overwritten.
1922 1922
1923 1923 An existing changeset may be placed under mq control with -r/--rev
1924 1924 (e.g. qimport --rev tip -n patch will place tip under mq control).
1925 1925 With -g/--git, patches imported with --rev will use the git diff
1926 1926 format. See the diffs help topic for information on why this is
1927 1927 important for preserving rename/copy information and permission
1928 1928 changes. Use :hg:`qfinish` to remove changesets from mq control.
1929 1929
1930 1930 To import a patch from standard input, pass - as the patch file.
1931 1931 When importing from standard input, a patch name must be specified
1932 1932 using the --name flag.
1933 1933
1934 1934 To import an existing patch while renaming it::
1935 1935
1936 1936 hg qimport -e existing-patch -n new-name
1937 1937
1938 1938 Returns 0 if import succeeded.
1939 1939 """
1940 1940 q = repo.mq
1941 1941 try:
1942 1942 q.qimport(repo, filename, patchname=opts.get('name'),
1943 1943 existing=opts.get('existing'), force=opts.get('force'),
1944 1944 rev=opts.get('rev'), git=opts.get('git'))
1945 1945 finally:
1946 1946 q.save_dirty()
1947 1947
1948 1948 if opts.get('push') and not opts.get('rev'):
1949 1949 return q.push(repo, None)
1950 1950 return 0
1951 1951
1952 1952 def qinit(ui, repo, create):
1953 1953 """initialize a new queue repository
1954 1954
1955 1955 This command also creates a series file for ordering patches, and
1956 1956 an mq-specific .hgignore file in the queue repository, to exclude
1957 1957 the status and guards files (these contain mostly transient state).
1958 1958
1959 1959 Returns 0 if initialization succeeded."""
1960 1960 q = repo.mq
1961 1961 r = q.init(repo, create)
1962 1962 q.save_dirty()
1963 1963 if r:
1964 1964 if not os.path.exists(r.wjoin('.hgignore')):
1965 1965 fp = r.wopener('.hgignore', 'w')
1966 1966 fp.write('^\\.hg\n')
1967 1967 fp.write('^\\.mq\n')
1968 1968 fp.write('syntax: glob\n')
1969 1969 fp.write('status\n')
1970 1970 fp.write('guards\n')
1971 1971 fp.close()
1972 1972 if not os.path.exists(r.wjoin('series')):
1973 1973 r.wopener('series', 'w').close()
1974 1974 r[None].add(['.hgignore', 'series'])
1975 1975 commands.add(ui, r)
1976 1976 return 0
1977 1977
1978 1978 def init(ui, repo, **opts):
1979 1979 """init a new queue repository (DEPRECATED)
1980 1980
1981 1981 The queue repository is unversioned by default. If
1982 1982 -c/--create-repo is specified, qinit will create a separate nested
1983 1983 repository for patches (qinit -c may also be run later to convert
1984 1984 an unversioned patch repository into a versioned one). You can use
1985 1985 qcommit to commit changes to this queue repository.
1986 1986
1987 1987 This command is deprecated. Without -c, it's implied by other relevant
1988 1988 commands. With -c, use :hg:`init --mq` instead."""
1989 1989 return qinit(ui, repo, create=opts.get('create_repo'))
1990 1990
1991 1991 def clone(ui, source, dest=None, **opts):
1992 1992 '''clone main and patch repository at same time
1993 1993
1994 1994 If source is local, destination will have no patches applied. If
1995 1995 source is remote, this command can not check if patches are
1996 1996 applied in source, so cannot guarantee that patches are not
1997 1997 applied in destination. If you clone remote repository, be sure
1998 1998 before that it has no patches applied.
1999 1999
2000 2000 Source patch repository is looked for in <src>/.hg/patches by
2001 2001 default. Use -p <url> to change.
2002 2002
2003 2003 The patch directory must be a nested Mercurial repository, as
2004 2004 would be created by :hg:`init --mq`.
2005 2005
2006 2006 Return 0 on success.
2007 2007 '''
2008 2008 def patchdir(repo):
2009 2009 url = repo.url()
2010 2010 if url.endswith('/'):
2011 2011 url = url[:-1]
2012 2012 return url + '/.hg/patches'
2013 2013 if dest is None:
2014 2014 dest = hg.defaultdest(source)
2015 2015 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2016 2016 if opts.get('patches'):
2017 2017 patchespath = ui.expandpath(opts.get('patches'))
2018 2018 else:
2019 2019 patchespath = patchdir(sr)
2020 2020 try:
2021 2021 hg.repository(ui, patchespath)
2022 2022 except error.RepoError:
2023 2023 raise util.Abort(_('versioned patch repository not found'
2024 2024 ' (see init --mq)'))
2025 2025 qbase, destrev = None, None
2026 2026 if sr.local():
2027 2027 if sr.mq.applied:
2028 2028 qbase = sr.mq.applied[0].node
2029 2029 if not hg.islocal(dest):
2030 2030 heads = set(sr.heads())
2031 2031 destrev = list(heads.difference(sr.heads(qbase)))
2032 2032 destrev.append(sr.changelog.parents(qbase)[0])
2033 2033 elif sr.capable('lookup'):
2034 2034 try:
2035 2035 qbase = sr.lookup('qbase')
2036 2036 except error.RepoError:
2037 2037 pass
2038 2038 ui.note(_('cloning main repository\n'))
2039 2039 sr, dr = hg.clone(ui, sr.url(), dest,
2040 2040 pull=opts.get('pull'),
2041 2041 rev=destrev,
2042 2042 update=False,
2043 2043 stream=opts.get('uncompressed'))
2044 2044 ui.note(_('cloning patch repository\n'))
2045 2045 hg.clone(ui, opts.get('patches') or patchdir(sr), patchdir(dr),
2046 2046 pull=opts.get('pull'), update=not opts.get('noupdate'),
2047 2047 stream=opts.get('uncompressed'))
2048 2048 if dr.local():
2049 2049 if qbase:
2050 2050 ui.note(_('stripping applied patches from destination '
2051 2051 'repository\n'))
2052 2052 dr.mq.strip(dr, [qbase], update=False, backup=None)
2053 2053 if not opts.get('noupdate'):
2054 2054 ui.note(_('updating destination repository\n'))
2055 2055 hg.update(dr, dr.changelog.tip())
2056 2056
2057 2057 def commit(ui, repo, *pats, **opts):
2058 2058 """commit changes in the queue repository (DEPRECATED)
2059 2059
2060 2060 This command is deprecated; use :hg:`commit --mq` instead."""
2061 2061 q = repo.mq
2062 2062 r = q.qrepo()
2063 2063 if not r:
2064 2064 raise util.Abort('no queue repository')
2065 2065 commands.commit(r.ui, r, *pats, **opts)
2066 2066
2067 2067 def series(ui, repo, **opts):
2068 2068 """print the entire series file
2069 2069
2070 2070 Returns 0 on success."""
2071 2071 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2072 2072 return 0
2073 2073
2074 2074 def top(ui, repo, **opts):
2075 2075 """print the name of the current patch
2076 2076
2077 2077 Returns 0 on success."""
2078 2078 q = repo.mq
2079 2079 t = q.applied and q.series_end(True) or 0
2080 2080 if t:
2081 2081 q.qseries(repo, start=t - 1, length=1, status='A',
2082 2082 summary=opts.get('summary'))
2083 2083 else:
2084 2084 ui.write(_("no patches applied\n"))
2085 2085 return 1
2086 2086
2087 2087 def next(ui, repo, **opts):
2088 2088 """print the name of the next patch
2089 2089
2090 2090 Returns 0 on success."""
2091 2091 q = repo.mq
2092 2092 end = q.series_end()
2093 2093 if end == len(q.series):
2094 2094 ui.write(_("all patches applied\n"))
2095 2095 return 1
2096 2096 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2097 2097
2098 2098 def prev(ui, repo, **opts):
2099 2099 """print the name of the previous patch
2100 2100
2101 2101 Returns 0 on success."""
2102 2102 q = repo.mq
2103 2103 l = len(q.applied)
2104 2104 if l == 1:
2105 2105 ui.write(_("only one patch applied\n"))
2106 2106 return 1
2107 2107 if not l:
2108 2108 ui.write(_("no patches applied\n"))
2109 2109 return 1
2110 2110 q.qseries(repo, start=l - 2, length=1, status='A',
2111 2111 summary=opts.get('summary'))
2112 2112
2113 2113 def setupheaderopts(ui, opts):
2114 2114 if not opts.get('user') and opts.get('currentuser'):
2115 2115 opts['user'] = ui.username()
2116 2116 if not opts.get('date') and opts.get('currentdate'):
2117 2117 opts['date'] = "%d %d" % util.makedate()
2118 2118
2119 2119 def new(ui, repo, patch, *args, **opts):
2120 2120 """create a new patch
2121 2121
2122 2122 qnew creates a new patch on top of the currently-applied patch (if
2123 2123 any). The patch will be initialized with any outstanding changes
2124 2124 in the working directory. You may also use -I/--include,
2125 2125 -X/--exclude, and/or a list of files after the patch name to add
2126 2126 only changes to matching files to the new patch, leaving the rest
2127 2127 as uncommitted modifications.
2128 2128
2129 2129 -u/--user and -d/--date can be used to set the (given) user and
2130 2130 date, respectively. -U/--currentuser and -D/--currentdate set user
2131 2131 to current user and date to current date.
2132 2132
2133 2133 -e/--edit, -m/--message or -l/--logfile set the patch header as
2134 2134 well as the commit message. If none is specified, the header is
2135 2135 empty and the commit message is '[mq]: PATCH'.
2136 2136
2137 2137 Use the -g/--git option to keep the patch in the git extended diff
2138 2138 format. Read the diffs help topic for more information on why this
2139 2139 is important for preserving permission changes and copy/rename
2140 2140 information.
2141 2141
2142 2142 Returns 0 on successful creation of a new patch.
2143 2143 """
2144 2144 msg = cmdutil.logmessage(opts)
2145 2145 def getmsg():
2146 2146 return ui.edit(msg, opts.get('user') or ui.username())
2147 2147 q = repo.mq
2148 2148 opts['msg'] = msg
2149 2149 if opts.get('edit'):
2150 2150 opts['msg'] = getmsg
2151 2151 else:
2152 2152 opts['msg'] = msg
2153 2153 setupheaderopts(ui, opts)
2154 2154 q.new(repo, patch, *args, **opts)
2155 2155 q.save_dirty()
2156 2156 return 0
2157 2157
2158 2158 def refresh(ui, repo, *pats, **opts):
2159 2159 """update the current patch
2160 2160
2161 2161 If any file patterns are provided, the refreshed patch will
2162 2162 contain only the modifications that match those patterns; the
2163 2163 remaining modifications will remain in the working directory.
2164 2164
2165 2165 If -s/--short is specified, files currently included in the patch
2166 2166 will be refreshed just like matched files and remain in the patch.
2167 2167
2168 2168 If -e/--edit is specified, Mercurial will start your configured editor for
2169 2169 you to enter a message. In case qrefresh fails, you will find a backup of
2170 2170 your message in ``.hg/last-message.txt``.
2171 2171
2172 2172 hg add/remove/copy/rename work as usual, though you might want to
2173 2173 use git-style patches (-g/--git or [diff] git=1) to track copies
2174 2174 and renames. See the diffs help topic for more information on the
2175 2175 git diff format.
2176 2176
2177 2177 Returns 0 on success.
2178 2178 """
2179 2179 q = repo.mq
2180 2180 message = cmdutil.logmessage(opts)
2181 2181 if opts.get('edit'):
2182 2182 if not q.applied:
2183 2183 ui.write(_("no patches applied\n"))
2184 2184 return 1
2185 2185 if message:
2186 2186 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2187 2187 patch = q.applied[-1].name
2188 2188 ph = patchheader(q.join(patch), q.plainmode)
2189 2189 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2190 2190 # We don't want to lose the patch message if qrefresh fails (issue2062)
2191 2191 msgfile = repo.opener('last-message.txt', 'wb')
2192 2192 msgfile.write(message)
2193 2193 msgfile.close()
2194 2194 setupheaderopts(ui, opts)
2195 2195 ret = q.refresh(repo, pats, msg=message, **opts)
2196 2196 q.save_dirty()
2197 2197 return ret
2198 2198
2199 2199 def diff(ui, repo, *pats, **opts):
2200 2200 """diff of the current patch and subsequent modifications
2201 2201
2202 2202 Shows a diff which includes the current patch as well as any
2203 2203 changes which have been made in the working directory since the
2204 2204 last refresh (thus showing what the current patch would become
2205 2205 after a qrefresh).
2206 2206
2207 2207 Use :hg:`diff` if you only want to see the changes made since the
2208 2208 last qrefresh, or :hg:`export qtip` if you want to see changes
2209 2209 made by the current patch without including changes made since the
2210 2210 qrefresh.
2211 2211
2212 2212 Returns 0 on success.
2213 2213 """
2214 2214 repo.mq.diff(repo, pats, opts)
2215 2215 return 0
2216 2216
2217 2217 def fold(ui, repo, *files, **opts):
2218 2218 """fold the named patches into the current patch
2219 2219
2220 2220 Patches must not yet be applied. Each patch will be successively
2221 2221 applied to the current patch in the order given. If all the
2222 2222 patches apply successfully, the current patch will be refreshed
2223 2223 with the new cumulative patch, and the folded patches will be
2224 2224 deleted. With -k/--keep, the folded patch files will not be
2225 2225 removed afterwards.
2226 2226
2227 2227 The header for each folded patch will be concatenated with the
2228 2228 current patch header, separated by a line of ``* * *``.
2229 2229
2230 2230 Returns 0 on success."""
2231 2231
2232 2232 q = repo.mq
2233 2233
2234 2234 if not files:
2235 2235 raise util.Abort(_('qfold requires at least one patch name'))
2236 2236 if not q.check_toppatch(repo)[0]:
2237 2237 raise util.Abort(_('no patches applied'))
2238 2238 q.check_localchanges(repo)
2239 2239
2240 2240 message = cmdutil.logmessage(opts)
2241 2241 if opts.get('edit'):
2242 2242 if message:
2243 2243 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2244 2244
2245 2245 parent = q.lookup('qtip')
2246 2246 patches = []
2247 2247 messages = []
2248 2248 for f in files:
2249 2249 p = q.lookup(f)
2250 2250 if p in patches or p == parent:
2251 2251 ui.warn(_('Skipping already folded patch %s\n') % p)
2252 2252 if q.isapplied(p):
2253 2253 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2254 2254 patches.append(p)
2255 2255
2256 2256 for p in patches:
2257 2257 if not message:
2258 2258 ph = patchheader(q.join(p), q.plainmode)
2259 2259 if ph.message:
2260 2260 messages.append(ph.message)
2261 2261 pf = q.join(p)
2262 2262 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2263 2263 if not patchsuccess:
2264 2264 raise util.Abort(_('error folding patch %s') % p)
2265 2265 cmdutil.updatedir(ui, repo, files)
2266 2266
2267 2267 if not message:
2268 2268 ph = patchheader(q.join(parent), q.plainmode)
2269 2269 message, user = ph.message, ph.user
2270 2270 for msg in messages:
2271 2271 message.append('* * *')
2272 2272 message.extend(msg)
2273 2273 message = '\n'.join(message)
2274 2274
2275 2275 if opts.get('edit'):
2276 2276 message = ui.edit(message, user or ui.username())
2277 2277
2278 2278 diffopts = q.patchopts(q.diffopts(), *patches)
2279 2279 q.refresh(repo, msg=message, git=diffopts.git)
2280 2280 q.delete(repo, patches, opts)
2281 2281 q.save_dirty()
2282 2282
2283 2283 def goto(ui, repo, patch, **opts):
2284 2284 '''push or pop patches until named patch is at top of stack
2285 2285
2286 2286 Returns 0 on success.'''
2287 2287 q = repo.mq
2288 2288 patch = q.lookup(patch)
2289 2289 if q.isapplied(patch):
2290 2290 ret = q.pop(repo, patch, force=opts.get('force'))
2291 2291 else:
2292 2292 ret = q.push(repo, patch, force=opts.get('force'))
2293 2293 q.save_dirty()
2294 2294 return ret
2295 2295
2296 2296 def guard(ui, repo, *args, **opts):
2297 2297 '''set or print guards for a patch
2298 2298
2299 2299 Guards control whether a patch can be pushed. A patch with no
2300 2300 guards is always pushed. A patch with a positive guard ("+foo") is
2301 2301 pushed only if the :hg:`qselect` command has activated it. A patch with
2302 2302 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2303 2303 has activated it.
2304 2304
2305 2305 With no arguments, print the currently active guards.
2306 2306 With arguments, set guards for the named patch.
2307 2307
2308 2308 .. note::
2309 2309 Specifying negative guards now requires '--'.
2310 2310
2311 2311 To set guards on another patch::
2312 2312
2313 2313 hg qguard other.patch -- +2.6.17 -stable
2314 2314
2315 2315 Returns 0 on success.
2316 2316 '''
2317 2317 def status(idx):
2318 2318 guards = q.series_guards[idx] or ['unguarded']
2319 2319 if q.series[idx] in applied:
2320 2320 state = 'applied'
2321 2321 elif q.pushable(idx)[0]:
2322 2322 state = 'unapplied'
2323 2323 else:
2324 2324 state = 'guarded'
2325 2325 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2326 2326 ui.write('%s: ' % ui.label(q.series[idx], label))
2327 2327
2328 2328 for i, guard in enumerate(guards):
2329 2329 if guard.startswith('+'):
2330 2330 ui.write(guard, label='qguard.positive')
2331 2331 elif guard.startswith('-'):
2332 2332 ui.write(guard, label='qguard.negative')
2333 2333 else:
2334 2334 ui.write(guard, label='qguard.unguarded')
2335 2335 if i != len(guards) - 1:
2336 2336 ui.write(' ')
2337 2337 ui.write('\n')
2338 2338 q = repo.mq
2339 2339 applied = set(p.name for p in q.applied)
2340 2340 patch = None
2341 2341 args = list(args)
2342 2342 if opts.get('list'):
2343 2343 if args or opts.get('none'):
2344 2344 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2345 2345 for i in xrange(len(q.series)):
2346 2346 status(i)
2347 2347 return
2348 2348 if not args or args[0][0:1] in '-+':
2349 2349 if not q.applied:
2350 2350 raise util.Abort(_('no patches applied'))
2351 2351 patch = q.applied[-1].name
2352 2352 if patch is None and args[0][0:1] not in '-+':
2353 2353 patch = args.pop(0)
2354 2354 if patch is None:
2355 2355 raise util.Abort(_('no patch to work with'))
2356 2356 if args or opts.get('none'):
2357 2357 idx = q.find_series(patch)
2358 2358 if idx is None:
2359 2359 raise util.Abort(_('no patch named %s') % patch)
2360 2360 q.set_guards(idx, args)
2361 2361 q.save_dirty()
2362 2362 else:
2363 2363 status(q.series.index(q.lookup(patch)))
2364 2364
2365 2365 def header(ui, repo, patch=None):
2366 2366 """print the header of the topmost or specified patch
2367 2367
2368 2368 Returns 0 on success."""
2369 2369 q = repo.mq
2370 2370
2371 2371 if patch:
2372 2372 patch = q.lookup(patch)
2373 2373 else:
2374 2374 if not q.applied:
2375 2375 ui.write(_('no patches applied\n'))
2376 2376 return 1
2377 2377 patch = q.lookup('qtip')
2378 2378 ph = patchheader(q.join(patch), q.plainmode)
2379 2379
2380 2380 ui.write('\n'.join(ph.message) + '\n')
2381 2381
2382 2382 def lastsavename(path):
2383 2383 (directory, base) = os.path.split(path)
2384 2384 names = os.listdir(directory)
2385 2385 namere = re.compile("%s.([0-9]+)" % base)
2386 2386 maxindex = None
2387 2387 maxname = None
2388 2388 for f in names:
2389 2389 m = namere.match(f)
2390 2390 if m:
2391 2391 index = int(m.group(1))
2392 2392 if maxindex is None or index > maxindex:
2393 2393 maxindex = index
2394 2394 maxname = f
2395 2395 if maxname:
2396 2396 return (os.path.join(directory, maxname), maxindex)
2397 2397 return (None, None)
2398 2398
2399 2399 def savename(path):
2400 2400 (last, index) = lastsavename(path)
2401 2401 if last is None:
2402 2402 index = 0
2403 2403 newpath = path + ".%d" % (index + 1)
2404 2404 return newpath
2405 2405
2406 2406 def push(ui, repo, patch=None, **opts):
2407 2407 """push the next patch onto the stack
2408 2408
2409 2409 When -f/--force is applied, all local changes in patched files
2410 2410 will be lost.
2411 2411
2412 2412 Return 0 on success.
2413 2413 """
2414 2414 q = repo.mq
2415 2415 mergeq = None
2416 2416
2417 2417 if opts.get('merge'):
2418 2418 if opts.get('name'):
2419 2419 newpath = repo.join(opts.get('name'))
2420 2420 else:
2421 2421 newpath, i = lastsavename(q.path)
2422 2422 if not newpath:
2423 2423 ui.warn(_("no saved queues found, please use -n\n"))
2424 2424 return 1
2425 2425 mergeq = queue(ui, repo.join(""), newpath)
2426 2426 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2427 2427 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2428 2428 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2429 2429 exact=opts.get('exact'))
2430 2430 return ret
2431 2431
2432 2432 def pop(ui, repo, patch=None, **opts):
2433 2433 """pop the current patch off the stack
2434 2434
2435 2435 By default, pops off the top of the patch stack. If given a patch
2436 2436 name, keeps popping off patches until the named patch is at the
2437 2437 top of the stack.
2438 2438
2439 2439 Return 0 on success.
2440 2440 """
2441 2441 localupdate = True
2442 2442 if opts.get('name'):
2443 2443 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2444 2444 ui.warn(_('using patch queue: %s\n') % q.path)
2445 2445 localupdate = False
2446 2446 else:
2447 2447 q = repo.mq
2448 2448 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2449 2449 all=opts.get('all'))
2450 2450 q.save_dirty()
2451 2451 return ret
2452 2452
2453 2453 def rename(ui, repo, patch, name=None, **opts):
2454 2454 """rename a patch
2455 2455
2456 2456 With one argument, renames the current patch to PATCH1.
2457 2457 With two arguments, renames PATCH1 to PATCH2.
2458 2458
2459 2459 Returns 0 on success."""
2460 2460
2461 2461 q = repo.mq
2462 2462
2463 2463 if not name:
2464 2464 name = patch
2465 2465 patch = None
2466 2466
2467 2467 if patch:
2468 2468 patch = q.lookup(patch)
2469 2469 else:
2470 2470 if not q.applied:
2471 2471 ui.write(_('no patches applied\n'))
2472 2472 return
2473 2473 patch = q.lookup('qtip')
2474 2474 absdest = q.join(name)
2475 2475 if os.path.isdir(absdest):
2476 2476 name = normname(os.path.join(name, os.path.basename(patch)))
2477 2477 absdest = q.join(name)
2478 2478 if os.path.exists(absdest):
2479 2479 raise util.Abort(_('%s already exists') % absdest)
2480 2480
2481 2481 if name in q.series:
2482 2482 raise util.Abort(
2483 2483 _('A patch named %s already exists in the series file') % name)
2484 2484
2485 2485 ui.note(_('renaming %s to %s\n') % (patch, name))
2486 2486 i = q.find_series(patch)
2487 2487 guards = q.guard_re.findall(q.full_series[i])
2488 2488 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2489 2489 q.parse_series()
2490 2490 q.series_dirty = 1
2491 2491
2492 2492 info = q.isapplied(patch)
2493 2493 if info:
2494 2494 q.applied[info[0]] = statusentry(info[1], name)
2495 2495 q.applied_dirty = 1
2496 2496
2497 2497 destdir = os.path.dirname(absdest)
2498 2498 if not os.path.isdir(destdir):
2499 2499 os.makedirs(destdir)
2500 2500 util.rename(q.join(patch), absdest)
2501 2501 r = q.qrepo()
2502 2502 if r and patch in r.dirstate:
2503 2503 wctx = r[None]
2504 2504 wlock = r.wlock()
2505 2505 try:
2506 2506 if r.dirstate[patch] == 'a':
2507 2507 r.dirstate.forget(patch)
2508 2508 r.dirstate.add(name)
2509 2509 else:
2510 2510 if r.dirstate[name] == 'r':
2511 2511 wctx.undelete([name])
2512 2512 wctx.copy(patch, name)
2513 2513 wctx.remove([patch], False)
2514 2514 finally:
2515 2515 wlock.release()
2516 2516
2517 2517 q.save_dirty()
2518 2518
2519 2519 def restore(ui, repo, rev, **opts):
2520 2520 """restore the queue state saved by a revision (DEPRECATED)
2521 2521
2522 2522 This command is deprecated, use :hg:`rebase` instead."""
2523 2523 rev = repo.lookup(rev)
2524 2524 q = repo.mq
2525 2525 q.restore(repo, rev, delete=opts.get('delete'),
2526 2526 qupdate=opts.get('update'))
2527 2527 q.save_dirty()
2528 2528 return 0
2529 2529
2530 2530 def save(ui, repo, **opts):
2531 2531 """save current queue state (DEPRECATED)
2532 2532
2533 2533 This command is deprecated, use :hg:`rebase` instead."""
2534 2534 q = repo.mq
2535 2535 message = cmdutil.logmessage(opts)
2536 2536 ret = q.save(repo, msg=message)
2537 2537 if ret:
2538 2538 return ret
2539 2539 q.save_dirty()
2540 2540 if opts.get('copy'):
2541 2541 path = q.path
2542 2542 if opts.get('name'):
2543 2543 newpath = os.path.join(q.basepath, opts.get('name'))
2544 2544 if os.path.exists(newpath):
2545 2545 if not os.path.isdir(newpath):
2546 2546 raise util.Abort(_('destination %s exists and is not '
2547 2547 'a directory') % newpath)
2548 2548 if not opts.get('force'):
2549 2549 raise util.Abort(_('destination %s exists, '
2550 2550 'use -f to force') % newpath)
2551 2551 else:
2552 2552 newpath = savename(path)
2553 2553 ui.warn(_("copy %s to %s\n") % (path, newpath))
2554 2554 util.copyfiles(path, newpath)
2555 2555 if opts.get('empty'):
2556 2556 try:
2557 2557 os.unlink(q.join(q.status_path))
2558 2558 except:
2559 2559 pass
2560 2560 return 0
2561 2561
2562 2562 def strip(ui, repo, *revs, **opts):
2563 2563 """strip changesets and all their descendants from the repository
2564 2564
2565 2565 The strip command removes the specified changesets and all their
2566 2566 descendants. If the working directory has uncommitted changes, the
2567 2567 operation is aborted unless the --force flag is supplied, in which
2568 2568 case changes will be discarded.
2569 2569
2570 2570 If a parent of the working directory is stripped, then the working
2571 2571 directory will automatically be updated to the most recent
2572 2572 available ancestor of the stripped parent after the operation
2573 2573 completes.
2574 2574
2575 2575 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2576 2576 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2577 2577 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2578 2578 where BUNDLE is the bundle file created by the strip. Note that
2579 2579 the local revision numbers will in general be different after the
2580 2580 restore.
2581 2581
2582 2582 Use the --no-backup option to discard the backup bundle once the
2583 2583 operation completes.
2584 2584
2585 2585 Return 0 on success.
2586 2586 """
2587 2587 backup = 'all'
2588 2588 if opts.get('backup'):
2589 2589 backup = 'strip'
2590 2590 elif opts.get('no_backup') or opts.get('nobackup'):
2591 2591 backup = 'none'
2592 2592
2593 2593 cl = repo.changelog
2594 2594 revs = set(cmdutil.revrange(repo, revs))
2595 2595 if not revs:
2596 2596 raise util.Abort(_('empty revision set'))
2597 2597
2598 2598 descendants = set(cl.descendants(*revs))
2599 2599 strippedrevs = revs.union(descendants)
2600 2600 roots = revs.difference(descendants)
2601 2601
2602 2602 update = False
2603 2603 # if one of the wdir parent is stripped we'll need
2604 2604 # to update away to an earlier revision
2605 2605 for p in repo.dirstate.parents():
2606 2606 if p != nullid and cl.rev(p) in strippedrevs:
2607 2607 update = True
2608 2608 break
2609 2609
2610 2610 rootnodes = set(cl.node(r) for r in roots)
2611 2611
2612 2612 q = repo.mq
2613 2613 if q.applied:
2614 2614 # refresh queue state if we're about to strip
2615 2615 # applied patches
2616 2616 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2617 2617 q.applied_dirty = True
2618 2618 start = 0
2619 2619 end = len(q.applied)
2620 2620 for i, statusentry in enumerate(q.applied):
2621 2621 if statusentry.node in rootnodes:
2622 2622 # if one of the stripped roots is an applied
2623 2623 # patch, only part of the queue is stripped
2624 2624 start = i
2625 2625 break
2626 2626 del q.applied[start:end]
2627 2627 q.save_dirty()
2628 2628
2629 2629 revs = list(rootnodes)
2630 2630 if update and opts.get('keep'):
2631 2631 wlock = repo.wlock()
2632 2632 try:
2633 2633 urev = repo.mq.qparents(repo, revs[0])
2634 2634 repo.dirstate.rebuild(urev, repo[urev].manifest())
2635 2635 repo.dirstate.write()
2636 2636 update = False
2637 2637 finally:
2638 2638 wlock.release()
2639 2639
2640 2640 repo.mq.strip(repo, revs, backup=backup, update=update,
2641 2641 force=opts.get('force'))
2642 2642 return 0
2643 2643
2644 2644 def select(ui, repo, *args, **opts):
2645 2645 '''set or print guarded patches to push
2646 2646
2647 2647 Use the :hg:`qguard` command to set or print guards on patch, then use
2648 2648 qselect to tell mq which guards to use. A patch will be pushed if
2649 2649 it has no guards or any positive guards match the currently
2650 2650 selected guard, but will not be pushed if any negative guards
2651 2651 match the current guard. For example::
2652 2652
2653 2653 qguard foo.patch -- -stable (negative guard)
2654 2654 qguard bar.patch +stable (positive guard)
2655 2655 qselect stable
2656 2656
2657 2657 This activates the "stable" guard. mq will skip foo.patch (because
2658 2658 it has a negative match) but push bar.patch (because it has a
2659 2659 positive match).
2660 2660
2661 2661 With no arguments, prints the currently active guards.
2662 2662 With one argument, sets the active guard.
2663 2663
2664 2664 Use -n/--none to deactivate guards (no other arguments needed).
2665 2665 When no guards are active, patches with positive guards are
2666 2666 skipped and patches with negative guards are pushed.
2667 2667
2668 2668 qselect can change the guards on applied patches. It does not pop
2669 2669 guarded patches by default. Use --pop to pop back to the last
2670 2670 applied patch that is not guarded. Use --reapply (which implies
2671 2671 --pop) to push back to the current patch afterwards, but skip
2672 2672 guarded patches.
2673 2673
2674 2674 Use -s/--series to print a list of all guards in the series file
2675 2675 (no other arguments needed). Use -v for more information.
2676 2676
2677 2677 Returns 0 on success.'''
2678 2678
2679 2679 q = repo.mq
2680 2680 guards = q.active()
2681 2681 if args or opts.get('none'):
2682 2682 old_unapplied = q.unapplied(repo)
2683 2683 old_guarded = [i for i in xrange(len(q.applied)) if
2684 2684 not q.pushable(i)[0]]
2685 2685 q.set_active(args)
2686 2686 q.save_dirty()
2687 2687 if not args:
2688 2688 ui.status(_('guards deactivated\n'))
2689 2689 if not opts.get('pop') and not opts.get('reapply'):
2690 2690 unapplied = q.unapplied(repo)
2691 2691 guarded = [i for i in xrange(len(q.applied))
2692 2692 if not q.pushable(i)[0]]
2693 2693 if len(unapplied) != len(old_unapplied):
2694 2694 ui.status(_('number of unguarded, unapplied patches has '
2695 2695 'changed from %d to %d\n') %
2696 2696 (len(old_unapplied), len(unapplied)))
2697 2697 if len(guarded) != len(old_guarded):
2698 2698 ui.status(_('number of guarded, applied patches has changed '
2699 2699 'from %d to %d\n') %
2700 2700 (len(old_guarded), len(guarded)))
2701 2701 elif opts.get('series'):
2702 2702 guards = {}
2703 2703 noguards = 0
2704 2704 for gs in q.series_guards:
2705 2705 if not gs:
2706 2706 noguards += 1
2707 2707 for g in gs:
2708 2708 guards.setdefault(g, 0)
2709 2709 guards[g] += 1
2710 2710 if ui.verbose:
2711 2711 guards['NONE'] = noguards
2712 2712 guards = guards.items()
2713 2713 guards.sort(key=lambda x: x[0][1:])
2714 2714 if guards:
2715 2715 ui.note(_('guards in series file:\n'))
2716 2716 for guard, count in guards:
2717 2717 ui.note('%2d ' % count)
2718 2718 ui.write(guard, '\n')
2719 2719 else:
2720 2720 ui.note(_('no guards in series file\n'))
2721 2721 else:
2722 2722 if guards:
2723 2723 ui.note(_('active guards:\n'))
2724 2724 for g in guards:
2725 2725 ui.write(g, '\n')
2726 2726 else:
2727 2727 ui.write(_('no active guards\n'))
2728 2728 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2729 2729 popped = False
2730 2730 if opts.get('pop') or opts.get('reapply'):
2731 2731 for i in xrange(len(q.applied)):
2732 2732 pushable, reason = q.pushable(i)
2733 2733 if not pushable:
2734 2734 ui.status(_('popping guarded patches\n'))
2735 2735 popped = True
2736 2736 if i == 0:
2737 2737 q.pop(repo, all=True)
2738 2738 else:
2739 2739 q.pop(repo, i - 1)
2740 2740 break
2741 2741 if popped:
2742 2742 try:
2743 2743 if reapply:
2744 2744 ui.status(_('reapplying unguarded patches\n'))
2745 2745 q.push(repo, reapply)
2746 2746 finally:
2747 2747 q.save_dirty()
2748 2748
2749 2749 def finish(ui, repo, *revrange, **opts):
2750 2750 """move applied patches into repository history
2751 2751
2752 2752 Finishes the specified revisions (corresponding to applied
2753 2753 patches) by moving them out of mq control into regular repository
2754 2754 history.
2755 2755
2756 2756 Accepts a revision range or the -a/--applied option. If --applied
2757 2757 is specified, all applied mq revisions are removed from mq
2758 2758 control. Otherwise, the given revisions must be at the base of the
2759 2759 stack of applied patches.
2760 2760
2761 2761 This can be especially useful if your changes have been applied to
2762 2762 an upstream repository, or if you are about to push your changes
2763 2763 to upstream.
2764 2764
2765 2765 Returns 0 on success.
2766 2766 """
2767 2767 if not opts.get('applied') and not revrange:
2768 2768 raise util.Abort(_('no revisions specified'))
2769 2769 elif opts.get('applied'):
2770 2770 revrange = ('qbase::qtip',) + revrange
2771 2771
2772 2772 q = repo.mq
2773 2773 if not q.applied:
2774 2774 ui.status(_('no patches applied\n'))
2775 2775 return 0
2776 2776
2777 2777 revs = cmdutil.revrange(repo, revrange)
2778 2778 q.finish(repo, revs)
2779 2779 q.save_dirty()
2780 2780 return 0
2781 2781
2782 2782 def qqueue(ui, repo, name=None, **opts):
2783 2783 '''manage multiple patch queues
2784 2784
2785 2785 Supports switching between different patch queues, as well as creating
2786 2786 new patch queues and deleting existing ones.
2787 2787
2788 2788 Omitting a queue name or specifying -l/--list will show you the registered
2789 2789 queues - by default the "normal" patches queue is registered. The currently
2790 2790 active queue will be marked with "(active)".
2791 2791
2792 2792 To create a new queue, use -c/--create. The queue is automatically made
2793 2793 active, except in the case where there are applied patches from the
2794 2794 currently active queue in the repository. Then the queue will only be
2795 2795 created and switching will fail.
2796 2796
2797 2797 To delete an existing queue, use --delete. You cannot delete the currently
2798 2798 active queue.
2799 2799
2800 2800 Returns 0 on success.
2801 2801 '''
2802 2802
2803 2803 q = repo.mq
2804 2804
2805 2805 _defaultqueue = 'patches'
2806 2806 _allqueues = 'patches.queues'
2807 2807 _activequeue = 'patches.queue'
2808 2808
2809 2809 def _getcurrent():
2810 2810 cur = os.path.basename(q.path)
2811 2811 if cur.startswith('patches-'):
2812 2812 cur = cur[8:]
2813 2813 return cur
2814 2814
2815 2815 def _noqueues():
2816 2816 try:
2817 2817 fh = repo.opener(_allqueues, 'r')
2818 2818 fh.close()
2819 2819 except IOError:
2820 2820 return True
2821 2821
2822 2822 return False
2823 2823
2824 2824 def _getqueues():
2825 2825 current = _getcurrent()
2826 2826
2827 2827 try:
2828 2828 fh = repo.opener(_allqueues, 'r')
2829 2829 queues = [queue.strip() for queue in fh if queue.strip()]
2830 2830 fh.close()
2831 2831 if current not in queues:
2832 2832 queues.append(current)
2833 2833 except IOError:
2834 2834 queues = [_defaultqueue]
2835 2835
2836 2836 return sorted(queues)
2837 2837
2838 2838 def _setactive(name):
2839 2839 if q.applied:
2840 2840 raise util.Abort(_('patches applied - cannot set new queue active'))
2841 2841 _setactivenocheck(name)
2842 2842
2843 2843 def _setactivenocheck(name):
2844 2844 fh = repo.opener(_activequeue, 'w')
2845 2845 if name != 'patches':
2846 2846 fh.write(name)
2847 2847 fh.close()
2848 2848
2849 2849 def _addqueue(name):
2850 2850 fh = repo.opener(_allqueues, 'a')
2851 2851 fh.write('%s\n' % (name,))
2852 2852 fh.close()
2853 2853
2854 2854 def _queuedir(name):
2855 2855 if name == 'patches':
2856 2856 return repo.join('patches')
2857 2857 else:
2858 2858 return repo.join('patches-' + name)
2859 2859
2860 2860 def _validname(name):
2861 2861 for n in name:
2862 2862 if n in ':\\/.':
2863 2863 return False
2864 2864 return True
2865 2865
2866 2866 def _delete(name):
2867 2867 if name not in existing:
2868 2868 raise util.Abort(_('cannot delete queue that does not exist'))
2869 2869
2870 2870 current = _getcurrent()
2871 2871
2872 2872 if name == current:
2873 2873 raise util.Abort(_('cannot delete currently active queue'))
2874 2874
2875 2875 fh = repo.opener('patches.queues.new', 'w')
2876 2876 for queue in existing:
2877 2877 if queue == name:
2878 2878 continue
2879 2879 fh.write('%s\n' % (queue,))
2880 2880 fh.close()
2881 2881 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2882 2882
2883 2883 if not name or opts.get('list'):
2884 2884 current = _getcurrent()
2885 2885 for queue in _getqueues():
2886 2886 ui.write('%s' % (queue,))
2887 2887 if queue == current and not ui.quiet:
2888 2888 ui.write(_(' (active)\n'))
2889 2889 else:
2890 2890 ui.write('\n')
2891 2891 return
2892 2892
2893 2893 if not _validname(name):
2894 2894 raise util.Abort(
2895 2895 _('invalid queue name, may not contain the characters ":\\/."'))
2896 2896
2897 2897 existing = _getqueues()
2898 2898
2899 2899 if opts.get('create'):
2900 2900 if name in existing:
2901 2901 raise util.Abort(_('queue "%s" already exists') % name)
2902 2902 if _noqueues():
2903 2903 _addqueue(_defaultqueue)
2904 2904 _addqueue(name)
2905 2905 _setactive(name)
2906 2906 elif opts.get('rename'):
2907 2907 current = _getcurrent()
2908 2908 if name == current:
2909 2909 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
2910 2910 if name in existing:
2911 2911 raise util.Abort(_('queue "%s" already exists') % name)
2912 2912
2913 2913 olddir = _queuedir(current)
2914 2914 newdir = _queuedir(name)
2915 2915
2916 2916 if os.path.exists(newdir):
2917 2917 raise util.Abort(_('non-queue directory "%s" already exists') %
2918 2918 newdir)
2919 2919
2920 2920 fh = repo.opener('patches.queues.new', 'w')
2921 2921 for queue in existing:
2922 2922 if queue == current:
2923 2923 fh.write('%s\n' % (name,))
2924 2924 if os.path.exists(olddir):
2925 2925 util.rename(olddir, newdir)
2926 2926 else:
2927 2927 fh.write('%s\n' % (queue,))
2928 2928 fh.close()
2929 2929 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2930 2930 _setactivenocheck(name)
2931 2931 elif opts.get('delete'):
2932 2932 _delete(name)
2933 2933 elif opts.get('purge'):
2934 2934 if name in existing:
2935 2935 _delete(name)
2936 2936 qdir = _queuedir(name)
2937 2937 if os.path.exists(qdir):
2938 2938 shutil.rmtree(qdir)
2939 2939 else:
2940 2940 if name not in existing:
2941 2941 raise util.Abort(_('use --create to create a new queue'))
2942 2942 _setactive(name)
2943 2943
2944 2944 def reposetup(ui, repo):
2945 2945 class mqrepo(repo.__class__):
2946 2946 @util.propertycache
2947 2947 def mq(self):
2948 2948 return queue(self.ui, self.join(""))
2949 2949
2950 2950 def abort_if_wdir_patched(self, errmsg, force=False):
2951 2951 if self.mq.applied and not force:
2952 2952 parents = self.dirstate.parents()
2953 2953 patches = [s.node for s in self.mq.applied]
2954 2954 if parents[0] in patches or parents[1] in patches:
2955 2955 raise util.Abort(errmsg)
2956 2956
2957 2957 def commit(self, text="", user=None, date=None, match=None,
2958 2958 force=False, editor=False, extra={}):
2959 2959 self.abort_if_wdir_patched(
2960 2960 _('cannot commit over an applied mq patch'),
2961 2961 force)
2962 2962
2963 2963 return super(mqrepo, self).commit(text, user, date, match, force,
2964 2964 editor, extra)
2965 2965
2966 2966 def checkpush(self, force, revs):
2967 2967 if self.mq.applied and not force:
2968 2968 haspatches = True
2969 2969 if revs:
2970 2970 # Assume applied patches have no non-patch descendants
2971 2971 # and are not on remote already. If they appear in the
2972 2972 # set of resolved 'revs', bail out.
2973 2973 applied = set(e.node for e in self.mq.applied)
2974 2974 haspatches = bool([n for n in revs if n in applied])
2975 2975 if haspatches:
2976 2976 raise util.Abort(_('source has mq patches applied'))
2977 2977 super(mqrepo, self).checkpush(force, revs)
2978 2978
2979 2979 def _findtags(self):
2980 2980 '''augment tags from base class with patch tags'''
2981 2981 result = super(mqrepo, self)._findtags()
2982 2982
2983 2983 q = self.mq
2984 2984 if not q.applied:
2985 2985 return result
2986 2986
2987 2987 mqtags = [(patch.node, patch.name) for patch in q.applied]
2988 2988
2989 2989 try:
2990 2990 r = self.changelog.rev(mqtags[-1][0])
2991 2991 except error.RepoLookupError:
2992 2992 self.ui.warn(_('mq status file refers to unknown node %s\n')
2993 2993 % short(mqtags[-1][0]))
2994 2994 return result
2995 2995
2996 2996 mqtags.append((mqtags[-1][0], 'qtip'))
2997 2997 mqtags.append((mqtags[0][0], 'qbase'))
2998 2998 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2999 2999 tags = result[0]
3000 3000 for patch in mqtags:
3001 3001 if patch[1] in tags:
3002 3002 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
3003 3003 % patch[1])
3004 3004 else:
3005 3005 tags[patch[1]] = patch[0]
3006 3006
3007 3007 return result
3008 3008
3009 3009 def _branchtags(self, partial, lrev):
3010 3010 q = self.mq
3011 3011 if not q.applied:
3012 3012 return super(mqrepo, self)._branchtags(partial, lrev)
3013 3013
3014 3014 cl = self.changelog
3015 3015 qbasenode = q.applied[0].node
3016 3016 try:
3017 3017 qbase = cl.rev(qbasenode)
3018 3018 except error.LookupError:
3019 3019 self.ui.warn(_('mq status file refers to unknown node %s\n')
3020 3020 % short(qbasenode))
3021 3021 return super(mqrepo, self)._branchtags(partial, lrev)
3022 3022
3023 3023 start = lrev + 1
3024 3024 if start < qbase:
3025 3025 # update the cache (excluding the patches) and save it
3026 3026 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3027 3027 self._updatebranchcache(partial, ctxgen)
3028 3028 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3029 3029 start = qbase
3030 3030 # if start = qbase, the cache is as updated as it should be.
3031 3031 # if start > qbase, the cache includes (part of) the patches.
3032 3032 # we might as well use it, but we won't save it.
3033 3033
3034 3034 # update the cache up to the tip
3035 3035 ctxgen = (self[r] for r in xrange(start, len(cl)))
3036 3036 self._updatebranchcache(partial, ctxgen)
3037 3037
3038 3038 return partial
3039 3039
3040 3040 if repo.local():
3041 3041 repo.__class__ = mqrepo
3042 3042
3043 3043 def mqimport(orig, ui, repo, *args, **kwargs):
3044 3044 if (hasattr(repo, 'abort_if_wdir_patched')
3045 3045 and not kwargs.get('no_commit', False)):
3046 3046 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
3047 3047 kwargs.get('force'))
3048 3048 return orig(ui, repo, *args, **kwargs)
3049 3049
3050 3050 def mqinit(orig, ui, *args, **kwargs):
3051 3051 mq = kwargs.pop('mq', None)
3052 3052
3053 3053 if not mq:
3054 3054 return orig(ui, *args, **kwargs)
3055 3055
3056 3056 if args:
3057 3057 repopath = args[0]
3058 3058 if not hg.islocal(repopath):
3059 3059 raise util.Abort(_('only a local queue repository '
3060 3060 'may be initialized'))
3061 3061 else:
3062 3062 repopath = cmdutil.findrepo(os.getcwd())
3063 3063 if not repopath:
3064 3064 raise util.Abort(_('there is no Mercurial repository here '
3065 3065 '(.hg not found)'))
3066 3066 repo = hg.repository(ui, repopath)
3067 3067 return qinit(ui, repo, True)
3068 3068
3069 3069 def mqcommand(orig, ui, repo, *args, **kwargs):
3070 3070 """Add --mq option to operate on patch repository instead of main"""
3071 3071
3072 3072 # some commands do not like getting unknown options
3073 3073 mq = kwargs.pop('mq', None)
3074 3074
3075 3075 if not mq:
3076 3076 return orig(ui, repo, *args, **kwargs)
3077 3077
3078 3078 q = repo.mq
3079 3079 r = q.qrepo()
3080 3080 if not r:
3081 3081 raise util.Abort(_('no queue repository'))
3082 3082 return orig(r.ui, r, *args, **kwargs)
3083 3083
3084 3084 def summary(orig, ui, repo, *args, **kwargs):
3085 3085 r = orig(ui, repo, *args, **kwargs)
3086 3086 q = repo.mq
3087 3087 m = []
3088 3088 a, u = len(q.applied), len(q.unapplied(repo))
3089 3089 if a:
3090 3090 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3091 3091 if u:
3092 3092 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3093 3093 if m:
3094 3094 ui.write("mq: %s\n" % ', '.join(m))
3095 3095 else:
3096 3096 ui.note(_("mq: (empty queue)\n"))
3097 3097 return r
3098 3098
3099 3099 def uisetup(ui):
3100 3100 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3101 3101
3102 3102 extensions.wrapcommand(commands.table, 'import', mqimport)
3103 3103 extensions.wrapcommand(commands.table, 'summary', summary)
3104 3104
3105 3105 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3106 3106 entry[1].extend(mqopt)
3107 3107
3108 3108 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
3109 3109
3110 3110 def dotable(cmdtable):
3111 3111 for cmd in cmdtable.keys():
3112 3112 cmd = cmdutil.parsealiases(cmd)[0]
3113 3113 if cmd in nowrap:
3114 3114 continue
3115 3115 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3116 3116 entry[1].extend(mqopt)
3117 3117
3118 3118 dotable(commands.table)
3119 3119
3120 3120 for extname, extmodule in extensions.extensions():
3121 3121 if extmodule.__file__ != __file__:
3122 3122 dotable(getattr(extmodule, 'cmdtable', {}))
3123 3123
3124 3124 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
3125 3125
3126 3126 cmdtable = {
3127 3127 "qapplied":
3128 3128 (applied,
3129 3129 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
3130 3130 _('hg qapplied [-1] [-s] [PATCH]')),
3131 3131 "qclone":
3132 3132 (clone,
3133 3133 [('', 'pull', None, _('use pull protocol to copy metadata')),
3134 3134 ('U', 'noupdate', None, _('do not update the new working directories')),
3135 3135 ('', 'uncompressed', None,
3136 3136 _('use uncompressed transfer (fast over LAN)')),
3137 3137 ('p', 'patches', '',
3138 3138 _('location of source patch repository'), _('REPO')),
3139 3139 ] + commands.remoteopts,
3140 3140 _('hg qclone [OPTION]... SOURCE [DEST]')),
3141 3141 "qcommit|qci":
3142 3142 (commit,
3143 3143 commands.table["^commit|ci"][1],
3144 3144 _('hg qcommit [OPTION]... [FILE]...')),
3145 3145 "^qdiff":
3146 3146 (diff,
3147 3147 commands.diffopts + commands.diffopts2 + commands.walkopts,
3148 3148 _('hg qdiff [OPTION]... [FILE]...')),
3149 3149 "qdelete|qremove|qrm":
3150 3150 (delete,
3151 3151 [('k', 'keep', None, _('keep patch file')),
3152 3152 ('r', 'rev', [],
3153 3153 _('stop managing a revision (DEPRECATED)'), _('REV'))],
3154 3154 _('hg qdelete [-k] [PATCH]...')),
3155 3155 'qfold':
3156 3156 (fold,
3157 3157 [('e', 'edit', None, _('edit patch header')),
3158 3158 ('k', 'keep', None, _('keep folded patch files')),
3159 3159 ] + commands.commitopts,
3160 3160 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
3161 3161 'qgoto':
3162 3162 (goto,
3163 3163 [('f', 'force', None, _('overwrite any local changes'))],
3164 3164 _('hg qgoto [OPTION]... PATCH')),
3165 3165 'qguard':
3166 3166 (guard,
3167 3167 [('l', 'list', None, _('list all patches and guards')),
3168 3168 ('n', 'none', None, _('drop all guards'))],
3169 3169 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
3170 3170 'qheader': (header, [], _('hg qheader [PATCH]')),
3171 3171 "qimport":
3172 3172 (qimport,
3173 3173 [('e', 'existing', None, _('import file in patch directory')),
3174 3174 ('n', 'name', '',
3175 3175 _('name of patch file'), _('NAME')),
3176 3176 ('f', 'force', None, _('overwrite existing files')),
3177 3177 ('r', 'rev', [],
3178 3178 _('place existing revisions under mq control'), _('REV')),
3179 3179 ('g', 'git', None, _('use git extended diff format')),
3180 3180 ('P', 'push', None, _('qpush after importing'))],
3181 3181 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
3182 3182 "^qinit":
3183 3183 (init,
3184 3184 [('c', 'create-repo', None, _('create queue repository'))],
3185 3185 _('hg qinit [-c]')),
3186 3186 "^qnew":
3187 3187 (new,
3188 3188 [('e', 'edit', None, _('edit commit message')),
3189 3189 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
3190 3190 ('g', 'git', None, _('use git extended diff format')),
3191 3191 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
3192 3192 ('u', 'user', '',
3193 3193 _('add "From: <USER>" to patch'), _('USER')),
3194 3194 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
3195 3195 ('d', 'date', '',
3196 3196 _('add "Date: <DATE>" to patch'), _('DATE'))
3197 3197 ] + commands.walkopts + commands.commitopts,
3198 3198 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
3199 3199 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
3200 3200 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
3201 3201 "^qpop":
3202 3202 (pop,
3203 3203 [('a', 'all', None, _('pop all patches')),
3204 3204 ('n', 'name', '',
3205 3205 _('queue name to pop (DEPRECATED)'), _('NAME')),
3206 3206 ('f', 'force', None, _('forget any local changes to patched files'))],
3207 3207 _('hg qpop [-a] [-f] [PATCH | INDEX]')),
3208 3208 "^qpush":
3209 3209 (push,
3210 3210 [('f', 'force', None, _('apply on top of local changes')),
3211 3211 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
3212 3212 ('l', 'list', None, _('list patch name in commit text')),
3213 3213 ('a', 'all', None, _('apply all patches')),
3214 3214 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
3215 3215 ('n', 'name', '',
3216 3216 _('merge queue name (DEPRECATED)'), _('NAME')),
3217 3217 ('', 'move', None, _('reorder patch series and apply only the patch'))],
3218 3218 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]')),
3219 3219 "^qrefresh":
3220 3220 (refresh,
3221 3221 [('e', 'edit', None, _('edit commit message')),
3222 3222 ('g', 'git', None, _('use git extended diff format')),
3223 3223 ('s', 'short', None,
3224 3224 _('refresh only files already in the patch and specified files')),
3225 3225 ('U', 'currentuser', None,
3226 3226 _('add/update author field in patch with current user')),
3227 3227 ('u', 'user', '',
3228 3228 _('add/update author field in patch with given user'), _('USER')),
3229 3229 ('D', 'currentdate', None,
3230 3230 _('add/update date field in patch with current date')),
3231 3231 ('d', 'date', '',
3232 3232 _('add/update date field in patch with given date'), _('DATE'))
3233 3233 ] + commands.walkopts + commands.commitopts,
3234 3234 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
3235 3235 'qrename|qmv':
3236 3236 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
3237 3237 "qrestore":
3238 3238 (restore,
3239 3239 [('d', 'delete', None, _('delete save entry')),
3240 3240 ('u', 'update', None, _('update queue working directory'))],
3241 3241 _('hg qrestore [-d] [-u] REV')),
3242 3242 "qsave":
3243 3243 (save,
3244 3244 [('c', 'copy', None, _('copy patch directory')),
3245 3245 ('n', 'name', '',
3246 3246 _('copy directory name'), _('NAME')),
3247 3247 ('e', 'empty', None, _('clear queue status file')),
3248 3248 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3249 3249 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
3250 3250 "qselect":
3251 3251 (select,
3252 3252 [('n', 'none', None, _('disable all guards')),
3253 3253 ('s', 'series', None, _('list all guards in series file')),
3254 3254 ('', 'pop', None, _('pop to before first guarded applied patch')),
3255 3255 ('', 'reapply', None, _('pop, then reapply patches'))],
3256 3256 _('hg qselect [OPTION]... [GUARD]...')),
3257 3257 "qseries":
3258 3258 (series,
3259 3259 [('m', 'missing', None, _('print patches not in series')),
3260 3260 ] + seriesopts,
3261 3261 _('hg qseries [-ms]')),
3262 3262 "strip":
3263 3263 (strip,
3264 3264 [('f', 'force', None, _('force removal of changesets, discard '
3265 3265 'uncommitted changes (no backup)')),
3266 3266 ('b', 'backup', None, _('bundle only changesets with local revision'
3267 3267 ' number greater than REV which are not'
3268 3268 ' descendants of REV (DEPRECATED)')),
3269 3269 ('n', 'no-backup', None, _('no backups')),
3270 3270 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
3271 3271 ('k', 'keep', None, _("do not modify working copy during strip"))],
3272 3272 _('hg strip [-k] [-f] [-n] REV...')),
3273 3273 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
3274 3274 "qunapplied":
3275 3275 (unapplied,
3276 3276 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
3277 3277 _('hg qunapplied [-1] [-s] [PATCH]')),
3278 3278 "qfinish":
3279 3279 (finish,
3280 3280 [('a', 'applied', None, _('finish all applied changesets'))],
3281 3281 _('hg qfinish [-a] [REV]...')),
3282 3282 'qqueue':
3283 3283 (qqueue,
3284 3284 [
3285 3285 ('l', 'list', False, _('list all available queues')),
3286 3286 ('c', 'create', False, _('create new queue')),
3287 3287 ('', 'rename', False, _('rename active queue')),
3288 3288 ('', 'delete', False, _('delete reference to queue')),
3289 3289 ('', 'purge', False, _('delete queue, and remove patch dir')),
3290 3290 ],
3291 3291 _('[OPTION] [QUEUE]')),
3292 3292 }
3293 3293
3294 3294 colortable = {'qguard.negative': 'red',
3295 3295 'qguard.positive': 'yellow',
3296 3296 'qguard.unguarded': 'green',
3297 3297 'qseries.applied': 'blue bold underline',
3298 3298 'qseries.guarded': 'black bold',
3299 3299 'qseries.missing': 'red bold',
3300 3300 'qseries.unapplied': 'black bold'}
@@ -1,643 +1,643 b''
1 1 # Patch transplanting extension for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''command to transplant changesets from another branch
9 9
10 10 This extension allows you to transplant patches from another branch.
11 11
12 12 Transplanted patches are recorded in .hg/transplant/transplants, as a
13 13 map from a changeset hash to its hash in the source repository.
14 14 '''
15 15
16 16 from mercurial.i18n import _
17 17 import os, tempfile
18 18 from mercurial import bundlerepo, cmdutil, hg, merge, match
19 19 from mercurial import patch, revlog, scmutil, util, error
20 20 from mercurial import revset, templatekw
21 21
22 22 class transplantentry(object):
23 23 def __init__(self, lnode, rnode):
24 24 self.lnode = lnode
25 25 self.rnode = rnode
26 26
27 27 class transplants(object):
28 28 def __init__(self, path=None, transplantfile=None, opener=None):
29 29 self.path = path
30 30 self.transplantfile = transplantfile
31 31 self.opener = opener
32 32
33 33 if not opener:
34 34 self.opener = scmutil.opener(self.path)
35 35 self.transplants = {}
36 36 self.dirty = False
37 37 self.read()
38 38
39 39 def read(self):
40 40 abspath = os.path.join(self.path, self.transplantfile)
41 41 if self.transplantfile and os.path.exists(abspath):
42 for line in self.opener(self.transplantfile).read().splitlines():
42 for line in self.opener.read(self.transplantfile).splitlines():
43 43 lnode, rnode = map(revlog.bin, line.split(':'))
44 44 list = self.transplants.setdefault(rnode, [])
45 45 list.append(transplantentry(lnode, rnode))
46 46
47 47 def write(self):
48 48 if self.dirty and self.transplantfile:
49 49 if not os.path.isdir(self.path):
50 50 os.mkdir(self.path)
51 51 fp = self.opener(self.transplantfile, 'w')
52 52 for list in self.transplants.itervalues():
53 53 for t in list:
54 54 l, r = map(revlog.hex, (t.lnode, t.rnode))
55 55 fp.write(l + ':' + r + '\n')
56 56 fp.close()
57 57 self.dirty = False
58 58
59 59 def get(self, rnode):
60 60 return self.transplants.get(rnode) or []
61 61
62 62 def set(self, lnode, rnode):
63 63 list = self.transplants.setdefault(rnode, [])
64 64 list.append(transplantentry(lnode, rnode))
65 65 self.dirty = True
66 66
67 67 def remove(self, transplant):
68 68 list = self.transplants.get(transplant.rnode)
69 69 if list:
70 70 del list[list.index(transplant)]
71 71 self.dirty = True
72 72
73 73 class transplanter(object):
74 74 def __init__(self, ui, repo):
75 75 self.ui = ui
76 76 self.path = repo.join('transplant')
77 77 self.opener = scmutil.opener(self.path)
78 78 self.transplants = transplants(self.path, 'transplants',
79 79 opener=self.opener)
80 80
81 81 def applied(self, repo, node, parent):
82 82 '''returns True if a node is already an ancestor of parent
83 83 or has already been transplanted'''
84 84 if hasnode(repo, node):
85 85 if node in repo.changelog.reachable(parent, stop=node):
86 86 return True
87 87 for t in self.transplants.get(node):
88 88 # it might have been stripped
89 89 if not hasnode(repo, t.lnode):
90 90 self.transplants.remove(t)
91 91 return False
92 92 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
93 93 return True
94 94 return False
95 95
96 96 def apply(self, repo, source, revmap, merges, opts={}):
97 97 '''apply the revisions in revmap one by one in revision order'''
98 98 revs = sorted(revmap)
99 99 p1, p2 = repo.dirstate.parents()
100 100 pulls = []
101 101 diffopts = patch.diffopts(self.ui, opts)
102 102 diffopts.git = True
103 103
104 104 lock = wlock = None
105 105 try:
106 106 wlock = repo.wlock()
107 107 lock = repo.lock()
108 108 for rev in revs:
109 109 node = revmap[rev]
110 110 revstr = '%s:%s' % (rev, revlog.short(node))
111 111
112 112 if self.applied(repo, node, p1):
113 113 self.ui.warn(_('skipping already applied revision %s\n') %
114 114 revstr)
115 115 continue
116 116
117 117 parents = source.changelog.parents(node)
118 118 if not opts.get('filter'):
119 119 # If the changeset parent is the same as the
120 120 # wdir's parent, just pull it.
121 121 if parents[0] == p1:
122 122 pulls.append(node)
123 123 p1 = node
124 124 continue
125 125 if pulls:
126 126 if source != repo:
127 127 repo.pull(source, heads=pulls)
128 128 merge.update(repo, pulls[-1], False, False, None)
129 129 p1, p2 = repo.dirstate.parents()
130 130 pulls = []
131 131
132 132 domerge = False
133 133 if node in merges:
134 134 # pulling all the merge revs at once would mean we
135 135 # couldn't transplant after the latest even if
136 136 # transplants before them fail.
137 137 domerge = True
138 138 if not hasnode(repo, node):
139 139 repo.pull(source, heads=[node])
140 140
141 141 if parents[1] != revlog.nullid:
142 142 self.ui.note(_('skipping merge changeset %s:%s\n')
143 143 % (rev, revlog.short(node)))
144 144 patchfile = None
145 145 else:
146 146 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
147 147 fp = os.fdopen(fd, 'w')
148 148 gen = patch.diff(source, parents[0], node, opts=diffopts)
149 149 for chunk in gen:
150 150 fp.write(chunk)
151 151 fp.close()
152 152
153 153 del revmap[rev]
154 154 if patchfile or domerge:
155 155 try:
156 156 n = self.applyone(repo, node,
157 157 source.changelog.read(node),
158 158 patchfile, merge=domerge,
159 159 log=opts.get('log'),
160 160 filter=opts.get('filter'))
161 161 if n and domerge:
162 162 self.ui.status(_('%s merged at %s\n') % (revstr,
163 163 revlog.short(n)))
164 164 elif n:
165 165 self.ui.status(_('%s transplanted to %s\n')
166 166 % (revlog.short(node),
167 167 revlog.short(n)))
168 168 finally:
169 169 if patchfile:
170 170 os.unlink(patchfile)
171 171 if pulls:
172 172 repo.pull(source, heads=pulls)
173 173 merge.update(repo, pulls[-1], False, False, None)
174 174 finally:
175 175 self.saveseries(revmap, merges)
176 176 self.transplants.write()
177 177 lock.release()
178 178 wlock.release()
179 179
180 180 def filter(self, filter, node, changelog, patchfile):
181 181 '''arbitrarily rewrite changeset before applying it'''
182 182
183 183 self.ui.status(_('filtering %s\n') % patchfile)
184 184 user, date, msg = (changelog[1], changelog[2], changelog[4])
185 185 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
186 186 fp = os.fdopen(fd, 'w')
187 187 fp.write("# HG changeset patch\n")
188 188 fp.write("# User %s\n" % user)
189 189 fp.write("# Date %d %d\n" % date)
190 190 fp.write(msg + '\n')
191 191 fp.close()
192 192
193 193 try:
194 194 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
195 195 util.shellquote(patchfile)),
196 196 environ={'HGUSER': changelog[1],
197 197 'HGREVISION': revlog.hex(node),
198 198 },
199 199 onerr=util.Abort, errprefix=_('filter failed'))
200 200 user, date, msg = self.parselog(file(headerfile))[1:4]
201 201 finally:
202 202 os.unlink(headerfile)
203 203
204 204 return (user, date, msg)
205 205
206 206 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
207 207 filter=None):
208 208 '''apply the patch in patchfile to the repository as a transplant'''
209 209 (manifest, user, (time, timezone), files, message) = cl[:5]
210 210 date = "%d %d" % (time, timezone)
211 211 extra = {'transplant_source': node}
212 212 if filter:
213 213 (user, date, message) = self.filter(filter, node, cl, patchfile)
214 214
215 215 if log:
216 216 # we don't translate messages inserted into commits
217 217 message += '\n(transplanted from %s)' % revlog.hex(node)
218 218
219 219 self.ui.status(_('applying %s\n') % revlog.short(node))
220 220 self.ui.note('%s %s\n%s\n' % (user, date, message))
221 221
222 222 if not patchfile and not merge:
223 223 raise util.Abort(_('can only omit patchfile if merging'))
224 224 if patchfile:
225 225 try:
226 226 files = {}
227 227 try:
228 228 patch.patch(patchfile, self.ui, cwd=repo.root,
229 229 files=files, eolmode=None)
230 230 if not files:
231 231 self.ui.warn(_('%s: empty changeset')
232 232 % revlog.hex(node))
233 233 return None
234 234 finally:
235 235 files = cmdutil.updatedir(self.ui, repo, files)
236 236 except Exception, inst:
237 237 seriespath = os.path.join(self.path, 'series')
238 238 if os.path.exists(seriespath):
239 239 os.unlink(seriespath)
240 240 p1 = repo.dirstate.p1()
241 241 p2 = node
242 242 self.log(user, date, message, p1, p2, merge=merge)
243 243 self.ui.write(str(inst) + '\n')
244 244 raise util.Abort(_('fix up the merge and run '
245 245 'hg transplant --continue'))
246 246 else:
247 247 files = None
248 248 if merge:
249 249 p1, p2 = repo.dirstate.parents()
250 250 repo.dirstate.setparents(p1, node)
251 251 m = match.always(repo.root, '')
252 252 else:
253 253 m = match.exact(repo.root, '', files)
254 254
255 255 n = repo.commit(message, user, date, extra=extra, match=m)
256 256 if not n:
257 257 # Crash here to prevent an unclear crash later, in
258 258 # transplants.write(). This can happen if patch.patch()
259 259 # does nothing but claims success or if repo.status() fails
260 260 # to report changes done by patch.patch(). These both
261 261 # appear to be bugs in other parts of Mercurial, but dying
262 262 # here, as soon as we can detect the problem, is preferable
263 263 # to silently dropping changesets on the floor.
264 264 raise RuntimeError('nothing committed after transplant')
265 265 if not merge:
266 266 self.transplants.set(n, node)
267 267
268 268 return n
269 269
270 270 def resume(self, repo, source, opts=None):
271 271 '''recover last transaction and apply remaining changesets'''
272 272 if os.path.exists(os.path.join(self.path, 'journal')):
273 273 n, node = self.recover(repo)
274 274 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
275 275 revlog.short(n)))
276 276 seriespath = os.path.join(self.path, 'series')
277 277 if not os.path.exists(seriespath):
278 278 self.transplants.write()
279 279 return
280 280 nodes, merges = self.readseries()
281 281 revmap = {}
282 282 for n in nodes:
283 283 revmap[source.changelog.rev(n)] = n
284 284 os.unlink(seriespath)
285 285
286 286 self.apply(repo, source, revmap, merges, opts)
287 287
288 288 def recover(self, repo):
289 289 '''commit working directory using journal metadata'''
290 290 node, user, date, message, parents = self.readlog()
291 291 merge = len(parents) == 2
292 292
293 293 if not user or not date or not message or not parents[0]:
294 294 raise util.Abort(_('transplant log file is corrupt'))
295 295
296 296 extra = {'transplant_source': node}
297 297 wlock = repo.wlock()
298 298 try:
299 299 p1, p2 = repo.dirstate.parents()
300 300 if p1 != parents[0]:
301 301 raise util.Abort(
302 302 _('working dir not at transplant parent %s') %
303 303 revlog.hex(parents[0]))
304 304 if merge:
305 305 repo.dirstate.setparents(p1, parents[1])
306 306 n = repo.commit(message, user, date, extra=extra)
307 307 if not n:
308 308 raise util.Abort(_('commit failed'))
309 309 if not merge:
310 310 self.transplants.set(n, node)
311 311 self.unlog()
312 312
313 313 return n, node
314 314 finally:
315 315 wlock.release()
316 316
317 317 def readseries(self):
318 318 nodes = []
319 319 merges = []
320 320 cur = nodes
321 for line in self.opener('series').read().splitlines():
321 for line in self.opener.read('series').splitlines():
322 322 if line.startswith('# Merges'):
323 323 cur = merges
324 324 continue
325 325 cur.append(revlog.bin(line))
326 326
327 327 return (nodes, merges)
328 328
329 329 def saveseries(self, revmap, merges):
330 330 if not revmap:
331 331 return
332 332
333 333 if not os.path.isdir(self.path):
334 334 os.mkdir(self.path)
335 335 series = self.opener('series', 'w')
336 336 for rev in sorted(revmap):
337 337 series.write(revlog.hex(revmap[rev]) + '\n')
338 338 if merges:
339 339 series.write('# Merges\n')
340 340 for m in merges:
341 341 series.write(revlog.hex(m) + '\n')
342 342 series.close()
343 343
344 344 def parselog(self, fp):
345 345 parents = []
346 346 message = []
347 347 node = revlog.nullid
348 348 inmsg = False
349 349 user = None
350 350 date = None
351 351 for line in fp.read().splitlines():
352 352 if inmsg:
353 353 message.append(line)
354 354 elif line.startswith('# User '):
355 355 user = line[7:]
356 356 elif line.startswith('# Date '):
357 357 date = line[7:]
358 358 elif line.startswith('# Node ID '):
359 359 node = revlog.bin(line[10:])
360 360 elif line.startswith('# Parent '):
361 361 parents.append(revlog.bin(line[9:]))
362 362 elif not line.startswith('# '):
363 363 inmsg = True
364 364 message.append(line)
365 365 if None in (user, date):
366 366 raise util.Abort(_("filter corrupted changeset (no user or date)"))
367 367 return (node, user, date, '\n'.join(message), parents)
368 368
369 369 def log(self, user, date, message, p1, p2, merge=False):
370 370 '''journal changelog metadata for later recover'''
371 371
372 372 if not os.path.isdir(self.path):
373 373 os.mkdir(self.path)
374 374 fp = self.opener('journal', 'w')
375 375 fp.write('# User %s\n' % user)
376 376 fp.write('# Date %s\n' % date)
377 377 fp.write('# Node ID %s\n' % revlog.hex(p2))
378 378 fp.write('# Parent ' + revlog.hex(p1) + '\n')
379 379 if merge:
380 380 fp.write('# Parent ' + revlog.hex(p2) + '\n')
381 381 fp.write(message.rstrip() + '\n')
382 382 fp.close()
383 383
384 384 def readlog(self):
385 385 return self.parselog(self.opener('journal'))
386 386
387 387 def unlog(self):
388 388 '''remove changelog journal'''
389 389 absdst = os.path.join(self.path, 'journal')
390 390 if os.path.exists(absdst):
391 391 os.unlink(absdst)
392 392
393 393 def transplantfilter(self, repo, source, root):
394 394 def matchfn(node):
395 395 if self.applied(repo, node, root):
396 396 return False
397 397 if source.changelog.parents(node)[1] != revlog.nullid:
398 398 return False
399 399 extra = source.changelog.read(node)[5]
400 400 cnode = extra.get('transplant_source')
401 401 if cnode and self.applied(repo, cnode, root):
402 402 return False
403 403 return True
404 404
405 405 return matchfn
406 406
407 407 def hasnode(repo, node):
408 408 try:
409 409 return repo.changelog.rev(node) is not None
410 410 except error.RevlogError:
411 411 return False
412 412
413 413 def browserevs(ui, repo, nodes, opts):
414 414 '''interactively transplant changesets'''
415 415 def browsehelp(ui):
416 416 ui.write(_('y: transplant this changeset\n'
417 417 'n: skip this changeset\n'
418 418 'm: merge at this changeset\n'
419 419 'p: show patch\n'
420 420 'c: commit selected changesets\n'
421 421 'q: cancel transplant\n'
422 422 '?: show this help\n'))
423 423
424 424 displayer = cmdutil.show_changeset(ui, repo, opts)
425 425 transplants = []
426 426 merges = []
427 427 for node in nodes:
428 428 displayer.show(repo[node])
429 429 action = None
430 430 while not action:
431 431 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
432 432 if action == '?':
433 433 browsehelp(ui)
434 434 action = None
435 435 elif action == 'p':
436 436 parent = repo.changelog.parents(node)[0]
437 437 for chunk in patch.diff(repo, parent, node):
438 438 ui.write(chunk)
439 439 action = None
440 440 elif action not in ('y', 'n', 'm', 'c', 'q'):
441 441 ui.write(_('no such option\n'))
442 442 action = None
443 443 if action == 'y':
444 444 transplants.append(node)
445 445 elif action == 'm':
446 446 merges.append(node)
447 447 elif action == 'c':
448 448 break
449 449 elif action == 'q':
450 450 transplants = ()
451 451 merges = ()
452 452 break
453 453 displayer.close()
454 454 return (transplants, merges)
455 455
456 456 def transplant(ui, repo, *revs, **opts):
457 457 '''transplant changesets from another branch
458 458
459 459 Selected changesets will be applied on top of the current working
460 460 directory with the log of the original changeset. The changesets
461 461 are copied and will thus appear twice in the history. Use the
462 462 rebase extension instead if you want to move a whole branch of
463 463 unpublished changesets.
464 464
465 465 If --log is specified, log messages will have a comment appended
466 466 of the form::
467 467
468 468 (transplanted from CHANGESETHASH)
469 469
470 470 You can rewrite the changelog message with the --filter option.
471 471 Its argument will be invoked with the current changelog message as
472 472 $1 and the patch as $2.
473 473
474 474 If --source/-s is specified, selects changesets from the named
475 475 repository. If --branch/-b is specified, selects changesets from
476 476 the branch holding the named revision, up to that revision. If
477 477 --all/-a is specified, all changesets on the branch will be
478 478 transplanted, otherwise you will be prompted to select the
479 479 changesets you want.
480 480
481 481 :hg:`transplant --branch REVISION --all` will transplant the
482 482 selected branch (up to the named revision) onto your current
483 483 working directory.
484 484
485 485 You can optionally mark selected transplanted changesets as merge
486 486 changesets. You will not be prompted to transplant any ancestors
487 487 of a merged transplant, and you can merge descendants of them
488 488 normally instead of transplanting them.
489 489
490 490 If no merges or revisions are provided, :hg:`transplant` will
491 491 start an interactive changeset browser.
492 492
493 493 If a changeset application fails, you can fix the merge by hand
494 494 and then resume where you left off by calling :hg:`transplant
495 495 --continue/-c`.
496 496 '''
497 497 def incwalk(repo, csets, match=util.always):
498 498 for node in csets:
499 499 if match(node):
500 500 yield node
501 501
502 502 def transplantwalk(repo, root, branches, match=util.always):
503 503 if not branches:
504 504 branches = repo.heads()
505 505 ancestors = []
506 506 for branch in branches:
507 507 ancestors.append(repo.changelog.ancestor(root, branch))
508 508 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
509 509 if match(node):
510 510 yield node
511 511
512 512 def checkopts(opts, revs):
513 513 if opts.get('continue'):
514 514 if opts.get('branch') or opts.get('all') or opts.get('merge'):
515 515 raise util.Abort(_('--continue is incompatible with '
516 516 'branch, all or merge'))
517 517 return
518 518 if not (opts.get('source') or revs or
519 519 opts.get('merge') or opts.get('branch')):
520 520 raise util.Abort(_('no source URL, branch tag or revision '
521 521 'list provided'))
522 522 if opts.get('all'):
523 523 if not opts.get('branch'):
524 524 raise util.Abort(_('--all requires a branch revision'))
525 525 if revs:
526 526 raise util.Abort(_('--all is incompatible with a '
527 527 'revision list'))
528 528
529 529 checkopts(opts, revs)
530 530
531 531 if not opts.get('log'):
532 532 opts['log'] = ui.config('transplant', 'log')
533 533 if not opts.get('filter'):
534 534 opts['filter'] = ui.config('transplant', 'filter')
535 535
536 536 tp = transplanter(ui, repo)
537 537
538 538 p1, p2 = repo.dirstate.parents()
539 539 if len(repo) > 0 and p1 == revlog.nullid:
540 540 raise util.Abort(_('no revision checked out'))
541 541 if not opts.get('continue'):
542 542 if p2 != revlog.nullid:
543 543 raise util.Abort(_('outstanding uncommitted merges'))
544 544 m, a, r, d = repo.status()[:4]
545 545 if m or a or r or d:
546 546 raise util.Abort(_('outstanding local changes'))
547 547
548 548 sourcerepo = opts.get('source')
549 549 if sourcerepo:
550 550 source = hg.repository(ui, ui.expandpath(sourcerepo))
551 551 branches = map(source.lookup, opts.get('branch', ()))
552 552 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, source,
553 553 onlyheads=branches, force=True)
554 554 else:
555 555 source = repo
556 556 branches = map(source.lookup, opts.get('branch', ()))
557 557 cleanupfn = None
558 558
559 559 try:
560 560 if opts.get('continue'):
561 561 tp.resume(repo, source, opts)
562 562 return
563 563
564 564 tf = tp.transplantfilter(repo, source, p1)
565 565 if opts.get('prune'):
566 566 prune = [source.lookup(r)
567 567 for r in cmdutil.revrange(source, opts.get('prune'))]
568 568 matchfn = lambda x: tf(x) and x not in prune
569 569 else:
570 570 matchfn = tf
571 571 merges = map(source.lookup, opts.get('merge', ()))
572 572 revmap = {}
573 573 if revs:
574 574 for r in cmdutil.revrange(source, revs):
575 575 revmap[int(r)] = source.lookup(r)
576 576 elif opts.get('all') or not merges:
577 577 if source != repo:
578 578 alltransplants = incwalk(source, csets, match=matchfn)
579 579 else:
580 580 alltransplants = transplantwalk(source, p1, branches,
581 581 match=matchfn)
582 582 if opts.get('all'):
583 583 revs = alltransplants
584 584 else:
585 585 revs, newmerges = browserevs(ui, source, alltransplants, opts)
586 586 merges.extend(newmerges)
587 587 for r in revs:
588 588 revmap[source.changelog.rev(r)] = r
589 589 for r in merges:
590 590 revmap[source.changelog.rev(r)] = r
591 591
592 592 tp.apply(repo, source, revmap, merges, opts)
593 593 finally:
594 594 if cleanupfn:
595 595 cleanupfn()
596 596
597 597 def revsettransplanted(repo, subset, x):
598 598 """``transplanted(set)``
599 599 Transplanted changesets in set.
600 600 """
601 601 if x:
602 602 s = revset.getset(repo, subset, x)
603 603 else:
604 604 s = subset
605 605 cs = set()
606 606 for r in xrange(0, len(repo)):
607 607 if repo[r].extra().get('transplant_source'):
608 608 cs.add(r)
609 609 return [r for r in s if r in cs]
610 610
611 611 def kwtransplanted(repo, ctx, **args):
612 612 """:transplanted: String. The node identifier of the transplanted
613 613 changeset if any."""
614 614 n = ctx.extra().get('transplant_source')
615 615 return n and revlog.hex(n) or ''
616 616
617 617 def extsetup(ui):
618 618 revset.symbols['transplanted'] = revsettransplanted
619 619 templatekw.keywords['transplanted'] = kwtransplanted
620 620
621 621 cmdtable = {
622 622 "transplant":
623 623 (transplant,
624 624 [('s', 'source', '',
625 625 _('pull patches from REPO'), _('REPO')),
626 626 ('b', 'branch', [],
627 627 _('pull patches from branch BRANCH'), _('BRANCH')),
628 628 ('a', 'all', None, _('pull all changesets up to BRANCH')),
629 629 ('p', 'prune', [],
630 630 _('skip over REV'), _('REV')),
631 631 ('m', 'merge', [],
632 632 _('merge at REV'), _('REV')),
633 633 ('', 'log', None, _('append transplant info to log message')),
634 634 ('c', 'continue', None, _('continue last transplant session '
635 635 'after repair')),
636 636 ('', 'filter', '',
637 637 _('filter changesets through command'), _('CMD'))],
638 638 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
639 639 '[-m REV] [REV]...'))
640 640 }
641 641
642 642 # tell hggettext to extract docstrings from these functions:
643 643 i18nfunctions = [revsettransplanted, kwtransplanted]
@@ -1,216 +1,216 b''
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 sha, refspec = line.strip().split(' ', 1)
30 30 refspec = encoding.tolocal(refspec)
31 31 try:
32 32 bookmarks[refspec] = repo.changelog.lookup(sha)
33 33 except error.RepoLookupError:
34 34 pass
35 35 except IOError, inst:
36 36 if inst.errno != errno.ENOENT:
37 37 raise
38 38 return bookmarks
39 39
40 40 def readcurrent(repo):
41 41 '''Get the current bookmark
42 42
43 43 If we use gittishsh branches we have a current bookmark that
44 44 we are on. This function returns the name of the bookmark. It
45 45 is stored in .hg/bookmarks.current
46 46 '''
47 47 mark = None
48 48 try:
49 49 file = repo.opener('bookmarks.current')
50 50 except IOError, inst:
51 51 if inst.errno != errno.ENOENT:
52 52 raise
53 53 return None
54 54 try:
55 55 # No readline() in posixfile_nt, reading everything is cheap
56 56 mark = encoding.tolocal((file.readlines() or [''])[0])
57 57 if mark == '' or mark not in repo._bookmarks:
58 58 mark = None
59 59 finally:
60 60 file.close()
61 61 return mark
62 62
63 63 def write(repo):
64 64 '''Write bookmarks
65 65
66 66 Write the given bookmark => hash dictionary to the .hg/bookmarks file
67 67 in a format equal to those of localtags.
68 68
69 69 We also store a backup of the previous state in undo.bookmarks that
70 70 can be copied back on rollback.
71 71 '''
72 72 refs = repo._bookmarks
73 73
74 74 try:
75 bms = repo.opener('bookmarks').read()
75 bms = repo.opener.read('bookmarks')
76 76 except IOError, inst:
77 77 if inst.errno != errno.ENOENT:
78 78 raise
79 79 bms = ''
80 repo.opener('undo.bookmarks', 'w').write(bms)
80 repo.opener.write('undo.bookmarks', bms)
81 81
82 82 if repo._bookmarkcurrent not in refs:
83 83 setcurrent(repo, None)
84 84 for mark in refs.keys():
85 85 if not valid(mark):
86 86 raise util.Abort(_("bookmark '%s' contains illegal "
87 87 "character" % mark))
88 88
89 89 wlock = repo.wlock()
90 90 try:
91 91
92 92 file = repo.opener('bookmarks', 'w', atomictemp=True)
93 93 for refspec, node in refs.iteritems():
94 94 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
95 95 file.rename()
96 96
97 97 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
98 98 try:
99 99 os.utime(repo.sjoin('00changelog.i'), None)
100 100 except OSError:
101 101 pass
102 102
103 103 finally:
104 104 wlock.release()
105 105
106 106 def setcurrent(repo, mark):
107 107 '''Set the name of the bookmark that we are currently on
108 108
109 109 Set the name of the bookmark that we are on (hg update <bookmark>).
110 110 The name is recorded in .hg/bookmarks.current
111 111 '''
112 112 current = repo._bookmarkcurrent
113 113 if current == mark:
114 114 return
115 115
116 116 if mark not in repo._bookmarks:
117 117 mark = ''
118 118 if not valid(mark):
119 119 raise util.Abort(_("bookmark '%s' contains illegal "
120 120 "character" % mark))
121 121
122 122 wlock = repo.wlock()
123 123 try:
124 124 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
125 125 file.write(mark)
126 126 file.rename()
127 127 finally:
128 128 wlock.release()
129 129 repo._bookmarkcurrent = mark
130 130
131 131 def updatecurrentbookmark(repo, oldnode, curbranch):
132 132 try:
133 133 update(repo, oldnode, repo.branchtags()[curbranch])
134 134 except KeyError:
135 135 if curbranch == "default": # no default branch!
136 136 update(repo, oldnode, repo.lookup("tip"))
137 137 else:
138 138 raise util.Abort(_("branch %s not found") % curbranch)
139 139
140 140 def update(repo, parents, node):
141 141 marks = repo._bookmarks
142 142 update = False
143 143 mark = repo._bookmarkcurrent
144 144 if mark and marks[mark] in parents:
145 145 old = repo[marks[mark]]
146 146 new = repo[node]
147 147 if new in old.descendants():
148 148 marks[mark] = new.node()
149 149 update = True
150 150 if update:
151 151 write(repo)
152 152
153 153 def listbookmarks(repo):
154 154 # We may try to list bookmarks on a repo type that does not
155 155 # support it (e.g., statichttprepository).
156 156 if not hasattr(repo, '_bookmarks'):
157 157 return {}
158 158
159 159 d = {}
160 160 for k, v in repo._bookmarks.iteritems():
161 161 d[k] = hex(v)
162 162 return d
163 163
164 164 def pushbookmark(repo, key, old, new):
165 165 w = repo.wlock()
166 166 try:
167 167 marks = repo._bookmarks
168 168 if hex(marks.get(key, '')) != old:
169 169 return False
170 170 if new == '':
171 171 del marks[key]
172 172 else:
173 173 if new not in repo:
174 174 return False
175 175 marks[key] = repo[new].node()
176 176 write(repo)
177 177 return True
178 178 finally:
179 179 w.release()
180 180
181 181 def updatefromremote(ui, repo, remote):
182 182 ui.debug("checking for updated bookmarks\n")
183 183 rb = remote.listkeys('bookmarks')
184 184 changed = False
185 185 for k in rb.keys():
186 186 if k in repo._bookmarks:
187 187 nr, nl = rb[k], repo._bookmarks[k]
188 188 if nr in repo:
189 189 cr = repo[nr]
190 190 cl = repo[nl]
191 191 if cl.rev() >= cr.rev():
192 192 continue
193 193 if cr in cl.descendants():
194 194 repo._bookmarks[k] = cr.node()
195 195 changed = True
196 196 ui.status(_("updating bookmark %s\n") % k)
197 197 else:
198 198 ui.warn(_("not updating divergent"
199 199 " bookmark %s\n") % k)
200 200 if changed:
201 201 write(repo)
202 202
203 203 def diff(ui, repo, remote):
204 204 ui.status(_("searching for changed bookmarks\n"))
205 205
206 206 lmarks = repo.listkeys('bookmarks')
207 207 rmarks = remote.listkeys('bookmarks')
208 208
209 209 diff = sorted(set(rmarks) - set(lmarks))
210 210 for k in diff:
211 211 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
212 212
213 213 if len(diff) <= 0:
214 214 ui.status(_("no changed bookmarks found\n"))
215 215 return 1
216 216 return 0
@@ -1,1397 +1,1397 b''
1 1 # cmdutil.py - help for command processing in 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, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, glob, tempfile
11 11 import util, scmutil, templater, patch, error, templatekw
12 12 import match as matchmod
13 13 import similar, revset, subrepo
14 14
15 15 revrangesep = ':'
16 16
17 17 def parsealiases(cmd):
18 18 return cmd.lstrip("^").split("|")
19 19
20 20 def findpossible(cmd, table, strict=False):
21 21 """
22 22 Return cmd -> (aliases, command table entry)
23 23 for each matching command.
24 24 Return debug commands (or their aliases) only if no normal command matches.
25 25 """
26 26 choice = {}
27 27 debugchoice = {}
28 28 for e in table.keys():
29 29 aliases = parsealiases(e)
30 30 found = None
31 31 if cmd in aliases:
32 32 found = cmd
33 33 elif not strict:
34 34 for a in aliases:
35 35 if a.startswith(cmd):
36 36 found = a
37 37 break
38 38 if found is not None:
39 39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 40 debugchoice[found] = (aliases, table[e])
41 41 else:
42 42 choice[found] = (aliases, table[e])
43 43
44 44 if not choice and debugchoice:
45 45 choice = debugchoice
46 46
47 47 return choice
48 48
49 49 def findcmd(cmd, table, strict=True):
50 50 """Return (aliases, command table entry) for command string."""
51 51 choice = findpossible(cmd, table, strict)
52 52
53 53 if cmd in choice:
54 54 return choice[cmd]
55 55
56 56 if len(choice) > 1:
57 57 clist = choice.keys()
58 58 clist.sort()
59 59 raise error.AmbiguousCommand(cmd, clist)
60 60
61 61 if choice:
62 62 return choice.values()[0]
63 63
64 64 raise error.UnknownCommand(cmd)
65 65
66 66 def findrepo(p):
67 67 while not os.path.isdir(os.path.join(p, ".hg")):
68 68 oldp, p = p, os.path.dirname(p)
69 69 if p == oldp:
70 70 return None
71 71
72 72 return p
73 73
74 74 def bail_if_changed(repo):
75 75 if repo.dirstate.p2() != nullid:
76 76 raise util.Abort(_('outstanding uncommitted merge'))
77 77 modified, added, removed, deleted = repo.status()[:4]
78 78 if modified or added or removed or deleted:
79 79 raise util.Abort(_("outstanding uncommitted changes"))
80 80
81 81 def logmessage(opts):
82 82 """ get the log message according to -m and -l option """
83 83 message = opts.get('message')
84 84 logfile = opts.get('logfile')
85 85
86 86 if message and logfile:
87 87 raise util.Abort(_('options --message and --logfile are mutually '
88 88 'exclusive'))
89 89 if not message and logfile:
90 90 try:
91 91 if logfile == '-':
92 92 message = sys.stdin.read()
93 93 else:
94 message = open(logfile).read()
94 message = util.readfile(logfile)
95 95 except IOError, inst:
96 96 raise util.Abort(_("can't read commit message '%s': %s") %
97 97 (logfile, inst.strerror))
98 98 return message
99 99
100 100 def loglimit(opts):
101 101 """get the log limit according to option -l/--limit"""
102 102 limit = opts.get('limit')
103 103 if limit:
104 104 try:
105 105 limit = int(limit)
106 106 except ValueError:
107 107 raise util.Abort(_('limit must be a positive integer'))
108 108 if limit <= 0:
109 109 raise util.Abort(_('limit must be positive'))
110 110 else:
111 111 limit = None
112 112 return limit
113 113
114 114 def revsingle(repo, revspec, default='.'):
115 115 if not revspec:
116 116 return repo[default]
117 117
118 118 l = revrange(repo, [revspec])
119 119 if len(l) < 1:
120 120 raise util.Abort(_('empty revision set'))
121 121 return repo[l[-1]]
122 122
123 123 def revpair(repo, revs):
124 124 if not revs:
125 125 return repo.dirstate.p1(), None
126 126
127 127 l = revrange(repo, revs)
128 128
129 129 if len(l) == 0:
130 130 return repo.dirstate.p1(), None
131 131
132 132 if len(l) == 1:
133 133 return repo.lookup(l[0]), None
134 134
135 135 return repo.lookup(l[0]), repo.lookup(l[-1])
136 136
137 137 def revrange(repo, revs):
138 138 """Yield revision as strings from a list of revision specifications."""
139 139
140 140 def revfix(repo, val, defval):
141 141 if not val and val != 0 and defval is not None:
142 142 return defval
143 143 return repo.changelog.rev(repo.lookup(val))
144 144
145 145 seen, l = set(), []
146 146 for spec in revs:
147 147 # attempt to parse old-style ranges first to deal with
148 148 # things like old-tag which contain query metacharacters
149 149 try:
150 150 if isinstance(spec, int):
151 151 seen.add(spec)
152 152 l.append(spec)
153 153 continue
154 154
155 155 if revrangesep in spec:
156 156 start, end = spec.split(revrangesep, 1)
157 157 start = revfix(repo, start, 0)
158 158 end = revfix(repo, end, len(repo) - 1)
159 159 step = start > end and -1 or 1
160 160 for rev in xrange(start, end + step, step):
161 161 if rev in seen:
162 162 continue
163 163 seen.add(rev)
164 164 l.append(rev)
165 165 continue
166 166 elif spec and spec in repo: # single unquoted rev
167 167 rev = revfix(repo, spec, None)
168 168 if rev in seen:
169 169 continue
170 170 seen.add(rev)
171 171 l.append(rev)
172 172 continue
173 173 except error.RepoLookupError:
174 174 pass
175 175
176 176 # fall through to new-style queries if old-style fails
177 177 m = revset.match(repo.ui, spec)
178 178 for r in m(repo, range(len(repo))):
179 179 if r not in seen:
180 180 l.append(r)
181 181 seen.update(l)
182 182
183 183 return l
184 184
185 185 def make_filename(repo, pat, node,
186 186 total=None, seqno=None, revwidth=None, pathname=None):
187 187 node_expander = {
188 188 'H': lambda: hex(node),
189 189 'R': lambda: str(repo.changelog.rev(node)),
190 190 'h': lambda: short(node),
191 191 }
192 192 expander = {
193 193 '%': lambda: '%',
194 194 'b': lambda: os.path.basename(repo.root),
195 195 }
196 196
197 197 try:
198 198 if node:
199 199 expander.update(node_expander)
200 200 if node:
201 201 expander['r'] = (lambda:
202 202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
203 203 if total is not None:
204 204 expander['N'] = lambda: str(total)
205 205 if seqno is not None:
206 206 expander['n'] = lambda: str(seqno)
207 207 if total is not None and seqno is not None:
208 208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
209 209 if pathname is not None:
210 210 expander['s'] = lambda: os.path.basename(pathname)
211 211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
212 212 expander['p'] = lambda: pathname
213 213
214 214 newname = []
215 215 patlen = len(pat)
216 216 i = 0
217 217 while i < patlen:
218 218 c = pat[i]
219 219 if c == '%':
220 220 i += 1
221 221 c = pat[i]
222 222 c = expander[c]()
223 223 newname.append(c)
224 224 i += 1
225 225 return ''.join(newname)
226 226 except KeyError, inst:
227 227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
228 228 inst.args[0])
229 229
230 230 def make_file(repo, pat, node=None,
231 231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
232 232
233 233 writable = mode not in ('r', 'rb')
234 234
235 235 if not pat or pat == '-':
236 236 fp = writable and sys.stdout or sys.stdin
237 237 return os.fdopen(os.dup(fp.fileno()), mode)
238 238 if hasattr(pat, 'write') and writable:
239 239 return pat
240 240 if hasattr(pat, 'read') and 'r' in mode:
241 241 return pat
242 242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
243 243 pathname),
244 244 mode)
245 245
246 246 def expandpats(pats):
247 247 if not util.expandglobs:
248 248 return list(pats)
249 249 ret = []
250 250 for p in pats:
251 251 kind, name = matchmod._patsplit(p, None)
252 252 if kind is None:
253 253 try:
254 254 globbed = glob.glob(name)
255 255 except re.error:
256 256 globbed = [name]
257 257 if globbed:
258 258 ret.extend(globbed)
259 259 continue
260 260 ret.append(p)
261 261 return ret
262 262
263 263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
264 264 if pats == ("",):
265 265 pats = []
266 266 if not globbed and default == 'relpath':
267 267 pats = expandpats(pats or [])
268 268 m = matchmod.match(repo.root, repo.getcwd(), pats,
269 269 opts.get('include'), opts.get('exclude'), default,
270 270 auditor=repo.auditor)
271 271 def badfn(f, msg):
272 272 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
273 273 m.bad = badfn
274 274 return m
275 275
276 276 def matchall(repo):
277 277 return matchmod.always(repo.root, repo.getcwd())
278 278
279 279 def matchfiles(repo, files):
280 280 return matchmod.exact(repo.root, repo.getcwd(), files)
281 281
282 282 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
283 283 if dry_run is None:
284 284 dry_run = opts.get('dry_run')
285 285 if similarity is None:
286 286 similarity = float(opts.get('similarity') or 0)
287 287 # we'd use status here, except handling of symlinks and ignore is tricky
288 288 added, unknown, deleted, removed = [], [], [], []
289 289 audit_path = scmutil.path_auditor(repo.root)
290 290 m = match(repo, pats, opts)
291 291 for abs in repo.walk(m):
292 292 target = repo.wjoin(abs)
293 293 good = True
294 294 try:
295 295 audit_path(abs)
296 296 except (OSError, util.Abort):
297 297 good = False
298 298 rel = m.rel(abs)
299 299 exact = m.exact(abs)
300 300 if good and abs not in repo.dirstate:
301 301 unknown.append(abs)
302 302 if repo.ui.verbose or not exact:
303 303 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
304 304 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
305 305 or (os.path.isdir(target) and not os.path.islink(target))):
306 306 deleted.append(abs)
307 307 if repo.ui.verbose or not exact:
308 308 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
309 309 # for finding renames
310 310 elif repo.dirstate[abs] == 'r':
311 311 removed.append(abs)
312 312 elif repo.dirstate[abs] == 'a':
313 313 added.append(abs)
314 314 copies = {}
315 315 if similarity > 0:
316 316 for old, new, score in similar.findrenames(repo,
317 317 added + unknown, removed + deleted, similarity):
318 318 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
319 319 repo.ui.status(_('recording removal of %s as rename to %s '
320 320 '(%d%% similar)\n') %
321 321 (m.rel(old), m.rel(new), score * 100))
322 322 copies[new] = old
323 323
324 324 if not dry_run:
325 325 wctx = repo[None]
326 326 wlock = repo.wlock()
327 327 try:
328 328 wctx.remove(deleted)
329 329 wctx.add(unknown)
330 330 for new, old in copies.iteritems():
331 331 wctx.copy(old, new)
332 332 finally:
333 333 wlock.release()
334 334
335 335 def updatedir(ui, repo, patches, similarity=0):
336 336 '''Update dirstate after patch application according to metadata'''
337 337 if not patches:
338 338 return
339 339 copies = []
340 340 removes = set()
341 341 cfiles = patches.keys()
342 342 cwd = repo.getcwd()
343 343 if cwd:
344 344 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
345 345 for f in patches:
346 346 gp = patches[f]
347 347 if not gp:
348 348 continue
349 349 if gp.op == 'RENAME':
350 350 copies.append((gp.oldpath, gp.path))
351 351 removes.add(gp.oldpath)
352 352 elif gp.op == 'COPY':
353 353 copies.append((gp.oldpath, gp.path))
354 354 elif gp.op == 'DELETE':
355 355 removes.add(gp.path)
356 356
357 357 wctx = repo[None]
358 358 for src, dst in copies:
359 359 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
360 360 if (not similarity) and removes:
361 361 wctx.remove(sorted(removes), True)
362 362
363 363 for f in patches:
364 364 gp = patches[f]
365 365 if gp and gp.mode:
366 366 islink, isexec = gp.mode
367 367 dst = repo.wjoin(gp.path)
368 368 # patch won't create empty files
369 369 if gp.op == 'ADD' and not os.path.lexists(dst):
370 370 flags = (isexec and 'x' or '') + (islink and 'l' or '')
371 371 repo.wwrite(gp.path, '', flags)
372 372 util.set_flags(dst, islink, isexec)
373 373 addremove(repo, cfiles, similarity=similarity)
374 374 files = patches.keys()
375 375 files.extend([r for r in removes if r not in files])
376 376 return sorted(files)
377 377
378 378 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
379 379 """Update the dirstate to reflect the intent of copying src to dst. For
380 380 different reasons it might not end with dst being marked as copied from src.
381 381 """
382 382 origsrc = repo.dirstate.copied(src) or src
383 383 if dst == origsrc: # copying back a copy?
384 384 if repo.dirstate[dst] not in 'mn' and not dryrun:
385 385 repo.dirstate.normallookup(dst)
386 386 else:
387 387 if repo.dirstate[origsrc] == 'a' and origsrc == src:
388 388 if not ui.quiet:
389 389 ui.warn(_("%s has not been committed yet, so no copy "
390 390 "data will be stored for %s.\n")
391 391 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
392 392 if repo.dirstate[dst] in '?r' and not dryrun:
393 393 wctx.add([dst])
394 394 elif not dryrun:
395 395 wctx.copy(origsrc, dst)
396 396
397 397 def copy(ui, repo, pats, opts, rename=False):
398 398 # called with the repo lock held
399 399 #
400 400 # hgsep => pathname that uses "/" to separate directories
401 401 # ossep => pathname that uses os.sep to separate directories
402 402 cwd = repo.getcwd()
403 403 targets = {}
404 404 after = opts.get("after")
405 405 dryrun = opts.get("dry_run")
406 406 wctx = repo[None]
407 407
408 408 def walkpat(pat):
409 409 srcs = []
410 410 badstates = after and '?' or '?r'
411 411 m = match(repo, [pat], opts, globbed=True)
412 412 for abs in repo.walk(m):
413 413 state = repo.dirstate[abs]
414 414 rel = m.rel(abs)
415 415 exact = m.exact(abs)
416 416 if state in badstates:
417 417 if exact and state == '?':
418 418 ui.warn(_('%s: not copying - file is not managed\n') % rel)
419 419 if exact and state == 'r':
420 420 ui.warn(_('%s: not copying - file has been marked for'
421 421 ' remove\n') % rel)
422 422 continue
423 423 # abs: hgsep
424 424 # rel: ossep
425 425 srcs.append((abs, rel, exact))
426 426 return srcs
427 427
428 428 # abssrc: hgsep
429 429 # relsrc: ossep
430 430 # otarget: ossep
431 431 def copyfile(abssrc, relsrc, otarget, exact):
432 432 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
433 433 reltarget = repo.pathto(abstarget, cwd)
434 434 target = repo.wjoin(abstarget)
435 435 src = repo.wjoin(abssrc)
436 436 state = repo.dirstate[abstarget]
437 437
438 438 scmutil.checkportable(ui, abstarget)
439 439
440 440 # check for collisions
441 441 prevsrc = targets.get(abstarget)
442 442 if prevsrc is not None:
443 443 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
444 444 (reltarget, repo.pathto(abssrc, cwd),
445 445 repo.pathto(prevsrc, cwd)))
446 446 return
447 447
448 448 # check for overwrites
449 449 exists = os.path.lexists(target)
450 450 if not after and exists or after and state in 'mn':
451 451 if not opts['force']:
452 452 ui.warn(_('%s: not overwriting - file exists\n') %
453 453 reltarget)
454 454 return
455 455
456 456 if after:
457 457 if not exists:
458 458 if rename:
459 459 ui.warn(_('%s: not recording move - %s does not exist\n') %
460 460 (relsrc, reltarget))
461 461 else:
462 462 ui.warn(_('%s: not recording copy - %s does not exist\n') %
463 463 (relsrc, reltarget))
464 464 return
465 465 elif not dryrun:
466 466 try:
467 467 if exists:
468 468 os.unlink(target)
469 469 targetdir = os.path.dirname(target) or '.'
470 470 if not os.path.isdir(targetdir):
471 471 os.makedirs(targetdir)
472 472 util.copyfile(src, target)
473 473 except IOError, inst:
474 474 if inst.errno == errno.ENOENT:
475 475 ui.warn(_('%s: deleted in working copy\n') % relsrc)
476 476 else:
477 477 ui.warn(_('%s: cannot copy - %s\n') %
478 478 (relsrc, inst.strerror))
479 479 return True # report a failure
480 480
481 481 if ui.verbose or not exact:
482 482 if rename:
483 483 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
484 484 else:
485 485 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
486 486
487 487 targets[abstarget] = abssrc
488 488
489 489 # fix up dirstate
490 490 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
491 491 if rename and not dryrun:
492 492 wctx.remove([abssrc], not after)
493 493
494 494 # pat: ossep
495 495 # dest ossep
496 496 # srcs: list of (hgsep, hgsep, ossep, bool)
497 497 # return: function that takes hgsep and returns ossep
498 498 def targetpathfn(pat, dest, srcs):
499 499 if os.path.isdir(pat):
500 500 abspfx = scmutil.canonpath(repo.root, cwd, pat)
501 501 abspfx = util.localpath(abspfx)
502 502 if destdirexists:
503 503 striplen = len(os.path.split(abspfx)[0])
504 504 else:
505 505 striplen = len(abspfx)
506 506 if striplen:
507 507 striplen += len(os.sep)
508 508 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
509 509 elif destdirexists:
510 510 res = lambda p: os.path.join(dest,
511 511 os.path.basename(util.localpath(p)))
512 512 else:
513 513 res = lambda p: dest
514 514 return res
515 515
516 516 # pat: ossep
517 517 # dest ossep
518 518 # srcs: list of (hgsep, hgsep, ossep, bool)
519 519 # return: function that takes hgsep and returns ossep
520 520 def targetpathafterfn(pat, dest, srcs):
521 521 if matchmod.patkind(pat):
522 522 # a mercurial pattern
523 523 res = lambda p: os.path.join(dest,
524 524 os.path.basename(util.localpath(p)))
525 525 else:
526 526 abspfx = scmutil.canonpath(repo.root, cwd, pat)
527 527 if len(abspfx) < len(srcs[0][0]):
528 528 # A directory. Either the target path contains the last
529 529 # component of the source path or it does not.
530 530 def evalpath(striplen):
531 531 score = 0
532 532 for s in srcs:
533 533 t = os.path.join(dest, util.localpath(s[0])[striplen:])
534 534 if os.path.lexists(t):
535 535 score += 1
536 536 return score
537 537
538 538 abspfx = util.localpath(abspfx)
539 539 striplen = len(abspfx)
540 540 if striplen:
541 541 striplen += len(os.sep)
542 542 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
543 543 score = evalpath(striplen)
544 544 striplen1 = len(os.path.split(abspfx)[0])
545 545 if striplen1:
546 546 striplen1 += len(os.sep)
547 547 if evalpath(striplen1) > score:
548 548 striplen = striplen1
549 549 res = lambda p: os.path.join(dest,
550 550 util.localpath(p)[striplen:])
551 551 else:
552 552 # a file
553 553 if destdirexists:
554 554 res = lambda p: os.path.join(dest,
555 555 os.path.basename(util.localpath(p)))
556 556 else:
557 557 res = lambda p: dest
558 558 return res
559 559
560 560
561 561 pats = expandpats(pats)
562 562 if not pats:
563 563 raise util.Abort(_('no source or destination specified'))
564 564 if len(pats) == 1:
565 565 raise util.Abort(_('no destination specified'))
566 566 dest = pats.pop()
567 567 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
568 568 if not destdirexists:
569 569 if len(pats) > 1 or matchmod.patkind(pats[0]):
570 570 raise util.Abort(_('with multiple sources, destination must be an '
571 571 'existing directory'))
572 572 if util.endswithsep(dest):
573 573 raise util.Abort(_('destination %s is not a directory') % dest)
574 574
575 575 tfn = targetpathfn
576 576 if after:
577 577 tfn = targetpathafterfn
578 578 copylist = []
579 579 for pat in pats:
580 580 srcs = walkpat(pat)
581 581 if not srcs:
582 582 continue
583 583 copylist.append((tfn(pat, dest, srcs), srcs))
584 584 if not copylist:
585 585 raise util.Abort(_('no files to copy'))
586 586
587 587 errors = 0
588 588 for targetpath, srcs in copylist:
589 589 for abssrc, relsrc, exact in srcs:
590 590 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
591 591 errors += 1
592 592
593 593 if errors:
594 594 ui.warn(_('(consider using --after)\n'))
595 595
596 596 return errors != 0
597 597
598 598 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
599 599 runargs=None, appendpid=False):
600 600 '''Run a command as a service.'''
601 601
602 602 if opts['daemon'] and not opts['daemon_pipefds']:
603 603 # Signal child process startup with file removal
604 604 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
605 605 os.close(lockfd)
606 606 try:
607 607 if not runargs:
608 608 runargs = util.hgcmd() + sys.argv[1:]
609 609 runargs.append('--daemon-pipefds=%s' % lockpath)
610 610 # Don't pass --cwd to the child process, because we've already
611 611 # changed directory.
612 612 for i in xrange(1, len(runargs)):
613 613 if runargs[i].startswith('--cwd='):
614 614 del runargs[i]
615 615 break
616 616 elif runargs[i].startswith('--cwd'):
617 617 del runargs[i:i + 2]
618 618 break
619 619 def condfn():
620 620 return not os.path.exists(lockpath)
621 621 pid = util.rundetached(runargs, condfn)
622 622 if pid < 0:
623 623 raise util.Abort(_('child process failed to start'))
624 624 finally:
625 625 try:
626 626 os.unlink(lockpath)
627 627 except OSError, e:
628 628 if e.errno != errno.ENOENT:
629 629 raise
630 630 if parentfn:
631 631 return parentfn(pid)
632 632 else:
633 633 return
634 634
635 635 if initfn:
636 636 initfn()
637 637
638 638 if opts['pid_file']:
639 639 mode = appendpid and 'a' or 'w'
640 640 fp = open(opts['pid_file'], mode)
641 641 fp.write(str(os.getpid()) + '\n')
642 642 fp.close()
643 643
644 644 if opts['daemon_pipefds']:
645 645 lockpath = opts['daemon_pipefds']
646 646 try:
647 647 os.setsid()
648 648 except AttributeError:
649 649 pass
650 650 os.unlink(lockpath)
651 651 util.hidewindow()
652 652 sys.stdout.flush()
653 653 sys.stderr.flush()
654 654
655 655 nullfd = os.open(util.nulldev, os.O_RDWR)
656 656 logfilefd = nullfd
657 657 if logfile:
658 658 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
659 659 os.dup2(nullfd, 0)
660 660 os.dup2(logfilefd, 1)
661 661 os.dup2(logfilefd, 2)
662 662 if nullfd not in (0, 1, 2):
663 663 os.close(nullfd)
664 664 if logfile and logfilefd not in (0, 1, 2):
665 665 os.close(logfilefd)
666 666
667 667 if runfn:
668 668 return runfn()
669 669
670 670 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
671 671 opts=None):
672 672 '''export changesets as hg patches.'''
673 673
674 674 total = len(revs)
675 675 revwidth = max([len(str(rev)) for rev in revs])
676 676
677 677 def single(rev, seqno, fp):
678 678 ctx = repo[rev]
679 679 node = ctx.node()
680 680 parents = [p.node() for p in ctx.parents() if p]
681 681 branch = ctx.branch()
682 682 if switch_parent:
683 683 parents.reverse()
684 684 prev = (parents and parents[0]) or nullid
685 685
686 686 shouldclose = False
687 687 if not fp:
688 688 fp = make_file(repo, template, node, total=total, seqno=seqno,
689 689 revwidth=revwidth, mode='ab')
690 690 if fp != template:
691 691 shouldclose = True
692 692 if fp != sys.stdout and hasattr(fp, 'name'):
693 693 repo.ui.note("%s\n" % fp.name)
694 694
695 695 fp.write("# HG changeset patch\n")
696 696 fp.write("# User %s\n" % ctx.user())
697 697 fp.write("# Date %d %d\n" % ctx.date())
698 698 if branch and branch != 'default':
699 699 fp.write("# Branch %s\n" % branch)
700 700 fp.write("# Node ID %s\n" % hex(node))
701 701 fp.write("# Parent %s\n" % hex(prev))
702 702 if len(parents) > 1:
703 703 fp.write("# Parent %s\n" % hex(parents[1]))
704 704 fp.write(ctx.description().rstrip())
705 705 fp.write("\n\n")
706 706
707 707 for chunk in patch.diff(repo, prev, node, opts=opts):
708 708 fp.write(chunk)
709 709
710 710 if shouldclose:
711 711 fp.close()
712 712
713 713 for seqno, rev in enumerate(revs):
714 714 single(rev, seqno + 1, fp)
715 715
716 716 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
717 717 changes=None, stat=False, fp=None, prefix='',
718 718 listsubrepos=False):
719 719 '''show diff or diffstat.'''
720 720 if fp is None:
721 721 write = ui.write
722 722 else:
723 723 def write(s, **kw):
724 724 fp.write(s)
725 725
726 726 if stat:
727 727 diffopts = diffopts.copy(context=0)
728 728 width = 80
729 729 if not ui.plain():
730 730 width = ui.termwidth()
731 731 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
732 732 prefix=prefix)
733 733 for chunk, label in patch.diffstatui(util.iterlines(chunks),
734 734 width=width,
735 735 git=diffopts.git):
736 736 write(chunk, label=label)
737 737 else:
738 738 for chunk, label in patch.diffui(repo, node1, node2, match,
739 739 changes, diffopts, prefix=prefix):
740 740 write(chunk, label=label)
741 741
742 742 if listsubrepos:
743 743 ctx1 = repo[node1]
744 744 ctx2 = repo[node2]
745 745 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
746 746 if node2 is not None:
747 747 node2 = ctx2.substate[subpath][1]
748 748 submatch = matchmod.narrowmatcher(subpath, match)
749 749 sub.diff(diffopts, node2, submatch, changes=changes,
750 750 stat=stat, fp=fp, prefix=prefix)
751 751
752 752 class changeset_printer(object):
753 753 '''show changeset information when templating not requested.'''
754 754
755 755 def __init__(self, ui, repo, patch, diffopts, buffered):
756 756 self.ui = ui
757 757 self.repo = repo
758 758 self.buffered = buffered
759 759 self.patch = patch
760 760 self.diffopts = diffopts
761 761 self.header = {}
762 762 self.hunk = {}
763 763 self.lastheader = None
764 764 self.footer = None
765 765
766 766 def flush(self, rev):
767 767 if rev in self.header:
768 768 h = self.header[rev]
769 769 if h != self.lastheader:
770 770 self.lastheader = h
771 771 self.ui.write(h)
772 772 del self.header[rev]
773 773 if rev in self.hunk:
774 774 self.ui.write(self.hunk[rev])
775 775 del self.hunk[rev]
776 776 return 1
777 777 return 0
778 778
779 779 def close(self):
780 780 if self.footer:
781 781 self.ui.write(self.footer)
782 782
783 783 def show(self, ctx, copies=None, matchfn=None, **props):
784 784 if self.buffered:
785 785 self.ui.pushbuffer()
786 786 self._show(ctx, copies, matchfn, props)
787 787 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
788 788 else:
789 789 self._show(ctx, copies, matchfn, props)
790 790
791 791 def _show(self, ctx, copies, matchfn, props):
792 792 '''show a single changeset or file revision'''
793 793 changenode = ctx.node()
794 794 rev = ctx.rev()
795 795
796 796 if self.ui.quiet:
797 797 self.ui.write("%d:%s\n" % (rev, short(changenode)),
798 798 label='log.node')
799 799 return
800 800
801 801 log = self.repo.changelog
802 802 date = util.datestr(ctx.date())
803 803
804 804 hexfunc = self.ui.debugflag and hex or short
805 805
806 806 parents = [(p, hexfunc(log.node(p)))
807 807 for p in self._meaningful_parentrevs(log, rev)]
808 808
809 809 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
810 810 label='log.changeset')
811 811
812 812 branch = ctx.branch()
813 813 # don't show the default branch name
814 814 if branch != 'default':
815 815 self.ui.write(_("branch: %s\n") % branch,
816 816 label='log.branch')
817 817 for bookmark in self.repo.nodebookmarks(changenode):
818 818 self.ui.write(_("bookmark: %s\n") % bookmark,
819 819 label='log.bookmark')
820 820 for tag in self.repo.nodetags(changenode):
821 821 self.ui.write(_("tag: %s\n") % tag,
822 822 label='log.tag')
823 823 for parent in parents:
824 824 self.ui.write(_("parent: %d:%s\n") % parent,
825 825 label='log.parent')
826 826
827 827 if self.ui.debugflag:
828 828 mnode = ctx.manifestnode()
829 829 self.ui.write(_("manifest: %d:%s\n") %
830 830 (self.repo.manifest.rev(mnode), hex(mnode)),
831 831 label='ui.debug log.manifest')
832 832 self.ui.write(_("user: %s\n") % ctx.user(),
833 833 label='log.user')
834 834 self.ui.write(_("date: %s\n") % date,
835 835 label='log.date')
836 836
837 837 if self.ui.debugflag:
838 838 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
839 839 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
840 840 files):
841 841 if value:
842 842 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
843 843 label='ui.debug log.files')
844 844 elif ctx.files() and self.ui.verbose:
845 845 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
846 846 label='ui.note log.files')
847 847 if copies and self.ui.verbose:
848 848 copies = ['%s (%s)' % c for c in copies]
849 849 self.ui.write(_("copies: %s\n") % ' '.join(copies),
850 850 label='ui.note log.copies')
851 851
852 852 extra = ctx.extra()
853 853 if extra and self.ui.debugflag:
854 854 for key, value in sorted(extra.items()):
855 855 self.ui.write(_("extra: %s=%s\n")
856 856 % (key, value.encode('string_escape')),
857 857 label='ui.debug log.extra')
858 858
859 859 description = ctx.description().strip()
860 860 if description:
861 861 if self.ui.verbose:
862 862 self.ui.write(_("description:\n"),
863 863 label='ui.note log.description')
864 864 self.ui.write(description,
865 865 label='ui.note log.description')
866 866 self.ui.write("\n\n")
867 867 else:
868 868 self.ui.write(_("summary: %s\n") %
869 869 description.splitlines()[0],
870 870 label='log.summary')
871 871 self.ui.write("\n")
872 872
873 873 self.showpatch(changenode, matchfn)
874 874
875 875 def showpatch(self, node, matchfn):
876 876 if not matchfn:
877 877 matchfn = self.patch
878 878 if matchfn:
879 879 stat = self.diffopts.get('stat')
880 880 diff = self.diffopts.get('patch')
881 881 diffopts = patch.diffopts(self.ui, self.diffopts)
882 882 prev = self.repo.changelog.parents(node)[0]
883 883 if stat:
884 884 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
885 885 match=matchfn, stat=True)
886 886 if diff:
887 887 if stat:
888 888 self.ui.write("\n")
889 889 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
890 890 match=matchfn, stat=False)
891 891 self.ui.write("\n")
892 892
893 893 def _meaningful_parentrevs(self, log, rev):
894 894 """Return list of meaningful (or all if debug) parentrevs for rev.
895 895
896 896 For merges (two non-nullrev revisions) both parents are meaningful.
897 897 Otherwise the first parent revision is considered meaningful if it
898 898 is not the preceding revision.
899 899 """
900 900 parents = log.parentrevs(rev)
901 901 if not self.ui.debugflag and parents[1] == nullrev:
902 902 if parents[0] >= rev - 1:
903 903 parents = []
904 904 else:
905 905 parents = [parents[0]]
906 906 return parents
907 907
908 908
909 909 class changeset_templater(changeset_printer):
910 910 '''format changeset information.'''
911 911
912 912 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
913 913 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
914 914 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
915 915 defaulttempl = {
916 916 'parent': '{rev}:{node|formatnode} ',
917 917 'manifest': '{rev}:{node|formatnode}',
918 918 'file_copy': '{name} ({source})',
919 919 'extra': '{key}={value|stringescape}'
920 920 }
921 921 # filecopy is preserved for compatibility reasons
922 922 defaulttempl['filecopy'] = defaulttempl['file_copy']
923 923 self.t = templater.templater(mapfile, {'formatnode': formatnode},
924 924 cache=defaulttempl)
925 925 self.cache = {}
926 926
927 927 def use_template(self, t):
928 928 '''set template string to use'''
929 929 self.t.cache['changeset'] = t
930 930
931 931 def _meaningful_parentrevs(self, ctx):
932 932 """Return list of meaningful (or all if debug) parentrevs for rev.
933 933 """
934 934 parents = ctx.parents()
935 935 if len(parents) > 1:
936 936 return parents
937 937 if self.ui.debugflag:
938 938 return [parents[0], self.repo['null']]
939 939 if parents[0].rev() >= ctx.rev() - 1:
940 940 return []
941 941 return parents
942 942
943 943 def _show(self, ctx, copies, matchfn, props):
944 944 '''show a single changeset or file revision'''
945 945
946 946 showlist = templatekw.showlist
947 947
948 948 # showparents() behaviour depends on ui trace level which
949 949 # causes unexpected behaviours at templating level and makes
950 950 # it harder to extract it in a standalone function. Its
951 951 # behaviour cannot be changed so leave it here for now.
952 952 def showparents(**args):
953 953 ctx = args['ctx']
954 954 parents = [[('rev', p.rev()), ('node', p.hex())]
955 955 for p in self._meaningful_parentrevs(ctx)]
956 956 return showlist('parent', parents, **args)
957 957
958 958 props = props.copy()
959 959 props.update(templatekw.keywords)
960 960 props['parents'] = showparents
961 961 props['templ'] = self.t
962 962 props['ctx'] = ctx
963 963 props['repo'] = self.repo
964 964 props['revcache'] = {'copies': copies}
965 965 props['cache'] = self.cache
966 966
967 967 # find correct templates for current mode
968 968
969 969 tmplmodes = [
970 970 (True, None),
971 971 (self.ui.verbose, 'verbose'),
972 972 (self.ui.quiet, 'quiet'),
973 973 (self.ui.debugflag, 'debug'),
974 974 ]
975 975
976 976 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
977 977 for mode, postfix in tmplmodes:
978 978 for type in types:
979 979 cur = postfix and ('%s_%s' % (type, postfix)) or type
980 980 if mode and cur in self.t:
981 981 types[type] = cur
982 982
983 983 try:
984 984
985 985 # write header
986 986 if types['header']:
987 987 h = templater.stringify(self.t(types['header'], **props))
988 988 if self.buffered:
989 989 self.header[ctx.rev()] = h
990 990 else:
991 991 if self.lastheader != h:
992 992 self.lastheader = h
993 993 self.ui.write(h)
994 994
995 995 # write changeset metadata, then patch if requested
996 996 key = types['changeset']
997 997 self.ui.write(templater.stringify(self.t(key, **props)))
998 998 self.showpatch(ctx.node(), matchfn)
999 999
1000 1000 if types['footer']:
1001 1001 if not self.footer:
1002 1002 self.footer = templater.stringify(self.t(types['footer'],
1003 1003 **props))
1004 1004
1005 1005 except KeyError, inst:
1006 1006 msg = _("%s: no key named '%s'")
1007 1007 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1008 1008 except SyntaxError, inst:
1009 1009 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1010 1010
1011 1011 def show_changeset(ui, repo, opts, buffered=False):
1012 1012 """show one changeset using template or regular display.
1013 1013
1014 1014 Display format will be the first non-empty hit of:
1015 1015 1. option 'template'
1016 1016 2. option 'style'
1017 1017 3. [ui] setting 'logtemplate'
1018 1018 4. [ui] setting 'style'
1019 1019 If all of these values are either the unset or the empty string,
1020 1020 regular display via changeset_printer() is done.
1021 1021 """
1022 1022 # options
1023 1023 patch = False
1024 1024 if opts.get('patch') or opts.get('stat'):
1025 1025 patch = matchall(repo)
1026 1026
1027 1027 tmpl = opts.get('template')
1028 1028 style = None
1029 1029 if tmpl:
1030 1030 tmpl = templater.parsestring(tmpl, quoted=False)
1031 1031 else:
1032 1032 style = opts.get('style')
1033 1033
1034 1034 # ui settings
1035 1035 if not (tmpl or style):
1036 1036 tmpl = ui.config('ui', 'logtemplate')
1037 1037 if tmpl:
1038 1038 tmpl = templater.parsestring(tmpl)
1039 1039 else:
1040 1040 style = util.expandpath(ui.config('ui', 'style', ''))
1041 1041
1042 1042 if not (tmpl or style):
1043 1043 return changeset_printer(ui, repo, patch, opts, buffered)
1044 1044
1045 1045 mapfile = None
1046 1046 if style and not tmpl:
1047 1047 mapfile = style
1048 1048 if not os.path.split(mapfile)[0]:
1049 1049 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1050 1050 or templater.templatepath(mapfile))
1051 1051 if mapname:
1052 1052 mapfile = mapname
1053 1053
1054 1054 try:
1055 1055 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1056 1056 except SyntaxError, inst:
1057 1057 raise util.Abort(inst.args[0])
1058 1058 if tmpl:
1059 1059 t.use_template(tmpl)
1060 1060 return t
1061 1061
1062 1062 def finddate(ui, repo, date):
1063 1063 """Find the tipmost changeset that matches the given date spec"""
1064 1064
1065 1065 df = util.matchdate(date)
1066 1066 m = matchall(repo)
1067 1067 results = {}
1068 1068
1069 1069 def prep(ctx, fns):
1070 1070 d = ctx.date()
1071 1071 if df(d[0]):
1072 1072 results[ctx.rev()] = d
1073 1073
1074 1074 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1075 1075 rev = ctx.rev()
1076 1076 if rev in results:
1077 1077 ui.status(_("Found revision %s from %s\n") %
1078 1078 (rev, util.datestr(results[rev])))
1079 1079 return str(rev)
1080 1080
1081 1081 raise util.Abort(_("revision matching date not found"))
1082 1082
1083 1083 def walkchangerevs(repo, match, opts, prepare):
1084 1084 '''Iterate over files and the revs in which they changed.
1085 1085
1086 1086 Callers most commonly need to iterate backwards over the history
1087 1087 in which they are interested. Doing so has awful (quadratic-looking)
1088 1088 performance, so we use iterators in a "windowed" way.
1089 1089
1090 1090 We walk a window of revisions in the desired order. Within the
1091 1091 window, we first walk forwards to gather data, then in the desired
1092 1092 order (usually backwards) to display it.
1093 1093
1094 1094 This function returns an iterator yielding contexts. Before
1095 1095 yielding each context, the iterator will first call the prepare
1096 1096 function on each context in the window in forward order.'''
1097 1097
1098 1098 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1099 1099 if start < end:
1100 1100 while start < end:
1101 1101 yield start, min(windowsize, end - start)
1102 1102 start += windowsize
1103 1103 if windowsize < sizelimit:
1104 1104 windowsize *= 2
1105 1105 else:
1106 1106 while start > end:
1107 1107 yield start, min(windowsize, start - end - 1)
1108 1108 start -= windowsize
1109 1109 if windowsize < sizelimit:
1110 1110 windowsize *= 2
1111 1111
1112 1112 follow = opts.get('follow') or opts.get('follow_first')
1113 1113
1114 1114 if not len(repo):
1115 1115 return []
1116 1116
1117 1117 if follow:
1118 1118 defrange = '%s:0' % repo['.'].rev()
1119 1119 else:
1120 1120 defrange = '-1:0'
1121 1121 revs = revrange(repo, opts['rev'] or [defrange])
1122 1122 if not revs:
1123 1123 return []
1124 1124 wanted = set()
1125 1125 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1126 1126 fncache = {}
1127 1127 change = util.cachefunc(repo.changectx)
1128 1128
1129 1129 # First step is to fill wanted, the set of revisions that we want to yield.
1130 1130 # When it does not induce extra cost, we also fill fncache for revisions in
1131 1131 # wanted: a cache of filenames that were changed (ctx.files()) and that
1132 1132 # match the file filtering conditions.
1133 1133
1134 1134 if not slowpath and not match.files():
1135 1135 # No files, no patterns. Display all revs.
1136 1136 wanted = set(revs)
1137 1137 copies = []
1138 1138
1139 1139 if not slowpath:
1140 1140 # We only have to read through the filelog to find wanted revisions
1141 1141
1142 1142 minrev, maxrev = min(revs), max(revs)
1143 1143 def filerevgen(filelog, last):
1144 1144 """
1145 1145 Only files, no patterns. Check the history of each file.
1146 1146
1147 1147 Examines filelog entries within minrev, maxrev linkrev range
1148 1148 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1149 1149 tuples in backwards order
1150 1150 """
1151 1151 cl_count = len(repo)
1152 1152 revs = []
1153 1153 for j in xrange(0, last + 1):
1154 1154 linkrev = filelog.linkrev(j)
1155 1155 if linkrev < minrev:
1156 1156 continue
1157 1157 # only yield rev for which we have the changelog, it can
1158 1158 # happen while doing "hg log" during a pull or commit
1159 1159 if linkrev >= cl_count:
1160 1160 break
1161 1161
1162 1162 parentlinkrevs = []
1163 1163 for p in filelog.parentrevs(j):
1164 1164 if p != nullrev:
1165 1165 parentlinkrevs.append(filelog.linkrev(p))
1166 1166 n = filelog.node(j)
1167 1167 revs.append((linkrev, parentlinkrevs,
1168 1168 follow and filelog.renamed(n)))
1169 1169
1170 1170 return reversed(revs)
1171 1171 def iterfiles():
1172 1172 for filename in match.files():
1173 1173 yield filename, None
1174 1174 for filename_node in copies:
1175 1175 yield filename_node
1176 1176 for file_, node in iterfiles():
1177 1177 filelog = repo.file(file_)
1178 1178 if not len(filelog):
1179 1179 if node is None:
1180 1180 # A zero count may be a directory or deleted file, so
1181 1181 # try to find matching entries on the slow path.
1182 1182 if follow:
1183 1183 raise util.Abort(
1184 1184 _('cannot follow nonexistent file: "%s"') % file_)
1185 1185 slowpath = True
1186 1186 break
1187 1187 else:
1188 1188 continue
1189 1189
1190 1190 if node is None:
1191 1191 last = len(filelog) - 1
1192 1192 else:
1193 1193 last = filelog.rev(node)
1194 1194
1195 1195
1196 1196 # keep track of all ancestors of the file
1197 1197 ancestors = set([filelog.linkrev(last)])
1198 1198
1199 1199 # iterate from latest to oldest revision
1200 1200 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1201 1201 if not follow:
1202 1202 if rev > maxrev:
1203 1203 continue
1204 1204 else:
1205 1205 # Note that last might not be the first interesting
1206 1206 # rev to us:
1207 1207 # if the file has been changed after maxrev, we'll
1208 1208 # have linkrev(last) > maxrev, and we still need
1209 1209 # to explore the file graph
1210 1210 if rev not in ancestors:
1211 1211 continue
1212 1212 # XXX insert 1327 fix here
1213 1213 if flparentlinkrevs:
1214 1214 ancestors.update(flparentlinkrevs)
1215 1215
1216 1216 fncache.setdefault(rev, []).append(file_)
1217 1217 wanted.add(rev)
1218 1218 if copied:
1219 1219 copies.append(copied)
1220 1220 if slowpath:
1221 1221 # We have to read the changelog to match filenames against
1222 1222 # changed files
1223 1223
1224 1224 if follow:
1225 1225 raise util.Abort(_('can only follow copies/renames for explicit '
1226 1226 'filenames'))
1227 1227
1228 1228 # The slow path checks files modified in every changeset.
1229 1229 for i in sorted(revs):
1230 1230 ctx = change(i)
1231 1231 matches = filter(match, ctx.files())
1232 1232 if matches:
1233 1233 fncache[i] = matches
1234 1234 wanted.add(i)
1235 1235
1236 1236 class followfilter(object):
1237 1237 def __init__(self, onlyfirst=False):
1238 1238 self.startrev = nullrev
1239 1239 self.roots = set()
1240 1240 self.onlyfirst = onlyfirst
1241 1241
1242 1242 def match(self, rev):
1243 1243 def realparents(rev):
1244 1244 if self.onlyfirst:
1245 1245 return repo.changelog.parentrevs(rev)[0:1]
1246 1246 else:
1247 1247 return filter(lambda x: x != nullrev,
1248 1248 repo.changelog.parentrevs(rev))
1249 1249
1250 1250 if self.startrev == nullrev:
1251 1251 self.startrev = rev
1252 1252 return True
1253 1253
1254 1254 if rev > self.startrev:
1255 1255 # forward: all descendants
1256 1256 if not self.roots:
1257 1257 self.roots.add(self.startrev)
1258 1258 for parent in realparents(rev):
1259 1259 if parent in self.roots:
1260 1260 self.roots.add(rev)
1261 1261 return True
1262 1262 else:
1263 1263 # backwards: all parents
1264 1264 if not self.roots:
1265 1265 self.roots.update(realparents(self.startrev))
1266 1266 if rev in self.roots:
1267 1267 self.roots.remove(rev)
1268 1268 self.roots.update(realparents(rev))
1269 1269 return True
1270 1270
1271 1271 return False
1272 1272
1273 1273 # it might be worthwhile to do this in the iterator if the rev range
1274 1274 # is descending and the prune args are all within that range
1275 1275 for rev in opts.get('prune', ()):
1276 1276 rev = repo.changelog.rev(repo.lookup(rev))
1277 1277 ff = followfilter()
1278 1278 stop = min(revs[0], revs[-1])
1279 1279 for x in xrange(rev, stop - 1, -1):
1280 1280 if ff.match(x):
1281 1281 wanted.discard(x)
1282 1282
1283 1283 # Now that wanted is correctly initialized, we can iterate over the
1284 1284 # revision range, yielding only revisions in wanted.
1285 1285 def iterate():
1286 1286 if follow and not match.files():
1287 1287 ff = followfilter(onlyfirst=opts.get('follow_first'))
1288 1288 def want(rev):
1289 1289 return ff.match(rev) and rev in wanted
1290 1290 else:
1291 1291 def want(rev):
1292 1292 return rev in wanted
1293 1293
1294 1294 for i, window in increasing_windows(0, len(revs)):
1295 1295 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1296 1296 for rev in sorted(nrevs):
1297 1297 fns = fncache.get(rev)
1298 1298 ctx = change(rev)
1299 1299 if not fns:
1300 1300 def fns_generator():
1301 1301 for f in ctx.files():
1302 1302 if match(f):
1303 1303 yield f
1304 1304 fns = fns_generator()
1305 1305 prepare(ctx, fns)
1306 1306 for rev in nrevs:
1307 1307 yield change(rev)
1308 1308 return iterate()
1309 1309
1310 1310 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1311 1311 join = lambda f: os.path.join(prefix, f)
1312 1312 bad = []
1313 1313 oldbad = match.bad
1314 1314 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1315 1315 names = []
1316 1316 wctx = repo[None]
1317 1317 cca = None
1318 1318 abort, warn = scmutil.checkportabilityalert(ui)
1319 1319 if abort or warn:
1320 1320 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1321 1321 for f in repo.walk(match):
1322 1322 exact = match.exact(f)
1323 1323 if exact or f not in repo.dirstate:
1324 1324 if cca:
1325 1325 cca(f)
1326 1326 names.append(f)
1327 1327 if ui.verbose or not exact:
1328 1328 ui.status(_('adding %s\n') % match.rel(join(f)))
1329 1329
1330 1330 if listsubrepos:
1331 1331 for subpath in wctx.substate:
1332 1332 sub = wctx.sub(subpath)
1333 1333 try:
1334 1334 submatch = matchmod.narrowmatcher(subpath, match)
1335 1335 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1336 1336 except error.LookupError:
1337 1337 ui.status(_("skipping missing subrepository: %s\n")
1338 1338 % join(subpath))
1339 1339
1340 1340 if not dryrun:
1341 1341 rejected = wctx.add(names, prefix)
1342 1342 bad.extend(f for f in rejected if f in match.files())
1343 1343 return bad
1344 1344
1345 1345 def commit(ui, repo, commitfunc, pats, opts):
1346 1346 '''commit the specified files or all outstanding changes'''
1347 1347 date = opts.get('date')
1348 1348 if date:
1349 1349 opts['date'] = util.parsedate(date)
1350 1350 message = logmessage(opts)
1351 1351
1352 1352 # extract addremove carefully -- this function can be called from a command
1353 1353 # that doesn't support addremove
1354 1354 if opts.get('addremove'):
1355 1355 addremove(repo, pats, opts)
1356 1356
1357 1357 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1358 1358
1359 1359 def commiteditor(repo, ctx, subs):
1360 1360 if ctx.description():
1361 1361 return ctx.description()
1362 1362 return commitforceeditor(repo, ctx, subs)
1363 1363
1364 1364 def commitforceeditor(repo, ctx, subs):
1365 1365 edittext = []
1366 1366 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1367 1367 if ctx.description():
1368 1368 edittext.append(ctx.description())
1369 1369 edittext.append("")
1370 1370 edittext.append("") # Empty line between message and comments.
1371 1371 edittext.append(_("HG: Enter commit message."
1372 1372 " Lines beginning with 'HG:' are removed."))
1373 1373 edittext.append(_("HG: Leave message empty to abort commit."))
1374 1374 edittext.append("HG: --")
1375 1375 edittext.append(_("HG: user: %s") % ctx.user())
1376 1376 if ctx.p2():
1377 1377 edittext.append(_("HG: branch merge"))
1378 1378 if ctx.branch():
1379 1379 edittext.append(_("HG: branch '%s'") % ctx.branch())
1380 1380 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1381 1381 edittext.extend([_("HG: added %s") % f for f in added])
1382 1382 edittext.extend([_("HG: changed %s") % f for f in modified])
1383 1383 edittext.extend([_("HG: removed %s") % f for f in removed])
1384 1384 if not added and not modified and not removed:
1385 1385 edittext.append(_("HG: no files changed"))
1386 1386 edittext.append("")
1387 1387 # run editor in the repository root
1388 1388 olddir = os.getcwd()
1389 1389 os.chdir(repo.root)
1390 1390 text = repo.ui.edit("\n".join(edittext), ctx.user())
1391 1391 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1392 1392 os.chdir(olddir)
1393 1393
1394 1394 if not text.strip():
1395 1395 raise util.Abort(_("empty commit message"))
1396 1396
1397 1397 return text
@@ -1,4969 +1,4969 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex, bin, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _, gettext
11 11 import os, re, sys, difflib, time, tempfile
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, sshserver, hbisect, hgweb, hgweb.server
15 15 import merge as mergemod
16 16 import minirst, revset, templatefilters
17 17 import dagparser, context, simplemerge
18 18 import random, setdiscovery, treediscovery, dagutil
19 19
20 20 # Commands start here, listed alphabetically
21 21
22 22 def add(ui, repo, *pats, **opts):
23 23 """add the specified files on the next commit
24 24
25 25 Schedule files to be version controlled and added to the
26 26 repository.
27 27
28 28 The files will be added to the repository at the next commit. To
29 29 undo an add before that, see :hg:`forget`.
30 30
31 31 If no names are given, add all files to the repository.
32 32
33 33 .. container:: verbose
34 34
35 35 An example showing how new (unknown) files are added
36 36 automatically by :hg:`add`::
37 37
38 38 $ ls
39 39 foo.c
40 40 $ hg status
41 41 ? foo.c
42 42 $ hg add
43 43 adding foo.c
44 44 $ hg status
45 45 A foo.c
46 46
47 47 Returns 0 if all files are successfully added.
48 48 """
49 49
50 50 m = cmdutil.match(repo, pats, opts)
51 51 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
52 52 opts.get('subrepos'), prefix="")
53 53 return rejected and 1 or 0
54 54
55 55 def addremove(ui, repo, *pats, **opts):
56 56 """add all new files, delete all missing files
57 57
58 58 Add all new files and remove all missing files from the
59 59 repository.
60 60
61 61 New files are ignored if they match any of the patterns in
62 62 ``.hgignore``. As with add, these changes take effect at the next
63 63 commit.
64 64
65 65 Use the -s/--similarity option to detect renamed files. With a
66 66 parameter greater than 0, this compares every removed file with
67 67 every added file and records those similar enough as renames. This
68 68 option takes a percentage between 0 (disabled) and 100 (files must
69 69 be identical) as its parameter. Detecting renamed files this way
70 70 can be expensive. After using this option, :hg:`status -C` can be
71 71 used to check which files were identified as moved or renamed.
72 72
73 73 Returns 0 if all files are successfully added.
74 74 """
75 75 try:
76 76 sim = float(opts.get('similarity') or 100)
77 77 except ValueError:
78 78 raise util.Abort(_('similarity must be a number'))
79 79 if sim < 0 or sim > 100:
80 80 raise util.Abort(_('similarity must be between 0 and 100'))
81 81 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
82 82
83 83 def annotate(ui, repo, *pats, **opts):
84 84 """show changeset information by line for each file
85 85
86 86 List changes in files, showing the revision id responsible for
87 87 each line
88 88
89 89 This command is useful for discovering when a change was made and
90 90 by whom.
91 91
92 92 Without the -a/--text option, annotate will avoid processing files
93 93 it detects as binary. With -a, annotate will annotate the file
94 94 anyway, although the results will probably be neither useful
95 95 nor desirable.
96 96
97 97 Returns 0 on success.
98 98 """
99 99 if opts.get('follow'):
100 100 # --follow is deprecated and now just an alias for -f/--file
101 101 # to mimic the behavior of Mercurial before version 1.5
102 102 opts['file'] = 1
103 103
104 104 datefunc = ui.quiet and util.shortdate or util.datestr
105 105 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
106 106
107 107 if not pats:
108 108 raise util.Abort(_('at least one filename or pattern is required'))
109 109
110 110 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
111 111 ('number', lambda x: str(x[0].rev())),
112 112 ('changeset', lambda x: short(x[0].node())),
113 113 ('date', getdate),
114 114 ('file', lambda x: x[0].path()),
115 115 ]
116 116
117 117 if (not opts.get('user') and not opts.get('changeset')
118 118 and not opts.get('date') and not opts.get('file')):
119 119 opts['number'] = 1
120 120
121 121 linenumber = opts.get('line_number') is not None
122 122 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
123 123 raise util.Abort(_('at least one of -n/-c is required for -l'))
124 124
125 125 funcmap = [func for op, func in opmap if opts.get(op)]
126 126 if linenumber:
127 127 lastfunc = funcmap[-1]
128 128 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
129 129
130 130 def bad(x, y):
131 131 raise util.Abort("%s: %s" % (x, y))
132 132
133 133 ctx = cmdutil.revsingle(repo, opts.get('rev'))
134 134 m = cmdutil.match(repo, pats, opts)
135 135 m.bad = bad
136 136 follow = not opts.get('no_follow')
137 137 for abs in ctx.walk(m):
138 138 fctx = ctx[abs]
139 139 if not opts.get('text') and util.binary(fctx.data()):
140 140 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
141 141 continue
142 142
143 143 lines = fctx.annotate(follow=follow, linenumber=linenumber)
144 144 pieces = []
145 145
146 146 for f in funcmap:
147 147 l = [f(n) for n, dummy in lines]
148 148 if l:
149 149 sized = [(x, encoding.colwidth(x)) for x in l]
150 150 ml = max([w for x, w in sized])
151 151 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
152 152
153 153 if pieces:
154 154 for p, l in zip(zip(*pieces), lines):
155 155 ui.write("%s: %s" % (" ".join(p), l[1]))
156 156
157 157 def archive(ui, repo, dest, **opts):
158 158 '''create an unversioned archive of a repository revision
159 159
160 160 By default, the revision used is the parent of the working
161 161 directory; use -r/--rev to specify a different revision.
162 162
163 163 The archive type is automatically detected based on file
164 164 extension (or override using -t/--type).
165 165
166 166 Valid types are:
167 167
168 168 :``files``: a directory full of files (default)
169 169 :``tar``: tar archive, uncompressed
170 170 :``tbz2``: tar archive, compressed using bzip2
171 171 :``tgz``: tar archive, compressed using gzip
172 172 :``uzip``: zip archive, uncompressed
173 173 :``zip``: zip archive, compressed using deflate
174 174
175 175 The exact name of the destination archive or directory is given
176 176 using a format string; see :hg:`help export` for details.
177 177
178 178 Each member added to an archive file has a directory prefix
179 179 prepended. Use -p/--prefix to specify a format string for the
180 180 prefix. The default is the basename of the archive, with suffixes
181 181 removed.
182 182
183 183 Returns 0 on success.
184 184 '''
185 185
186 186 ctx = cmdutil.revsingle(repo, opts.get('rev'))
187 187 if not ctx:
188 188 raise util.Abort(_('no working directory: please specify a revision'))
189 189 node = ctx.node()
190 190 dest = cmdutil.make_filename(repo, dest, node)
191 191 if os.path.realpath(dest) == repo.root:
192 192 raise util.Abort(_('repository root cannot be destination'))
193 193
194 194 kind = opts.get('type') or archival.guesskind(dest) or 'files'
195 195 prefix = opts.get('prefix')
196 196
197 197 if dest == '-':
198 198 if kind == 'files':
199 199 raise util.Abort(_('cannot archive plain files to stdout'))
200 200 dest = sys.stdout
201 201 if not prefix:
202 202 prefix = os.path.basename(repo.root) + '-%h'
203 203
204 204 prefix = cmdutil.make_filename(repo, prefix, node)
205 205 matchfn = cmdutil.match(repo, [], opts)
206 206 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
207 207 matchfn, prefix, subrepos=opts.get('subrepos'))
208 208
209 209 def backout(ui, repo, node=None, rev=None, **opts):
210 210 '''reverse effect of earlier changeset
211 211
212 212 Prepare a new changeset with the effect of REV undone in the
213 213 current working directory.
214 214
215 215 If REV is the parent of the working directory, then this new changeset
216 216 is committed automatically. Otherwise, hg needs to merge the
217 217 changes and the merged result is left uncommitted.
218 218
219 219 By default, the pending changeset will have one parent,
220 220 maintaining a linear history. With --merge, the pending changeset
221 221 will instead have two parents: the old parent of the working
222 222 directory and a new child of REV that simply undoes REV.
223 223
224 224 Before version 1.7, the behavior without --merge was equivalent to
225 225 specifying --merge followed by :hg:`update --clean .` to cancel
226 226 the merge and leave the child of REV as a head to be merged
227 227 separately.
228 228
229 229 See :hg:`help dates` for a list of formats valid for -d/--date.
230 230
231 231 Returns 0 on success.
232 232 '''
233 233 if rev and node:
234 234 raise util.Abort(_("please specify just one revision"))
235 235
236 236 if not rev:
237 237 rev = node
238 238
239 239 if not rev:
240 240 raise util.Abort(_("please specify a revision to backout"))
241 241
242 242 date = opts.get('date')
243 243 if date:
244 244 opts['date'] = util.parsedate(date)
245 245
246 246 cmdutil.bail_if_changed(repo)
247 247 node = cmdutil.revsingle(repo, rev).node()
248 248
249 249 op1, op2 = repo.dirstate.parents()
250 250 a = repo.changelog.ancestor(op1, node)
251 251 if a != node:
252 252 raise util.Abort(_('cannot backout change on a different branch'))
253 253
254 254 p1, p2 = repo.changelog.parents(node)
255 255 if p1 == nullid:
256 256 raise util.Abort(_('cannot backout a change with no parents'))
257 257 if p2 != nullid:
258 258 if not opts.get('parent'):
259 259 raise util.Abort(_('cannot backout a merge changeset without '
260 260 '--parent'))
261 261 p = repo.lookup(opts['parent'])
262 262 if p not in (p1, p2):
263 263 raise util.Abort(_('%s is not a parent of %s') %
264 264 (short(p), short(node)))
265 265 parent = p
266 266 else:
267 267 if opts.get('parent'):
268 268 raise util.Abort(_('cannot use --parent on non-merge changeset'))
269 269 parent = p1
270 270
271 271 # the backout should appear on the same branch
272 272 branch = repo.dirstate.branch()
273 273 hg.clean(repo, node, show_stats=False)
274 274 repo.dirstate.setbranch(branch)
275 275 revert_opts = opts.copy()
276 276 revert_opts['date'] = None
277 277 revert_opts['all'] = True
278 278 revert_opts['rev'] = hex(parent)
279 279 revert_opts['no_backup'] = None
280 280 revert(ui, repo, **revert_opts)
281 281 if not opts.get('merge') and op1 != node:
282 282 try:
283 283 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
284 284 return hg.update(repo, op1)
285 285 finally:
286 286 ui.setconfig('ui', 'forcemerge', '')
287 287
288 288 commit_opts = opts.copy()
289 289 commit_opts['addremove'] = False
290 290 if not commit_opts['message'] and not commit_opts['logfile']:
291 291 # we don't translate commit messages
292 292 commit_opts['message'] = "Backed out changeset %s" % short(node)
293 293 commit_opts['force_editor'] = True
294 294 commit(ui, repo, **commit_opts)
295 295 def nice(node):
296 296 return '%d:%s' % (repo.changelog.rev(node), short(node))
297 297 ui.status(_('changeset %s backs out changeset %s\n') %
298 298 (nice(repo.changelog.tip()), nice(node)))
299 299 if opts.get('merge') and op1 != node:
300 300 hg.clean(repo, op1, show_stats=False)
301 301 ui.status(_('merging with changeset %s\n')
302 302 % nice(repo.changelog.tip()))
303 303 try:
304 304 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
305 305 return hg.merge(repo, hex(repo.changelog.tip()))
306 306 finally:
307 307 ui.setconfig('ui', 'forcemerge', '')
308 308 return 0
309 309
310 310 def bisect(ui, repo, rev=None, extra=None, command=None,
311 311 reset=None, good=None, bad=None, skip=None, extend=None,
312 312 noupdate=None):
313 313 """subdivision search of changesets
314 314
315 315 This command helps to find changesets which introduce problems. To
316 316 use, mark the earliest changeset you know exhibits the problem as
317 317 bad, then mark the latest changeset which is free from the problem
318 318 as good. Bisect will update your working directory to a revision
319 319 for testing (unless the -U/--noupdate option is specified). Once
320 320 you have performed tests, mark the working directory as good or
321 321 bad, and bisect will either update to another candidate changeset
322 322 or announce that it has found the bad revision.
323 323
324 324 As a shortcut, you can also use the revision argument to mark a
325 325 revision as good or bad without checking it out first.
326 326
327 327 If you supply a command, it will be used for automatic bisection.
328 328 Its exit status will be used to mark revisions as good or bad:
329 329 status 0 means good, 125 means to skip the revision, 127
330 330 (command not found) will abort the bisection, and any other
331 331 non-zero exit status means the revision is bad.
332 332
333 333 Returns 0 on success.
334 334 """
335 335 def extendbisectrange(nodes, good):
336 336 # bisect is incomplete when it ends on a merge node and
337 337 # one of the parent was not checked.
338 338 parents = repo[nodes[0]].parents()
339 339 if len(parents) > 1:
340 340 side = good and state['bad'] or state['good']
341 341 num = len(set(i.node() for i in parents) & set(side))
342 342 if num == 1:
343 343 return parents[0].ancestor(parents[1])
344 344 return None
345 345
346 346 def print_result(nodes, good):
347 347 displayer = cmdutil.show_changeset(ui, repo, {})
348 348 if len(nodes) == 1:
349 349 # narrowed it down to a single revision
350 350 if good:
351 351 ui.write(_("The first good revision is:\n"))
352 352 else:
353 353 ui.write(_("The first bad revision is:\n"))
354 354 displayer.show(repo[nodes[0]])
355 355 extendnode = extendbisectrange(nodes, good)
356 356 if extendnode is not None:
357 357 ui.write(_('Not all ancestors of this changeset have been'
358 358 ' checked.\nUse bisect --extend to continue the '
359 359 'bisection from\nthe common ancestor, %s.\n')
360 360 % extendnode)
361 361 else:
362 362 # multiple possible revisions
363 363 if good:
364 364 ui.write(_("Due to skipped revisions, the first "
365 365 "good revision could be any of:\n"))
366 366 else:
367 367 ui.write(_("Due to skipped revisions, the first "
368 368 "bad revision could be any of:\n"))
369 369 for n in nodes:
370 370 displayer.show(repo[n])
371 371 displayer.close()
372 372
373 373 def check_state(state, interactive=True):
374 374 if not state['good'] or not state['bad']:
375 375 if (good or bad or skip or reset) and interactive:
376 376 return
377 377 if not state['good']:
378 378 raise util.Abort(_('cannot bisect (no known good revisions)'))
379 379 else:
380 380 raise util.Abort(_('cannot bisect (no known bad revisions)'))
381 381 return True
382 382
383 383 # backward compatibility
384 384 if rev in "good bad reset init".split():
385 385 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
386 386 cmd, rev, extra = rev, extra, None
387 387 if cmd == "good":
388 388 good = True
389 389 elif cmd == "bad":
390 390 bad = True
391 391 else:
392 392 reset = True
393 393 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
394 394 raise util.Abort(_('incompatible arguments'))
395 395
396 396 if reset:
397 397 p = repo.join("bisect.state")
398 398 if os.path.exists(p):
399 399 os.unlink(p)
400 400 return
401 401
402 402 state = hbisect.load_state(repo)
403 403
404 404 if command:
405 405 changesets = 1
406 406 try:
407 407 while changesets:
408 408 # update state
409 409 status = util.system(command)
410 410 if status == 125:
411 411 transition = "skip"
412 412 elif status == 0:
413 413 transition = "good"
414 414 # status < 0 means process was killed
415 415 elif status == 127:
416 416 raise util.Abort(_("failed to execute %s") % command)
417 417 elif status < 0:
418 418 raise util.Abort(_("%s killed") % command)
419 419 else:
420 420 transition = "bad"
421 421 ctx = cmdutil.revsingle(repo, rev)
422 422 rev = None # clear for future iterations
423 423 state[transition].append(ctx.node())
424 424 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
425 425 check_state(state, interactive=False)
426 426 # bisect
427 427 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
428 428 # update to next check
429 429 cmdutil.bail_if_changed(repo)
430 430 hg.clean(repo, nodes[0], show_stats=False)
431 431 finally:
432 432 hbisect.save_state(repo, state)
433 433 print_result(nodes, good)
434 434 return
435 435
436 436 # update state
437 437
438 438 if rev:
439 439 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
440 440 else:
441 441 nodes = [repo.lookup('.')]
442 442
443 443 if good or bad or skip:
444 444 if good:
445 445 state['good'] += nodes
446 446 elif bad:
447 447 state['bad'] += nodes
448 448 elif skip:
449 449 state['skip'] += nodes
450 450 hbisect.save_state(repo, state)
451 451
452 452 if not check_state(state):
453 453 return
454 454
455 455 # actually bisect
456 456 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
457 457 if extend:
458 458 if not changesets:
459 459 extendnode = extendbisectrange(nodes, good)
460 460 if extendnode is not None:
461 461 ui.write(_("Extending search to changeset %d:%s\n"
462 462 % (extendnode.rev(), extendnode)))
463 463 if noupdate:
464 464 return
465 465 cmdutil.bail_if_changed(repo)
466 466 return hg.clean(repo, extendnode.node())
467 467 raise util.Abort(_("nothing to extend"))
468 468
469 469 if changesets == 0:
470 470 print_result(nodes, good)
471 471 else:
472 472 assert len(nodes) == 1 # only a single node can be tested next
473 473 node = nodes[0]
474 474 # compute the approximate number of remaining tests
475 475 tests, size = 0, 2
476 476 while size <= changesets:
477 477 tests, size = tests + 1, size * 2
478 478 rev = repo.changelog.rev(node)
479 479 ui.write(_("Testing changeset %d:%s "
480 480 "(%d changesets remaining, ~%d tests)\n")
481 481 % (rev, short(node), changesets, tests))
482 482 if not noupdate:
483 483 cmdutil.bail_if_changed(repo)
484 484 return hg.clean(repo, node)
485 485
486 486 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
487 487 '''track a line of development with movable markers
488 488
489 489 Bookmarks are pointers to certain commits that move when
490 490 committing. Bookmarks are local. They can be renamed, copied and
491 491 deleted. It is possible to use bookmark names in :hg:`merge` and
492 492 :hg:`update` to merge and update respectively to a given bookmark.
493 493
494 494 You can use :hg:`bookmark NAME` to set a bookmark on the working
495 495 directory's parent revision with the given name. If you specify
496 496 a revision using -r REV (where REV may be an existing bookmark),
497 497 the bookmark is assigned to that revision.
498 498
499 499 Bookmarks can be pushed and pulled between repositories (see :hg:`help
500 500 push` and :hg:`help pull`). This requires both the local and remote
501 501 repositories to support bookmarks. For versions prior to 1.8, this means
502 502 the bookmarks extension must be enabled.
503 503 '''
504 504 hexfn = ui.debugflag and hex or short
505 505 marks = repo._bookmarks
506 506 cur = repo.changectx('.').node()
507 507
508 508 if rename:
509 509 if rename not in marks:
510 510 raise util.Abort(_("bookmark '%s' does not exist") % rename)
511 511 if mark in marks and not force:
512 512 raise util.Abort(_("bookmark '%s' already exists "
513 513 "(use -f to force)") % mark)
514 514 if mark is None:
515 515 raise util.Abort(_("new bookmark name required"))
516 516 marks[mark] = marks[rename]
517 517 if repo._bookmarkcurrent == rename:
518 518 bookmarks.setcurrent(repo, mark)
519 519 del marks[rename]
520 520 bookmarks.write(repo)
521 521 return
522 522
523 523 if delete:
524 524 if mark is None:
525 525 raise util.Abort(_("bookmark name required"))
526 526 if mark not in marks:
527 527 raise util.Abort(_("bookmark '%s' does not exist") % mark)
528 528 if mark == repo._bookmarkcurrent:
529 529 bookmarks.setcurrent(repo, None)
530 530 del marks[mark]
531 531 bookmarks.write(repo)
532 532 return
533 533
534 534 if mark is not None:
535 535 if "\n" in mark:
536 536 raise util.Abort(_("bookmark name cannot contain newlines"))
537 537 mark = mark.strip()
538 538 if not mark:
539 539 raise util.Abort(_("bookmark names cannot consist entirely of "
540 540 "whitespace"))
541 541 if mark in marks and not force:
542 542 raise util.Abort(_("bookmark '%s' already exists "
543 543 "(use -f to force)") % mark)
544 544 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
545 545 and not force):
546 546 raise util.Abort(
547 547 _("a bookmark cannot have the name of an existing branch"))
548 548 if rev:
549 549 marks[mark] = repo.lookup(rev)
550 550 else:
551 551 marks[mark] = repo.changectx('.').node()
552 552 if repo.changectx('.').node() == marks[mark]:
553 553 bookmarks.setcurrent(repo, mark)
554 554 bookmarks.write(repo)
555 555 return
556 556
557 557 if mark is None:
558 558 if rev:
559 559 raise util.Abort(_("bookmark name required"))
560 560 if len(marks) == 0:
561 561 ui.status(_("no bookmarks set\n"))
562 562 else:
563 563 for bmark, n in sorted(marks.iteritems()):
564 564 current = repo._bookmarkcurrent
565 565 if bmark == current and n == cur:
566 566 prefix, label = '*', 'bookmarks.current'
567 567 else:
568 568 prefix, label = ' ', ''
569 569
570 570 if ui.quiet:
571 571 ui.write("%s\n" % bmark, label=label)
572 572 else:
573 573 ui.write(" %s %-25s %d:%s\n" % (
574 574 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
575 575 label=label)
576 576 return
577 577
578 578 def branch(ui, repo, label=None, **opts):
579 579 """set or show the current branch name
580 580
581 581 With no argument, show the current branch name. With one argument,
582 582 set the working directory branch name (the branch will not exist
583 583 in the repository until the next commit). Standard practice
584 584 recommends that primary development take place on the 'default'
585 585 branch.
586 586
587 587 Unless -f/--force is specified, branch will not let you set a
588 588 branch name that already exists, even if it's inactive.
589 589
590 590 Use -C/--clean to reset the working directory branch to that of
591 591 the parent of the working directory, negating a previous branch
592 592 change.
593 593
594 594 Use the command :hg:`update` to switch to an existing branch. Use
595 595 :hg:`commit --close-branch` to mark this branch as closed.
596 596
597 597 Returns 0 on success.
598 598 """
599 599
600 600 if opts.get('clean'):
601 601 label = repo[None].p1().branch()
602 602 repo.dirstate.setbranch(label)
603 603 ui.status(_('reset working directory to branch %s\n') % label)
604 604 elif label:
605 605 if not opts.get('force') and label in repo.branchtags():
606 606 if label not in [p.branch() for p in repo.parents()]:
607 607 raise util.Abort(_('a branch of the same name already exists'
608 608 " (use 'hg update' to switch to it)"))
609 609 repo.dirstate.setbranch(label)
610 610 ui.status(_('marked working directory as branch %s\n') % label)
611 611 else:
612 612 ui.write("%s\n" % repo.dirstate.branch())
613 613
614 614 def branches(ui, repo, active=False, closed=False):
615 615 """list repository named branches
616 616
617 617 List the repository's named branches, indicating which ones are
618 618 inactive. If -c/--closed is specified, also list branches which have
619 619 been marked closed (see :hg:`commit --close-branch`).
620 620
621 621 If -a/--active is specified, only show active branches. A branch
622 622 is considered active if it contains repository heads.
623 623
624 624 Use the command :hg:`update` to switch to an existing branch.
625 625
626 626 Returns 0.
627 627 """
628 628
629 629 hexfunc = ui.debugflag and hex or short
630 630 activebranches = [repo[n].branch() for n in repo.heads()]
631 631 def testactive(tag, node):
632 632 realhead = tag in activebranches
633 633 open = node in repo.branchheads(tag, closed=False)
634 634 return realhead and open
635 635 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
636 636 for tag, node in repo.branchtags().items()],
637 637 reverse=True)
638 638
639 639 for isactive, node, tag in branches:
640 640 if (not active) or isactive:
641 641 if ui.quiet:
642 642 ui.write("%s\n" % tag)
643 643 else:
644 644 hn = repo.lookup(node)
645 645 if isactive:
646 646 label = 'branches.active'
647 647 notice = ''
648 648 elif hn not in repo.branchheads(tag, closed=False):
649 649 if not closed:
650 650 continue
651 651 label = 'branches.closed'
652 652 notice = _(' (closed)')
653 653 else:
654 654 label = 'branches.inactive'
655 655 notice = _(' (inactive)')
656 656 if tag == repo.dirstate.branch():
657 657 label = 'branches.current'
658 658 rev = str(node).rjust(31 - encoding.colwidth(tag))
659 659 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
660 660 tag = ui.label(tag, label)
661 661 ui.write("%s %s%s\n" % (tag, rev, notice))
662 662
663 663 def bundle(ui, repo, fname, dest=None, **opts):
664 664 """create a changegroup file
665 665
666 666 Generate a compressed changegroup file collecting changesets not
667 667 known to be in another repository.
668 668
669 669 If you omit the destination repository, then hg assumes the
670 670 destination will have all the nodes you specify with --base
671 671 parameters. To create a bundle containing all changesets, use
672 672 -a/--all (or --base null).
673 673
674 674 You can change compression method with the -t/--type option.
675 675 The available compression methods are: none, bzip2, and
676 676 gzip (by default, bundles are compressed using bzip2).
677 677
678 678 The bundle file can then be transferred using conventional means
679 679 and applied to another repository with the unbundle or pull
680 680 command. This is useful when direct push and pull are not
681 681 available or when exporting an entire repository is undesirable.
682 682
683 683 Applying bundles preserves all changeset contents including
684 684 permissions, copy/rename information, and revision history.
685 685
686 686 Returns 0 on success, 1 if no changes found.
687 687 """
688 688 revs = None
689 689 if 'rev' in opts:
690 690 revs = cmdutil.revrange(repo, opts['rev'])
691 691
692 692 if opts.get('all'):
693 693 base = ['null']
694 694 else:
695 695 base = cmdutil.revrange(repo, opts.get('base'))
696 696 if base:
697 697 if dest:
698 698 raise util.Abort(_("--base is incompatible with specifying "
699 699 "a destination"))
700 700 common = [repo.lookup(rev) for rev in base]
701 701 else:
702 702 dest = ui.expandpath(dest or 'default-push', dest or 'default')
703 703 dest, branches = hg.parseurl(dest, opts.get('branch'))
704 704 other = hg.repository(hg.remoteui(repo, opts), dest)
705 705 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
706 706 inc = discovery.findcommonincoming(repo, other, force=opts.get('force'))
707 707 common, _anyinc, _heads = inc
708 708
709 709 nodes = revs and map(repo.lookup, revs) or revs
710 710 cg = repo.getbundle('bundle', common=common, heads=nodes)
711 711 if not cg:
712 712 ui.status(_("no changes found\n"))
713 713 return 1
714 714
715 715 bundletype = opts.get('type', 'bzip2').lower()
716 716 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
717 717 bundletype = btypes.get(bundletype)
718 718 if bundletype not in changegroup.bundletypes:
719 719 raise util.Abort(_('unknown bundle type specified with --type'))
720 720
721 721 changegroup.writebundle(cg, fname, bundletype)
722 722
723 723 def cat(ui, repo, file1, *pats, **opts):
724 724 """output the current or given revision of files
725 725
726 726 Print the specified files as they were at the given revision. If
727 727 no revision is given, the parent of the working directory is used,
728 728 or tip if no revision is checked out.
729 729
730 730 Output may be to a file, in which case the name of the file is
731 731 given using a format string. The formatting rules are the same as
732 732 for the export command, with the following additions:
733 733
734 734 :``%s``: basename of file being printed
735 735 :``%d``: dirname of file being printed, or '.' if in repository root
736 736 :``%p``: root-relative path name of file being printed
737 737
738 738 Returns 0 on success.
739 739 """
740 740 ctx = cmdutil.revsingle(repo, opts.get('rev'))
741 741 err = 1
742 742 m = cmdutil.match(repo, (file1,) + pats, opts)
743 743 for abs in ctx.walk(m):
744 744 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
745 745 data = ctx[abs].data()
746 746 if opts.get('decode'):
747 747 data = repo.wwritedata(abs, data)
748 748 fp.write(data)
749 749 fp.close()
750 750 err = 0
751 751 return err
752 752
753 753 def clone(ui, source, dest=None, **opts):
754 754 """make a copy of an existing repository
755 755
756 756 Create a copy of an existing repository in a new directory.
757 757
758 758 If no destination directory name is specified, it defaults to the
759 759 basename of the source.
760 760
761 761 The location of the source is added to the new repository's
762 762 ``.hg/hgrc`` file, as the default to be used for future pulls.
763 763
764 764 See :hg:`help urls` for valid source format details.
765 765
766 766 It is possible to specify an ``ssh://`` URL as the destination, but no
767 767 ``.hg/hgrc`` and working directory will be created on the remote side.
768 768 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
769 769
770 770 A set of changesets (tags, or branch names) to pull may be specified
771 771 by listing each changeset (tag, or branch name) with -r/--rev.
772 772 If -r/--rev is used, the cloned repository will contain only a subset
773 773 of the changesets of the source repository. Only the set of changesets
774 774 defined by all -r/--rev options (including all their ancestors)
775 775 will be pulled into the destination repository.
776 776 No subsequent changesets (including subsequent tags) will be present
777 777 in the destination.
778 778
779 779 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
780 780 local source repositories.
781 781
782 782 For efficiency, hardlinks are used for cloning whenever the source
783 783 and destination are on the same filesystem (note this applies only
784 784 to the repository data, not to the working directory). Some
785 785 filesystems, such as AFS, implement hardlinking incorrectly, but
786 786 do not report errors. In these cases, use the --pull option to
787 787 avoid hardlinking.
788 788
789 789 In some cases, you can clone repositories and the working directory
790 790 using full hardlinks with ::
791 791
792 792 $ cp -al REPO REPOCLONE
793 793
794 794 This is the fastest way to clone, but it is not always safe. The
795 795 operation is not atomic (making sure REPO is not modified during
796 796 the operation is up to you) and you have to make sure your editor
797 797 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
798 798 this is not compatible with certain extensions that place their
799 799 metadata under the .hg directory, such as mq.
800 800
801 801 Mercurial will update the working directory to the first applicable
802 802 revision from this list:
803 803
804 804 a) null if -U or the source repository has no changesets
805 805 b) if -u . and the source repository is local, the first parent of
806 806 the source repository's working directory
807 807 c) the changeset specified with -u (if a branch name, this means the
808 808 latest head of that branch)
809 809 d) the changeset specified with -r
810 810 e) the tipmost head specified with -b
811 811 f) the tipmost head specified with the url#branch source syntax
812 812 g) the tipmost head of the default branch
813 813 h) tip
814 814
815 815 Returns 0 on success.
816 816 """
817 817 if opts.get('noupdate') and opts.get('updaterev'):
818 818 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
819 819
820 820 r = hg.clone(hg.remoteui(ui, opts), source, dest,
821 821 pull=opts.get('pull'),
822 822 stream=opts.get('uncompressed'),
823 823 rev=opts.get('rev'),
824 824 update=opts.get('updaterev') or not opts.get('noupdate'),
825 825 branch=opts.get('branch'))
826 826
827 827 return r is None
828 828
829 829 def commit(ui, repo, *pats, **opts):
830 830 """commit the specified files or all outstanding changes
831 831
832 832 Commit changes to the given files into the repository. Unlike a
833 833 centralized SCM, this operation is a local operation. See
834 834 :hg:`push` for a way to actively distribute your changes.
835 835
836 836 If a list of files is omitted, all changes reported by :hg:`status`
837 837 will be committed.
838 838
839 839 If you are committing the result of a merge, do not provide any
840 840 filenames or -I/-X filters.
841 841
842 842 If no commit message is specified, Mercurial starts your
843 843 configured editor where you can enter a message. In case your
844 844 commit fails, you will find a backup of your message in
845 845 ``.hg/last-message.txt``.
846 846
847 847 See :hg:`help dates` for a list of formats valid for -d/--date.
848 848
849 849 Returns 0 on success, 1 if nothing changed.
850 850 """
851 851 extra = {}
852 852 if opts.get('close_branch'):
853 853 if repo['.'].node() not in repo.branchheads():
854 854 # The topo heads set is included in the branch heads set of the
855 855 # current branch, so it's sufficient to test branchheads
856 856 raise util.Abort(_('can only close branch heads'))
857 857 extra['close'] = 1
858 858 e = cmdutil.commiteditor
859 859 if opts.get('force_editor'):
860 860 e = cmdutil.commitforceeditor
861 861
862 862 def commitfunc(ui, repo, message, match, opts):
863 863 return repo.commit(message, opts.get('user'), opts.get('date'), match,
864 864 editor=e, extra=extra)
865 865
866 866 branch = repo[None].branch()
867 867 bheads = repo.branchheads(branch)
868 868
869 869 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
870 870 if not node:
871 871 stat = repo.status(match=cmdutil.match(repo, pats, opts))
872 872 if stat[3]:
873 873 ui.status(_("nothing changed (%d missing files, see 'hg status')\n")
874 874 % len(stat[3]))
875 875 else:
876 876 ui.status(_("nothing changed\n"))
877 877 return 1
878 878
879 879 ctx = repo[node]
880 880 parents = ctx.parents()
881 881
882 882 if bheads and not [x for x in parents
883 883 if x.node() in bheads and x.branch() == branch]:
884 884 ui.status(_('created new head\n'))
885 885 # The message is not printed for initial roots. For the other
886 886 # changesets, it is printed in the following situations:
887 887 #
888 888 # Par column: for the 2 parents with ...
889 889 # N: null or no parent
890 890 # B: parent is on another named branch
891 891 # C: parent is a regular non head changeset
892 892 # H: parent was a branch head of the current branch
893 893 # Msg column: whether we print "created new head" message
894 894 # In the following, it is assumed that there already exists some
895 895 # initial branch heads of the current branch, otherwise nothing is
896 896 # printed anyway.
897 897 #
898 898 # Par Msg Comment
899 899 # NN y additional topo root
900 900 #
901 901 # BN y additional branch root
902 902 # CN y additional topo head
903 903 # HN n usual case
904 904 #
905 905 # BB y weird additional branch root
906 906 # CB y branch merge
907 907 # HB n merge with named branch
908 908 #
909 909 # CC y additional head from merge
910 910 # CH n merge with a head
911 911 #
912 912 # HH n head merge: head count decreases
913 913
914 914 if not opts.get('close_branch'):
915 915 for r in parents:
916 916 if r.extra().get('close') and r.branch() == branch:
917 917 ui.status(_('reopening closed branch head %d\n') % r)
918 918
919 919 if ui.debugflag:
920 920 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
921 921 elif ui.verbose:
922 922 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
923 923
924 924 def copy(ui, repo, *pats, **opts):
925 925 """mark files as copied for the next commit
926 926
927 927 Mark dest as having copies of source files. If dest is a
928 928 directory, copies are put in that directory. If dest is a file,
929 929 the source must be a single file.
930 930
931 931 By default, this command copies the contents of files as they
932 932 exist in the working directory. If invoked with -A/--after, the
933 933 operation is recorded, but no copying is performed.
934 934
935 935 This command takes effect with the next commit. To undo a copy
936 936 before that, see :hg:`revert`.
937 937
938 938 Returns 0 on success, 1 if errors are encountered.
939 939 """
940 940 wlock = repo.wlock(False)
941 941 try:
942 942 return cmdutil.copy(ui, repo, pats, opts)
943 943 finally:
944 944 wlock.release()
945 945
946 946 def debugancestor(ui, repo, *args):
947 947 """find the ancestor revision of two revisions in a given index"""
948 948 if len(args) == 3:
949 949 index, rev1, rev2 = args
950 950 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
951 951 lookup = r.lookup
952 952 elif len(args) == 2:
953 953 if not repo:
954 954 raise util.Abort(_("there is no Mercurial repository here "
955 955 "(.hg not found)"))
956 956 rev1, rev2 = args
957 957 r = repo.changelog
958 958 lookup = repo.lookup
959 959 else:
960 960 raise util.Abort(_('either two or three arguments required'))
961 961 a = r.ancestor(lookup(rev1), lookup(rev2))
962 962 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
963 963
964 964 def debugbuilddag(ui, repo, text,
965 965 mergeable_file=False,
966 966 overwritten_file=False,
967 967 new_file=False):
968 968 """builds a repo with a given dag from scratch in the current empty repo
969 969
970 970 Elements:
971 971
972 972 - "+n" is a linear run of n nodes based on the current default parent
973 973 - "." is a single node based on the current default parent
974 974 - "$" resets the default parent to null (implied at the start);
975 975 otherwise the default parent is always the last node created
976 976 - "<p" sets the default parent to the backref p
977 977 - "*p" is a fork at parent p, which is a backref
978 978 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
979 979 - "/p2" is a merge of the preceding node and p2
980 980 - ":tag" defines a local tag for the preceding node
981 981 - "@branch" sets the named branch for subsequent nodes
982 982 - "#...\\n" is a comment up to the end of the line
983 983
984 984 Whitespace between the above elements is ignored.
985 985
986 986 A backref is either
987 987
988 988 - a number n, which references the node curr-n, where curr is the current
989 989 node, or
990 990 - the name of a local tag you placed earlier using ":tag", or
991 991 - empty to denote the default parent.
992 992
993 993 All string valued-elements are either strictly alphanumeric, or must
994 994 be enclosed in double quotes ("..."), with "\\" as escape character.
995 995 """
996 996
997 997 cl = repo.changelog
998 998 if len(cl) > 0:
999 999 raise util.Abort(_('repository is not empty'))
1000 1000
1001 1001 if mergeable_file:
1002 1002 linesperrev = 2
1003 1003 # determine number of revs in DAG
1004 1004 n = 0
1005 1005 for type, data in dagparser.parsedag(text):
1006 1006 if type == 'n':
1007 1007 n += 1
1008 1008 # make a file with k lines per rev
1009 1009 initialmergedlines = [str(i) for i in xrange(0, n * linesperrev)]
1010 1010 initialmergedlines.append("")
1011 1011
1012 1012 tags = []
1013 1013
1014 1014 tr = repo.transaction("builddag")
1015 1015 try:
1016 1016
1017 1017 at = -1
1018 1018 atbranch = 'default'
1019 1019 nodeids = []
1020 1020 for type, data in dagparser.parsedag(text):
1021 1021 if type == 'n':
1022 1022 ui.note('node %s\n' % str(data))
1023 1023 id, ps = data
1024 1024
1025 1025 files = []
1026 1026 fctxs = {}
1027 1027
1028 1028 p2 = None
1029 1029 if mergeable_file:
1030 1030 fn = "mf"
1031 1031 p1 = repo[ps[0]]
1032 1032 if len(ps) > 1:
1033 1033 p2 = repo[ps[1]]
1034 1034 pa = p1.ancestor(p2)
1035 1035 base, local, other = [x[fn].data() for x in pa, p1, p2]
1036 1036 m3 = simplemerge.Merge3Text(base, local, other)
1037 1037 ml = [l.strip() for l in m3.merge_lines()]
1038 1038 ml.append("")
1039 1039 elif at > 0:
1040 1040 ml = p1[fn].data().split("\n")
1041 1041 else:
1042 1042 ml = initialmergedlines
1043 1043 ml[id * linesperrev] += " r%i" % id
1044 1044 mergedtext = "\n".join(ml)
1045 1045 files.append(fn)
1046 1046 fctxs[fn] = context.memfilectx(fn, mergedtext)
1047 1047
1048 1048 if overwritten_file:
1049 1049 fn = "of"
1050 1050 files.append(fn)
1051 1051 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1052 1052
1053 1053 if new_file:
1054 1054 fn = "nf%i" % id
1055 1055 files.append(fn)
1056 1056 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1057 1057 if len(ps) > 1:
1058 1058 if not p2:
1059 1059 p2 = repo[ps[1]]
1060 1060 for fn in p2:
1061 1061 if fn.startswith("nf"):
1062 1062 files.append(fn)
1063 1063 fctxs[fn] = p2[fn]
1064 1064
1065 1065 def fctxfn(repo, cx, path):
1066 1066 return fctxs.get(path)
1067 1067
1068 1068 if len(ps) == 0 or ps[0] < 0:
1069 1069 pars = [None, None]
1070 1070 elif len(ps) == 1:
1071 1071 pars = [nodeids[ps[0]], None]
1072 1072 else:
1073 1073 pars = [nodeids[p] for p in ps]
1074 1074 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1075 1075 date=(id, 0),
1076 1076 user="debugbuilddag",
1077 1077 extra={'branch': atbranch})
1078 1078 nodeid = repo.commitctx(cx)
1079 1079 nodeids.append(nodeid)
1080 1080 at = id
1081 1081 elif type == 'l':
1082 1082 id, name = data
1083 1083 ui.note('tag %s\n' % name)
1084 1084 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1085 1085 elif type == 'a':
1086 1086 ui.note('branch %s\n' % data)
1087 1087 atbranch = data
1088 1088 tr.close()
1089 1089 finally:
1090 1090 tr.release()
1091 1091
1092 1092 if tags:
1093 1093 tagsf = repo.opener("localtags", "w")
1094 1094 try:
1095 1095 tagsf.write("".join(tags))
1096 1096 finally:
1097 1097 tagsf.close()
1098 1098
1099 1099 def debugcommands(ui, cmd='', *args):
1100 1100 """list all available commands and options"""
1101 1101 for cmd, vals in sorted(table.iteritems()):
1102 1102 cmd = cmd.split('|')[0].strip('^')
1103 1103 opts = ', '.join([i[1] for i in vals[1]])
1104 1104 ui.write('%s: %s\n' % (cmd, opts))
1105 1105
1106 1106 def debugcomplete(ui, cmd='', **opts):
1107 1107 """returns the completion list associated with the given command"""
1108 1108
1109 1109 if opts.get('options'):
1110 1110 options = []
1111 1111 otables = [globalopts]
1112 1112 if cmd:
1113 1113 aliases, entry = cmdutil.findcmd(cmd, table, False)
1114 1114 otables.append(entry[1])
1115 1115 for t in otables:
1116 1116 for o in t:
1117 1117 if "(DEPRECATED)" in o[3]:
1118 1118 continue
1119 1119 if o[0]:
1120 1120 options.append('-%s' % o[0])
1121 1121 options.append('--%s' % o[1])
1122 1122 ui.write("%s\n" % "\n".join(options))
1123 1123 return
1124 1124
1125 1125 cmdlist = cmdutil.findpossible(cmd, table)
1126 1126 if ui.verbose:
1127 1127 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1128 1128 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1129 1129
1130 1130 def debugfsinfo(ui, path = "."):
1131 1131 """show information detected about current filesystem"""
1132 open('.debugfsinfo', 'w').write('')
1132 util.writefile('.debugfsinfo', '')
1133 1133 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1134 1134 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1135 1135 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1136 1136 and 'yes' or 'no'))
1137 1137 os.unlink('.debugfsinfo')
1138 1138
1139 1139 def debugrebuildstate(ui, repo, rev="tip"):
1140 1140 """rebuild the dirstate as it would look like for the given revision"""
1141 1141 ctx = cmdutil.revsingle(repo, rev)
1142 1142 wlock = repo.wlock()
1143 1143 try:
1144 1144 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1145 1145 finally:
1146 1146 wlock.release()
1147 1147
1148 1148 def debugcheckstate(ui, repo):
1149 1149 """validate the correctness of the current dirstate"""
1150 1150 parent1, parent2 = repo.dirstate.parents()
1151 1151 m1 = repo[parent1].manifest()
1152 1152 m2 = repo[parent2].manifest()
1153 1153 errors = 0
1154 1154 for f in repo.dirstate:
1155 1155 state = repo.dirstate[f]
1156 1156 if state in "nr" and f not in m1:
1157 1157 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1158 1158 errors += 1
1159 1159 if state in "a" and f in m1:
1160 1160 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1161 1161 errors += 1
1162 1162 if state in "m" and f not in m1 and f not in m2:
1163 1163 ui.warn(_("%s in state %s, but not in either manifest\n") %
1164 1164 (f, state))
1165 1165 errors += 1
1166 1166 for f in m1:
1167 1167 state = repo.dirstate[f]
1168 1168 if state not in "nrm":
1169 1169 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1170 1170 errors += 1
1171 1171 if errors:
1172 1172 error = _(".hg/dirstate inconsistent with current parent's manifest")
1173 1173 raise util.Abort(error)
1174 1174
1175 1175 def showconfig(ui, repo, *values, **opts):
1176 1176 """show combined config settings from all hgrc files
1177 1177
1178 1178 With no arguments, print names and values of all config items.
1179 1179
1180 1180 With one argument of the form section.name, print just the value
1181 1181 of that config item.
1182 1182
1183 1183 With multiple arguments, print names and values of all config
1184 1184 items with matching section names.
1185 1185
1186 1186 With --debug, the source (filename and line number) is printed
1187 1187 for each config item.
1188 1188
1189 1189 Returns 0 on success.
1190 1190 """
1191 1191
1192 1192 for f in scmutil.rcpath():
1193 1193 ui.debug(_('read config from: %s\n') % f)
1194 1194 untrusted = bool(opts.get('untrusted'))
1195 1195 if values:
1196 1196 sections = [v for v in values if '.' not in v]
1197 1197 items = [v for v in values if '.' in v]
1198 1198 if len(items) > 1 or items and sections:
1199 1199 raise util.Abort(_('only one config item permitted'))
1200 1200 for section, name, value in ui.walkconfig(untrusted=untrusted):
1201 1201 value = str(value).replace('\n', '\\n')
1202 1202 sectname = section + '.' + name
1203 1203 if values:
1204 1204 for v in values:
1205 1205 if v == section:
1206 1206 ui.debug('%s: ' %
1207 1207 ui.configsource(section, name, untrusted))
1208 1208 ui.write('%s=%s\n' % (sectname, value))
1209 1209 elif v == sectname:
1210 1210 ui.debug('%s: ' %
1211 1211 ui.configsource(section, name, untrusted))
1212 1212 ui.write(value, '\n')
1213 1213 else:
1214 1214 ui.debug('%s: ' %
1215 1215 ui.configsource(section, name, untrusted))
1216 1216 ui.write('%s=%s\n' % (sectname, value))
1217 1217
1218 1218 def debugknown(ui, repopath, *ids, **opts):
1219 1219 """test whether node ids are known to a repo
1220 1220
1221 1221 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1222 1222 indicating unknown/known.
1223 1223 """
1224 1224 repo = hg.repository(ui, repopath)
1225 1225 if not repo.capable('known'):
1226 1226 raise util.Abort("known() not supported by target repository")
1227 1227 flags = repo.known([bin(s) for s in ids])
1228 1228 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1229 1229
1230 1230 def debugbundle(ui, bundlepath, all=None, **opts):
1231 1231 """lists the contents of a bundle"""
1232 1232 f = url.open(ui, bundlepath)
1233 1233 try:
1234 1234 gen = changegroup.readbundle(f, bundlepath)
1235 1235 if all:
1236 1236 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1237 1237
1238 1238 def showchunks(named):
1239 1239 ui.write("\n%s\n" % named)
1240 1240 chain = None
1241 1241 while 1:
1242 1242 chunkdata = gen.deltachunk(chain)
1243 1243 if not chunkdata:
1244 1244 break
1245 1245 node = chunkdata['node']
1246 1246 p1 = chunkdata['p1']
1247 1247 p2 = chunkdata['p2']
1248 1248 cs = chunkdata['cs']
1249 1249 deltabase = chunkdata['deltabase']
1250 1250 delta = chunkdata['delta']
1251 1251 ui.write("%s %s %s %s %s %s\n" %
1252 1252 (hex(node), hex(p1), hex(p2),
1253 1253 hex(cs), hex(deltabase), len(delta)))
1254 1254 chain = node
1255 1255
1256 1256 chunkdata = gen.changelogheader()
1257 1257 showchunks("changelog")
1258 1258 chunkdata = gen.manifestheader()
1259 1259 showchunks("manifest")
1260 1260 while 1:
1261 1261 chunkdata = gen.filelogheader()
1262 1262 if not chunkdata:
1263 1263 break
1264 1264 fname = chunkdata['filename']
1265 1265 showchunks(fname)
1266 1266 else:
1267 1267 chunkdata = gen.changelogheader()
1268 1268 chain = None
1269 1269 while 1:
1270 1270 chunkdata = gen.deltachunk(chain)
1271 1271 if not chunkdata:
1272 1272 break
1273 1273 node = chunkdata['node']
1274 1274 ui.write("%s\n" % hex(node))
1275 1275 chain = node
1276 1276 finally:
1277 1277 f.close()
1278 1278
1279 1279 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1280 1280 """retrieves a bundle from a repo
1281 1281
1282 1282 Every ID must be a full-length hex node id string. Saves the bundle to the
1283 1283 given file.
1284 1284 """
1285 1285 repo = hg.repository(ui, repopath)
1286 1286 if not repo.capable('getbundle'):
1287 1287 raise util.Abort("getbundle() not supported by target repository")
1288 1288 args = {}
1289 1289 if common:
1290 1290 args['common'] = [bin(s) for s in common]
1291 1291 if head:
1292 1292 args['heads'] = [bin(s) for s in head]
1293 1293 bundle = repo.getbundle('debug', **args)
1294 1294
1295 1295 bundletype = opts.get('type', 'bzip2').lower()
1296 1296 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1297 1297 bundletype = btypes.get(bundletype)
1298 1298 if bundletype not in changegroup.bundletypes:
1299 1299 raise util.Abort(_('unknown bundle type specified with --type'))
1300 1300 changegroup.writebundle(bundle, bundlepath, bundletype)
1301 1301
1302 1302 def debugpushkey(ui, repopath, namespace, *keyinfo):
1303 1303 '''access the pushkey key/value protocol
1304 1304
1305 1305 With two args, list the keys in the given namespace.
1306 1306
1307 1307 With five args, set a key to new if it currently is set to old.
1308 1308 Reports success or failure.
1309 1309 '''
1310 1310
1311 1311 target = hg.repository(ui, repopath)
1312 1312 if keyinfo:
1313 1313 key, old, new = keyinfo
1314 1314 r = target.pushkey(namespace, key, old, new)
1315 1315 ui.status(str(r) + '\n')
1316 1316 return not r
1317 1317 else:
1318 1318 for k, v in target.listkeys(namespace).iteritems():
1319 1319 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1320 1320 v.encode('string-escape')))
1321 1321
1322 1322 def debugrevspec(ui, repo, expr):
1323 1323 '''parse and apply a revision specification'''
1324 1324 if ui.verbose:
1325 1325 tree = revset.parse(expr)[0]
1326 1326 ui.note(tree, "\n")
1327 1327 newtree = revset.findaliases(ui, tree)
1328 1328 if newtree != tree:
1329 1329 ui.note(newtree, "\n")
1330 1330 func = revset.match(ui, expr)
1331 1331 for c in func(repo, range(len(repo))):
1332 1332 ui.write("%s\n" % c)
1333 1333
1334 1334 def debugsetparents(ui, repo, rev1, rev2=None):
1335 1335 """manually set the parents of the current working directory
1336 1336
1337 1337 This is useful for writing repository conversion tools, but should
1338 1338 be used with care.
1339 1339
1340 1340 Returns 0 on success.
1341 1341 """
1342 1342
1343 1343 r1 = cmdutil.revsingle(repo, rev1).node()
1344 1344 r2 = cmdutil.revsingle(repo, rev2, 'null').node()
1345 1345
1346 1346 wlock = repo.wlock()
1347 1347 try:
1348 1348 repo.dirstate.setparents(r1, r2)
1349 1349 finally:
1350 1350 wlock.release()
1351 1351
1352 1352 def debugstate(ui, repo, nodates=None, datesort=None):
1353 1353 """show the contents of the current dirstate"""
1354 1354 timestr = ""
1355 1355 showdate = not nodates
1356 1356 if datesort:
1357 1357 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
1358 1358 else:
1359 1359 keyfunc = None # sort by filename
1360 1360 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
1361 1361 if showdate:
1362 1362 if ent[3] == -1:
1363 1363 # Pad or slice to locale representation
1364 1364 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1365 1365 time.localtime(0)))
1366 1366 timestr = 'unset'
1367 1367 timestr = (timestr[:locale_len] +
1368 1368 ' ' * (locale_len - len(timestr)))
1369 1369 else:
1370 1370 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1371 1371 time.localtime(ent[3]))
1372 1372 if ent[1] & 020000:
1373 1373 mode = 'lnk'
1374 1374 else:
1375 1375 mode = '%3o' % (ent[1] & 0777)
1376 1376 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1377 1377 for f in repo.dirstate.copies():
1378 1378 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1379 1379
1380 1380 def debugsub(ui, repo, rev=None):
1381 1381 ctx = cmdutil.revsingle(repo, rev, None)
1382 1382 for k, v in sorted(ctx.substate.items()):
1383 1383 ui.write('path %s\n' % k)
1384 1384 ui.write(' source %s\n' % v[0])
1385 1385 ui.write(' revision %s\n' % v[1])
1386 1386
1387 1387 def debugdag(ui, repo, file_=None, *revs, **opts):
1388 1388 """format the changelog or an index DAG as a concise textual description
1389 1389
1390 1390 If you pass a revlog index, the revlog's DAG is emitted. If you list
1391 1391 revision numbers, they get labelled in the output as rN.
1392 1392
1393 1393 Otherwise, the changelog DAG of the current repo is emitted.
1394 1394 """
1395 1395 spaces = opts.get('spaces')
1396 1396 dots = opts.get('dots')
1397 1397 if file_:
1398 1398 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1399 1399 revs = set((int(r) for r in revs))
1400 1400 def events():
1401 1401 for r in rlog:
1402 1402 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1403 1403 if r in revs:
1404 1404 yield 'l', (r, "r%i" % r)
1405 1405 elif repo:
1406 1406 cl = repo.changelog
1407 1407 tags = opts.get('tags')
1408 1408 branches = opts.get('branches')
1409 1409 if tags:
1410 1410 labels = {}
1411 1411 for l, n in repo.tags().items():
1412 1412 labels.setdefault(cl.rev(n), []).append(l)
1413 1413 def events():
1414 1414 b = "default"
1415 1415 for r in cl:
1416 1416 if branches:
1417 1417 newb = cl.read(cl.node(r))[5]['branch']
1418 1418 if newb != b:
1419 1419 yield 'a', newb
1420 1420 b = newb
1421 1421 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1422 1422 if tags:
1423 1423 ls = labels.get(r)
1424 1424 if ls:
1425 1425 for l in ls:
1426 1426 yield 'l', (r, l)
1427 1427 else:
1428 1428 raise util.Abort(_('need repo for changelog dag'))
1429 1429
1430 1430 for line in dagparser.dagtextlines(events(),
1431 1431 addspaces=spaces,
1432 1432 wraplabels=True,
1433 1433 wrapannotations=True,
1434 1434 wrapnonlinear=dots,
1435 1435 usedots=dots,
1436 1436 maxlinewidth=70):
1437 1437 ui.write(line)
1438 1438 ui.write("\n")
1439 1439
1440 1440 def debugdata(ui, repo, file_, rev):
1441 1441 """dump the contents of a data file revision"""
1442 1442 r = None
1443 1443 if repo:
1444 1444 filelog = repo.file(file_)
1445 1445 if len(filelog):
1446 1446 r = filelog
1447 1447 if not r:
1448 1448 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
1449 1449 file_[:-2] + ".i")
1450 1450 try:
1451 1451 ui.write(r.revision(r.lookup(rev)))
1452 1452 except KeyError:
1453 1453 raise util.Abort(_('invalid revision identifier %s') % rev)
1454 1454
1455 1455 def debugdate(ui, date, range=None, **opts):
1456 1456 """parse and display a date"""
1457 1457 if opts["extended"]:
1458 1458 d = util.parsedate(date, util.extendeddateformats)
1459 1459 else:
1460 1460 d = util.parsedate(date)
1461 1461 ui.write("internal: %s %s\n" % d)
1462 1462 ui.write("standard: %s\n" % util.datestr(d))
1463 1463 if range:
1464 1464 m = util.matchdate(range)
1465 1465 ui.write("match: %s\n" % m(d[0]))
1466 1466
1467 1467 def debugignore(ui, repo, *values, **opts):
1468 1468 """display the combined ignore pattern"""
1469 1469 ignore = repo.dirstate._ignore
1470 1470 if hasattr(ignore, 'includepat'):
1471 1471 ui.write("%s\n" % ignore.includepat)
1472 1472 else:
1473 1473 raise util.Abort(_("no ignore patterns found"))
1474 1474
1475 1475 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1476 1476 """runs the changeset discovery protocol in isolation"""
1477 1477 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1478 1478 remote = hg.repository(hg.remoteui(repo, opts), remoteurl)
1479 1479 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1480 1480
1481 1481 # make sure tests are repeatable
1482 1482 random.seed(12323)
1483 1483
1484 1484 def doit(localheads, remoteheads):
1485 1485 if opts.get('old'):
1486 1486 if localheads:
1487 1487 raise util.Abort('cannot use localheads with old style discovery')
1488 1488 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1489 1489 force=True)
1490 1490 common = set(common)
1491 1491 if not opts.get('nonheads'):
1492 1492 ui.write("unpruned common: %s\n" % " ".join([short(n)
1493 1493 for n in common]))
1494 1494 dag = dagutil.revlogdag(repo.changelog)
1495 1495 all = dag.ancestorset(dag.internalizeall(common))
1496 1496 common = dag.externalizeall(dag.headsetofconnecteds(all))
1497 1497 else:
1498 1498 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1499 1499 common = set(common)
1500 1500 rheads = set(hds)
1501 1501 lheads = set(repo.heads())
1502 1502 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1503 1503 if lheads <= common:
1504 1504 ui.write("local is subset\n")
1505 1505 elif rheads <= common:
1506 1506 ui.write("remote is subset\n")
1507 1507
1508 1508 serverlogs = opts.get('serverlog')
1509 1509 if serverlogs:
1510 1510 for filename in serverlogs:
1511 1511 logfile = open(filename, 'r')
1512 1512 try:
1513 1513 line = logfile.readline()
1514 1514 while line:
1515 1515 parts = line.strip().split(';')
1516 1516 op = parts[1]
1517 1517 if op == 'cg':
1518 1518 pass
1519 1519 elif op == 'cgss':
1520 1520 doit(parts[2].split(' '), parts[3].split(' '))
1521 1521 elif op == 'unb':
1522 1522 doit(parts[3].split(' '), parts[2].split(' '))
1523 1523 line = logfile.readline()
1524 1524 finally:
1525 1525 logfile.close()
1526 1526
1527 1527 else:
1528 1528 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1529 1529 opts.get('remote_head'))
1530 1530 localrevs = opts.get('local_head')
1531 1531 doit(localrevs, remoterevs)
1532 1532
1533 1533
1534 1534 def debugindex(ui, repo, file_, **opts):
1535 1535 """dump the contents of an index file"""
1536 1536 r = None
1537 1537 if repo:
1538 1538 filelog = repo.file(file_)
1539 1539 if len(filelog):
1540 1540 r = filelog
1541 1541
1542 1542 format = opts.get('format', 0)
1543 1543 if format not in (0, 1):
1544 1544 raise util.Abort(_("unknown format %d") % format)
1545 1545
1546 1546 if not r:
1547 1547 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1548 1548
1549 1549 if format == 0:
1550 1550 ui.write(" rev offset length base linkrev"
1551 1551 " nodeid p1 p2\n")
1552 1552 elif format == 1:
1553 1553 ui.write(" rev flag offset length"
1554 1554 " size base link p1 p2 nodeid\n")
1555 1555
1556 1556 for i in r:
1557 1557 node = r.node(i)
1558 1558 if format == 0:
1559 1559 try:
1560 1560 pp = r.parents(node)
1561 1561 except:
1562 1562 pp = [nullid, nullid]
1563 1563 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1564 1564 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1565 1565 short(node), short(pp[0]), short(pp[1])))
1566 1566 elif format == 1:
1567 1567 pr = r.parentrevs(i)
1568 1568 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1569 1569 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1570 1570 r.base(i), r.linkrev(i), pr[0], pr[1], short(node)))
1571 1571
1572 1572 def debugindexdot(ui, repo, file_):
1573 1573 """dump an index DAG as a graphviz dot file"""
1574 1574 r = None
1575 1575 if repo:
1576 1576 filelog = repo.file(file_)
1577 1577 if len(filelog):
1578 1578 r = filelog
1579 1579 if not r:
1580 1580 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1581 1581 ui.write("digraph G {\n")
1582 1582 for i in r:
1583 1583 node = r.node(i)
1584 1584 pp = r.parents(node)
1585 1585 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1586 1586 if pp[1] != nullid:
1587 1587 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1588 1588 ui.write("}\n")
1589 1589
1590 1590 def debuginstall(ui):
1591 1591 '''test Mercurial installation
1592 1592
1593 1593 Returns 0 on success.
1594 1594 '''
1595 1595
1596 1596 def writetemp(contents):
1597 1597 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1598 1598 f = os.fdopen(fd, "wb")
1599 1599 f.write(contents)
1600 1600 f.close()
1601 1601 return name
1602 1602
1603 1603 problems = 0
1604 1604
1605 1605 # encoding
1606 1606 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1607 1607 try:
1608 1608 encoding.fromlocal("test")
1609 1609 except util.Abort, inst:
1610 1610 ui.write(" %s\n" % inst)
1611 1611 ui.write(_(" (check that your locale is properly set)\n"))
1612 1612 problems += 1
1613 1613
1614 1614 # compiled modules
1615 1615 ui.status(_("Checking installed modules (%s)...\n")
1616 1616 % os.path.dirname(__file__))
1617 1617 try:
1618 1618 import bdiff, mpatch, base85, osutil
1619 1619 except Exception, inst:
1620 1620 ui.write(" %s\n" % inst)
1621 1621 ui.write(_(" One or more extensions could not be found"))
1622 1622 ui.write(_(" (check that you compiled the extensions)\n"))
1623 1623 problems += 1
1624 1624
1625 1625 # templates
1626 1626 ui.status(_("Checking templates...\n"))
1627 1627 try:
1628 1628 import templater
1629 1629 templater.templater(templater.templatepath("map-cmdline.default"))
1630 1630 except Exception, inst:
1631 1631 ui.write(" %s\n" % inst)
1632 1632 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1633 1633 problems += 1
1634 1634
1635 1635 # editor
1636 1636 ui.status(_("Checking commit editor...\n"))
1637 1637 editor = ui.geteditor()
1638 1638 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1639 1639 if not cmdpath:
1640 1640 if editor == 'vi':
1641 1641 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1642 1642 ui.write(_(" (specify a commit editor in your configuration"
1643 1643 " file)\n"))
1644 1644 else:
1645 1645 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1646 1646 ui.write(_(" (specify a commit editor in your configuration"
1647 1647 " file)\n"))
1648 1648 problems += 1
1649 1649
1650 1650 # check username
1651 1651 ui.status(_("Checking username...\n"))
1652 1652 try:
1653 1653 ui.username()
1654 1654 except util.Abort, e:
1655 1655 ui.write(" %s\n" % e)
1656 1656 ui.write(_(" (specify a username in your configuration file)\n"))
1657 1657 problems += 1
1658 1658
1659 1659 if not problems:
1660 1660 ui.status(_("No problems detected\n"))
1661 1661 else:
1662 1662 ui.write(_("%s problems detected,"
1663 1663 " please check your install!\n") % problems)
1664 1664
1665 1665 return problems
1666 1666
1667 1667 def debugrename(ui, repo, file1, *pats, **opts):
1668 1668 """dump rename information"""
1669 1669
1670 1670 ctx = cmdutil.revsingle(repo, opts.get('rev'))
1671 1671 m = cmdutil.match(repo, (file1,) + pats, opts)
1672 1672 for abs in ctx.walk(m):
1673 1673 fctx = ctx[abs]
1674 1674 o = fctx.filelog().renamed(fctx.filenode())
1675 1675 rel = m.rel(abs)
1676 1676 if o:
1677 1677 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1678 1678 else:
1679 1679 ui.write(_("%s not renamed\n") % rel)
1680 1680
1681 1681 def debugwalk(ui, repo, *pats, **opts):
1682 1682 """show how files match on given patterns"""
1683 1683 m = cmdutil.match(repo, pats, opts)
1684 1684 items = list(repo.walk(m))
1685 1685 if not items:
1686 1686 return
1687 1687 fmt = 'f %%-%ds %%-%ds %%s' % (
1688 1688 max([len(abs) for abs in items]),
1689 1689 max([len(m.rel(abs)) for abs in items]))
1690 1690 for abs in items:
1691 1691 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1692 1692 ui.write("%s\n" % line.rstrip())
1693 1693
1694 1694 def debugwireargs(ui, repopath, *vals, **opts):
1695 1695 repo = hg.repository(hg.remoteui(ui, opts), repopath)
1696 1696 for opt in remoteopts:
1697 1697 del opts[opt[1]]
1698 1698 args = {}
1699 1699 for k, v in opts.iteritems():
1700 1700 if v:
1701 1701 args[k] = v
1702 1702 # run twice to check that we don't mess up the stream for the next command
1703 1703 res1 = repo.debugwireargs(*vals, **args)
1704 1704 res2 = repo.debugwireargs(*vals, **args)
1705 1705 ui.write("%s\n" % res1)
1706 1706 if res1 != res2:
1707 1707 ui.warn("%s\n" % res2)
1708 1708
1709 1709 def diff(ui, repo, *pats, **opts):
1710 1710 """diff repository (or selected files)
1711 1711
1712 1712 Show differences between revisions for the specified files.
1713 1713
1714 1714 Differences between files are shown using the unified diff format.
1715 1715
1716 1716 .. note::
1717 1717 diff may generate unexpected results for merges, as it will
1718 1718 default to comparing against the working directory's first
1719 1719 parent changeset if no revisions are specified.
1720 1720
1721 1721 When two revision arguments are given, then changes are shown
1722 1722 between those revisions. If only one revision is specified then
1723 1723 that revision is compared to the working directory, and, when no
1724 1724 revisions are specified, the working directory files are compared
1725 1725 to its parent.
1726 1726
1727 1727 Alternatively you can specify -c/--change with a revision to see
1728 1728 the changes in that changeset relative to its first parent.
1729 1729
1730 1730 Without the -a/--text option, diff will avoid generating diffs of
1731 1731 files it detects as binary. With -a, diff will generate a diff
1732 1732 anyway, probably with undesirable results.
1733 1733
1734 1734 Use the -g/--git option to generate diffs in the git extended diff
1735 1735 format. For more information, read :hg:`help diffs`.
1736 1736
1737 1737 Returns 0 on success.
1738 1738 """
1739 1739
1740 1740 revs = opts.get('rev')
1741 1741 change = opts.get('change')
1742 1742 stat = opts.get('stat')
1743 1743 reverse = opts.get('reverse')
1744 1744
1745 1745 if revs and change:
1746 1746 msg = _('cannot specify --rev and --change at the same time')
1747 1747 raise util.Abort(msg)
1748 1748 elif change:
1749 1749 node2 = cmdutil.revsingle(repo, change, None).node()
1750 1750 node1 = repo[node2].p1().node()
1751 1751 else:
1752 1752 node1, node2 = cmdutil.revpair(repo, revs)
1753 1753
1754 1754 if reverse:
1755 1755 node1, node2 = node2, node1
1756 1756
1757 1757 diffopts = patch.diffopts(ui, opts)
1758 1758 m = cmdutil.match(repo, pats, opts)
1759 1759 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1760 1760 listsubrepos=opts.get('subrepos'))
1761 1761
1762 1762 def export(ui, repo, *changesets, **opts):
1763 1763 """dump the header and diffs for one or more changesets
1764 1764
1765 1765 Print the changeset header and diffs for one or more revisions.
1766 1766
1767 1767 The information shown in the changeset header is: author, date,
1768 1768 branch name (if non-default), changeset hash, parent(s) and commit
1769 1769 comment.
1770 1770
1771 1771 .. note::
1772 1772 export may generate unexpected diff output for merge
1773 1773 changesets, as it will compare the merge changeset against its
1774 1774 first parent only.
1775 1775
1776 1776 Output may be to a file, in which case the name of the file is
1777 1777 given using a format string. The formatting rules are as follows:
1778 1778
1779 1779 :``%%``: literal "%" character
1780 1780 :``%H``: changeset hash (40 hexadecimal digits)
1781 1781 :``%N``: number of patches being generated
1782 1782 :``%R``: changeset revision number
1783 1783 :``%b``: basename of the exporting repository
1784 1784 :``%h``: short-form changeset hash (12 hexadecimal digits)
1785 1785 :``%n``: zero-padded sequence number, starting at 1
1786 1786 :``%r``: zero-padded changeset revision number
1787 1787
1788 1788 Without the -a/--text option, export will avoid generating diffs
1789 1789 of files it detects as binary. With -a, export will generate a
1790 1790 diff anyway, probably with undesirable results.
1791 1791
1792 1792 Use the -g/--git option to generate diffs in the git extended diff
1793 1793 format. See :hg:`help diffs` for more information.
1794 1794
1795 1795 With the --switch-parent option, the diff will be against the
1796 1796 second parent. It can be useful to review a merge.
1797 1797
1798 1798 Returns 0 on success.
1799 1799 """
1800 1800 changesets += tuple(opts.get('rev', []))
1801 1801 if not changesets:
1802 1802 raise util.Abort(_("export requires at least one changeset"))
1803 1803 revs = cmdutil.revrange(repo, changesets)
1804 1804 if len(revs) > 1:
1805 1805 ui.note(_('exporting patches:\n'))
1806 1806 else:
1807 1807 ui.note(_('exporting patch:\n'))
1808 1808 cmdutil.export(repo, revs, template=opts.get('output'),
1809 1809 switch_parent=opts.get('switch_parent'),
1810 1810 opts=patch.diffopts(ui, opts))
1811 1811
1812 1812 def forget(ui, repo, *pats, **opts):
1813 1813 """forget the specified files on the next commit
1814 1814
1815 1815 Mark the specified files so they will no longer be tracked
1816 1816 after the next commit.
1817 1817
1818 1818 This only removes files from the current branch, not from the
1819 1819 entire project history, and it does not delete them from the
1820 1820 working directory.
1821 1821
1822 1822 To undo a forget before the next commit, see :hg:`add`.
1823 1823
1824 1824 Returns 0 on success.
1825 1825 """
1826 1826
1827 1827 if not pats:
1828 1828 raise util.Abort(_('no files specified'))
1829 1829
1830 1830 m = cmdutil.match(repo, pats, opts)
1831 1831 s = repo.status(match=m, clean=True)
1832 1832 forget = sorted(s[0] + s[1] + s[3] + s[6])
1833 1833 errs = 0
1834 1834
1835 1835 for f in m.files():
1836 1836 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1837 1837 ui.warn(_('not removing %s: file is already untracked\n')
1838 1838 % m.rel(f))
1839 1839 errs = 1
1840 1840
1841 1841 for f in forget:
1842 1842 if ui.verbose or not m.exact(f):
1843 1843 ui.status(_('removing %s\n') % m.rel(f))
1844 1844
1845 1845 repo[None].remove(forget, unlink=False)
1846 1846 return errs
1847 1847
1848 1848 def grep(ui, repo, pattern, *pats, **opts):
1849 1849 """search for a pattern in specified files and revisions
1850 1850
1851 1851 Search revisions of files for a regular expression.
1852 1852
1853 1853 This command behaves differently than Unix grep. It only accepts
1854 1854 Python/Perl regexps. It searches repository history, not the
1855 1855 working directory. It always prints the revision number in which a
1856 1856 match appears.
1857 1857
1858 1858 By default, grep only prints output for the first revision of a
1859 1859 file in which it finds a match. To get it to print every revision
1860 1860 that contains a change in match status ("-" for a match that
1861 1861 becomes a non-match, or "+" for a non-match that becomes a match),
1862 1862 use the --all flag.
1863 1863
1864 1864 Returns 0 if a match is found, 1 otherwise.
1865 1865 """
1866 1866 reflags = 0
1867 1867 if opts.get('ignore_case'):
1868 1868 reflags |= re.I
1869 1869 try:
1870 1870 regexp = re.compile(pattern, reflags)
1871 1871 except re.error, inst:
1872 1872 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1873 1873 return 1
1874 1874 sep, eol = ':', '\n'
1875 1875 if opts.get('print0'):
1876 1876 sep = eol = '\0'
1877 1877
1878 1878 getfile = util.lrucachefunc(repo.file)
1879 1879
1880 1880 def matchlines(body):
1881 1881 begin = 0
1882 1882 linenum = 0
1883 1883 while True:
1884 1884 match = regexp.search(body, begin)
1885 1885 if not match:
1886 1886 break
1887 1887 mstart, mend = match.span()
1888 1888 linenum += body.count('\n', begin, mstart) + 1
1889 1889 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1890 1890 begin = body.find('\n', mend) + 1 or len(body)
1891 1891 lend = begin - 1
1892 1892 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1893 1893
1894 1894 class linestate(object):
1895 1895 def __init__(self, line, linenum, colstart, colend):
1896 1896 self.line = line
1897 1897 self.linenum = linenum
1898 1898 self.colstart = colstart
1899 1899 self.colend = colend
1900 1900
1901 1901 def __hash__(self):
1902 1902 return hash((self.linenum, self.line))
1903 1903
1904 1904 def __eq__(self, other):
1905 1905 return self.line == other.line
1906 1906
1907 1907 matches = {}
1908 1908 copies = {}
1909 1909 def grepbody(fn, rev, body):
1910 1910 matches[rev].setdefault(fn, [])
1911 1911 m = matches[rev][fn]
1912 1912 for lnum, cstart, cend, line in matchlines(body):
1913 1913 s = linestate(line, lnum, cstart, cend)
1914 1914 m.append(s)
1915 1915
1916 1916 def difflinestates(a, b):
1917 1917 sm = difflib.SequenceMatcher(None, a, b)
1918 1918 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1919 1919 if tag == 'insert':
1920 1920 for i in xrange(blo, bhi):
1921 1921 yield ('+', b[i])
1922 1922 elif tag == 'delete':
1923 1923 for i in xrange(alo, ahi):
1924 1924 yield ('-', a[i])
1925 1925 elif tag == 'replace':
1926 1926 for i in xrange(alo, ahi):
1927 1927 yield ('-', a[i])
1928 1928 for i in xrange(blo, bhi):
1929 1929 yield ('+', b[i])
1930 1930
1931 1931 def display(fn, ctx, pstates, states):
1932 1932 rev = ctx.rev()
1933 1933 datefunc = ui.quiet and util.shortdate or util.datestr
1934 1934 found = False
1935 1935 filerevmatches = {}
1936 1936 def binary():
1937 1937 flog = getfile(fn)
1938 1938 return util.binary(flog.read(ctx.filenode(fn)))
1939 1939
1940 1940 if opts.get('all'):
1941 1941 iter = difflinestates(pstates, states)
1942 1942 else:
1943 1943 iter = [('', l) for l in states]
1944 1944 for change, l in iter:
1945 1945 cols = [fn, str(rev)]
1946 1946 before, match, after = None, None, None
1947 1947 if opts.get('line_number'):
1948 1948 cols.append(str(l.linenum))
1949 1949 if opts.get('all'):
1950 1950 cols.append(change)
1951 1951 if opts.get('user'):
1952 1952 cols.append(ui.shortuser(ctx.user()))
1953 1953 if opts.get('date'):
1954 1954 cols.append(datefunc(ctx.date()))
1955 1955 if opts.get('files_with_matches'):
1956 1956 c = (fn, rev)
1957 1957 if c in filerevmatches:
1958 1958 continue
1959 1959 filerevmatches[c] = 1
1960 1960 else:
1961 1961 before = l.line[:l.colstart]
1962 1962 match = l.line[l.colstart:l.colend]
1963 1963 after = l.line[l.colend:]
1964 1964 ui.write(sep.join(cols))
1965 1965 if before is not None:
1966 1966 if not opts.get('text') and binary():
1967 1967 ui.write(sep + " Binary file matches")
1968 1968 else:
1969 1969 ui.write(sep + before)
1970 1970 ui.write(match, label='grep.match')
1971 1971 ui.write(after)
1972 1972 ui.write(eol)
1973 1973 found = True
1974 1974 return found
1975 1975
1976 1976 skip = {}
1977 1977 revfiles = {}
1978 1978 matchfn = cmdutil.match(repo, pats, opts)
1979 1979 found = False
1980 1980 follow = opts.get('follow')
1981 1981
1982 1982 def prep(ctx, fns):
1983 1983 rev = ctx.rev()
1984 1984 pctx = ctx.p1()
1985 1985 parent = pctx.rev()
1986 1986 matches.setdefault(rev, {})
1987 1987 matches.setdefault(parent, {})
1988 1988 files = revfiles.setdefault(rev, [])
1989 1989 for fn in fns:
1990 1990 flog = getfile(fn)
1991 1991 try:
1992 1992 fnode = ctx.filenode(fn)
1993 1993 except error.LookupError:
1994 1994 continue
1995 1995
1996 1996 copied = flog.renamed(fnode)
1997 1997 copy = follow and copied and copied[0]
1998 1998 if copy:
1999 1999 copies.setdefault(rev, {})[fn] = copy
2000 2000 if fn in skip:
2001 2001 if copy:
2002 2002 skip[copy] = True
2003 2003 continue
2004 2004 files.append(fn)
2005 2005
2006 2006 if fn not in matches[rev]:
2007 2007 grepbody(fn, rev, flog.read(fnode))
2008 2008
2009 2009 pfn = copy or fn
2010 2010 if pfn not in matches[parent]:
2011 2011 try:
2012 2012 fnode = pctx.filenode(pfn)
2013 2013 grepbody(pfn, parent, flog.read(fnode))
2014 2014 except error.LookupError:
2015 2015 pass
2016 2016
2017 2017 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2018 2018 rev = ctx.rev()
2019 2019 parent = ctx.p1().rev()
2020 2020 for fn in sorted(revfiles.get(rev, [])):
2021 2021 states = matches[rev][fn]
2022 2022 copy = copies.get(rev, {}).get(fn)
2023 2023 if fn in skip:
2024 2024 if copy:
2025 2025 skip[copy] = True
2026 2026 continue
2027 2027 pstates = matches.get(parent, {}).get(copy or fn, [])
2028 2028 if pstates or states:
2029 2029 r = display(fn, ctx, pstates, states)
2030 2030 found = found or r
2031 2031 if r and not opts.get('all'):
2032 2032 skip[fn] = True
2033 2033 if copy:
2034 2034 skip[copy] = True
2035 2035 del matches[rev]
2036 2036 del revfiles[rev]
2037 2037
2038 2038 return not found
2039 2039
2040 2040 def heads(ui, repo, *branchrevs, **opts):
2041 2041 """show current repository heads or show branch heads
2042 2042
2043 2043 With no arguments, show all repository branch heads.
2044 2044
2045 2045 Repository "heads" are changesets with no child changesets. They are
2046 2046 where development generally takes place and are the usual targets
2047 2047 for update and merge operations. Branch heads are changesets that have
2048 2048 no child changeset on the same branch.
2049 2049
2050 2050 If one or more REVs are given, only branch heads on the branches
2051 2051 associated with the specified changesets are shown.
2052 2052
2053 2053 If -c/--closed is specified, also show branch heads marked closed
2054 2054 (see :hg:`commit --close-branch`).
2055 2055
2056 2056 If STARTREV is specified, only those heads that are descendants of
2057 2057 STARTREV will be displayed.
2058 2058
2059 2059 If -t/--topo is specified, named branch mechanics will be ignored and only
2060 2060 changesets without children will be shown.
2061 2061
2062 2062 Returns 0 if matching heads are found, 1 if not.
2063 2063 """
2064 2064
2065 2065 start = None
2066 2066 if 'rev' in opts:
2067 2067 start = cmdutil.revsingle(repo, opts['rev'], None).node()
2068 2068
2069 2069 if opts.get('topo'):
2070 2070 heads = [repo[h] for h in repo.heads(start)]
2071 2071 else:
2072 2072 heads = []
2073 2073 for b, ls in repo.branchmap().iteritems():
2074 2074 if start is None:
2075 2075 heads += [repo[h] for h in ls]
2076 2076 continue
2077 2077 startrev = repo.changelog.rev(start)
2078 2078 descendants = set(repo.changelog.descendants(startrev))
2079 2079 descendants.add(startrev)
2080 2080 rev = repo.changelog.rev
2081 2081 heads += [repo[h] for h in ls if rev(h) in descendants]
2082 2082
2083 2083 if branchrevs:
2084 2084 branches = set(repo[br].branch() for br in branchrevs)
2085 2085 heads = [h for h in heads if h.branch() in branches]
2086 2086
2087 2087 if not opts.get('closed'):
2088 2088 heads = [h for h in heads if not h.extra().get('close')]
2089 2089
2090 2090 if opts.get('active') and branchrevs:
2091 2091 dagheads = repo.heads(start)
2092 2092 heads = [h for h in heads if h.node() in dagheads]
2093 2093
2094 2094 if branchrevs:
2095 2095 haveheads = set(h.branch() for h in heads)
2096 2096 if branches - haveheads:
2097 2097 headless = ', '.join(b for b in branches - haveheads)
2098 2098 msg = _('no open branch heads found on branches %s')
2099 2099 if opts.get('rev'):
2100 2100 msg += _(' (started at %s)' % opts['rev'])
2101 2101 ui.warn((msg + '\n') % headless)
2102 2102
2103 2103 if not heads:
2104 2104 return 1
2105 2105
2106 2106 heads = sorted(heads, key=lambda x: -x.rev())
2107 2107 displayer = cmdutil.show_changeset(ui, repo, opts)
2108 2108 for ctx in heads:
2109 2109 displayer.show(ctx)
2110 2110 displayer.close()
2111 2111
2112 2112 def help_(ui, name=None, with_version=False, unknowncmd=False, full=True):
2113 2113 """show help for a given topic or a help overview
2114 2114
2115 2115 With no arguments, print a list of commands with short help messages.
2116 2116
2117 2117 Given a topic, extension, or command name, print help for that
2118 2118 topic.
2119 2119
2120 2120 Returns 0 if successful.
2121 2121 """
2122 2122 option_lists = []
2123 2123 textwidth = min(ui.termwidth(), 80) - 2
2124 2124
2125 2125 def addglobalopts(aliases):
2126 2126 if ui.verbose:
2127 2127 option_lists.append((_("global options:"), globalopts))
2128 2128 if name == 'shortlist':
2129 2129 option_lists.append((_('use "hg help" for the full list '
2130 2130 'of commands'), ()))
2131 2131 else:
2132 2132 if name == 'shortlist':
2133 2133 msg = _('use "hg help" for the full list of commands '
2134 2134 'or "hg -v" for details')
2135 2135 elif name and not full:
2136 2136 msg = _('use "hg help %s" to show the full help text' % name)
2137 2137 elif aliases:
2138 2138 msg = _('use "hg -v help%s" to show builtin aliases and '
2139 2139 'global options') % (name and " " + name or "")
2140 2140 else:
2141 2141 msg = _('use "hg -v help %s" to show global options') % name
2142 2142 option_lists.append((msg, ()))
2143 2143
2144 2144 def helpcmd(name):
2145 2145 if with_version:
2146 2146 version_(ui)
2147 2147 ui.write('\n')
2148 2148
2149 2149 try:
2150 2150 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
2151 2151 except error.AmbiguousCommand, inst:
2152 2152 # py3k fix: except vars can't be used outside the scope of the
2153 2153 # except block, nor can be used inside a lambda. python issue4617
2154 2154 prefix = inst.args[0]
2155 2155 select = lambda c: c.lstrip('^').startswith(prefix)
2156 2156 helplist(_('list of commands:\n\n'), select)
2157 2157 return
2158 2158
2159 2159 # check if it's an invalid alias and display its error if it is
2160 2160 if getattr(entry[0], 'badalias', False):
2161 2161 if not unknowncmd:
2162 2162 entry[0](ui)
2163 2163 return
2164 2164
2165 2165 # synopsis
2166 2166 if len(entry) > 2:
2167 2167 if entry[2].startswith('hg'):
2168 2168 ui.write("%s\n" % entry[2])
2169 2169 else:
2170 2170 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2171 2171 else:
2172 2172 ui.write('hg %s\n' % aliases[0])
2173 2173
2174 2174 # aliases
2175 2175 if full and not ui.quiet and len(aliases) > 1:
2176 2176 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2177 2177
2178 2178 # description
2179 2179 doc = gettext(entry[0].__doc__)
2180 2180 if not doc:
2181 2181 doc = _("(no help text available)")
2182 2182 if hasattr(entry[0], 'definition'): # aliased command
2183 2183 if entry[0].definition.startswith('!'): # shell alias
2184 2184 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2185 2185 else:
2186 2186 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2187 2187 if ui.quiet or not full:
2188 2188 doc = doc.splitlines()[0]
2189 2189 keep = ui.verbose and ['verbose'] or []
2190 2190 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2191 2191 ui.write("\n%s\n" % formatted)
2192 2192 if pruned:
2193 2193 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2194 2194
2195 2195 if not ui.quiet:
2196 2196 # options
2197 2197 if entry[1]:
2198 2198 option_lists.append((_("options:\n"), entry[1]))
2199 2199
2200 2200 addglobalopts(False)
2201 2201
2202 2202 def helplist(header, select=None):
2203 2203 h = {}
2204 2204 cmds = {}
2205 2205 for c, e in table.iteritems():
2206 2206 f = c.split("|", 1)[0]
2207 2207 if select and not select(f):
2208 2208 continue
2209 2209 if (not select and name != 'shortlist' and
2210 2210 e[0].__module__ != __name__):
2211 2211 continue
2212 2212 if name == "shortlist" and not f.startswith("^"):
2213 2213 continue
2214 2214 f = f.lstrip("^")
2215 2215 if not ui.debugflag and f.startswith("debug"):
2216 2216 continue
2217 2217 doc = e[0].__doc__
2218 2218 if doc and 'DEPRECATED' in doc and not ui.verbose:
2219 2219 continue
2220 2220 doc = gettext(doc)
2221 2221 if not doc:
2222 2222 doc = _("(no help text available)")
2223 2223 h[f] = doc.splitlines()[0].rstrip()
2224 2224 cmds[f] = c.lstrip("^")
2225 2225
2226 2226 if not h:
2227 2227 ui.status(_('no commands defined\n'))
2228 2228 return
2229 2229
2230 2230 ui.status(header)
2231 2231 fns = sorted(h)
2232 2232 m = max(map(len, fns))
2233 2233 for f in fns:
2234 2234 if ui.verbose:
2235 2235 commands = cmds[f].replace("|",", ")
2236 2236 ui.write(" %s:\n %s\n"%(commands, h[f]))
2237 2237 else:
2238 2238 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2239 2239 initindent=' %-*s ' % (m, f),
2240 2240 hangindent=' ' * (m + 4))))
2241 2241
2242 2242 if not ui.quiet:
2243 2243 addglobalopts(True)
2244 2244
2245 2245 def helptopic(name):
2246 2246 for names, header, doc in help.helptable:
2247 2247 if name in names:
2248 2248 break
2249 2249 else:
2250 2250 raise error.UnknownCommand(name)
2251 2251
2252 2252 # description
2253 2253 if not doc:
2254 2254 doc = _("(no help text available)")
2255 2255 if hasattr(doc, '__call__'):
2256 2256 doc = doc()
2257 2257
2258 2258 ui.write("%s\n\n" % header)
2259 2259 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2260 2260
2261 2261 def helpext(name):
2262 2262 try:
2263 2263 mod = extensions.find(name)
2264 2264 doc = gettext(mod.__doc__) or _('no help text available')
2265 2265 except KeyError:
2266 2266 mod = None
2267 2267 doc = extensions.disabledext(name)
2268 2268 if not doc:
2269 2269 raise error.UnknownCommand(name)
2270 2270
2271 2271 if '\n' not in doc:
2272 2272 head, tail = doc, ""
2273 2273 else:
2274 2274 head, tail = doc.split('\n', 1)
2275 2275 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2276 2276 if tail:
2277 2277 ui.write(minirst.format(tail, textwidth))
2278 2278 ui.status('\n\n')
2279 2279
2280 2280 if mod:
2281 2281 try:
2282 2282 ct = mod.cmdtable
2283 2283 except AttributeError:
2284 2284 ct = {}
2285 2285 modcmds = set([c.split('|', 1)[0] for c in ct])
2286 2286 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2287 2287 else:
2288 2288 ui.write(_('use "hg help extensions" for information on enabling '
2289 2289 'extensions\n'))
2290 2290
2291 2291 def helpextcmd(name):
2292 2292 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2293 2293 doc = gettext(mod.__doc__).splitlines()[0]
2294 2294
2295 2295 msg = help.listexts(_("'%s' is provided by the following "
2296 2296 "extension:") % cmd, {ext: doc}, len(ext),
2297 2297 indent=4)
2298 2298 ui.write(minirst.format(msg, textwidth))
2299 2299 ui.write('\n\n')
2300 2300 ui.write(_('use "hg help extensions" for information on enabling '
2301 2301 'extensions\n'))
2302 2302
2303 2303 help.addtopichook('revsets', revset.makedoc)
2304 2304 help.addtopichook('templates', templatekw.makedoc)
2305 2305 help.addtopichook('templates', templatefilters.makedoc)
2306 2306
2307 2307 if name and name != 'shortlist':
2308 2308 i = None
2309 2309 if unknowncmd:
2310 2310 queries = (helpextcmd,)
2311 2311 else:
2312 2312 queries = (helptopic, helpcmd, helpext, helpextcmd)
2313 2313 for f in queries:
2314 2314 try:
2315 2315 f(name)
2316 2316 i = None
2317 2317 break
2318 2318 except error.UnknownCommand, inst:
2319 2319 i = inst
2320 2320 if i:
2321 2321 raise i
2322 2322
2323 2323 else:
2324 2324 # program name
2325 2325 if ui.verbose or with_version:
2326 2326 version_(ui)
2327 2327 else:
2328 2328 ui.status(_("Mercurial Distributed SCM\n"))
2329 2329 ui.status('\n')
2330 2330
2331 2331 # list of commands
2332 2332 if name == "shortlist":
2333 2333 header = _('basic commands:\n\n')
2334 2334 else:
2335 2335 header = _('list of commands:\n\n')
2336 2336
2337 2337 helplist(header)
2338 2338 if name != 'shortlist':
2339 2339 exts, maxlength = extensions.enabled()
2340 2340 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2341 2341 if text:
2342 2342 ui.write("\n%s\n" % minirst.format(text, textwidth))
2343 2343
2344 2344 # list all option lists
2345 2345 opt_output = []
2346 2346 multioccur = False
2347 2347 for title, options in option_lists:
2348 2348 opt_output.append(("\n%s" % title, None))
2349 2349 for option in options:
2350 2350 if len(option) == 5:
2351 2351 shortopt, longopt, default, desc, optlabel = option
2352 2352 else:
2353 2353 shortopt, longopt, default, desc = option
2354 2354 optlabel = _("VALUE") # default label
2355 2355
2356 2356 if _("DEPRECATED") in desc and not ui.verbose:
2357 2357 continue
2358 2358 if isinstance(default, list):
2359 2359 numqualifier = " %s [+]" % optlabel
2360 2360 multioccur = True
2361 2361 elif (default is not None) and not isinstance(default, bool):
2362 2362 numqualifier = " %s" % optlabel
2363 2363 else:
2364 2364 numqualifier = ""
2365 2365 opt_output.append(("%2s%s" %
2366 2366 (shortopt and "-%s" % shortopt,
2367 2367 longopt and " --%s%s" %
2368 2368 (longopt, numqualifier)),
2369 2369 "%s%s" % (desc,
2370 2370 default
2371 2371 and _(" (default: %s)") % default
2372 2372 or "")))
2373 2373 if multioccur:
2374 2374 msg = _("\n[+] marked option can be specified multiple times")
2375 2375 if ui.verbose and name != 'shortlist':
2376 2376 opt_output.append((msg, None))
2377 2377 else:
2378 2378 opt_output.insert(-1, (msg, None))
2379 2379
2380 2380 if not name:
2381 2381 ui.write(_("\nadditional help topics:\n\n"))
2382 2382 topics = []
2383 2383 for names, header, doc in help.helptable:
2384 2384 topics.append((sorted(names, key=len, reverse=True)[0], header))
2385 2385 topics_len = max([len(s[0]) for s in topics])
2386 2386 for t, desc in topics:
2387 2387 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2388 2388
2389 2389 if opt_output:
2390 2390 colwidth = encoding.colwidth
2391 2391 # normalize: (opt or message, desc or None, width of opt)
2392 2392 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2393 2393 for opt, desc in opt_output]
2394 2394 hanging = max([e[2] for e in entries])
2395 2395 for opt, desc, width in entries:
2396 2396 if desc:
2397 2397 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2398 2398 hangindent = ' ' * (hanging + 3)
2399 2399 ui.write('%s\n' % (util.wrap(desc, textwidth,
2400 2400 initindent=initindent,
2401 2401 hangindent=hangindent)))
2402 2402 else:
2403 2403 ui.write("%s\n" % opt)
2404 2404
2405 2405 def identify(ui, repo, source=None, rev=None,
2406 2406 num=None, id=None, branch=None, tags=None, bookmarks=None):
2407 2407 """identify the working copy or specified revision
2408 2408
2409 2409 Print a summary identifying the repository state at REV using one or
2410 2410 two parent hash identifiers, followed by a "+" if the working
2411 2411 directory has uncommitted changes, the branch name (if not default),
2412 2412 a list of tags, and a list of bookmarks.
2413 2413
2414 2414 When REV is not given, print a summary of the current state of the
2415 2415 repository.
2416 2416
2417 2417 Specifying a path to a repository root or Mercurial bundle will
2418 2418 cause lookup to operate on that repository/bundle.
2419 2419
2420 2420 Returns 0 if successful.
2421 2421 """
2422 2422
2423 2423 if not repo and not source:
2424 2424 raise util.Abort(_("there is no Mercurial repository here "
2425 2425 "(.hg not found)"))
2426 2426
2427 2427 hexfunc = ui.debugflag and hex or short
2428 2428 default = not (num or id or branch or tags or bookmarks)
2429 2429 output = []
2430 2430 revs = []
2431 2431
2432 2432 if source:
2433 2433 source, branches = hg.parseurl(ui.expandpath(source))
2434 2434 repo = hg.repository(ui, source)
2435 2435 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2436 2436
2437 2437 if not repo.local():
2438 2438 if num or branch or tags:
2439 2439 raise util.Abort(
2440 2440 _("can't query remote revision number, branch, or tags"))
2441 2441 if not rev and revs:
2442 2442 rev = revs[0]
2443 2443 if not rev:
2444 2444 rev = "tip"
2445 2445
2446 2446 remoterev = repo.lookup(rev)
2447 2447 if default or id:
2448 2448 output = [hexfunc(remoterev)]
2449 2449
2450 2450 def getbms():
2451 2451 bms = []
2452 2452
2453 2453 if 'bookmarks' in repo.listkeys('namespaces'):
2454 2454 hexremoterev = hex(remoterev)
2455 2455 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
2456 2456 if bmr == hexremoterev]
2457 2457
2458 2458 return bms
2459 2459
2460 2460 if bookmarks:
2461 2461 output.extend(getbms())
2462 2462 elif default and not ui.quiet:
2463 2463 # multiple bookmarks for a single parent separated by '/'
2464 2464 bm = '/'.join(getbms())
2465 2465 if bm:
2466 2466 output.append(bm)
2467 2467 else:
2468 2468 if not rev:
2469 2469 ctx = repo[None]
2470 2470 parents = ctx.parents()
2471 2471 changed = ""
2472 2472 if default or id or num:
2473 2473 changed = util.any(repo.status()) and "+" or ""
2474 2474 if default or id:
2475 2475 output = ["%s%s" %
2476 2476 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2477 2477 if num:
2478 2478 output.append("%s%s" %
2479 2479 ('+'.join([str(p.rev()) for p in parents]), changed))
2480 2480 else:
2481 2481 ctx = cmdutil.revsingle(repo, rev)
2482 2482 if default or id:
2483 2483 output = [hexfunc(ctx.node())]
2484 2484 if num:
2485 2485 output.append(str(ctx.rev()))
2486 2486
2487 2487 if default and not ui.quiet:
2488 2488 b = ctx.branch()
2489 2489 if b != 'default':
2490 2490 output.append("(%s)" % b)
2491 2491
2492 2492 # multiple tags for a single parent separated by '/'
2493 2493 t = '/'.join(ctx.tags())
2494 2494 if t:
2495 2495 output.append(t)
2496 2496
2497 2497 # multiple bookmarks for a single parent separated by '/'
2498 2498 bm = '/'.join(ctx.bookmarks())
2499 2499 if bm:
2500 2500 output.append(bm)
2501 2501 else:
2502 2502 if branch:
2503 2503 output.append(ctx.branch())
2504 2504
2505 2505 if tags:
2506 2506 output.extend(ctx.tags())
2507 2507
2508 2508 if bookmarks:
2509 2509 output.extend(ctx.bookmarks())
2510 2510
2511 2511 ui.write("%s\n" % ' '.join(output))
2512 2512
2513 2513 def import_(ui, repo, patch1, *patches, **opts):
2514 2514 """import an ordered set of patches
2515 2515
2516 2516 Import a list of patches and commit them individually (unless
2517 2517 --no-commit is specified).
2518 2518
2519 2519 If there are outstanding changes in the working directory, import
2520 2520 will abort unless given the -f/--force flag.
2521 2521
2522 2522 You can import a patch straight from a mail message. Even patches
2523 2523 as attachments work (to use the body part, it must have type
2524 2524 text/plain or text/x-patch). From and Subject headers of email
2525 2525 message are used as default committer and commit message. All
2526 2526 text/plain body parts before first diff are added to commit
2527 2527 message.
2528 2528
2529 2529 If the imported patch was generated by :hg:`export`, user and
2530 2530 description from patch override values from message headers and
2531 2531 body. Values given on command line with -m/--message and -u/--user
2532 2532 override these.
2533 2533
2534 2534 If --exact is specified, import will set the working directory to
2535 2535 the parent of each patch before applying it, and will abort if the
2536 2536 resulting changeset has a different ID than the one recorded in
2537 2537 the patch. This may happen due to character set problems or other
2538 2538 deficiencies in the text patch format.
2539 2539
2540 2540 With -s/--similarity, hg will attempt to discover renames and
2541 2541 copies in the patch in the same way as 'addremove'.
2542 2542
2543 2543 To read a patch from standard input, use "-" as the patch name. If
2544 2544 a URL is specified, the patch will be downloaded from it.
2545 2545 See :hg:`help dates` for a list of formats valid for -d/--date.
2546 2546
2547 2547 Returns 0 on success.
2548 2548 """
2549 2549 patches = (patch1,) + patches
2550 2550
2551 2551 date = opts.get('date')
2552 2552 if date:
2553 2553 opts['date'] = util.parsedate(date)
2554 2554
2555 2555 try:
2556 2556 sim = float(opts.get('similarity') or 0)
2557 2557 except ValueError:
2558 2558 raise util.Abort(_('similarity must be a number'))
2559 2559 if sim < 0 or sim > 100:
2560 2560 raise util.Abort(_('similarity must be between 0 and 100'))
2561 2561
2562 2562 if opts.get('exact') or not opts.get('force'):
2563 2563 cmdutil.bail_if_changed(repo)
2564 2564
2565 2565 d = opts["base"]
2566 2566 strip = opts["strip"]
2567 2567 wlock = lock = None
2568 2568 msgs = []
2569 2569
2570 2570 def tryone(ui, hunk):
2571 2571 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2572 2572 patch.extract(ui, hunk)
2573 2573
2574 2574 if not tmpname:
2575 2575 return None
2576 2576 commitid = _('to working directory')
2577 2577
2578 2578 try:
2579 2579 cmdline_message = cmdutil.logmessage(opts)
2580 2580 if cmdline_message:
2581 2581 # pickup the cmdline msg
2582 2582 message = cmdline_message
2583 2583 elif message:
2584 2584 # pickup the patch msg
2585 2585 message = message.strip()
2586 2586 else:
2587 2587 # launch the editor
2588 2588 message = None
2589 2589 ui.debug('message:\n%s\n' % message)
2590 2590
2591 2591 wp = repo.parents()
2592 2592 if opts.get('exact'):
2593 2593 if not nodeid or not p1:
2594 2594 raise util.Abort(_('not a Mercurial patch'))
2595 2595 p1 = repo.lookup(p1)
2596 2596 p2 = repo.lookup(p2 or hex(nullid))
2597 2597
2598 2598 if p1 != wp[0].node():
2599 2599 hg.clean(repo, p1)
2600 2600 repo.dirstate.setparents(p1, p2)
2601 2601 elif p2:
2602 2602 try:
2603 2603 p1 = repo.lookup(p1)
2604 2604 p2 = repo.lookup(p2)
2605 2605 if p1 == wp[0].node():
2606 2606 repo.dirstate.setparents(p1, p2)
2607 2607 except error.RepoError:
2608 2608 pass
2609 2609 if opts.get('exact') or opts.get('import_branch'):
2610 2610 repo.dirstate.setbranch(branch or 'default')
2611 2611
2612 2612 files = {}
2613 2613 try:
2614 2614 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2615 2615 files=files, eolmode=None)
2616 2616 finally:
2617 2617 files = cmdutil.updatedir(ui, repo, files,
2618 2618 similarity=sim / 100.0)
2619 2619 if opts.get('no_commit'):
2620 2620 if message:
2621 2621 msgs.append(message)
2622 2622 else:
2623 2623 if opts.get('exact'):
2624 2624 m = None
2625 2625 else:
2626 2626 m = cmdutil.matchfiles(repo, files or [])
2627 2627 n = repo.commit(message, opts.get('user') or user,
2628 2628 opts.get('date') or date, match=m,
2629 2629 editor=cmdutil.commiteditor)
2630 2630 if opts.get('exact'):
2631 2631 if hex(n) != nodeid:
2632 2632 repo.rollback()
2633 2633 raise util.Abort(_('patch is damaged'
2634 2634 ' or loses information'))
2635 2635 # Force a dirstate write so that the next transaction
2636 2636 # backups an up-do-date file.
2637 2637 repo.dirstate.write()
2638 2638 if n:
2639 2639 commitid = short(n)
2640 2640
2641 2641 return commitid
2642 2642 finally:
2643 2643 os.unlink(tmpname)
2644 2644
2645 2645 try:
2646 2646 wlock = repo.wlock()
2647 2647 lock = repo.lock()
2648 2648 lastcommit = None
2649 2649 for p in patches:
2650 2650 pf = os.path.join(d, p)
2651 2651
2652 2652 if pf == '-':
2653 2653 ui.status(_("applying patch from stdin\n"))
2654 2654 pf = sys.stdin
2655 2655 else:
2656 2656 ui.status(_("applying %s\n") % p)
2657 2657 pf = url.open(ui, pf)
2658 2658
2659 2659 haspatch = False
2660 2660 for hunk in patch.split(pf):
2661 2661 commitid = tryone(ui, hunk)
2662 2662 if commitid:
2663 2663 haspatch = True
2664 2664 if lastcommit:
2665 2665 ui.status(_('applied %s\n') % lastcommit)
2666 2666 lastcommit = commitid
2667 2667
2668 2668 if not haspatch:
2669 2669 raise util.Abort(_('no diffs found'))
2670 2670
2671 2671 if msgs:
2672 repo.opener('last-message.txt', 'wb').write('\n* * *\n'.join(msgs))
2672 repo.opener.write('last-message.txt', '\n* * *\n'.join(msgs))
2673 2673 finally:
2674 2674 release(lock, wlock)
2675 2675
2676 2676 def incoming(ui, repo, source="default", **opts):
2677 2677 """show new changesets found in source
2678 2678
2679 2679 Show new changesets found in the specified path/URL or the default
2680 2680 pull location. These are the changesets that would have been pulled
2681 2681 if a pull at the time you issued this command.
2682 2682
2683 2683 For remote repository, using --bundle avoids downloading the
2684 2684 changesets twice if the incoming is followed by a pull.
2685 2685
2686 2686 See pull for valid source format details.
2687 2687
2688 2688 Returns 0 if there are incoming changes, 1 otherwise.
2689 2689 """
2690 2690 if opts.get('bundle') and opts.get('subrepos'):
2691 2691 raise util.Abort(_('cannot combine --bundle and --subrepos'))
2692 2692
2693 2693 if opts.get('bookmarks'):
2694 2694 source, branches = hg.parseurl(ui.expandpath(source),
2695 2695 opts.get('branch'))
2696 2696 other = hg.repository(hg.remoteui(repo, opts), source)
2697 2697 if 'bookmarks' not in other.listkeys('namespaces'):
2698 2698 ui.warn(_("remote doesn't support bookmarks\n"))
2699 2699 return 0
2700 2700 ui.status(_('comparing with %s\n') % util.hidepassword(source))
2701 2701 return bookmarks.diff(ui, repo, other)
2702 2702
2703 2703 ret = hg.incoming(ui, repo, source, opts)
2704 2704 return ret
2705 2705
2706 2706 def init(ui, dest=".", **opts):
2707 2707 """create a new repository in the given directory
2708 2708
2709 2709 Initialize a new repository in the given directory. If the given
2710 2710 directory does not exist, it will be created.
2711 2711
2712 2712 If no directory is given, the current directory is used.
2713 2713
2714 2714 It is possible to specify an ``ssh://`` URL as the destination.
2715 2715 See :hg:`help urls` for more information.
2716 2716
2717 2717 Returns 0 on success.
2718 2718 """
2719 2719 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=1)
2720 2720
2721 2721 def locate(ui, repo, *pats, **opts):
2722 2722 """locate files matching specific patterns
2723 2723
2724 2724 Print files under Mercurial control in the working directory whose
2725 2725 names match the given patterns.
2726 2726
2727 2727 By default, this command searches all directories in the working
2728 2728 directory. To search just the current directory and its
2729 2729 subdirectories, use "--include .".
2730 2730
2731 2731 If no patterns are given to match, this command prints the names
2732 2732 of all files under Mercurial control in the working directory.
2733 2733
2734 2734 If you want to feed the output of this command into the "xargs"
2735 2735 command, use the -0 option to both this command and "xargs". This
2736 2736 will avoid the problem of "xargs" treating single filenames that
2737 2737 contain whitespace as multiple filenames.
2738 2738
2739 2739 Returns 0 if a match is found, 1 otherwise.
2740 2740 """
2741 2741 end = opts.get('print0') and '\0' or '\n'
2742 2742 rev = cmdutil.revsingle(repo, opts.get('rev'), None).node()
2743 2743
2744 2744 ret = 1
2745 2745 m = cmdutil.match(repo, pats, opts, default='relglob')
2746 2746 m.bad = lambda x, y: False
2747 2747 for abs in repo[rev].walk(m):
2748 2748 if not rev and abs not in repo.dirstate:
2749 2749 continue
2750 2750 if opts.get('fullpath'):
2751 2751 ui.write(repo.wjoin(abs), end)
2752 2752 else:
2753 2753 ui.write(((pats and m.rel(abs)) or abs), end)
2754 2754 ret = 0
2755 2755
2756 2756 return ret
2757 2757
2758 2758 def log(ui, repo, *pats, **opts):
2759 2759 """show revision history of entire repository or files
2760 2760
2761 2761 Print the revision history of the specified files or the entire
2762 2762 project.
2763 2763
2764 2764 File history is shown without following rename or copy history of
2765 2765 files. Use -f/--follow with a filename to follow history across
2766 2766 renames and copies. --follow without a filename will only show
2767 2767 ancestors or descendants of the starting revision. --follow-first
2768 2768 only follows the first parent of merge revisions.
2769 2769
2770 2770 If no revision range is specified, the default is ``tip:0`` unless
2771 2771 --follow is set, in which case the working directory parent is
2772 2772 used as the starting revision. You can specify a revision set for
2773 2773 log, see :hg:`help revsets` for more information.
2774 2774
2775 2775 See :hg:`help dates` for a list of formats valid for -d/--date.
2776 2776
2777 2777 By default this command prints revision number and changeset id,
2778 2778 tags, non-trivial parents, user, date and time, and a summary for
2779 2779 each commit. When the -v/--verbose switch is used, the list of
2780 2780 changed files and full commit message are shown.
2781 2781
2782 2782 .. note::
2783 2783 log -p/--patch may generate unexpected diff output for merge
2784 2784 changesets, as it will only compare the merge changeset against
2785 2785 its first parent. Also, only files different from BOTH parents
2786 2786 will appear in files:.
2787 2787
2788 2788 Returns 0 on success.
2789 2789 """
2790 2790
2791 2791 matchfn = cmdutil.match(repo, pats, opts)
2792 2792 limit = cmdutil.loglimit(opts)
2793 2793 count = 0
2794 2794
2795 2795 endrev = None
2796 2796 if opts.get('copies') and opts.get('rev'):
2797 2797 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2798 2798
2799 2799 df = False
2800 2800 if opts["date"]:
2801 2801 df = util.matchdate(opts["date"])
2802 2802
2803 2803 branches = opts.get('branch', []) + opts.get('only_branch', [])
2804 2804 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2805 2805
2806 2806 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2807 2807 def prep(ctx, fns):
2808 2808 rev = ctx.rev()
2809 2809 parents = [p for p in repo.changelog.parentrevs(rev)
2810 2810 if p != nullrev]
2811 2811 if opts.get('no_merges') and len(parents) == 2:
2812 2812 return
2813 2813 if opts.get('only_merges') and len(parents) != 2:
2814 2814 return
2815 2815 if opts.get('branch') and ctx.branch() not in opts['branch']:
2816 2816 return
2817 2817 if df and not df(ctx.date()[0]):
2818 2818 return
2819 2819 if opts['user'] and not [k for k in opts['user']
2820 2820 if k.lower() in ctx.user().lower()]:
2821 2821 return
2822 2822 if opts.get('keyword'):
2823 2823 for k in [kw.lower() for kw in opts['keyword']]:
2824 2824 if (k in ctx.user().lower() or
2825 2825 k in ctx.description().lower() or
2826 2826 k in " ".join(ctx.files()).lower()):
2827 2827 break
2828 2828 else:
2829 2829 return
2830 2830
2831 2831 copies = None
2832 2832 if opts.get('copies') and rev:
2833 2833 copies = []
2834 2834 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2835 2835 for fn in ctx.files():
2836 2836 rename = getrenamed(fn, rev)
2837 2837 if rename:
2838 2838 copies.append((fn, rename[0]))
2839 2839
2840 2840 revmatchfn = None
2841 2841 if opts.get('patch') or opts.get('stat'):
2842 2842 if opts.get('follow') or opts.get('follow_first'):
2843 2843 # note: this might be wrong when following through merges
2844 2844 revmatchfn = cmdutil.match(repo, fns, default='path')
2845 2845 else:
2846 2846 revmatchfn = matchfn
2847 2847
2848 2848 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2849 2849
2850 2850 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2851 2851 if count == limit:
2852 2852 break
2853 2853 if displayer.flush(ctx.rev()):
2854 2854 count += 1
2855 2855 displayer.close()
2856 2856
2857 2857 def manifest(ui, repo, node=None, rev=None):
2858 2858 """output the current or given revision of the project manifest
2859 2859
2860 2860 Print a list of version controlled files for the given revision.
2861 2861 If no revision is given, the first parent of the working directory
2862 2862 is used, or the null revision if no revision is checked out.
2863 2863
2864 2864 With -v, print file permissions, symlink and executable bits.
2865 2865 With --debug, print file revision hashes.
2866 2866
2867 2867 Returns 0 on success.
2868 2868 """
2869 2869
2870 2870 if rev and node:
2871 2871 raise util.Abort(_("please specify just one revision"))
2872 2872
2873 2873 if not node:
2874 2874 node = rev
2875 2875
2876 2876 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2877 2877 ctx = cmdutil.revsingle(repo, node)
2878 2878 for f in ctx:
2879 2879 if ui.debugflag:
2880 2880 ui.write("%40s " % hex(ctx.manifest()[f]))
2881 2881 if ui.verbose:
2882 2882 ui.write(decor[ctx.flags(f)])
2883 2883 ui.write("%s\n" % f)
2884 2884
2885 2885 def merge(ui, repo, node=None, **opts):
2886 2886 """merge working directory with another revision
2887 2887
2888 2888 The current working directory is updated with all changes made in
2889 2889 the requested revision since the last common predecessor revision.
2890 2890
2891 2891 Files that changed between either parent are marked as changed for
2892 2892 the next commit and a commit must be performed before any further
2893 2893 updates to the repository are allowed. The next commit will have
2894 2894 two parents.
2895 2895
2896 2896 ``--tool`` can be used to specify the merge tool used for file
2897 2897 merges. It overrides the HGMERGE environment variable and your
2898 2898 configuration files. See :hg:`help merge-tools` for options.
2899 2899
2900 2900 If no revision is specified, the working directory's parent is a
2901 2901 head revision, and the current branch contains exactly one other
2902 2902 head, the other head is merged with by default. Otherwise, an
2903 2903 explicit revision with which to merge with must be provided.
2904 2904
2905 2905 :hg:`resolve` must be used to resolve unresolved files.
2906 2906
2907 2907 To undo an uncommitted merge, use :hg:`update --clean .` which
2908 2908 will check out a clean copy of the original merge parent, losing
2909 2909 all changes.
2910 2910
2911 2911 Returns 0 on success, 1 if there are unresolved files.
2912 2912 """
2913 2913
2914 2914 if opts.get('rev') and node:
2915 2915 raise util.Abort(_("please specify just one revision"))
2916 2916 if not node:
2917 2917 node = opts.get('rev')
2918 2918
2919 2919 if not node:
2920 2920 branch = repo[None].branch()
2921 2921 bheads = repo.branchheads(branch)
2922 2922 if len(bheads) > 2:
2923 2923 raise util.Abort(_(
2924 2924 'branch \'%s\' has %d heads - '
2925 2925 'please merge with an explicit rev\n'
2926 2926 '(run \'hg heads .\' to see heads)')
2927 2927 % (branch, len(bheads)))
2928 2928
2929 2929 parent = repo.dirstate.p1()
2930 2930 if len(bheads) == 1:
2931 2931 if len(repo.heads()) > 1:
2932 2932 raise util.Abort(_(
2933 2933 'branch \'%s\' has one head - '
2934 2934 'please merge with an explicit rev\n'
2935 2935 '(run \'hg heads\' to see all heads)')
2936 2936 % branch)
2937 2937 msg = _('there is nothing to merge')
2938 2938 if parent != repo.lookup(repo[None].branch()):
2939 2939 msg = _('%s - use "hg update" instead') % msg
2940 2940 raise util.Abort(msg)
2941 2941
2942 2942 if parent not in bheads:
2943 2943 raise util.Abort(_('working dir not at a head rev - '
2944 2944 'use "hg update" or merge with an explicit rev'))
2945 2945 node = parent == bheads[0] and bheads[-1] or bheads[0]
2946 2946 else:
2947 2947 node = cmdutil.revsingle(repo, node).node()
2948 2948
2949 2949 if opts.get('preview'):
2950 2950 # find nodes that are ancestors of p2 but not of p1
2951 2951 p1 = repo.lookup('.')
2952 2952 p2 = repo.lookup(node)
2953 2953 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2954 2954
2955 2955 displayer = cmdutil.show_changeset(ui, repo, opts)
2956 2956 for node in nodes:
2957 2957 displayer.show(repo[node])
2958 2958 displayer.close()
2959 2959 return 0
2960 2960
2961 2961 try:
2962 2962 # ui.forcemerge is an internal variable, do not document
2963 2963 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2964 2964 return hg.merge(repo, node, force=opts.get('force'))
2965 2965 finally:
2966 2966 ui.setconfig('ui', 'forcemerge', '')
2967 2967
2968 2968 def outgoing(ui, repo, dest=None, **opts):
2969 2969 """show changesets not found in the destination
2970 2970
2971 2971 Show changesets not found in the specified destination repository
2972 2972 or the default push location. These are the changesets that would
2973 2973 be pushed if a push was requested.
2974 2974
2975 2975 See pull for details of valid destination formats.
2976 2976
2977 2977 Returns 0 if there are outgoing changes, 1 otherwise.
2978 2978 """
2979 2979
2980 2980 if opts.get('bookmarks'):
2981 2981 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2982 2982 dest, branches = hg.parseurl(dest, opts.get('branch'))
2983 2983 other = hg.repository(hg.remoteui(repo, opts), dest)
2984 2984 if 'bookmarks' not in other.listkeys('namespaces'):
2985 2985 ui.warn(_("remote doesn't support bookmarks\n"))
2986 2986 return 0
2987 2987 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
2988 2988 return bookmarks.diff(ui, other, repo)
2989 2989
2990 2990 ret = hg.outgoing(ui, repo, dest, opts)
2991 2991 return ret
2992 2992
2993 2993 def parents(ui, repo, file_=None, **opts):
2994 2994 """show the parents of the working directory or revision
2995 2995
2996 2996 Print the working directory's parent revisions. If a revision is
2997 2997 given via -r/--rev, the parent of that revision will be printed.
2998 2998 If a file argument is given, the revision in which the file was
2999 2999 last changed (before the working directory revision or the
3000 3000 argument to --rev if given) is printed.
3001 3001
3002 3002 Returns 0 on success.
3003 3003 """
3004 3004
3005 3005 ctx = cmdutil.revsingle(repo, opts.get('rev'), None)
3006 3006
3007 3007 if file_:
3008 3008 m = cmdutil.match(repo, (file_,), opts)
3009 3009 if m.anypats() or len(m.files()) != 1:
3010 3010 raise util.Abort(_('can only specify an explicit filename'))
3011 3011 file_ = m.files()[0]
3012 3012 filenodes = []
3013 3013 for cp in ctx.parents():
3014 3014 if not cp:
3015 3015 continue
3016 3016 try:
3017 3017 filenodes.append(cp.filenode(file_))
3018 3018 except error.LookupError:
3019 3019 pass
3020 3020 if not filenodes:
3021 3021 raise util.Abort(_("'%s' not found in manifest!") % file_)
3022 3022 fl = repo.file(file_)
3023 3023 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
3024 3024 else:
3025 3025 p = [cp.node() for cp in ctx.parents()]
3026 3026
3027 3027 displayer = cmdutil.show_changeset(ui, repo, opts)
3028 3028 for n in p:
3029 3029 if n != nullid:
3030 3030 displayer.show(repo[n])
3031 3031 displayer.close()
3032 3032
3033 3033 def paths(ui, repo, search=None):
3034 3034 """show aliases for remote repositories
3035 3035
3036 3036 Show definition of symbolic path name NAME. If no name is given,
3037 3037 show definition of all available names.
3038 3038
3039 3039 Path names are defined in the [paths] section of your
3040 3040 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3041 3041 repository, ``.hg/hgrc`` is used, too.
3042 3042
3043 3043 The path names ``default`` and ``default-push`` have a special
3044 3044 meaning. When performing a push or pull operation, they are used
3045 3045 as fallbacks if no location is specified on the command-line.
3046 3046 When ``default-push`` is set, it will be used for push and
3047 3047 ``default`` will be used for pull; otherwise ``default`` is used
3048 3048 as the fallback for both. When cloning a repository, the clone
3049 3049 source is written as ``default`` in ``.hg/hgrc``. Note that
3050 3050 ``default`` and ``default-push`` apply to all inbound (e.g.
3051 3051 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
3052 3052 :hg:`bundle`) operations.
3053 3053
3054 3054 See :hg:`help urls` for more information.
3055 3055
3056 3056 Returns 0 on success.
3057 3057 """
3058 3058 if search:
3059 3059 for name, path in ui.configitems("paths"):
3060 3060 if name == search:
3061 3061 ui.write("%s\n" % util.hidepassword(path))
3062 3062 return
3063 3063 ui.warn(_("not found!\n"))
3064 3064 return 1
3065 3065 else:
3066 3066 for name, path in ui.configitems("paths"):
3067 3067 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
3068 3068
3069 3069 def postincoming(ui, repo, modheads, optupdate, checkout):
3070 3070 if modheads == 0:
3071 3071 return
3072 3072 if optupdate:
3073 3073 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
3074 3074 return hg.update(repo, checkout)
3075 3075 else:
3076 3076 ui.status(_("not updating, since new heads added\n"))
3077 3077 if modheads > 1:
3078 3078 currentbranchheads = len(repo.branchheads())
3079 3079 if currentbranchheads == modheads:
3080 3080 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3081 3081 elif currentbranchheads > 1:
3082 3082 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
3083 3083 else:
3084 3084 ui.status(_("(run 'hg heads' to see heads)\n"))
3085 3085 else:
3086 3086 ui.status(_("(run 'hg update' to get a working copy)\n"))
3087 3087
3088 3088 def pull(ui, repo, source="default", **opts):
3089 3089 """pull changes from the specified source
3090 3090
3091 3091 Pull changes from a remote repository to a local one.
3092 3092
3093 3093 This finds all changes from the repository at the specified path
3094 3094 or URL and adds them to a local repository (the current one unless
3095 3095 -R is specified). By default, this does not update the copy of the
3096 3096 project in the working directory.
3097 3097
3098 3098 Use :hg:`incoming` if you want to see what would have been added
3099 3099 by a pull at the time you issued this command. If you then decide
3100 3100 to add those changes to the repository, you should use :hg:`pull
3101 3101 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3102 3102
3103 3103 If SOURCE is omitted, the 'default' path will be used.
3104 3104 See :hg:`help urls` for more information.
3105 3105
3106 3106 Returns 0 on success, 1 if an update had unresolved files.
3107 3107 """
3108 3108 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3109 3109 other = hg.repository(hg.remoteui(repo, opts), source)
3110 3110 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3111 3111 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3112 3112
3113 3113 if opts.get('bookmark'):
3114 3114 if not revs:
3115 3115 revs = []
3116 3116 rb = other.listkeys('bookmarks')
3117 3117 for b in opts['bookmark']:
3118 3118 if b not in rb:
3119 3119 raise util.Abort(_('remote bookmark %s not found!') % b)
3120 3120 revs.append(rb[b])
3121 3121
3122 3122 if revs:
3123 3123 try:
3124 3124 revs = [other.lookup(rev) for rev in revs]
3125 3125 except error.CapabilityError:
3126 3126 err = _("other repository doesn't support revision lookup, "
3127 3127 "so a rev cannot be specified.")
3128 3128 raise util.Abort(err)
3129 3129
3130 3130 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
3131 3131 bookmarks.updatefromremote(ui, repo, other)
3132 3132 if checkout:
3133 3133 checkout = str(repo.changelog.rev(other.lookup(checkout)))
3134 3134 repo._subtoppath = source
3135 3135 try:
3136 3136 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
3137 3137
3138 3138 finally:
3139 3139 del repo._subtoppath
3140 3140
3141 3141 # update specified bookmarks
3142 3142 if opts.get('bookmark'):
3143 3143 for b in opts['bookmark']:
3144 3144 # explicit pull overrides local bookmark if any
3145 3145 ui.status(_("importing bookmark %s\n") % b)
3146 3146 repo._bookmarks[b] = repo[rb[b]].node()
3147 3147 bookmarks.write(repo)
3148 3148
3149 3149 return ret
3150 3150
3151 3151 def push(ui, repo, dest=None, **opts):
3152 3152 """push changes to the specified destination
3153 3153
3154 3154 Push changesets from the local repository to the specified
3155 3155 destination.
3156 3156
3157 3157 This operation is symmetrical to pull: it is identical to a pull
3158 3158 in the destination repository from the current one.
3159 3159
3160 3160 By default, push will not allow creation of new heads at the
3161 3161 destination, since multiple heads would make it unclear which head
3162 3162 to use. In this situation, it is recommended to pull and merge
3163 3163 before pushing.
3164 3164
3165 3165 Use --new-branch if you want to allow push to create a new named
3166 3166 branch that is not present at the destination. This allows you to
3167 3167 only create a new branch without forcing other changes.
3168 3168
3169 3169 Use -f/--force to override the default behavior and push all
3170 3170 changesets on all branches.
3171 3171
3172 3172 If -r/--rev is used, the specified revision and all its ancestors
3173 3173 will be pushed to the remote repository.
3174 3174
3175 3175 Please see :hg:`help urls` for important details about ``ssh://``
3176 3176 URLs. If DESTINATION is omitted, a default path will be used.
3177 3177
3178 3178 Returns 0 if push was successful, 1 if nothing to push.
3179 3179 """
3180 3180
3181 3181 if opts.get('bookmark'):
3182 3182 for b in opts['bookmark']:
3183 3183 # translate -B options to -r so changesets get pushed
3184 3184 if b in repo._bookmarks:
3185 3185 opts.setdefault('rev', []).append(b)
3186 3186 else:
3187 3187 # if we try to push a deleted bookmark, translate it to null
3188 3188 # this lets simultaneous -r, -b options continue working
3189 3189 opts.setdefault('rev', []).append("null")
3190 3190
3191 3191 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3192 3192 dest, branches = hg.parseurl(dest, opts.get('branch'))
3193 3193 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
3194 3194 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
3195 3195 other = hg.repository(hg.remoteui(repo, opts), dest)
3196 3196 if revs:
3197 3197 revs = [repo.lookup(rev) for rev in revs]
3198 3198
3199 3199 repo._subtoppath = dest
3200 3200 try:
3201 3201 # push subrepos depth-first for coherent ordering
3202 3202 c = repo['']
3203 3203 subs = c.substate # only repos that are committed
3204 3204 for s in sorted(subs):
3205 3205 if not c.sub(s).push(opts.get('force')):
3206 3206 return False
3207 3207 finally:
3208 3208 del repo._subtoppath
3209 3209 result = repo.push(other, opts.get('force'), revs=revs,
3210 3210 newbranch=opts.get('new_branch'))
3211 3211
3212 3212 result = (result == 0)
3213 3213
3214 3214 if opts.get('bookmark'):
3215 3215 rb = other.listkeys('bookmarks')
3216 3216 for b in opts['bookmark']:
3217 3217 # explicit push overrides remote bookmark if any
3218 3218 if b in repo._bookmarks:
3219 3219 ui.status(_("exporting bookmark %s\n") % b)
3220 3220 new = repo[b].hex()
3221 3221 elif b in rb:
3222 3222 ui.status(_("deleting remote bookmark %s\n") % b)
3223 3223 new = '' # delete
3224 3224 else:
3225 3225 ui.warn(_('bookmark %s does not exist on the local '
3226 3226 'or remote repository!\n') % b)
3227 3227 return 2
3228 3228 old = rb.get(b, '')
3229 3229 r = other.pushkey('bookmarks', b, old, new)
3230 3230 if not r:
3231 3231 ui.warn(_('updating bookmark %s failed!\n') % b)
3232 3232 if not result:
3233 3233 result = 2
3234 3234
3235 3235 return result
3236 3236
3237 3237 def recover(ui, repo):
3238 3238 """roll back an interrupted transaction
3239 3239
3240 3240 Recover from an interrupted commit or pull.
3241 3241
3242 3242 This command tries to fix the repository status after an
3243 3243 interrupted operation. It should only be necessary when Mercurial
3244 3244 suggests it.
3245 3245
3246 3246 Returns 0 if successful, 1 if nothing to recover or verify fails.
3247 3247 """
3248 3248 if repo.recover():
3249 3249 return hg.verify(repo)
3250 3250 return 1
3251 3251
3252 3252 def remove(ui, repo, *pats, **opts):
3253 3253 """remove the specified files on the next commit
3254 3254
3255 3255 Schedule the indicated files for removal from the repository.
3256 3256
3257 3257 This only removes files from the current branch, not from the
3258 3258 entire project history. -A/--after can be used to remove only
3259 3259 files that have already been deleted, -f/--force can be used to
3260 3260 force deletion, and -Af can be used to remove files from the next
3261 3261 revision without deleting them from the working directory.
3262 3262
3263 3263 The following table details the behavior of remove for different
3264 3264 file states (columns) and option combinations (rows). The file
3265 3265 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3266 3266 reported by :hg:`status`). The actions are Warn, Remove (from
3267 3267 branch) and Delete (from disk)::
3268 3268
3269 3269 A C M !
3270 3270 none W RD W R
3271 3271 -f R RD RD R
3272 3272 -A W W W R
3273 3273 -Af R R R R
3274 3274
3275 3275 This command schedules the files to be removed at the next commit.
3276 3276 To undo a remove before that, see :hg:`revert`.
3277 3277
3278 3278 Returns 0 on success, 1 if any warnings encountered.
3279 3279 """
3280 3280
3281 3281 ret = 0
3282 3282 after, force = opts.get('after'), opts.get('force')
3283 3283 if not pats and not after:
3284 3284 raise util.Abort(_('no files specified'))
3285 3285
3286 3286 m = cmdutil.match(repo, pats, opts)
3287 3287 s = repo.status(match=m, clean=True)
3288 3288 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3289 3289
3290 3290 for f in m.files():
3291 3291 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3292 3292 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3293 3293 ret = 1
3294 3294
3295 3295 if force:
3296 3296 remove, forget = modified + deleted + clean, added
3297 3297 elif after:
3298 3298 remove, forget = deleted, []
3299 3299 for f in modified + added + clean:
3300 3300 ui.warn(_('not removing %s: file still exists (use -f'
3301 3301 ' to force removal)\n') % m.rel(f))
3302 3302 ret = 1
3303 3303 else:
3304 3304 remove, forget = deleted + clean, []
3305 3305 for f in modified:
3306 3306 ui.warn(_('not removing %s: file is modified (use -f'
3307 3307 ' to force removal)\n') % m.rel(f))
3308 3308 ret = 1
3309 3309 for f in added:
3310 3310 ui.warn(_('not removing %s: file has been marked for add (use -f'
3311 3311 ' to force removal)\n') % m.rel(f))
3312 3312 ret = 1
3313 3313
3314 3314 for f in sorted(remove + forget):
3315 3315 if ui.verbose or not m.exact(f):
3316 3316 ui.status(_('removing %s\n') % m.rel(f))
3317 3317
3318 3318 repo[None].forget(forget)
3319 3319 repo[None].remove(remove, unlink=not after)
3320 3320 return ret
3321 3321
3322 3322 def rename(ui, repo, *pats, **opts):
3323 3323 """rename files; equivalent of copy + remove
3324 3324
3325 3325 Mark dest as copies of sources; mark sources for deletion. If dest
3326 3326 is a directory, copies are put in that directory. If dest is a
3327 3327 file, there can only be one source.
3328 3328
3329 3329 By default, this command copies the contents of files as they
3330 3330 exist in the working directory. If invoked with -A/--after, the
3331 3331 operation is recorded, but no copying is performed.
3332 3332
3333 3333 This command takes effect at the next commit. To undo a rename
3334 3334 before that, see :hg:`revert`.
3335 3335
3336 3336 Returns 0 on success, 1 if errors are encountered.
3337 3337 """
3338 3338 wlock = repo.wlock(False)
3339 3339 try:
3340 3340 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3341 3341 finally:
3342 3342 wlock.release()
3343 3343
3344 3344 def resolve(ui, repo, *pats, **opts):
3345 3345 """redo merges or set/view the merge status of files
3346 3346
3347 3347 Merges with unresolved conflicts are often the result of
3348 3348 non-interactive merging using the ``internal:merge`` configuration
3349 3349 setting, or a command-line merge tool like ``diff3``. The resolve
3350 3350 command is used to manage the files involved in a merge, after
3351 3351 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3352 3352 working directory must have two parents).
3353 3353
3354 3354 The resolve command can be used in the following ways:
3355 3355
3356 3356 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3357 3357 files, discarding any previous merge attempts. Re-merging is not
3358 3358 performed for files already marked as resolved. Use ``--all/-a``
3359 3359 to selects all unresolved files. ``--tool`` can be used to specify
3360 3360 the merge tool used for the given files. It overrides the HGMERGE
3361 3361 environment variable and your configuration files.
3362 3362
3363 3363 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3364 3364 (e.g. after having manually fixed-up the files). The default is
3365 3365 to mark all unresolved files.
3366 3366
3367 3367 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3368 3368 default is to mark all resolved files.
3369 3369
3370 3370 - :hg:`resolve -l`: list files which had or still have conflicts.
3371 3371 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3372 3372
3373 3373 Note that Mercurial will not let you commit files with unresolved
3374 3374 merge conflicts. You must use :hg:`resolve -m ...` before you can
3375 3375 commit after a conflicting merge.
3376 3376
3377 3377 Returns 0 on success, 1 if any files fail a resolve attempt.
3378 3378 """
3379 3379
3380 3380 all, mark, unmark, show, nostatus = \
3381 3381 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3382 3382
3383 3383 if (show and (mark or unmark)) or (mark and unmark):
3384 3384 raise util.Abort(_("too many options specified"))
3385 3385 if pats and all:
3386 3386 raise util.Abort(_("can't specify --all and patterns"))
3387 3387 if not (all or pats or show or mark or unmark):
3388 3388 raise util.Abort(_('no files or directories specified; '
3389 3389 'use --all to remerge all files'))
3390 3390
3391 3391 ms = mergemod.mergestate(repo)
3392 3392 m = cmdutil.match(repo, pats, opts)
3393 3393 ret = 0
3394 3394
3395 3395 for f in ms:
3396 3396 if m(f):
3397 3397 if show:
3398 3398 if nostatus:
3399 3399 ui.write("%s\n" % f)
3400 3400 else:
3401 3401 ui.write("%s %s\n" % (ms[f].upper(), f),
3402 3402 label='resolve.' +
3403 3403 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3404 3404 elif mark:
3405 3405 ms.mark(f, "r")
3406 3406 elif unmark:
3407 3407 ms.mark(f, "u")
3408 3408 else:
3409 3409 wctx = repo[None]
3410 3410 mctx = wctx.parents()[-1]
3411 3411
3412 3412 # backup pre-resolve (merge uses .orig for its own purposes)
3413 3413 a = repo.wjoin(f)
3414 3414 util.copyfile(a, a + ".resolve")
3415 3415
3416 3416 try:
3417 3417 # resolve file
3418 3418 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3419 3419 if ms.resolve(f, wctx, mctx):
3420 3420 ret = 1
3421 3421 finally:
3422 3422 ui.setconfig('ui', 'forcemerge', '')
3423 3423
3424 3424 # replace filemerge's .orig file with our resolve file
3425 3425 util.rename(a + ".resolve", a + ".orig")
3426 3426
3427 3427 ms.commit()
3428 3428 return ret
3429 3429
3430 3430 def revert(ui, repo, *pats, **opts):
3431 3431 """restore individual files or directories to an earlier state
3432 3432
3433 3433 .. note::
3434 3434 This command is most likely not what you are looking for.
3435 3435 Revert will partially overwrite content in the working
3436 3436 directory without changing the working directory parents. Use
3437 3437 :hg:`update -r rev` to check out earlier revisions, or
3438 3438 :hg:`update --clean .` to undo a merge which has added another
3439 3439 parent.
3440 3440
3441 3441 With no revision specified, revert the named files or directories
3442 3442 to the contents they had in the parent of the working directory.
3443 3443 This restores the contents of the affected files to an unmodified
3444 3444 state and unschedules adds, removes, copies, and renames. If the
3445 3445 working directory has two parents, you must explicitly specify a
3446 3446 revision.
3447 3447
3448 3448 Using the -r/--rev option, revert the given files or directories
3449 3449 to their contents as of a specific revision. This can be helpful
3450 3450 to "roll back" some or all of an earlier change. See :hg:`help
3451 3451 dates` for a list of formats valid for -d/--date.
3452 3452
3453 3453 Revert modifies the working directory. It does not commit any
3454 3454 changes, or change the parent of the working directory. If you
3455 3455 revert to a revision other than the parent of the working
3456 3456 directory, the reverted files will thus appear modified
3457 3457 afterwards.
3458 3458
3459 3459 If a file has been deleted, it is restored. Files scheduled for
3460 3460 addition are just unscheduled and left as they are. If the
3461 3461 executable mode of a file was changed, it is reset.
3462 3462
3463 3463 If names are given, all files matching the names are reverted.
3464 3464 If no arguments are given, no files are reverted.
3465 3465
3466 3466 Modified files are saved with a .orig suffix before reverting.
3467 3467 To disable these backups, use --no-backup.
3468 3468
3469 3469 Returns 0 on success.
3470 3470 """
3471 3471
3472 3472 if opts.get("date"):
3473 3473 if opts.get("rev"):
3474 3474 raise util.Abort(_("you can't specify a revision and a date"))
3475 3475 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3476 3476
3477 3477 parent, p2 = repo.dirstate.parents()
3478 3478 if not opts.get('rev') and p2 != nullid:
3479 3479 raise util.Abort(_('uncommitted merge - '
3480 3480 'use "hg update", see "hg help revert"'))
3481 3481
3482 3482 if not pats and not opts.get('all'):
3483 3483 raise util.Abort(_('no files or directories specified; '
3484 3484 'use --all to revert the whole repo'))
3485 3485
3486 3486 ctx = cmdutil.revsingle(repo, opts.get('rev'))
3487 3487 node = ctx.node()
3488 3488 mf = ctx.manifest()
3489 3489 if node == parent:
3490 3490 pmf = mf
3491 3491 else:
3492 3492 pmf = None
3493 3493
3494 3494 # need all matching names in dirstate and manifest of target rev,
3495 3495 # so have to walk both. do not print errors if files exist in one
3496 3496 # but not other.
3497 3497
3498 3498 names = {}
3499 3499
3500 3500 wlock = repo.wlock()
3501 3501 try:
3502 3502 # walk dirstate.
3503 3503
3504 3504 m = cmdutil.match(repo, pats, opts)
3505 3505 m.bad = lambda x, y: False
3506 3506 for abs in repo.walk(m):
3507 3507 names[abs] = m.rel(abs), m.exact(abs)
3508 3508
3509 3509 # walk target manifest.
3510 3510
3511 3511 def badfn(path, msg):
3512 3512 if path in names:
3513 3513 return
3514 3514 path_ = path + '/'
3515 3515 for f in names:
3516 3516 if f.startswith(path_):
3517 3517 return
3518 3518 ui.warn("%s: %s\n" % (m.rel(path), msg))
3519 3519
3520 3520 m = cmdutil.match(repo, pats, opts)
3521 3521 m.bad = badfn
3522 3522 for abs in repo[node].walk(m):
3523 3523 if abs not in names:
3524 3524 names[abs] = m.rel(abs), m.exact(abs)
3525 3525
3526 3526 m = cmdutil.matchfiles(repo, names)
3527 3527 changes = repo.status(match=m)[:4]
3528 3528 modified, added, removed, deleted = map(set, changes)
3529 3529
3530 3530 # if f is a rename, also revert the source
3531 3531 cwd = repo.getcwd()
3532 3532 for f in added:
3533 3533 src = repo.dirstate.copied(f)
3534 3534 if src and src not in names and repo.dirstate[src] == 'r':
3535 3535 removed.add(src)
3536 3536 names[src] = (repo.pathto(src, cwd), True)
3537 3537
3538 3538 def removeforget(abs):
3539 3539 if repo.dirstate[abs] == 'a':
3540 3540 return _('forgetting %s\n')
3541 3541 return _('removing %s\n')
3542 3542
3543 3543 revert = ([], _('reverting %s\n'))
3544 3544 add = ([], _('adding %s\n'))
3545 3545 remove = ([], removeforget)
3546 3546 undelete = ([], _('undeleting %s\n'))
3547 3547
3548 3548 disptable = (
3549 3549 # dispatch table:
3550 3550 # file state
3551 3551 # action if in target manifest
3552 3552 # action if not in target manifest
3553 3553 # make backup if in target manifest
3554 3554 # make backup if not in target manifest
3555 3555 (modified, revert, remove, True, True),
3556 3556 (added, revert, remove, True, False),
3557 3557 (removed, undelete, None, False, False),
3558 3558 (deleted, revert, remove, False, False),
3559 3559 )
3560 3560
3561 3561 for abs, (rel, exact) in sorted(names.items()):
3562 3562 mfentry = mf.get(abs)
3563 3563 target = repo.wjoin(abs)
3564 3564 def handle(xlist, dobackup):
3565 3565 xlist[0].append(abs)
3566 3566 if (dobackup and not opts.get('no_backup') and
3567 3567 os.path.lexists(target)):
3568 3568 bakname = "%s.orig" % rel
3569 3569 ui.note(_('saving current version of %s as %s\n') %
3570 3570 (rel, bakname))
3571 3571 if not opts.get('dry_run'):
3572 3572 util.rename(target, bakname)
3573 3573 if ui.verbose or not exact:
3574 3574 msg = xlist[1]
3575 3575 if not isinstance(msg, basestring):
3576 3576 msg = msg(abs)
3577 3577 ui.status(msg % rel)
3578 3578 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3579 3579 if abs not in table:
3580 3580 continue
3581 3581 # file has changed in dirstate
3582 3582 if mfentry:
3583 3583 handle(hitlist, backuphit)
3584 3584 elif misslist is not None:
3585 3585 handle(misslist, backupmiss)
3586 3586 break
3587 3587 else:
3588 3588 if abs not in repo.dirstate:
3589 3589 if mfentry:
3590 3590 handle(add, True)
3591 3591 elif exact:
3592 3592 ui.warn(_('file not managed: %s\n') % rel)
3593 3593 continue
3594 3594 # file has not changed in dirstate
3595 3595 if node == parent:
3596 3596 if exact:
3597 3597 ui.warn(_('no changes needed to %s\n') % rel)
3598 3598 continue
3599 3599 if pmf is None:
3600 3600 # only need parent manifest in this unlikely case,
3601 3601 # so do not read by default
3602 3602 pmf = repo[parent].manifest()
3603 3603 if abs in pmf:
3604 3604 if mfentry:
3605 3605 # if version of file is same in parent and target
3606 3606 # manifests, do nothing
3607 3607 if (pmf[abs] != mfentry or
3608 3608 pmf.flags(abs) != mf.flags(abs)):
3609 3609 handle(revert, False)
3610 3610 else:
3611 3611 handle(remove, False)
3612 3612
3613 3613 if not opts.get('dry_run'):
3614 3614 def checkout(f):
3615 3615 fc = ctx[f]
3616 3616 repo.wwrite(f, fc.data(), fc.flags())
3617 3617
3618 3618 audit_path = scmutil.path_auditor(repo.root)
3619 3619 for f in remove[0]:
3620 3620 if repo.dirstate[f] == 'a':
3621 3621 repo.dirstate.forget(f)
3622 3622 continue
3623 3623 audit_path(f)
3624 3624 try:
3625 3625 util.unlinkpath(repo.wjoin(f))
3626 3626 except OSError:
3627 3627 pass
3628 3628 repo.dirstate.remove(f)
3629 3629
3630 3630 normal = None
3631 3631 if node == parent:
3632 3632 # We're reverting to our parent. If possible, we'd like status
3633 3633 # to report the file as clean. We have to use normallookup for
3634 3634 # merges to avoid losing information about merged/dirty files.
3635 3635 if p2 != nullid:
3636 3636 normal = repo.dirstate.normallookup
3637 3637 else:
3638 3638 normal = repo.dirstate.normal
3639 3639 for f in revert[0]:
3640 3640 checkout(f)
3641 3641 if normal:
3642 3642 normal(f)
3643 3643
3644 3644 for f in add[0]:
3645 3645 checkout(f)
3646 3646 repo.dirstate.add(f)
3647 3647
3648 3648 normal = repo.dirstate.normallookup
3649 3649 if node == parent and p2 == nullid:
3650 3650 normal = repo.dirstate.normal
3651 3651 for f in undelete[0]:
3652 3652 checkout(f)
3653 3653 normal(f)
3654 3654
3655 3655 finally:
3656 3656 wlock.release()
3657 3657
3658 3658 def rollback(ui, repo, **opts):
3659 3659 """roll back the last transaction (dangerous)
3660 3660
3661 3661 This command should be used with care. There is only one level of
3662 3662 rollback, and there is no way to undo a rollback. It will also
3663 3663 restore the dirstate at the time of the last transaction, losing
3664 3664 any dirstate changes since that time. This command does not alter
3665 3665 the working directory.
3666 3666
3667 3667 Transactions are used to encapsulate the effects of all commands
3668 3668 that create new changesets or propagate existing changesets into a
3669 3669 repository. For example, the following commands are transactional,
3670 3670 and their effects can be rolled back:
3671 3671
3672 3672 - commit
3673 3673 - import
3674 3674 - pull
3675 3675 - push (with this repository as the destination)
3676 3676 - unbundle
3677 3677
3678 3678 This command is not intended for use on public repositories. Once
3679 3679 changes are visible for pull by other users, rolling a transaction
3680 3680 back locally is ineffective (someone else may already have pulled
3681 3681 the changes). Furthermore, a race is possible with readers of the
3682 3682 repository; for example an in-progress pull from the repository
3683 3683 may fail if a rollback is performed.
3684 3684
3685 3685 Returns 0 on success, 1 if no rollback data is available.
3686 3686 """
3687 3687 return repo.rollback(opts.get('dry_run'))
3688 3688
3689 3689 def root(ui, repo):
3690 3690 """print the root (top) of the current working directory
3691 3691
3692 3692 Print the root directory of the current repository.
3693 3693
3694 3694 Returns 0 on success.
3695 3695 """
3696 3696 ui.write(repo.root + "\n")
3697 3697
3698 3698 def serve(ui, repo, **opts):
3699 3699 """start stand-alone webserver
3700 3700
3701 3701 Start a local HTTP repository browser and pull server. You can use
3702 3702 this for ad-hoc sharing and browsing of repositories. It is
3703 3703 recommended to use a real web server to serve a repository for
3704 3704 longer periods of time.
3705 3705
3706 3706 Please note that the server does not implement access control.
3707 3707 This means that, by default, anybody can read from the server and
3708 3708 nobody can write to it by default. Set the ``web.allow_push``
3709 3709 option to ``*`` to allow everybody to push to the server. You
3710 3710 should use a real web server if you need to authenticate users.
3711 3711
3712 3712 By default, the server logs accesses to stdout and errors to
3713 3713 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3714 3714 files.
3715 3715
3716 3716 To have the server choose a free port number to listen on, specify
3717 3717 a port number of 0; in this case, the server will print the port
3718 3718 number it uses.
3719 3719
3720 3720 Returns 0 on success.
3721 3721 """
3722 3722
3723 3723 if opts["stdio"]:
3724 3724 if repo is None:
3725 3725 raise error.RepoError(_("There is no Mercurial repository here"
3726 3726 " (.hg not found)"))
3727 3727 s = sshserver.sshserver(ui, repo)
3728 3728 s.serve_forever()
3729 3729
3730 3730 # this way we can check if something was given in the command-line
3731 3731 if opts.get('port'):
3732 3732 opts['port'] = util.getport(opts.get('port'))
3733 3733
3734 3734 baseui = repo and repo.baseui or ui
3735 3735 optlist = ("name templates style address port prefix ipv6"
3736 3736 " accesslog errorlog certificate encoding")
3737 3737 for o in optlist.split():
3738 3738 val = opts.get(o, '')
3739 3739 if val in (None, ''): # should check against default options instead
3740 3740 continue
3741 3741 baseui.setconfig("web", o, val)
3742 3742 if repo and repo.ui != baseui:
3743 3743 repo.ui.setconfig("web", o, val)
3744 3744
3745 3745 o = opts.get('web_conf') or opts.get('webdir_conf')
3746 3746 if not o:
3747 3747 if not repo:
3748 3748 raise error.RepoError(_("There is no Mercurial repository"
3749 3749 " here (.hg not found)"))
3750 3750 o = repo.root
3751 3751
3752 3752 app = hgweb.hgweb(o, baseui=ui)
3753 3753
3754 3754 class service(object):
3755 3755 def init(self):
3756 3756 util.set_signal_handler()
3757 3757 self.httpd = hgweb.server.create_server(ui, app)
3758 3758
3759 3759 if opts['port'] and not ui.verbose:
3760 3760 return
3761 3761
3762 3762 if self.httpd.prefix:
3763 3763 prefix = self.httpd.prefix.strip('/') + '/'
3764 3764 else:
3765 3765 prefix = ''
3766 3766
3767 3767 port = ':%d' % self.httpd.port
3768 3768 if port == ':80':
3769 3769 port = ''
3770 3770
3771 3771 bindaddr = self.httpd.addr
3772 3772 if bindaddr == '0.0.0.0':
3773 3773 bindaddr = '*'
3774 3774 elif ':' in bindaddr: # IPv6
3775 3775 bindaddr = '[%s]' % bindaddr
3776 3776
3777 3777 fqaddr = self.httpd.fqaddr
3778 3778 if ':' in fqaddr:
3779 3779 fqaddr = '[%s]' % fqaddr
3780 3780 if opts['port']:
3781 3781 write = ui.status
3782 3782 else:
3783 3783 write = ui.write
3784 3784 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3785 3785 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3786 3786
3787 3787 def run(self):
3788 3788 self.httpd.serve_forever()
3789 3789
3790 3790 service = service()
3791 3791
3792 3792 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3793 3793
3794 3794 def status(ui, repo, *pats, **opts):
3795 3795 """show changed files in the working directory
3796 3796
3797 3797 Show status of files in the repository. If names are given, only
3798 3798 files that match are shown. Files that are clean or ignored or
3799 3799 the source of a copy/move operation, are not listed unless
3800 3800 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3801 3801 Unless options described with "show only ..." are given, the
3802 3802 options -mardu are used.
3803 3803
3804 3804 Option -q/--quiet hides untracked (unknown and ignored) files
3805 3805 unless explicitly requested with -u/--unknown or -i/--ignored.
3806 3806
3807 3807 .. note::
3808 3808 status may appear to disagree with diff if permissions have
3809 3809 changed or a merge has occurred. The standard diff format does
3810 3810 not report permission changes and diff only reports changes
3811 3811 relative to one merge parent.
3812 3812
3813 3813 If one revision is given, it is used as the base revision.
3814 3814 If two revisions are given, the differences between them are
3815 3815 shown. The --change option can also be used as a shortcut to list
3816 3816 the changed files of a revision from its first parent.
3817 3817
3818 3818 The codes used to show the status of files are::
3819 3819
3820 3820 M = modified
3821 3821 A = added
3822 3822 R = removed
3823 3823 C = clean
3824 3824 ! = missing (deleted by non-hg command, but still tracked)
3825 3825 ? = not tracked
3826 3826 I = ignored
3827 3827 = origin of the previous file listed as A (added)
3828 3828
3829 3829 Returns 0 on success.
3830 3830 """
3831 3831
3832 3832 revs = opts.get('rev')
3833 3833 change = opts.get('change')
3834 3834
3835 3835 if revs and change:
3836 3836 msg = _('cannot specify --rev and --change at the same time')
3837 3837 raise util.Abort(msg)
3838 3838 elif change:
3839 3839 node2 = repo.lookup(change)
3840 3840 node1 = repo[node2].p1().node()
3841 3841 else:
3842 3842 node1, node2 = cmdutil.revpair(repo, revs)
3843 3843
3844 3844 cwd = (pats and repo.getcwd()) or ''
3845 3845 end = opts.get('print0') and '\0' or '\n'
3846 3846 copy = {}
3847 3847 states = 'modified added removed deleted unknown ignored clean'.split()
3848 3848 show = [k for k in states if opts.get(k)]
3849 3849 if opts.get('all'):
3850 3850 show += ui.quiet and (states[:4] + ['clean']) or states
3851 3851 if not show:
3852 3852 show = ui.quiet and states[:4] or states[:5]
3853 3853
3854 3854 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3855 3855 'ignored' in show, 'clean' in show, 'unknown' in show,
3856 3856 opts.get('subrepos'))
3857 3857 changestates = zip(states, 'MAR!?IC', stat)
3858 3858
3859 3859 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3860 3860 ctxn = repo[nullid]
3861 3861 ctx1 = repo[node1]
3862 3862 ctx2 = repo[node2]
3863 3863 added = stat[1]
3864 3864 if node2 is None:
3865 3865 added = stat[0] + stat[1] # merged?
3866 3866
3867 3867 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3868 3868 if k in added:
3869 3869 copy[k] = v
3870 3870 elif v in added:
3871 3871 copy[v] = k
3872 3872
3873 3873 for state, char, files in changestates:
3874 3874 if state in show:
3875 3875 format = "%s %%s%s" % (char, end)
3876 3876 if opts.get('no_status'):
3877 3877 format = "%%s%s" % end
3878 3878
3879 3879 for f in files:
3880 3880 ui.write(format % repo.pathto(f, cwd),
3881 3881 label='status.' + state)
3882 3882 if f in copy:
3883 3883 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3884 3884 label='status.copied')
3885 3885
3886 3886 def summary(ui, repo, **opts):
3887 3887 """summarize working directory state
3888 3888
3889 3889 This generates a brief summary of the working directory state,
3890 3890 including parents, branch, commit status, and available updates.
3891 3891
3892 3892 With the --remote option, this will check the default paths for
3893 3893 incoming and outgoing changes. This can be time-consuming.
3894 3894
3895 3895 Returns 0 on success.
3896 3896 """
3897 3897
3898 3898 ctx = repo[None]
3899 3899 parents = ctx.parents()
3900 3900 pnode = parents[0].node()
3901 3901
3902 3902 for p in parents:
3903 3903 # label with log.changeset (instead of log.parent) since this
3904 3904 # shows a working directory parent *changeset*:
3905 3905 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3906 3906 label='log.changeset')
3907 3907 ui.write(' '.join(p.tags()), label='log.tag')
3908 3908 if p.bookmarks():
3909 3909 ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark')
3910 3910 if p.rev() == -1:
3911 3911 if not len(repo):
3912 3912 ui.write(_(' (empty repository)'))
3913 3913 else:
3914 3914 ui.write(_(' (no revision checked out)'))
3915 3915 ui.write('\n')
3916 3916 if p.description():
3917 3917 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3918 3918 label='log.summary')
3919 3919
3920 3920 branch = ctx.branch()
3921 3921 bheads = repo.branchheads(branch)
3922 3922 m = _('branch: %s\n') % branch
3923 3923 if branch != 'default':
3924 3924 ui.write(m, label='log.branch')
3925 3925 else:
3926 3926 ui.status(m, label='log.branch')
3927 3927
3928 3928 st = list(repo.status(unknown=True))[:6]
3929 3929
3930 3930 c = repo.dirstate.copies()
3931 3931 copied, renamed = [], []
3932 3932 for d, s in c.iteritems():
3933 3933 if s in st[2]:
3934 3934 st[2].remove(s)
3935 3935 renamed.append(d)
3936 3936 else:
3937 3937 copied.append(d)
3938 3938 if d in st[1]:
3939 3939 st[1].remove(d)
3940 3940 st.insert(3, renamed)
3941 3941 st.insert(4, copied)
3942 3942
3943 3943 ms = mergemod.mergestate(repo)
3944 3944 st.append([f for f in ms if ms[f] == 'u'])
3945 3945
3946 3946 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3947 3947 st.append(subs)
3948 3948
3949 3949 labels = [ui.label(_('%d modified'), 'status.modified'),
3950 3950 ui.label(_('%d added'), 'status.added'),
3951 3951 ui.label(_('%d removed'), 'status.removed'),
3952 3952 ui.label(_('%d renamed'), 'status.copied'),
3953 3953 ui.label(_('%d copied'), 'status.copied'),
3954 3954 ui.label(_('%d deleted'), 'status.deleted'),
3955 3955 ui.label(_('%d unknown'), 'status.unknown'),
3956 3956 ui.label(_('%d ignored'), 'status.ignored'),
3957 3957 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3958 3958 ui.label(_('%d subrepos'), 'status.modified')]
3959 3959 t = []
3960 3960 for s, l in zip(st, labels):
3961 3961 if s:
3962 3962 t.append(l % len(s))
3963 3963
3964 3964 t = ', '.join(t)
3965 3965 cleanworkdir = False
3966 3966
3967 3967 if len(parents) > 1:
3968 3968 t += _(' (merge)')
3969 3969 elif branch != parents[0].branch():
3970 3970 t += _(' (new branch)')
3971 3971 elif (parents[0].extra().get('close') and
3972 3972 pnode in repo.branchheads(branch, closed=True)):
3973 3973 t += _(' (head closed)')
3974 3974 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3975 3975 t += _(' (clean)')
3976 3976 cleanworkdir = True
3977 3977 elif pnode not in bheads:
3978 3978 t += _(' (new branch head)')
3979 3979
3980 3980 if cleanworkdir:
3981 3981 ui.status(_('commit: %s\n') % t.strip())
3982 3982 else:
3983 3983 ui.write(_('commit: %s\n') % t.strip())
3984 3984
3985 3985 # all ancestors of branch heads - all ancestors of parent = new csets
3986 3986 new = [0] * len(repo)
3987 3987 cl = repo.changelog
3988 3988 for a in [cl.rev(n) for n in bheads]:
3989 3989 new[a] = 1
3990 3990 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3991 3991 new[a] = 1
3992 3992 for a in [p.rev() for p in parents]:
3993 3993 if a >= 0:
3994 3994 new[a] = 0
3995 3995 for a in cl.ancestors(*[p.rev() for p in parents]):
3996 3996 new[a] = 0
3997 3997 new = sum(new)
3998 3998
3999 3999 if new == 0:
4000 4000 ui.status(_('update: (current)\n'))
4001 4001 elif pnode not in bheads:
4002 4002 ui.write(_('update: %d new changesets (update)\n') % new)
4003 4003 else:
4004 4004 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4005 4005 (new, len(bheads)))
4006 4006
4007 4007 if opts.get('remote'):
4008 4008 t = []
4009 4009 source, branches = hg.parseurl(ui.expandpath('default'))
4010 4010 other = hg.repository(hg.remoteui(repo, {}), source)
4011 4011 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4012 4012 ui.debug('comparing with %s\n' % util.hidepassword(source))
4013 4013 repo.ui.pushbuffer()
4014 4014 common, incoming, rheads = discovery.findcommonincoming(repo, other)
4015 4015 repo.ui.popbuffer()
4016 4016 if incoming:
4017 4017 t.append(_('1 or more incoming'))
4018 4018
4019 4019 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
4020 4020 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
4021 4021 other = hg.repository(hg.remoteui(repo, {}), dest)
4022 4022 ui.debug('comparing with %s\n' % util.hidepassword(dest))
4023 4023 repo.ui.pushbuffer()
4024 4024 common, _anyinc, _heads = discovery.findcommonincoming(repo, other)
4025 4025 repo.ui.popbuffer()
4026 4026 o = repo.changelog.findmissing(common=common)
4027 4027 if o:
4028 4028 t.append(_('%d outgoing') % len(o))
4029 4029 if 'bookmarks' in other.listkeys('namespaces'):
4030 4030 lmarks = repo.listkeys('bookmarks')
4031 4031 rmarks = other.listkeys('bookmarks')
4032 4032 diff = set(rmarks) - set(lmarks)
4033 4033 if len(diff) > 0:
4034 4034 t.append(_('%d incoming bookmarks') % len(diff))
4035 4035 diff = set(lmarks) - set(rmarks)
4036 4036 if len(diff) > 0:
4037 4037 t.append(_('%d outgoing bookmarks') % len(diff))
4038 4038
4039 4039 if t:
4040 4040 ui.write(_('remote: %s\n') % (', '.join(t)))
4041 4041 else:
4042 4042 ui.status(_('remote: (synced)\n'))
4043 4043
4044 4044 def tag(ui, repo, name1, *names, **opts):
4045 4045 """add one or more tags for the current or given revision
4046 4046
4047 4047 Name a particular revision using <name>.
4048 4048
4049 4049 Tags are used to name particular revisions of the repository and are
4050 4050 very useful to compare different revisions, to go back to significant
4051 4051 earlier versions or to mark branch points as releases, etc. Changing
4052 4052 an existing tag is normally disallowed; use -f/--force to override.
4053 4053
4054 4054 If no revision is given, the parent of the working directory is
4055 4055 used, or tip if no revision is checked out.
4056 4056
4057 4057 To facilitate version control, distribution, and merging of tags,
4058 4058 they are stored as a file named ".hgtags" which is managed similarly
4059 4059 to other project files and can be hand-edited if necessary. This
4060 4060 also means that tagging creates a new commit. The file
4061 4061 ".hg/localtags" is used for local tags (not shared among
4062 4062 repositories).
4063 4063
4064 4064 Tag commits are usually made at the head of a branch. If the parent
4065 4065 of the working directory is not a branch head, :hg:`tag` aborts; use
4066 4066 -f/--force to force the tag commit to be based on a non-head
4067 4067 changeset.
4068 4068
4069 4069 See :hg:`help dates` for a list of formats valid for -d/--date.
4070 4070
4071 4071 Since tag names have priority over branch names during revision
4072 4072 lookup, using an existing branch name as a tag name is discouraged.
4073 4073
4074 4074 Returns 0 on success.
4075 4075 """
4076 4076
4077 4077 rev_ = "."
4078 4078 names = [t.strip() for t in (name1,) + names]
4079 4079 if len(names) != len(set(names)):
4080 4080 raise util.Abort(_('tag names must be unique'))
4081 4081 for n in names:
4082 4082 if n in ['tip', '.', 'null']:
4083 4083 raise util.Abort(_('the name \'%s\' is reserved') % n)
4084 4084 if not n:
4085 4085 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
4086 4086 if opts.get('rev') and opts.get('remove'):
4087 4087 raise util.Abort(_("--rev and --remove are incompatible"))
4088 4088 if opts.get('rev'):
4089 4089 rev_ = opts['rev']
4090 4090 message = opts.get('message')
4091 4091 if opts.get('remove'):
4092 4092 expectedtype = opts.get('local') and 'local' or 'global'
4093 4093 for n in names:
4094 4094 if not repo.tagtype(n):
4095 4095 raise util.Abort(_('tag \'%s\' does not exist') % n)
4096 4096 if repo.tagtype(n) != expectedtype:
4097 4097 if expectedtype == 'global':
4098 4098 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
4099 4099 else:
4100 4100 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
4101 4101 rev_ = nullid
4102 4102 if not message:
4103 4103 # we don't translate commit messages
4104 4104 message = 'Removed tag %s' % ', '.join(names)
4105 4105 elif not opts.get('force'):
4106 4106 for n in names:
4107 4107 if n in repo.tags():
4108 4108 raise util.Abort(_('tag \'%s\' already exists '
4109 4109 '(use -f to force)') % n)
4110 4110 if not opts.get('local'):
4111 4111 p1, p2 = repo.dirstate.parents()
4112 4112 if p2 != nullid:
4113 4113 raise util.Abort(_('uncommitted merge'))
4114 4114 bheads = repo.branchheads()
4115 4115 if not opts.get('force') and bheads and p1 not in bheads:
4116 4116 raise util.Abort(_('not at a branch head (use -f to force)'))
4117 4117 r = cmdutil.revsingle(repo, rev_).node()
4118 4118
4119 4119 if not message:
4120 4120 # we don't translate commit messages
4121 4121 message = ('Added tag %s for changeset %s' %
4122 4122 (', '.join(names), short(r)))
4123 4123
4124 4124 date = opts.get('date')
4125 4125 if date:
4126 4126 date = util.parsedate(date)
4127 4127
4128 4128 if opts.get('edit'):
4129 4129 message = ui.edit(message, ui.username())
4130 4130
4131 4131 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
4132 4132
4133 4133 def tags(ui, repo):
4134 4134 """list repository tags
4135 4135
4136 4136 This lists both regular and local tags. When the -v/--verbose
4137 4137 switch is used, a third column "local" is printed for local tags.
4138 4138
4139 4139 Returns 0 on success.
4140 4140 """
4141 4141
4142 4142 hexfunc = ui.debugflag and hex or short
4143 4143 tagtype = ""
4144 4144
4145 4145 for t, n in reversed(repo.tagslist()):
4146 4146 if ui.quiet:
4147 4147 ui.write("%s\n" % t)
4148 4148 continue
4149 4149
4150 4150 hn = hexfunc(n)
4151 4151 r = "%5d:%s" % (repo.changelog.rev(n), hn)
4152 4152 spaces = " " * (30 - encoding.colwidth(t))
4153 4153
4154 4154 if ui.verbose:
4155 4155 if repo.tagtype(t) == 'local':
4156 4156 tagtype = " local"
4157 4157 else:
4158 4158 tagtype = ""
4159 4159 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
4160 4160
4161 4161 def tip(ui, repo, **opts):
4162 4162 """show the tip revision
4163 4163
4164 4164 The tip revision (usually just called the tip) is the changeset
4165 4165 most recently added to the repository (and therefore the most
4166 4166 recently changed head).
4167 4167
4168 4168 If you have just made a commit, that commit will be the tip. If
4169 4169 you have just pulled changes from another repository, the tip of
4170 4170 that repository becomes the current tip. The "tip" tag is special
4171 4171 and cannot be renamed or assigned to a different changeset.
4172 4172
4173 4173 Returns 0 on success.
4174 4174 """
4175 4175 displayer = cmdutil.show_changeset(ui, repo, opts)
4176 4176 displayer.show(repo[len(repo) - 1])
4177 4177 displayer.close()
4178 4178
4179 4179 def unbundle(ui, repo, fname1, *fnames, **opts):
4180 4180 """apply one or more changegroup files
4181 4181
4182 4182 Apply one or more compressed changegroup files generated by the
4183 4183 bundle command.
4184 4184
4185 4185 Returns 0 on success, 1 if an update has unresolved files.
4186 4186 """
4187 4187 fnames = (fname1,) + fnames
4188 4188
4189 4189 lock = repo.lock()
4190 4190 wc = repo['.']
4191 4191 try:
4192 4192 for fname in fnames:
4193 4193 f = url.open(ui, fname)
4194 4194 gen = changegroup.readbundle(f, fname)
4195 4195 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
4196 4196 lock=lock)
4197 4197 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
4198 4198 finally:
4199 4199 lock.release()
4200 4200 return postincoming(ui, repo, modheads, opts.get('update'), None)
4201 4201
4202 4202 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
4203 4203 """update working directory (or switch revisions)
4204 4204
4205 4205 Update the repository's working directory to the specified
4206 4206 changeset. If no changeset is specified, update to the tip of the
4207 4207 current named branch.
4208 4208
4209 4209 If the changeset is not a descendant of the working directory's
4210 4210 parent, the update is aborted. With the -c/--check option, the
4211 4211 working directory is checked for uncommitted changes; if none are
4212 4212 found, the working directory is updated to the specified
4213 4213 changeset.
4214 4214
4215 4215 The following rules apply when the working directory contains
4216 4216 uncommitted changes:
4217 4217
4218 4218 1. If neither -c/--check nor -C/--clean is specified, and if
4219 4219 the requested changeset is an ancestor or descendant of
4220 4220 the working directory's parent, the uncommitted changes
4221 4221 are merged into the requested changeset and the merged
4222 4222 result is left uncommitted. If the requested changeset is
4223 4223 not an ancestor or descendant (that is, it is on another
4224 4224 branch), the update is aborted and the uncommitted changes
4225 4225 are preserved.
4226 4226
4227 4227 2. With the -c/--check option, the update is aborted and the
4228 4228 uncommitted changes are preserved.
4229 4229
4230 4230 3. With the -C/--clean option, uncommitted changes are discarded and
4231 4231 the working directory is updated to the requested changeset.
4232 4232
4233 4233 Use null as the changeset to remove the working directory (like
4234 4234 :hg:`clone -U`).
4235 4235
4236 4236 If you want to update just one file to an older changeset, use
4237 4237 :hg:`revert`.
4238 4238
4239 4239 See :hg:`help dates` for a list of formats valid for -d/--date.
4240 4240
4241 4241 Returns 0 on success, 1 if there are unresolved files.
4242 4242 """
4243 4243 if rev and node:
4244 4244 raise util.Abort(_("please specify just one revision"))
4245 4245
4246 4246 if rev is None or rev == '':
4247 4247 rev = node
4248 4248
4249 4249 # if we defined a bookmark, we have to remember the original bookmark name
4250 4250 brev = rev
4251 4251 rev = cmdutil.revsingle(repo, rev, rev).rev()
4252 4252
4253 4253 if check and clean:
4254 4254 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4255 4255
4256 4256 if check:
4257 4257 # we could use dirty() but we can ignore merge and branch trivia
4258 4258 c = repo[None]
4259 4259 if c.modified() or c.added() or c.removed():
4260 4260 raise util.Abort(_("uncommitted local changes"))
4261 4261
4262 4262 if date:
4263 4263 if rev is not None:
4264 4264 raise util.Abort(_("you can't specify a revision and a date"))
4265 4265 rev = cmdutil.finddate(ui, repo, date)
4266 4266
4267 4267 if clean or check:
4268 4268 ret = hg.clean(repo, rev)
4269 4269 else:
4270 4270 ret = hg.update(repo, rev)
4271 4271
4272 4272 if brev in repo._bookmarks:
4273 4273 bookmarks.setcurrent(repo, brev)
4274 4274
4275 4275 return ret
4276 4276
4277 4277 def verify(ui, repo):
4278 4278 """verify the integrity of the repository
4279 4279
4280 4280 Verify the integrity of the current repository.
4281 4281
4282 4282 This will perform an extensive check of the repository's
4283 4283 integrity, validating the hashes and checksums of each entry in
4284 4284 the changelog, manifest, and tracked files, as well as the
4285 4285 integrity of their crosslinks and indices.
4286 4286
4287 4287 Returns 0 on success, 1 if errors are encountered.
4288 4288 """
4289 4289 return hg.verify(repo)
4290 4290
4291 4291 def version_(ui):
4292 4292 """output version and copyright information"""
4293 4293 ui.write(_("Mercurial Distributed SCM (version %s)\n")
4294 4294 % util.version())
4295 4295 ui.status(_(
4296 4296 "(see http://mercurial.selenic.com for more information)\n"
4297 4297 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
4298 4298 "This is free software; see the source for copying conditions. "
4299 4299 "There is NO\nwarranty; "
4300 4300 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
4301 4301 ))
4302 4302
4303 4303 # Command options and aliases are listed here, alphabetically
4304 4304
4305 4305 globalopts = [
4306 4306 ('R', 'repository', '',
4307 4307 _('repository root directory or name of overlay bundle file'),
4308 4308 _('REPO')),
4309 4309 ('', 'cwd', '',
4310 4310 _('change working directory'), _('DIR')),
4311 4311 ('y', 'noninteractive', None,
4312 4312 _('do not prompt, assume \'yes\' for any required answers')),
4313 4313 ('q', 'quiet', None, _('suppress output')),
4314 4314 ('v', 'verbose', None, _('enable additional output')),
4315 4315 ('', 'config', [],
4316 4316 _('set/override config option (use \'section.name=value\')'),
4317 4317 _('CONFIG')),
4318 4318 ('', 'debug', None, _('enable debugging output')),
4319 4319 ('', 'debugger', None, _('start debugger')),
4320 4320 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
4321 4321 _('ENCODE')),
4322 4322 ('', 'encodingmode', encoding.encodingmode,
4323 4323 _('set the charset encoding mode'), _('MODE')),
4324 4324 ('', 'traceback', None, _('always print a traceback on exception')),
4325 4325 ('', 'time', None, _('time how long the command takes')),
4326 4326 ('', 'profile', None, _('print command execution profile')),
4327 4327 ('', 'version', None, _('output version information and exit')),
4328 4328 ('h', 'help', None, _('display help and exit')),
4329 4329 ]
4330 4330
4331 4331 dryrunopts = [('n', 'dry-run', None,
4332 4332 _('do not perform actions, just print output'))]
4333 4333
4334 4334 remoteopts = [
4335 4335 ('e', 'ssh', '',
4336 4336 _('specify ssh command to use'), _('CMD')),
4337 4337 ('', 'remotecmd', '',
4338 4338 _('specify hg command to run on the remote side'), _('CMD')),
4339 4339 ('', 'insecure', None,
4340 4340 _('do not verify server certificate (ignoring web.cacerts config)')),
4341 4341 ]
4342 4342
4343 4343 walkopts = [
4344 4344 ('I', 'include', [],
4345 4345 _('include names matching the given patterns'), _('PATTERN')),
4346 4346 ('X', 'exclude', [],
4347 4347 _('exclude names matching the given patterns'), _('PATTERN')),
4348 4348 ]
4349 4349
4350 4350 commitopts = [
4351 4351 ('m', 'message', '',
4352 4352 _('use text as commit message'), _('TEXT')),
4353 4353 ('l', 'logfile', '',
4354 4354 _('read commit message from file'), _('FILE')),
4355 4355 ]
4356 4356
4357 4357 commitopts2 = [
4358 4358 ('d', 'date', '',
4359 4359 _('record the specified date as commit date'), _('DATE')),
4360 4360 ('u', 'user', '',
4361 4361 _('record the specified user as committer'), _('USER')),
4362 4362 ]
4363 4363
4364 4364 templateopts = [
4365 4365 ('', 'style', '',
4366 4366 _('display using template map file'), _('STYLE')),
4367 4367 ('', 'template', '',
4368 4368 _('display with template'), _('TEMPLATE')),
4369 4369 ]
4370 4370
4371 4371 logopts = [
4372 4372 ('p', 'patch', None, _('show patch')),
4373 4373 ('g', 'git', None, _('use git extended diff format')),
4374 4374 ('l', 'limit', '',
4375 4375 _('limit number of changes displayed'), _('NUM')),
4376 4376 ('M', 'no-merges', None, _('do not show merges')),
4377 4377 ('', 'stat', None, _('output diffstat-style summary of changes')),
4378 4378 ] + templateopts
4379 4379
4380 4380 diffopts = [
4381 4381 ('a', 'text', None, _('treat all files as text')),
4382 4382 ('g', 'git', None, _('use git extended diff format')),
4383 4383 ('', 'nodates', None, _('omit dates from diff headers'))
4384 4384 ]
4385 4385
4386 4386 diffopts2 = [
4387 4387 ('p', 'show-function', None, _('show which function each change is in')),
4388 4388 ('', 'reverse', None, _('produce a diff that undoes the changes')),
4389 4389 ('w', 'ignore-all-space', None,
4390 4390 _('ignore white space when comparing lines')),
4391 4391 ('b', 'ignore-space-change', None,
4392 4392 _('ignore changes in the amount of white space')),
4393 4393 ('B', 'ignore-blank-lines', None,
4394 4394 _('ignore changes whose lines are all blank')),
4395 4395 ('U', 'unified', '',
4396 4396 _('number of lines of context to show'), _('NUM')),
4397 4397 ('', 'stat', None, _('output diffstat-style summary of changes')),
4398 4398 ]
4399 4399
4400 4400 similarityopts = [
4401 4401 ('s', 'similarity', '',
4402 4402 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4403 4403 ]
4404 4404
4405 4405 subrepoopts = [
4406 4406 ('S', 'subrepos', None,
4407 4407 _('recurse into subrepositories'))
4408 4408 ]
4409 4409
4410 4410 table = {
4411 4411 "^add": (add, walkopts + subrepoopts + dryrunopts,
4412 4412 _('[OPTION]... [FILE]...')),
4413 4413 "addremove":
4414 4414 (addremove, similarityopts + walkopts + dryrunopts,
4415 4415 _('[OPTION]... [FILE]...')),
4416 4416 "^annotate|blame":
4417 4417 (annotate,
4418 4418 [('r', 'rev', '',
4419 4419 _('annotate the specified revision'), _('REV')),
4420 4420 ('', 'follow', None,
4421 4421 _('follow copies/renames and list the filename (DEPRECATED)')),
4422 4422 ('', 'no-follow', None, _("don't follow copies and renames")),
4423 4423 ('a', 'text', None, _('treat all files as text')),
4424 4424 ('u', 'user', None, _('list the author (long with -v)')),
4425 4425 ('f', 'file', None, _('list the filename')),
4426 4426 ('d', 'date', None, _('list the date (short with -q)')),
4427 4427 ('n', 'number', None, _('list the revision number (default)')),
4428 4428 ('c', 'changeset', None, _('list the changeset')),
4429 4429 ('l', 'line-number', None,
4430 4430 _('show line number at the first appearance'))
4431 4431 ] + walkopts,
4432 4432 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4433 4433 "archive":
4434 4434 (archive,
4435 4435 [('', 'no-decode', None, _('do not pass files through decoders')),
4436 4436 ('p', 'prefix', '',
4437 4437 _('directory prefix for files in archive'), _('PREFIX')),
4438 4438 ('r', 'rev', '',
4439 4439 _('revision to distribute'), _('REV')),
4440 4440 ('t', 'type', '',
4441 4441 _('type of distribution to create'), _('TYPE')),
4442 4442 ] + subrepoopts + walkopts,
4443 4443 _('[OPTION]... DEST')),
4444 4444 "backout":
4445 4445 (backout,
4446 4446 [('', 'merge', None,
4447 4447 _('merge with old dirstate parent after backout')),
4448 4448 ('', 'parent', '',
4449 4449 _('parent to choose when backing out merge'), _('REV')),
4450 4450 ('t', 'tool', '',
4451 4451 _('specify merge tool')),
4452 4452 ('r', 'rev', '',
4453 4453 _('revision to backout'), _('REV')),
4454 4454 ] + walkopts + commitopts + commitopts2,
4455 4455 _('[OPTION]... [-r] REV')),
4456 4456 "bisect":
4457 4457 (bisect,
4458 4458 [('r', 'reset', False, _('reset bisect state')),
4459 4459 ('g', 'good', False, _('mark changeset good')),
4460 4460 ('b', 'bad', False, _('mark changeset bad')),
4461 4461 ('s', 'skip', False, _('skip testing changeset')),
4462 4462 ('e', 'extend', False, _('extend the bisect range')),
4463 4463 ('c', 'command', '',
4464 4464 _('use command to check changeset state'), _('CMD')),
4465 4465 ('U', 'noupdate', False, _('do not update to target'))],
4466 4466 _("[-gbsr] [-U] [-c CMD] [REV]")),
4467 4467 "bookmarks":
4468 4468 (bookmark,
4469 4469 [('f', 'force', False, _('force')),
4470 4470 ('r', 'rev', '', _('revision'), _('REV')),
4471 4471 ('d', 'delete', False, _('delete a given bookmark')),
4472 4472 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
4473 4473 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
4474 4474 "branch":
4475 4475 (branch,
4476 4476 [('f', 'force', None,
4477 4477 _('set branch name even if it shadows an existing branch')),
4478 4478 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4479 4479 _('[-fC] [NAME]')),
4480 4480 "branches":
4481 4481 (branches,
4482 4482 [('a', 'active', False,
4483 4483 _('show only branches that have unmerged heads')),
4484 4484 ('c', 'closed', False,
4485 4485 _('show normal and closed branches'))],
4486 4486 _('[-ac]')),
4487 4487 "bundle":
4488 4488 (bundle,
4489 4489 [('f', 'force', None,
4490 4490 _('run even when the destination is unrelated')),
4491 4491 ('r', 'rev', [],
4492 4492 _('a changeset intended to be added to the destination'),
4493 4493 _('REV')),
4494 4494 ('b', 'branch', [],
4495 4495 _('a specific branch you would like to bundle'),
4496 4496 _('BRANCH')),
4497 4497 ('', 'base', [],
4498 4498 _('a base changeset assumed to be available at the destination'),
4499 4499 _('REV')),
4500 4500 ('a', 'all', None, _('bundle all changesets in the repository')),
4501 4501 ('t', 'type', 'bzip2',
4502 4502 _('bundle compression type to use'), _('TYPE')),
4503 4503 ] + remoteopts,
4504 4504 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4505 4505 "cat":
4506 4506 (cat,
4507 4507 [('o', 'output', '',
4508 4508 _('print output to file with formatted name'), _('FORMAT')),
4509 4509 ('r', 'rev', '',
4510 4510 _('print the given revision'), _('REV')),
4511 4511 ('', 'decode', None, _('apply any matching decode filter')),
4512 4512 ] + walkopts,
4513 4513 _('[OPTION]... FILE...')),
4514 4514 "^clone":
4515 4515 (clone,
4516 4516 [('U', 'noupdate', None,
4517 4517 _('the clone will include an empty working copy (only a repository)')),
4518 4518 ('u', 'updaterev', '',
4519 4519 _('revision, tag or branch to check out'), _('REV')),
4520 4520 ('r', 'rev', [],
4521 4521 _('include the specified changeset'), _('REV')),
4522 4522 ('b', 'branch', [],
4523 4523 _('clone only the specified branch'), _('BRANCH')),
4524 4524 ('', 'pull', None, _('use pull protocol to copy metadata')),
4525 4525 ('', 'uncompressed', None,
4526 4526 _('use uncompressed transfer (fast over LAN)')),
4527 4527 ] + remoteopts,
4528 4528 _('[OPTION]... SOURCE [DEST]')),
4529 4529 "^commit|ci":
4530 4530 (commit,
4531 4531 [('A', 'addremove', None,
4532 4532 _('mark new/missing files as added/removed before committing')),
4533 4533 ('', 'close-branch', None,
4534 4534 _('mark a branch as closed, hiding it from the branch list')),
4535 4535 ] + walkopts + commitopts + commitopts2,
4536 4536 _('[OPTION]... [FILE]...')),
4537 4537 "copy|cp":
4538 4538 (copy,
4539 4539 [('A', 'after', None, _('record a copy that has already occurred')),
4540 4540 ('f', 'force', None,
4541 4541 _('forcibly copy over an existing managed file')),
4542 4542 ] + walkopts + dryrunopts,
4543 4543 _('[OPTION]... [SOURCE]... DEST')),
4544 4544 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4545 4545 "debugbuilddag":
4546 4546 (debugbuilddag,
4547 4547 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4548 4548 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4549 4549 ('n', 'new-file', None, _('add new file at each rev')),
4550 4550 ],
4551 4551 _('[OPTION]... TEXT')),
4552 4552 "debugbundle":
4553 4553 (debugbundle,
4554 4554 [('a', 'all', None, _('show all details')),
4555 4555 ],
4556 4556 _('FILE')),
4557 4557 "debugcheckstate": (debugcheckstate, [], ''),
4558 4558 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4559 4559 "debugcomplete":
4560 4560 (debugcomplete,
4561 4561 [('o', 'options', None, _('show the command options'))],
4562 4562 _('[-o] CMD')),
4563 4563 "debugdag":
4564 4564 (debugdag,
4565 4565 [('t', 'tags', None, _('use tags as labels')),
4566 4566 ('b', 'branches', None, _('annotate with branch names')),
4567 4567 ('', 'dots', None, _('use dots for runs')),
4568 4568 ('s', 'spaces', None, _('separate elements by spaces')),
4569 4569 ],
4570 4570 _('[OPTION]... [FILE [REV]...]')),
4571 4571 "debugdate":
4572 4572 (debugdate,
4573 4573 [('e', 'extended', None, _('try extended date formats'))],
4574 4574 _('[-e] DATE [RANGE]')),
4575 4575 "debugdata": (debugdata, [], _('FILE REV')),
4576 4576 "debugdiscovery": (debugdiscovery,
4577 4577 [('', 'old', None,
4578 4578 _('use old-style discovery')),
4579 4579 ('', 'nonheads', None,
4580 4580 _('use old-style discovery with non-heads included')),
4581 4581 ] + remoteopts,
4582 4582 _('[-l REV] [-r REV] [-b BRANCH]...'
4583 4583 ' [OTHER]')),
4584 4584 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4585 4585 "debuggetbundle":
4586 4586 (debuggetbundle,
4587 4587 [('H', 'head', [], _('id of head node'), _('ID')),
4588 4588 ('C', 'common', [], _('id of common node'), _('ID')),
4589 4589 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
4590 4590 ],
4591 4591 _('REPO FILE [-H|-C ID]...')),
4592 4592 "debugignore": (debugignore, [], ''),
4593 4593 "debugindex": (debugindex,
4594 4594 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
4595 4595 _('FILE')),
4596 4596 "debugindexdot": (debugindexdot, [], _('FILE')),
4597 4597 "debuginstall": (debuginstall, [], ''),
4598 4598 "debugknown": (debugknown, [], _('REPO ID...')),
4599 4599 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4600 4600 "debugrebuildstate":
4601 4601 (debugrebuildstate,
4602 4602 [('r', 'rev', '',
4603 4603 _('revision to rebuild to'), _('REV'))],
4604 4604 _('[-r REV] [REV]')),
4605 4605 "debugrename":
4606 4606 (debugrename,
4607 4607 [('r', 'rev', '',
4608 4608 _('revision to debug'), _('REV'))],
4609 4609 _('[-r REV] FILE')),
4610 4610 "debugrevspec":
4611 4611 (debugrevspec, [], ('REVSPEC')),
4612 4612 "debugsetparents":
4613 4613 (debugsetparents, [], _('REV1 [REV2]')),
4614 4614 "debugstate":
4615 4615 (debugstate,
4616 4616 [('', 'nodates', None, _('do not display the saved mtime')),
4617 4617 ('', 'datesort', None, _('sort by saved mtime'))],
4618 4618 _('[OPTION]...')),
4619 4619 "debugsub":
4620 4620 (debugsub,
4621 4621 [('r', 'rev', '',
4622 4622 _('revision to check'), _('REV'))],
4623 4623 _('[-r REV] [REV]')),
4624 4624 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4625 4625 "debugwireargs":
4626 4626 (debugwireargs,
4627 4627 [('', 'three', '', 'three'),
4628 4628 ('', 'four', '', 'four'),
4629 4629 ('', 'five', '', 'five'),
4630 4630 ] + remoteopts,
4631 4631 _('REPO [OPTIONS]... [ONE [TWO]]')),
4632 4632 "^diff":
4633 4633 (diff,
4634 4634 [('r', 'rev', [],
4635 4635 _('revision'), _('REV')),
4636 4636 ('c', 'change', '',
4637 4637 _('change made by revision'), _('REV'))
4638 4638 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4639 4639 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4640 4640 "^export":
4641 4641 (export,
4642 4642 [('o', 'output', '',
4643 4643 _('print output to file with formatted name'), _('FORMAT')),
4644 4644 ('', 'switch-parent', None, _('diff against the second parent')),
4645 4645 ('r', 'rev', [],
4646 4646 _('revisions to export'), _('REV')),
4647 4647 ] + diffopts,
4648 4648 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4649 4649 "^forget":
4650 4650 (forget,
4651 4651 [] + walkopts,
4652 4652 _('[OPTION]... FILE...')),
4653 4653 "grep":
4654 4654 (grep,
4655 4655 [('0', 'print0', None, _('end fields with NUL')),
4656 4656 ('', 'all', None, _('print all revisions that match')),
4657 4657 ('a', 'text', None, _('treat all files as text')),
4658 4658 ('f', 'follow', None,
4659 4659 _('follow changeset history,'
4660 4660 ' or file history across copies and renames')),
4661 4661 ('i', 'ignore-case', None, _('ignore case when matching')),
4662 4662 ('l', 'files-with-matches', None,
4663 4663 _('print only filenames and revisions that match')),
4664 4664 ('n', 'line-number', None, _('print matching line numbers')),
4665 4665 ('r', 'rev', [],
4666 4666 _('only search files changed within revision range'), _('REV')),
4667 4667 ('u', 'user', None, _('list the author (long with -v)')),
4668 4668 ('d', 'date', None, _('list the date (short with -q)')),
4669 4669 ] + walkopts,
4670 4670 _('[OPTION]... PATTERN [FILE]...')),
4671 4671 "heads":
4672 4672 (heads,
4673 4673 [('r', 'rev', '',
4674 4674 _('show only heads which are descendants of STARTREV'),
4675 4675 _('STARTREV')),
4676 4676 ('t', 'topo', False, _('show topological heads only')),
4677 4677 ('a', 'active', False,
4678 4678 _('show active branchheads only (DEPRECATED)')),
4679 4679 ('c', 'closed', False,
4680 4680 _('show normal and closed branch heads')),
4681 4681 ] + templateopts,
4682 4682 _('[-ac] [-r STARTREV] [REV]...')),
4683 4683 "help": (help_, [], _('[TOPIC]')),
4684 4684 "identify|id":
4685 4685 (identify,
4686 4686 [('r', 'rev', '',
4687 4687 _('identify the specified revision'), _('REV')),
4688 4688 ('n', 'num', None, _('show local revision number')),
4689 4689 ('i', 'id', None, _('show global revision id')),
4690 4690 ('b', 'branch', None, _('show branch')),
4691 4691 ('t', 'tags', None, _('show tags')),
4692 4692 ('B', 'bookmarks', None, _('show bookmarks'))],
4693 4693 _('[-nibtB] [-r REV] [SOURCE]')),
4694 4694 "import|patch":
4695 4695 (import_,
4696 4696 [('p', 'strip', 1,
4697 4697 _('directory strip option for patch. This has the same '
4698 4698 'meaning as the corresponding patch option'),
4699 4699 _('NUM')),
4700 4700 ('b', 'base', '',
4701 4701 _('base path'), _('PATH')),
4702 4702 ('f', 'force', None,
4703 4703 _('skip check for outstanding uncommitted changes')),
4704 4704 ('', 'no-commit', None,
4705 4705 _("don't commit, just update the working directory")),
4706 4706 ('', 'exact', None,
4707 4707 _('apply patch to the nodes from which it was generated')),
4708 4708 ('', 'import-branch', None,
4709 4709 _('use any branch information in patch (implied by --exact)'))] +
4710 4710 commitopts + commitopts2 + similarityopts,
4711 4711 _('[OPTION]... PATCH...')),
4712 4712 "incoming|in":
4713 4713 (incoming,
4714 4714 [('f', 'force', None,
4715 4715 _('run even if remote repository is unrelated')),
4716 4716 ('n', 'newest-first', None, _('show newest record first')),
4717 4717 ('', 'bundle', '',
4718 4718 _('file to store the bundles into'), _('FILE')),
4719 4719 ('r', 'rev', [],
4720 4720 _('a remote changeset intended to be added'), _('REV')),
4721 4721 ('B', 'bookmarks', False, _("compare bookmarks")),
4722 4722 ('b', 'branch', [],
4723 4723 _('a specific branch you would like to pull'), _('BRANCH')),
4724 4724 ] + logopts + remoteopts + subrepoopts,
4725 4725 _('[-p] [-n] [-M] [-f] [-r REV]...'
4726 4726 ' [--bundle FILENAME] [SOURCE]')),
4727 4727 "^init":
4728 4728 (init,
4729 4729 remoteopts,
4730 4730 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4731 4731 "locate":
4732 4732 (locate,
4733 4733 [('r', 'rev', '',
4734 4734 _('search the repository as it is in REV'), _('REV')),
4735 4735 ('0', 'print0', None,
4736 4736 _('end filenames with NUL, for use with xargs')),
4737 4737 ('f', 'fullpath', None,
4738 4738 _('print complete paths from the filesystem root')),
4739 4739 ] + walkopts,
4740 4740 _('[OPTION]... [PATTERN]...')),
4741 4741 "^log|history":
4742 4742 (log,
4743 4743 [('f', 'follow', None,
4744 4744 _('follow changeset history,'
4745 4745 ' or file history across copies and renames')),
4746 4746 ('', 'follow-first', None,
4747 4747 _('only follow the first parent of merge changesets')),
4748 4748 ('d', 'date', '',
4749 4749 _('show revisions matching date spec'), _('DATE')),
4750 4750 ('C', 'copies', None, _('show copied files')),
4751 4751 ('k', 'keyword', [],
4752 4752 _('do case-insensitive search for a given text'), _('TEXT')),
4753 4753 ('r', 'rev', [],
4754 4754 _('show the specified revision or range'), _('REV')),
4755 4755 ('', 'removed', None, _('include revisions where files were removed')),
4756 4756 ('m', 'only-merges', None, _('show only merges')),
4757 4757 ('u', 'user', [],
4758 4758 _('revisions committed by user'), _('USER')),
4759 4759 ('', 'only-branch', [],
4760 4760 _('show only changesets within the given named branch (DEPRECATED)'),
4761 4761 _('BRANCH')),
4762 4762 ('b', 'branch', [],
4763 4763 _('show changesets within the given named branch'), _('BRANCH')),
4764 4764 ('P', 'prune', [],
4765 4765 _('do not display revision or any of its ancestors'), _('REV')),
4766 4766 ] + logopts + walkopts,
4767 4767 _('[OPTION]... [FILE]')),
4768 4768 "manifest":
4769 4769 (manifest,
4770 4770 [('r', 'rev', '',
4771 4771 _('revision to display'), _('REV'))],
4772 4772 _('[-r REV]')),
4773 4773 "^merge":
4774 4774 (merge,
4775 4775 [('f', 'force', None, _('force a merge with outstanding changes')),
4776 4776 ('t', 'tool', '', _('specify merge tool')),
4777 4777 ('r', 'rev', '',
4778 4778 _('revision to merge'), _('REV')),
4779 4779 ('P', 'preview', None,
4780 4780 _('review revisions to merge (no merge is performed)'))],
4781 4781 _('[-P] [-f] [[-r] REV]')),
4782 4782 "outgoing|out":
4783 4783 (outgoing,
4784 4784 [('f', 'force', None,
4785 4785 _('run even when the destination is unrelated')),
4786 4786 ('r', 'rev', [],
4787 4787 _('a changeset intended to be included in the destination'),
4788 4788 _('REV')),
4789 4789 ('n', 'newest-first', None, _('show newest record first')),
4790 4790 ('B', 'bookmarks', False, _("compare bookmarks")),
4791 4791 ('b', 'branch', [],
4792 4792 _('a specific branch you would like to push'), _('BRANCH')),
4793 4793 ] + logopts + remoteopts + subrepoopts,
4794 4794 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4795 4795 "parents":
4796 4796 (parents,
4797 4797 [('r', 'rev', '',
4798 4798 _('show parents of the specified revision'), _('REV')),
4799 4799 ] + templateopts,
4800 4800 _('[-r REV] [FILE]')),
4801 4801 "paths": (paths, [], _('[NAME]')),
4802 4802 "^pull":
4803 4803 (pull,
4804 4804 [('u', 'update', None,
4805 4805 _('update to new branch head if changesets were pulled')),
4806 4806 ('f', 'force', None,
4807 4807 _('run even when remote repository is unrelated')),
4808 4808 ('r', 'rev', [],
4809 4809 _('a remote changeset intended to be added'), _('REV')),
4810 4810 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4811 4811 ('b', 'branch', [],
4812 4812 _('a specific branch you would like to pull'), _('BRANCH')),
4813 4813 ] + remoteopts,
4814 4814 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4815 4815 "^push":
4816 4816 (push,
4817 4817 [('f', 'force', None, _('force push')),
4818 4818 ('r', 'rev', [],
4819 4819 _('a changeset intended to be included in the destination'),
4820 4820 _('REV')),
4821 4821 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4822 4822 ('b', 'branch', [],
4823 4823 _('a specific branch you would like to push'), _('BRANCH')),
4824 4824 ('', 'new-branch', False, _('allow pushing a new branch')),
4825 4825 ] + remoteopts,
4826 4826 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4827 4827 "recover": (recover, []),
4828 4828 "^remove|rm":
4829 4829 (remove,
4830 4830 [('A', 'after', None, _('record delete for missing files')),
4831 4831 ('f', 'force', None,
4832 4832 _('remove (and delete) file even if added or modified')),
4833 4833 ] + walkopts,
4834 4834 _('[OPTION]... FILE...')),
4835 4835 "rename|move|mv":
4836 4836 (rename,
4837 4837 [('A', 'after', None, _('record a rename that has already occurred')),
4838 4838 ('f', 'force', None,
4839 4839 _('forcibly copy over an existing managed file')),
4840 4840 ] + walkopts + dryrunopts,
4841 4841 _('[OPTION]... SOURCE... DEST')),
4842 4842 "resolve":
4843 4843 (resolve,
4844 4844 [('a', 'all', None, _('select all unresolved files')),
4845 4845 ('l', 'list', None, _('list state of files needing merge')),
4846 4846 ('m', 'mark', None, _('mark files as resolved')),
4847 4847 ('u', 'unmark', None, _('mark files as unresolved')),
4848 4848 ('t', 'tool', '', _('specify merge tool')),
4849 4849 ('n', 'no-status', None, _('hide status prefix'))]
4850 4850 + walkopts,
4851 4851 _('[OPTION]... [FILE]...')),
4852 4852 "revert":
4853 4853 (revert,
4854 4854 [('a', 'all', None, _('revert all changes when no arguments given')),
4855 4855 ('d', 'date', '',
4856 4856 _('tipmost revision matching date'), _('DATE')),
4857 4857 ('r', 'rev', '',
4858 4858 _('revert to the specified revision'), _('REV')),
4859 4859 ('', 'no-backup', None, _('do not save backup copies of files')),
4860 4860 ] + walkopts + dryrunopts,
4861 4861 _('[OPTION]... [-r REV] [NAME]...')),
4862 4862 "rollback": (rollback, dryrunopts),
4863 4863 "root": (root, []),
4864 4864 "^serve":
4865 4865 (serve,
4866 4866 [('A', 'accesslog', '',
4867 4867 _('name of access log file to write to'), _('FILE')),
4868 4868 ('d', 'daemon', None, _('run server in background')),
4869 4869 ('', 'daemon-pipefds', '',
4870 4870 _('used internally by daemon mode'), _('NUM')),
4871 4871 ('E', 'errorlog', '',
4872 4872 _('name of error log file to write to'), _('FILE')),
4873 4873 # use string type, then we can check if something was passed
4874 4874 ('p', 'port', '',
4875 4875 _('port to listen on (default: 8000)'), _('PORT')),
4876 4876 ('a', 'address', '',
4877 4877 _('address to listen on (default: all interfaces)'), _('ADDR')),
4878 4878 ('', 'prefix', '',
4879 4879 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4880 4880 ('n', 'name', '',
4881 4881 _('name to show in web pages (default: working directory)'),
4882 4882 _('NAME')),
4883 4883 ('', 'web-conf', '',
4884 4884 _('name of the hgweb config file (see "hg help hgweb")'),
4885 4885 _('FILE')),
4886 4886 ('', 'webdir-conf', '',
4887 4887 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4888 4888 ('', 'pid-file', '',
4889 4889 _('name of file to write process ID to'), _('FILE')),
4890 4890 ('', 'stdio', None, _('for remote clients')),
4891 4891 ('t', 'templates', '',
4892 4892 _('web templates to use'), _('TEMPLATE')),
4893 4893 ('', 'style', '',
4894 4894 _('template style to use'), _('STYLE')),
4895 4895 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4896 4896 ('', 'certificate', '',
4897 4897 _('SSL certificate file'), _('FILE'))],
4898 4898 _('[OPTION]...')),
4899 4899 "showconfig|debugconfig":
4900 4900 (showconfig,
4901 4901 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4902 4902 _('[-u] [NAME]...')),
4903 4903 "^summary|sum":
4904 4904 (summary,
4905 4905 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4906 4906 "^status|st":
4907 4907 (status,
4908 4908 [('A', 'all', None, _('show status of all files')),
4909 4909 ('m', 'modified', None, _('show only modified files')),
4910 4910 ('a', 'added', None, _('show only added files')),
4911 4911 ('r', 'removed', None, _('show only removed files')),
4912 4912 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4913 4913 ('c', 'clean', None, _('show only files without changes')),
4914 4914 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4915 4915 ('i', 'ignored', None, _('show only ignored files')),
4916 4916 ('n', 'no-status', None, _('hide status prefix')),
4917 4917 ('C', 'copies', None, _('show source of copied files')),
4918 4918 ('0', 'print0', None,
4919 4919 _('end filenames with NUL, for use with xargs')),
4920 4920 ('', 'rev', [],
4921 4921 _('show difference from revision'), _('REV')),
4922 4922 ('', 'change', '',
4923 4923 _('list the changed files of a revision'), _('REV')),
4924 4924 ] + walkopts + subrepoopts,
4925 4925 _('[OPTION]... [FILE]...')),
4926 4926 "tag":
4927 4927 (tag,
4928 4928 [('f', 'force', None, _('force tag')),
4929 4929 ('l', 'local', None, _('make the tag local')),
4930 4930 ('r', 'rev', '',
4931 4931 _('revision to tag'), _('REV')),
4932 4932 ('', 'remove', None, _('remove a tag')),
4933 4933 # -l/--local is already there, commitopts cannot be used
4934 4934 ('e', 'edit', None, _('edit commit message')),
4935 4935 ('m', 'message', '',
4936 4936 _('use <text> as commit message'), _('TEXT')),
4937 4937 ] + commitopts2,
4938 4938 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4939 4939 "tags": (tags, [], ''),
4940 4940 "tip":
4941 4941 (tip,
4942 4942 [('p', 'patch', None, _('show patch')),
4943 4943 ('g', 'git', None, _('use git extended diff format')),
4944 4944 ] + templateopts,
4945 4945 _('[-p] [-g]')),
4946 4946 "unbundle":
4947 4947 (unbundle,
4948 4948 [('u', 'update', None,
4949 4949 _('update to new branch head if changesets were unbundled'))],
4950 4950 _('[-u] FILE...')),
4951 4951 "^update|up|checkout|co":
4952 4952 (update,
4953 4953 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4954 4954 ('c', 'check', None,
4955 4955 _('update across branches if no uncommitted changes')),
4956 4956 ('d', 'date', '',
4957 4957 _('tipmost revision matching date'), _('DATE')),
4958 4958 ('r', 'rev', '',
4959 4959 _('revision'), _('REV'))],
4960 4960 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4961 4961 "verify": (verify, []),
4962 4962 "version": (version_, []),
4963 4963 }
4964 4964
4965 4965 norepo = ("clone init version help debugcommands debugcomplete"
4966 4966 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
4967 4967 " debugknown debuggetbundle debugbundle")
4968 4968 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4969 4969 " debugdata debugindex debugindexdot")
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
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