##// END OF EJS Templates
termwidth: move to ui.ui from util
Augie Fackler -
r12689:c52c629c default
parent child Browse files
Show More
@@ -1,197 +1,197
1 1 # churn.py - create a graph of revisions count grouped by template
2 2 #
3 3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
4 4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''command to display statistics about repository history'''
10 10
11 11 from mercurial.i18n import _
12 12 from mercurial import patch, cmdutil, util, templater, commands
13 13 import os
14 14 import time, datetime
15 15
16 16 def maketemplater(ui, repo, tmpl):
17 17 tmpl = templater.parsestring(tmpl, quoted=False)
18 18 try:
19 19 t = cmdutil.changeset_templater(ui, repo, False, None, None, False)
20 20 except SyntaxError, inst:
21 21 raise util.Abort(inst.args[0])
22 22 t.use_template(tmpl)
23 23 return t
24 24
25 25 def changedlines(ui, repo, ctx1, ctx2, fns):
26 26 added, removed = 0, 0
27 27 fmatch = cmdutil.matchfiles(repo, fns)
28 28 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
29 29 for l in diff.split('\n'):
30 30 if l.startswith("+") and not l.startswith("+++ "):
31 31 added += 1
32 32 elif l.startswith("-") and not l.startswith("--- "):
33 33 removed += 1
34 34 return (added, removed)
35 35
36 36 def countrate(ui, repo, amap, *pats, **opts):
37 37 """Calculate stats"""
38 38 if opts.get('dateformat'):
39 39 def getkey(ctx):
40 40 t, tz = ctx.date()
41 41 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
42 42 return date.strftime(opts['dateformat'])
43 43 else:
44 44 tmpl = opts.get('template', '{author|email}')
45 45 tmpl = maketemplater(ui, repo, tmpl)
46 46 def getkey(ctx):
47 47 ui.pushbuffer()
48 48 tmpl.show(ctx)
49 49 return ui.popbuffer()
50 50
51 51 state = {'count': 0}
52 52 rate = {}
53 53 df = False
54 54 if opts.get('date'):
55 55 df = util.matchdate(opts['date'])
56 56
57 57 m = cmdutil.match(repo, pats, opts)
58 58 def prep(ctx, fns):
59 59 rev = ctx.rev()
60 60 if df and not df(ctx.date()[0]): # doesn't match date format
61 61 return
62 62
63 63 key = getkey(ctx)
64 64 key = amap.get(key, key) # alias remap
65 65 if opts.get('changesets'):
66 66 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
67 67 else:
68 68 parents = ctx.parents()
69 69 if len(parents) > 1:
70 70 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
71 71 return
72 72
73 73 ctx1 = parents[0]
74 74 lines = changedlines(ui, repo, ctx1, ctx, fns)
75 75 rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
76 76
77 77 state['count'] += 1
78 78 ui.progress(_('analyzing'), state['count'], total=len(repo))
79 79
80 80 for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
81 81 continue
82 82
83 83 ui.progress(_('analyzing'), None)
84 84
85 85 return rate
86 86
87 87
88 88 def churn(ui, repo, *pats, **opts):
89 89 '''histogram of changes to the repository
90 90
91 91 This command will display a histogram representing the number
92 92 of changed lines or revisions, grouped according to the given
93 93 template. The default template will group changes by author.
94 94 The --dateformat option may be used to group the results by
95 95 date instead.
96 96
97 97 Statistics are based on the number of changed lines, or
98 98 alternatively the number of matching revisions if the
99 99 --changesets option is specified.
100 100
101 101 Examples::
102 102
103 103 # display count of changed lines for every committer
104 104 hg churn -t '{author|email}'
105 105
106 106 # display daily activity graph
107 107 hg churn -f '%H' -s -c
108 108
109 109 # display activity of developers by month
110 110 hg churn -f '%Y-%m' -s -c
111 111
112 112 # display count of lines changed in every year
113 113 hg churn -f '%Y' -s
114 114
115 115 It is possible to map alternate email addresses to a main address
116 116 by providing a file using the following format::
117 117
118 118 <alias email> = <actual email>
119 119
120 120 Such a file may be specified with the --aliases option, otherwise
121 121 a .hgchurn file will be looked for in the working directory root.
122 122 '''
123 123 def pad(s, l):
124 124 return (s + " " * l)[:l]
125 125
126 126 amap = {}
127 127 aliases = opts.get('aliases')
128 128 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
129 129 aliases = repo.wjoin('.hgchurn')
130 130 if aliases:
131 131 for l in open(aliases, "r"):
132 132 try:
133 133 alias, actual = l.split('=' in l and '=' or None, 1)
134 134 amap[alias.strip()] = actual.strip()
135 135 except ValueError:
136 136 l = l.strip()
137 137 if l:
138 138 ui.warn(_("skipping malformed alias: %s\n" % l))
139 139 continue
140 140
141 141 rate = countrate(ui, repo, amap, *pats, **opts).items()
142 142 if not rate:
143 143 return
144 144
145 145 sortkey = ((not opts.get('sort')) and (lambda x: -sum(x[1])) or None)
146 146 rate.sort(key=sortkey)
147 147
148 148 # Be careful not to have a zero maxcount (issue833)
149 149 maxcount = float(max(sum(v) for k, v in rate)) or 1.0
150 150 maxname = max(len(k) for k, v in rate)
151 151
152 ttywidth = util.termwidth()
152 ttywidth = ui.termwidth()
153 153 ui.debug("assuming %i character terminal\n" % ttywidth)
154 154 width = ttywidth - maxname - 2 - 2 - 2
155 155
156 156 if opts.get('diffstat'):
157 157 width -= 15
158 158 def format(name, diffstat):
159 159 added, removed = diffstat
160 160 return "%s %15s %s%s\n" % (pad(name, maxname),
161 161 '+%d/-%d' % (added, removed),
162 162 ui.label('+' * charnum(added),
163 163 'diffstat.inserted'),
164 164 ui.label('-' * charnum(removed),
165 165 'diffstat.deleted'))
166 166 else:
167 167 width -= 6
168 168 def format(name, count):
169 169 return "%s %6d %s\n" % (pad(name, maxname), sum(count),
170 170 '*' * charnum(sum(count)))
171 171
172 172 def charnum(count):
173 173 return int(round(count * width / maxcount))
174 174
175 175 for name, count in rate:
176 176 ui.write(format(name, count))
177 177
178 178
179 179 cmdtable = {
180 180 "churn":
181 181 (churn,
182 182 [('r', 'rev', [],
183 183 _('count rate for the specified revision or range'), _('REV')),
184 184 ('d', 'date', '',
185 185 _('count rate for revisions matching date spec'), _('DATE')),
186 186 ('t', 'template', '{author|email}',
187 187 _('template to group changesets'), _('TEMPLATE')),
188 188 ('f', 'dateformat', '',
189 189 _('strftime-compatible format for grouping by date'), _('FORMAT')),
190 190 ('c', 'changesets', False, _('count rate by number of changesets')),
191 191 ('s', 'sort', False, _('sort by key (default: sort by count)')),
192 192 ('', 'diffstat', False, _('display added/removed lines separately')),
193 193 ('', 'aliases', '',
194 194 _('file with email aliases'), _('FILE')),
195 195 ] + commands.walkopts,
196 196 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]")),
197 197 }
@@ -1,3190 +1,3190
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, 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 diffstart = 0
90 90
91 91 for line in file(pf):
92 92 line = line.rstrip()
93 93 if (line.startswith('diff --git')
94 94 or (diffstart and line.startswith('+++ '))):
95 95 diffstart = 2
96 96 break
97 97 diffstart = 0 # reset
98 98 if line.startswith("--- "):
99 99 diffstart = 1
100 100 continue
101 101 elif format == "hgpatch":
102 102 # parse values when importing the result of an hg export
103 103 if line.startswith("# User "):
104 104 user = line[7:]
105 105 elif line.startswith("# Date "):
106 106 date = line[7:]
107 107 elif line.startswith("# Parent "):
108 108 parent = line[9:]
109 109 elif not line.startswith("# ") and line:
110 110 message.append(line)
111 111 format = None
112 112 elif line == '# HG changeset patch':
113 113 message = []
114 114 format = "hgpatch"
115 115 elif (format != "tagdone" and (line.startswith("Subject: ") or
116 116 line.startswith("subject: "))):
117 117 subject = line[9:]
118 118 format = "tag"
119 119 elif (format != "tagdone" and (line.startswith("From: ") or
120 120 line.startswith("from: "))):
121 121 user = line[6:]
122 122 format = "tag"
123 123 elif (format != "tagdone" and (line.startswith("Date: ") or
124 124 line.startswith("date: "))):
125 125 date = line[6:]
126 126 format = "tag"
127 127 elif format == "tag" and line == "":
128 128 # when looking for tags (subject: from: etc) they
129 129 # end once you find a blank line in the source
130 130 format = "tagdone"
131 131 elif message or line:
132 132 message.append(line)
133 133 comments.append(line)
134 134
135 135 eatdiff(message)
136 136 eatdiff(comments)
137 137 eatempty(message)
138 138 eatempty(comments)
139 139
140 140 # make sure message isn't empty
141 141 if format and format.startswith("tag") and subject:
142 142 message.insert(0, "")
143 143 message.insert(0, subject)
144 144
145 145 self.message = message
146 146 self.comments = comments
147 147 self.user = user
148 148 self.date = date
149 149 self.parent = parent
150 150 self.haspatch = diffstart > 1
151 151 self.plainmode = plainmode
152 152
153 153 def setuser(self, user):
154 154 if not self.updateheader(['From: ', '# User '], user):
155 155 try:
156 156 patchheaderat = self.comments.index('# HG changeset patch')
157 157 self.comments.insert(patchheaderat + 1, '# User ' + user)
158 158 except ValueError:
159 159 if self.plainmode or self._hasheader(['Date: ']):
160 160 self.comments = ['From: ' + user] + self.comments
161 161 else:
162 162 tmp = ['# HG changeset patch', '# User ' + user, '']
163 163 self.comments = tmp + self.comments
164 164 self.user = user
165 165
166 166 def setdate(self, date):
167 167 if not self.updateheader(['Date: ', '# Date '], date):
168 168 try:
169 169 patchheaderat = self.comments.index('# HG changeset patch')
170 170 self.comments.insert(patchheaderat + 1, '# Date ' + date)
171 171 except ValueError:
172 172 if self.plainmode or self._hasheader(['From: ']):
173 173 self.comments = ['Date: ' + date] + self.comments
174 174 else:
175 175 tmp = ['# HG changeset patch', '# Date ' + date, '']
176 176 self.comments = tmp + self.comments
177 177 self.date = date
178 178
179 179 def setparent(self, parent):
180 180 if not self.updateheader(['# Parent '], parent):
181 181 try:
182 182 patchheaderat = self.comments.index('# HG changeset patch')
183 183 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
184 184 except ValueError:
185 185 pass
186 186 self.parent = parent
187 187
188 188 def setmessage(self, message):
189 189 if self.comments:
190 190 self._delmsg()
191 191 self.message = [message]
192 192 self.comments += self.message
193 193
194 194 def updateheader(self, prefixes, new):
195 195 '''Update all references to a field in the patch header.
196 196 Return whether the field is present.'''
197 197 res = False
198 198 for prefix in prefixes:
199 199 for i in xrange(len(self.comments)):
200 200 if self.comments[i].startswith(prefix):
201 201 self.comments[i] = prefix + new
202 202 res = True
203 203 break
204 204 return res
205 205
206 206 def _hasheader(self, prefixes):
207 207 '''Check if a header starts with any of the given prefixes.'''
208 208 for prefix in prefixes:
209 209 for comment in self.comments:
210 210 if comment.startswith(prefix):
211 211 return True
212 212 return False
213 213
214 214 def __str__(self):
215 215 if not self.comments:
216 216 return ''
217 217 return '\n'.join(self.comments) + '\n\n'
218 218
219 219 def _delmsg(self):
220 220 '''Remove existing message, keeping the rest of the comments fields.
221 221 If comments contains 'subject: ', message will prepend
222 222 the field and a blank line.'''
223 223 if self.message:
224 224 subj = 'subject: ' + self.message[0].lower()
225 225 for i in xrange(len(self.comments)):
226 226 if subj == self.comments[i].lower():
227 227 del self.comments[i]
228 228 self.message = self.message[2:]
229 229 break
230 230 ci = 0
231 231 for mi in self.message:
232 232 while mi != self.comments[ci]:
233 233 ci += 1
234 234 del self.comments[ci]
235 235
236 236 class queue(object):
237 237 def __init__(self, ui, path, patchdir=None):
238 238 self.basepath = path
239 239 try:
240 240 fh = open(os.path.join(path, 'patches.queue'))
241 241 cur = fh.read().rstrip()
242 242 if not cur:
243 243 curpath = os.path.join(path, 'patches')
244 244 else:
245 245 curpath = os.path.join(path, 'patches-' + cur)
246 246 except IOError:
247 247 curpath = os.path.join(path, 'patches')
248 248 self.path = patchdir or curpath
249 249 self.opener = util.opener(self.path)
250 250 self.ui = ui
251 251 self.applied_dirty = 0
252 252 self.series_dirty = 0
253 253 self.added = []
254 254 self.series_path = "series"
255 255 self.status_path = "status"
256 256 self.guards_path = "guards"
257 257 self.active_guards = None
258 258 self.guards_dirty = False
259 259 # Handle mq.git as a bool with extended values
260 260 try:
261 261 gitmode = ui.configbool('mq', 'git', None)
262 262 if gitmode is None:
263 263 raise error.ConfigError()
264 264 self.gitmode = gitmode and 'yes' or 'no'
265 265 except error.ConfigError:
266 266 self.gitmode = ui.config('mq', 'git', 'auto').lower()
267 267 self.plainmode = ui.configbool('mq', 'plain', False)
268 268
269 269 @util.propertycache
270 270 def applied(self):
271 271 if os.path.exists(self.join(self.status_path)):
272 272 def parse(l):
273 273 n, name = l.split(':', 1)
274 274 return statusentry(bin(n), name)
275 275 lines = self.opener(self.status_path).read().splitlines()
276 276 return [parse(l) for l in lines]
277 277 return []
278 278
279 279 @util.propertycache
280 280 def full_series(self):
281 281 if os.path.exists(self.join(self.series_path)):
282 282 return self.opener(self.series_path).read().splitlines()
283 283 return []
284 284
285 285 @util.propertycache
286 286 def series(self):
287 287 self.parse_series()
288 288 return self.series
289 289
290 290 @util.propertycache
291 291 def series_guards(self):
292 292 self.parse_series()
293 293 return self.series_guards
294 294
295 295 def invalidate(self):
296 296 for a in 'applied full_series series series_guards'.split():
297 297 if a in self.__dict__:
298 298 delattr(self, a)
299 299 self.applied_dirty = 0
300 300 self.series_dirty = 0
301 301 self.guards_dirty = False
302 302 self.active_guards = None
303 303
304 304 def diffopts(self, opts={}, patchfn=None):
305 305 diffopts = patch.diffopts(self.ui, opts)
306 306 if self.gitmode == 'auto':
307 307 diffopts.upgrade = True
308 308 elif self.gitmode == 'keep':
309 309 pass
310 310 elif self.gitmode in ('yes', 'no'):
311 311 diffopts.git = self.gitmode == 'yes'
312 312 else:
313 313 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
314 314 ' got %s') % self.gitmode)
315 315 if patchfn:
316 316 diffopts = self.patchopts(diffopts, patchfn)
317 317 return diffopts
318 318
319 319 def patchopts(self, diffopts, *patches):
320 320 """Return a copy of input diff options with git set to true if
321 321 referenced patch is a git patch and should be preserved as such.
322 322 """
323 323 diffopts = diffopts.copy()
324 324 if not diffopts.git and self.gitmode == 'keep':
325 325 for patchfn in patches:
326 326 patchf = self.opener(patchfn, 'r')
327 327 # if the patch was a git patch, refresh it as a git patch
328 328 for line in patchf:
329 329 if line.startswith('diff --git'):
330 330 diffopts.git = True
331 331 break
332 332 patchf.close()
333 333 return diffopts
334 334
335 335 def join(self, *p):
336 336 return os.path.join(self.path, *p)
337 337
338 338 def find_series(self, patch):
339 339 def matchpatch(l):
340 340 l = l.split('#', 1)[0]
341 341 return l.strip() == patch
342 342 for index, l in enumerate(self.full_series):
343 343 if matchpatch(l):
344 344 return index
345 345 return None
346 346
347 347 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
348 348
349 349 def parse_series(self):
350 350 self.series = []
351 351 self.series_guards = []
352 352 for l in self.full_series:
353 353 h = l.find('#')
354 354 if h == -1:
355 355 patch = l
356 356 comment = ''
357 357 elif h == 0:
358 358 continue
359 359 else:
360 360 patch = l[:h]
361 361 comment = l[h:]
362 362 patch = patch.strip()
363 363 if patch:
364 364 if patch in self.series:
365 365 raise util.Abort(_('%s appears more than once in %s') %
366 366 (patch, self.join(self.series_path)))
367 367 self.series.append(patch)
368 368 self.series_guards.append(self.guard_re.findall(comment))
369 369
370 370 def check_guard(self, guard):
371 371 if not guard:
372 372 return _('guard cannot be an empty string')
373 373 bad_chars = '# \t\r\n\f'
374 374 first = guard[0]
375 375 if first in '-+':
376 376 return (_('guard %r starts with invalid character: %r') %
377 377 (guard, first))
378 378 for c in bad_chars:
379 379 if c in guard:
380 380 return _('invalid character in guard %r: %r') % (guard, c)
381 381
382 382 def set_active(self, guards):
383 383 for guard in guards:
384 384 bad = self.check_guard(guard)
385 385 if bad:
386 386 raise util.Abort(bad)
387 387 guards = sorted(set(guards))
388 388 self.ui.debug('active guards: %s\n' % ' '.join(guards))
389 389 self.active_guards = guards
390 390 self.guards_dirty = True
391 391
392 392 def active(self):
393 393 if self.active_guards is None:
394 394 self.active_guards = []
395 395 try:
396 396 guards = self.opener(self.guards_path).read().split()
397 397 except IOError, err:
398 398 if err.errno != errno.ENOENT:
399 399 raise
400 400 guards = []
401 401 for i, guard in enumerate(guards):
402 402 bad = self.check_guard(guard)
403 403 if bad:
404 404 self.ui.warn('%s:%d: %s\n' %
405 405 (self.join(self.guards_path), i + 1, bad))
406 406 else:
407 407 self.active_guards.append(guard)
408 408 return self.active_guards
409 409
410 410 def set_guards(self, idx, guards):
411 411 for g in guards:
412 412 if len(g) < 2:
413 413 raise util.Abort(_('guard %r too short') % g)
414 414 if g[0] not in '-+':
415 415 raise util.Abort(_('guard %r starts with invalid char') % g)
416 416 bad = self.check_guard(g[1:])
417 417 if bad:
418 418 raise util.Abort(bad)
419 419 drop = self.guard_re.sub('', self.full_series[idx])
420 420 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
421 421 self.parse_series()
422 422 self.series_dirty = True
423 423
424 424 def pushable(self, idx):
425 425 if isinstance(idx, str):
426 426 idx = self.series.index(idx)
427 427 patchguards = self.series_guards[idx]
428 428 if not patchguards:
429 429 return True, None
430 430 guards = self.active()
431 431 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
432 432 if exactneg:
433 433 return False, exactneg[0]
434 434 pos = [g for g in patchguards if g[0] == '+']
435 435 exactpos = [g for g in pos if g[1:] in guards]
436 436 if pos:
437 437 if exactpos:
438 438 return True, exactpos[0]
439 439 return False, pos
440 440 return True, ''
441 441
442 442 def explain_pushable(self, idx, all_patches=False):
443 443 write = all_patches and self.ui.write or self.ui.warn
444 444 if all_patches or self.ui.verbose:
445 445 if isinstance(idx, str):
446 446 idx = self.series.index(idx)
447 447 pushable, why = self.pushable(idx)
448 448 if all_patches and pushable:
449 449 if why is None:
450 450 write(_('allowing %s - no guards in effect\n') %
451 451 self.series[idx])
452 452 else:
453 453 if not why:
454 454 write(_('allowing %s - no matching negative guards\n') %
455 455 self.series[idx])
456 456 else:
457 457 write(_('allowing %s - guarded by %r\n') %
458 458 (self.series[idx], why))
459 459 if not pushable:
460 460 if why:
461 461 write(_('skipping %s - guarded by %r\n') %
462 462 (self.series[idx], why))
463 463 else:
464 464 write(_('skipping %s - no matching guards\n') %
465 465 self.series[idx])
466 466
467 467 def save_dirty(self):
468 468 def write_list(items, path):
469 469 fp = self.opener(path, 'w')
470 470 for i in items:
471 471 fp.write("%s\n" % i)
472 472 fp.close()
473 473 if self.applied_dirty:
474 474 write_list(map(str, self.applied), self.status_path)
475 475 if self.series_dirty:
476 476 write_list(self.full_series, self.series_path)
477 477 if self.guards_dirty:
478 478 write_list(self.active_guards, self.guards_path)
479 479 if self.added:
480 480 qrepo = self.qrepo()
481 481 if qrepo:
482 482 qrepo[None].add(f for f in self.added if f not in qrepo[None])
483 483 self.added = []
484 484
485 485 def removeundo(self, repo):
486 486 undo = repo.sjoin('undo')
487 487 if not os.path.exists(undo):
488 488 return
489 489 try:
490 490 os.unlink(undo)
491 491 except OSError, inst:
492 492 self.ui.warn(_('error removing undo: %s\n') % str(inst))
493 493
494 494 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
495 495 fp=None, changes=None, opts={}):
496 496 stat = opts.get('stat')
497 497 m = cmdutil.match(repo, files, opts)
498 498 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
499 499 changes, stat, fp)
500 500
501 501 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
502 502 # first try just applying the patch
503 503 (err, n) = self.apply(repo, [patch], update_status=False,
504 504 strict=True, merge=rev)
505 505
506 506 if err == 0:
507 507 return (err, n)
508 508
509 509 if n is None:
510 510 raise util.Abort(_("apply failed for patch %s") % patch)
511 511
512 512 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
513 513
514 514 # apply failed, strip away that rev and merge.
515 515 hg.clean(repo, head)
516 516 self.strip(repo, [n], update=False, backup='strip')
517 517
518 518 ctx = repo[rev]
519 519 ret = hg.merge(repo, rev)
520 520 if ret:
521 521 raise util.Abort(_("update returned %d") % ret)
522 522 n = repo.commit(ctx.description(), ctx.user(), force=True)
523 523 if n is None:
524 524 raise util.Abort(_("repo commit failed"))
525 525 try:
526 526 ph = patchheader(mergeq.join(patch), self.plainmode)
527 527 except:
528 528 raise util.Abort(_("unable to read %s") % patch)
529 529
530 530 diffopts = self.patchopts(diffopts, patch)
531 531 patchf = self.opener(patch, "w")
532 532 comments = str(ph)
533 533 if comments:
534 534 patchf.write(comments)
535 535 self.printdiff(repo, diffopts, head, n, fp=patchf)
536 536 patchf.close()
537 537 self.removeundo(repo)
538 538 return (0, n)
539 539
540 540 def qparents(self, repo, rev=None):
541 541 if rev is None:
542 542 (p1, p2) = repo.dirstate.parents()
543 543 if p2 == nullid:
544 544 return p1
545 545 if not self.applied:
546 546 return None
547 547 return self.applied[-1].node
548 548 p1, p2 = repo.changelog.parents(rev)
549 549 if p2 != nullid and p2 in [x.node for x in self.applied]:
550 550 return p2
551 551 return p1
552 552
553 553 def mergepatch(self, repo, mergeq, series, diffopts):
554 554 if not self.applied:
555 555 # each of the patches merged in will have two parents. This
556 556 # can confuse the qrefresh, qdiff, and strip code because it
557 557 # needs to know which parent is actually in the patch queue.
558 558 # so, we insert a merge marker with only one parent. This way
559 559 # the first patch in the queue is never a merge patch
560 560 #
561 561 pname = ".hg.patches.merge.marker"
562 562 n = repo.commit('[mq]: merge marker', force=True)
563 563 self.removeundo(repo)
564 564 self.applied.append(statusentry(n, pname))
565 565 self.applied_dirty = 1
566 566
567 567 head = self.qparents(repo)
568 568
569 569 for patch in series:
570 570 patch = mergeq.lookup(patch, strict=True)
571 571 if not patch:
572 572 self.ui.warn(_("patch %s does not exist\n") % patch)
573 573 return (1, None)
574 574 pushable, reason = self.pushable(patch)
575 575 if not pushable:
576 576 self.explain_pushable(patch, all_patches=True)
577 577 continue
578 578 info = mergeq.isapplied(patch)
579 579 if not info:
580 580 self.ui.warn(_("patch %s is not applied\n") % patch)
581 581 return (1, None)
582 582 rev = info[1]
583 583 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
584 584 if head:
585 585 self.applied.append(statusentry(head, patch))
586 586 self.applied_dirty = 1
587 587 if err:
588 588 return (err, head)
589 589 self.save_dirty()
590 590 return (0, head)
591 591
592 592 def patch(self, repo, patchfile):
593 593 '''Apply patchfile to the working directory.
594 594 patchfile: name of patch file'''
595 595 files = {}
596 596 try:
597 597 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
598 598 files=files, eolmode=None)
599 599 except Exception, inst:
600 600 self.ui.note(str(inst) + '\n')
601 601 if not self.ui.verbose:
602 602 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
603 603 return (False, files, False)
604 604
605 605 return (True, files, fuzz)
606 606
607 607 def apply(self, repo, series, list=False, update_status=True,
608 608 strict=False, patchdir=None, merge=None, all_files=None):
609 609 wlock = lock = tr = None
610 610 try:
611 611 wlock = repo.wlock()
612 612 lock = repo.lock()
613 613 tr = repo.transaction("qpush")
614 614 try:
615 615 ret = self._apply(repo, series, list, update_status,
616 616 strict, patchdir, merge, all_files=all_files)
617 617 tr.close()
618 618 self.save_dirty()
619 619 return ret
620 620 except:
621 621 try:
622 622 tr.abort()
623 623 finally:
624 624 repo.invalidate()
625 625 repo.dirstate.invalidate()
626 626 raise
627 627 finally:
628 628 release(tr, lock, wlock)
629 629 self.removeundo(repo)
630 630
631 631 def _apply(self, repo, series, list=False, update_status=True,
632 632 strict=False, patchdir=None, merge=None, all_files=None):
633 633 '''returns (error, hash)
634 634 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
635 635 # TODO unify with commands.py
636 636 if not patchdir:
637 637 patchdir = self.path
638 638 err = 0
639 639 n = None
640 640 for patchname in series:
641 641 pushable, reason = self.pushable(patchname)
642 642 if not pushable:
643 643 self.explain_pushable(patchname, all_patches=True)
644 644 continue
645 645 self.ui.status(_("applying %s\n") % patchname)
646 646 pf = os.path.join(patchdir, patchname)
647 647
648 648 try:
649 649 ph = patchheader(self.join(patchname), self.plainmode)
650 650 except:
651 651 self.ui.warn(_("unable to read %s\n") % patchname)
652 652 err = 1
653 653 break
654 654
655 655 message = ph.message
656 656 if not message:
657 657 message = "imported patch %s\n" % patchname
658 658 else:
659 659 if list:
660 660 message.append("\nimported patch %s" % patchname)
661 661 message = '\n'.join(message)
662 662
663 663 if ph.haspatch:
664 664 (patcherr, files, fuzz) = self.patch(repo, pf)
665 665 if all_files is not None:
666 666 all_files.update(files)
667 667 patcherr = not patcherr
668 668 else:
669 669 self.ui.warn(_("patch %s is empty\n") % patchname)
670 670 patcherr, files, fuzz = 0, [], 0
671 671
672 672 if merge and files:
673 673 # Mark as removed/merged and update dirstate parent info
674 674 removed = []
675 675 merged = []
676 676 for f in files:
677 677 if os.path.lexists(repo.wjoin(f)):
678 678 merged.append(f)
679 679 else:
680 680 removed.append(f)
681 681 for f in removed:
682 682 repo.dirstate.remove(f)
683 683 for f in merged:
684 684 repo.dirstate.merge(f)
685 685 p1, p2 = repo.dirstate.parents()
686 686 repo.dirstate.setparents(p1, merge)
687 687
688 688 files = cmdutil.updatedir(self.ui, repo, files)
689 689 match = cmdutil.matchfiles(repo, files or [])
690 690 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
691 691
692 692 if n is None:
693 693 raise util.Abort(_("repo commit failed"))
694 694
695 695 if update_status:
696 696 self.applied.append(statusentry(n, patchname))
697 697
698 698 if patcherr:
699 699 self.ui.warn(_("patch failed, rejects left in working dir\n"))
700 700 err = 2
701 701 break
702 702
703 703 if fuzz and strict:
704 704 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
705 705 err = 3
706 706 break
707 707 return (err, n)
708 708
709 709 def _cleanup(self, patches, numrevs, keep=False):
710 710 if not keep:
711 711 r = self.qrepo()
712 712 if r:
713 713 r[None].remove(patches, True)
714 714 else:
715 715 for p in patches:
716 716 os.unlink(self.join(p))
717 717
718 718 if numrevs:
719 719 del self.applied[:numrevs]
720 720 self.applied_dirty = 1
721 721
722 722 for i in sorted([self.find_series(p) for p in patches], reverse=True):
723 723 del self.full_series[i]
724 724 self.parse_series()
725 725 self.series_dirty = 1
726 726
727 727 def _revpatches(self, repo, revs):
728 728 firstrev = repo[self.applied[0].node].rev()
729 729 patches = []
730 730 for i, rev in enumerate(revs):
731 731
732 732 if rev < firstrev:
733 733 raise util.Abort(_('revision %d is not managed') % rev)
734 734
735 735 ctx = repo[rev]
736 736 base = self.applied[i].node
737 737 if ctx.node() != base:
738 738 msg = _('cannot delete revision %d above applied patches')
739 739 raise util.Abort(msg % rev)
740 740
741 741 patch = self.applied[i].name
742 742 for fmt in ('[mq]: %s', 'imported patch %s'):
743 743 if ctx.description() == fmt % patch:
744 744 msg = _('patch %s finalized without changeset message\n')
745 745 repo.ui.status(msg % patch)
746 746 break
747 747
748 748 patches.append(patch)
749 749 return patches
750 750
751 751 def finish(self, repo, revs):
752 752 patches = self._revpatches(repo, sorted(revs))
753 753 self._cleanup(patches, len(patches))
754 754
755 755 def delete(self, repo, patches, opts):
756 756 if not patches and not opts.get('rev'):
757 757 raise util.Abort(_('qdelete requires at least one revision or '
758 758 'patch name'))
759 759
760 760 realpatches = []
761 761 for patch in patches:
762 762 patch = self.lookup(patch, strict=True)
763 763 info = self.isapplied(patch)
764 764 if info:
765 765 raise util.Abort(_("cannot delete applied patch %s") % patch)
766 766 if patch not in self.series:
767 767 raise util.Abort(_("patch %s not in series file") % patch)
768 768 if patch not in realpatches:
769 769 realpatches.append(patch)
770 770
771 771 numrevs = 0
772 772 if opts.get('rev'):
773 773 if not self.applied:
774 774 raise util.Abort(_('no patches applied'))
775 775 revs = cmdutil.revrange(repo, opts.get('rev'))
776 776 if len(revs) > 1 and revs[0] > revs[1]:
777 777 revs.reverse()
778 778 revpatches = self._revpatches(repo, revs)
779 779 realpatches += revpatches
780 780 numrevs = len(revpatches)
781 781
782 782 self._cleanup(realpatches, numrevs, opts.get('keep'))
783 783
784 784 def check_toppatch(self, repo):
785 785 if self.applied:
786 786 top = self.applied[-1].node
787 787 patch = self.applied[-1].name
788 788 pp = repo.dirstate.parents()
789 789 if top not in pp:
790 790 raise util.Abort(_("working directory revision is not qtip"))
791 791 return top, patch
792 792 return None, None
793 793
794 794 def check_localchanges(self, repo, force=False, refresh=True):
795 795 m, a, r, d = repo.status()[:4]
796 796 if (m or a or r or d) and not force:
797 797 if refresh:
798 798 raise util.Abort(_("local changes found, refresh first"))
799 799 else:
800 800 raise util.Abort(_("local changes found"))
801 801 return m, a, r, d
802 802
803 803 _reserved = ('series', 'status', 'guards')
804 804 def check_reserved_name(self, name):
805 805 if (name in self._reserved or name.startswith('.hg')
806 806 or name.startswith('.mq') or '#' in name or ':' in name):
807 807 raise util.Abort(_('"%s" cannot be used as the name of a patch')
808 808 % name)
809 809
810 810 def new(self, repo, patchfn, *pats, **opts):
811 811 """options:
812 812 msg: a string or a no-argument function returning a string
813 813 """
814 814 msg = opts.get('msg')
815 815 user = opts.get('user')
816 816 date = opts.get('date')
817 817 if date:
818 818 date = util.parsedate(date)
819 819 diffopts = self.diffopts({'git': opts.get('git')})
820 820 self.check_reserved_name(patchfn)
821 821 if os.path.exists(self.join(patchfn)):
822 822 raise util.Abort(_('patch "%s" already exists') % patchfn)
823 823 if opts.get('include') or opts.get('exclude') or pats:
824 824 match = cmdutil.match(repo, pats, opts)
825 825 # detect missing files in pats
826 826 def badfn(f, msg):
827 827 raise util.Abort('%s: %s' % (f, msg))
828 828 match.bad = badfn
829 829 m, a, r, d = repo.status(match=match)[:4]
830 830 else:
831 831 m, a, r, d = self.check_localchanges(repo, force=True)
832 832 match = cmdutil.matchfiles(repo, m + a + r)
833 833 if len(repo[None].parents()) > 1:
834 834 raise util.Abort(_('cannot manage merge changesets'))
835 835 commitfiles = m + a + r
836 836 self.check_toppatch(repo)
837 837 insert = self.full_series_end()
838 838 wlock = repo.wlock()
839 839 try:
840 840 # if patch file write fails, abort early
841 841 p = self.opener(patchfn, "w")
842 842 try:
843 843 if self.plainmode:
844 844 if user:
845 845 p.write("From: " + user + "\n")
846 846 if not date:
847 847 p.write("\n")
848 848 if date:
849 849 p.write("Date: %d %d\n\n" % date)
850 850 else:
851 851 p.write("# HG changeset patch\n")
852 852 p.write("# Parent "
853 853 + hex(repo[None].parents()[0].node()) + "\n")
854 854 if user:
855 855 p.write("# User " + user + "\n")
856 856 if date:
857 857 p.write("# Date %s %s\n\n" % date)
858 858 if hasattr(msg, '__call__'):
859 859 msg = msg()
860 860 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
861 861 n = repo.commit(commitmsg, user, date, match=match, force=True)
862 862 if n is None:
863 863 raise util.Abort(_("repo commit failed"))
864 864 try:
865 865 self.full_series[insert:insert] = [patchfn]
866 866 self.applied.append(statusentry(n, patchfn))
867 867 self.parse_series()
868 868 self.series_dirty = 1
869 869 self.applied_dirty = 1
870 870 if msg:
871 871 msg = msg + "\n\n"
872 872 p.write(msg)
873 873 if commitfiles:
874 874 parent = self.qparents(repo, n)
875 875 chunks = patch.diff(repo, node1=parent, node2=n,
876 876 match=match, opts=diffopts)
877 877 for chunk in chunks:
878 878 p.write(chunk)
879 879 p.close()
880 880 wlock.release()
881 881 wlock = None
882 882 r = self.qrepo()
883 883 if r:
884 884 r[None].add([patchfn])
885 885 except:
886 886 repo.rollback()
887 887 raise
888 888 except Exception:
889 889 patchpath = self.join(patchfn)
890 890 try:
891 891 os.unlink(patchpath)
892 892 except:
893 893 self.ui.warn(_('error unlinking %s\n') % patchpath)
894 894 raise
895 895 self.removeundo(repo)
896 896 finally:
897 897 release(wlock)
898 898
899 899 def strip(self, repo, revs, update=True, backup="all", force=None):
900 900 wlock = lock = None
901 901 try:
902 902 wlock = repo.wlock()
903 903 lock = repo.lock()
904 904
905 905 if update:
906 906 self.check_localchanges(repo, force=force, refresh=False)
907 907 urev = self.qparents(repo, revs[0])
908 908 hg.clean(repo, urev)
909 909 repo.dirstate.write()
910 910
911 911 self.removeundo(repo)
912 912 for rev in revs:
913 913 repair.strip(self.ui, repo, rev, backup)
914 914 # strip may have unbundled a set of backed up revisions after
915 915 # the actual strip
916 916 self.removeundo(repo)
917 917 finally:
918 918 release(lock, wlock)
919 919
920 920 def isapplied(self, patch):
921 921 """returns (index, rev, patch)"""
922 922 for i, a in enumerate(self.applied):
923 923 if a.name == patch:
924 924 return (i, a.node, a.name)
925 925 return None
926 926
927 927 # if the exact patch name does not exist, we try a few
928 928 # variations. If strict is passed, we try only #1
929 929 #
930 930 # 1) a number to indicate an offset in the series file
931 931 # 2) a unique substring of the patch name was given
932 932 # 3) patchname[-+]num to indicate an offset in the series file
933 933 def lookup(self, patch, strict=False):
934 934 patch = patch and str(patch)
935 935
936 936 def partial_name(s):
937 937 if s in self.series:
938 938 return s
939 939 matches = [x for x in self.series if s in x]
940 940 if len(matches) > 1:
941 941 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
942 942 for m in matches:
943 943 self.ui.warn(' %s\n' % m)
944 944 return None
945 945 if matches:
946 946 return matches[0]
947 947 if self.series and self.applied:
948 948 if s == 'qtip':
949 949 return self.series[self.series_end(True)-1]
950 950 if s == 'qbase':
951 951 return self.series[0]
952 952 return None
953 953
954 954 if patch is None:
955 955 return None
956 956 if patch in self.series:
957 957 return patch
958 958
959 959 if not os.path.isfile(self.join(patch)):
960 960 try:
961 961 sno = int(patch)
962 962 except (ValueError, OverflowError):
963 963 pass
964 964 else:
965 965 if -len(self.series) <= sno < len(self.series):
966 966 return self.series[sno]
967 967
968 968 if not strict:
969 969 res = partial_name(patch)
970 970 if res:
971 971 return res
972 972 minus = patch.rfind('-')
973 973 if minus >= 0:
974 974 res = partial_name(patch[:minus])
975 975 if res:
976 976 i = self.series.index(res)
977 977 try:
978 978 off = int(patch[minus + 1:] or 1)
979 979 except (ValueError, OverflowError):
980 980 pass
981 981 else:
982 982 if i - off >= 0:
983 983 return self.series[i - off]
984 984 plus = patch.rfind('+')
985 985 if plus >= 0:
986 986 res = partial_name(patch[:plus])
987 987 if res:
988 988 i = self.series.index(res)
989 989 try:
990 990 off = int(patch[plus + 1:] or 1)
991 991 except (ValueError, OverflowError):
992 992 pass
993 993 else:
994 994 if i + off < len(self.series):
995 995 return self.series[i + off]
996 996 raise util.Abort(_("patch %s not in series") % patch)
997 997
998 998 def push(self, repo, patch=None, force=False, list=False,
999 999 mergeq=None, all=False, move=False):
1000 1000 diffopts = self.diffopts()
1001 1001 wlock = repo.wlock()
1002 1002 try:
1003 1003 heads = []
1004 1004 for b, ls in repo.branchmap().iteritems():
1005 1005 heads += ls
1006 1006 if not heads:
1007 1007 heads = [nullid]
1008 1008 if repo.dirstate.parents()[0] not in heads:
1009 1009 self.ui.status(_("(working directory not at a head)\n"))
1010 1010
1011 1011 if not self.series:
1012 1012 self.ui.warn(_('no patches in series\n'))
1013 1013 return 0
1014 1014
1015 1015 patch = self.lookup(patch)
1016 1016 # Suppose our series file is: A B C and the current 'top'
1017 1017 # patch is B. qpush C should be performed (moving forward)
1018 1018 # qpush B is a NOP (no change) qpush A is an error (can't
1019 1019 # go backwards with qpush)
1020 1020 if patch:
1021 1021 info = self.isapplied(patch)
1022 1022 if info:
1023 1023 if info[0] < len(self.applied) - 1:
1024 1024 raise util.Abort(
1025 1025 _("cannot push to a previous patch: %s") % patch)
1026 1026 self.ui.warn(
1027 1027 _('qpush: %s is already at the top\n') % patch)
1028 1028 return 0
1029 1029 pushable, reason = self.pushable(patch)
1030 1030 if not pushable:
1031 1031 if reason:
1032 1032 reason = _('guarded by %r') % reason
1033 1033 else:
1034 1034 reason = _('no matching guards')
1035 1035 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1036 1036 return 1
1037 1037 elif all:
1038 1038 patch = self.series[-1]
1039 1039 if self.isapplied(patch):
1040 1040 self.ui.warn(_('all patches are currently applied\n'))
1041 1041 return 0
1042 1042
1043 1043 # Following the above example, starting at 'top' of B:
1044 1044 # qpush should be performed (pushes C), but a subsequent
1045 1045 # qpush without an argument is an error (nothing to
1046 1046 # apply). This allows a loop of "...while hg qpush..." to
1047 1047 # work as it detects an error when done
1048 1048 start = self.series_end()
1049 1049 if start == len(self.series):
1050 1050 self.ui.warn(_('patch series already fully applied\n'))
1051 1051 return 1
1052 1052 if not force:
1053 1053 self.check_localchanges(repo)
1054 1054
1055 1055 if move:
1056 1056 if not patch:
1057 1057 raise util.Abort(_("please specify the patch to move"))
1058 1058 for i, rpn in enumerate(self.full_series[start:]):
1059 1059 # strip markers for patch guards
1060 1060 if self.guard_re.split(rpn, 1)[0] == patch:
1061 1061 break
1062 1062 index = start + i
1063 1063 assert index < len(self.full_series)
1064 1064 fullpatch = self.full_series[index]
1065 1065 del self.full_series[index]
1066 1066 self.full_series.insert(start, fullpatch)
1067 1067 self.parse_series()
1068 1068 self.series_dirty = 1
1069 1069
1070 1070 self.applied_dirty = 1
1071 1071 if start > 0:
1072 1072 self.check_toppatch(repo)
1073 1073 if not patch:
1074 1074 patch = self.series[start]
1075 1075 end = start + 1
1076 1076 else:
1077 1077 end = self.series.index(patch, start) + 1
1078 1078
1079 1079 s = self.series[start:end]
1080 1080 all_files = set()
1081 1081 try:
1082 1082 if mergeq:
1083 1083 ret = self.mergepatch(repo, mergeq, s, diffopts)
1084 1084 else:
1085 1085 ret = self.apply(repo, s, list, all_files=all_files)
1086 1086 except:
1087 1087 self.ui.warn(_('cleaning up working directory...'))
1088 1088 node = repo.dirstate.parents()[0]
1089 1089 hg.revert(repo, node, None)
1090 1090 # only remove unknown files that we know we touched or
1091 1091 # created while patching
1092 1092 for f in all_files:
1093 1093 if f not in repo.dirstate:
1094 1094 try:
1095 1095 util.unlink(repo.wjoin(f))
1096 1096 except OSError, inst:
1097 1097 if inst.errno != errno.ENOENT:
1098 1098 raise
1099 1099 self.ui.warn(_('done\n'))
1100 1100 raise
1101 1101
1102 1102 if not self.applied:
1103 1103 return ret[0]
1104 1104 top = self.applied[-1].name
1105 1105 if ret[0] and ret[0] > 1:
1106 1106 msg = _("errors during apply, please fix and refresh %s\n")
1107 1107 self.ui.write(msg % top)
1108 1108 else:
1109 1109 self.ui.write(_("now at: %s\n") % top)
1110 1110 return ret[0]
1111 1111
1112 1112 finally:
1113 1113 wlock.release()
1114 1114
1115 1115 def pop(self, repo, patch=None, force=False, update=True, all=False):
1116 1116 wlock = repo.wlock()
1117 1117 try:
1118 1118 if patch:
1119 1119 # index, rev, patch
1120 1120 info = self.isapplied(patch)
1121 1121 if not info:
1122 1122 patch = self.lookup(patch)
1123 1123 info = self.isapplied(patch)
1124 1124 if not info:
1125 1125 raise util.Abort(_("patch %s is not applied") % patch)
1126 1126
1127 1127 if not self.applied:
1128 1128 # Allow qpop -a to work repeatedly,
1129 1129 # but not qpop without an argument
1130 1130 self.ui.warn(_("no patches applied\n"))
1131 1131 return not all
1132 1132
1133 1133 if all:
1134 1134 start = 0
1135 1135 elif patch:
1136 1136 start = info[0] + 1
1137 1137 else:
1138 1138 start = len(self.applied) - 1
1139 1139
1140 1140 if start >= len(self.applied):
1141 1141 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1142 1142 return
1143 1143
1144 1144 if not update:
1145 1145 parents = repo.dirstate.parents()
1146 1146 rr = [x.node for x in self.applied]
1147 1147 for p in parents:
1148 1148 if p in rr:
1149 1149 self.ui.warn(_("qpop: forcing dirstate update\n"))
1150 1150 update = True
1151 1151 else:
1152 1152 parents = [p.node() for p in repo[None].parents()]
1153 1153 needupdate = False
1154 1154 for entry in self.applied[start:]:
1155 1155 if entry.node in parents:
1156 1156 needupdate = True
1157 1157 break
1158 1158 update = needupdate
1159 1159
1160 1160 if not force and update:
1161 1161 self.check_localchanges(repo)
1162 1162
1163 1163 self.applied_dirty = 1
1164 1164 end = len(self.applied)
1165 1165 rev = self.applied[start].node
1166 1166 if update:
1167 1167 top = self.check_toppatch(repo)[0]
1168 1168
1169 1169 try:
1170 1170 heads = repo.changelog.heads(rev)
1171 1171 except error.LookupError:
1172 1172 node = short(rev)
1173 1173 raise util.Abort(_('trying to pop unknown node %s') % node)
1174 1174
1175 1175 if heads != [self.applied[-1].node]:
1176 1176 raise util.Abort(_("popping would remove a revision not "
1177 1177 "managed by this patch queue"))
1178 1178
1179 1179 # we know there are no local changes, so we can make a simplified
1180 1180 # form of hg.update.
1181 1181 if update:
1182 1182 qp = self.qparents(repo, rev)
1183 1183 ctx = repo[qp]
1184 1184 m, a, r, d = repo.status(qp, top)[:4]
1185 1185 if d:
1186 1186 raise util.Abort(_("deletions found between repo revs"))
1187 1187 for f in a:
1188 1188 try:
1189 1189 util.unlink(repo.wjoin(f))
1190 1190 except OSError, e:
1191 1191 if e.errno != errno.ENOENT:
1192 1192 raise
1193 1193 repo.dirstate.forget(f)
1194 1194 for f in m + r:
1195 1195 fctx = ctx[f]
1196 1196 repo.wwrite(f, fctx.data(), fctx.flags())
1197 1197 repo.dirstate.normal(f)
1198 1198 repo.dirstate.setparents(qp, nullid)
1199 1199 for patch in reversed(self.applied[start:end]):
1200 1200 self.ui.status(_("popping %s\n") % patch.name)
1201 1201 del self.applied[start:end]
1202 1202 self.strip(repo, [rev], update=False, backup='strip')
1203 1203 if self.applied:
1204 1204 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1205 1205 else:
1206 1206 self.ui.write(_("patch queue now empty\n"))
1207 1207 finally:
1208 1208 wlock.release()
1209 1209
1210 1210 def diff(self, repo, pats, opts):
1211 1211 top, patch = self.check_toppatch(repo)
1212 1212 if not top:
1213 1213 self.ui.write(_("no patches applied\n"))
1214 1214 return
1215 1215 qp = self.qparents(repo, top)
1216 1216 if opts.get('reverse'):
1217 1217 node1, node2 = None, qp
1218 1218 else:
1219 1219 node1, node2 = qp, None
1220 1220 diffopts = self.diffopts(opts, patch)
1221 1221 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1222 1222
1223 1223 def refresh(self, repo, pats=None, **opts):
1224 1224 if not self.applied:
1225 1225 self.ui.write(_("no patches applied\n"))
1226 1226 return 1
1227 1227 msg = opts.get('msg', '').rstrip()
1228 1228 newuser = opts.get('user')
1229 1229 newdate = opts.get('date')
1230 1230 if newdate:
1231 1231 newdate = '%d %d' % util.parsedate(newdate)
1232 1232 wlock = repo.wlock()
1233 1233
1234 1234 try:
1235 1235 self.check_toppatch(repo)
1236 1236 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1237 1237 if repo.changelog.heads(top) != [top]:
1238 1238 raise util.Abort(_("cannot refresh a revision with children"))
1239 1239
1240 1240 cparents = repo.changelog.parents(top)
1241 1241 patchparent = self.qparents(repo, top)
1242 1242 ph = patchheader(self.join(patchfn), self.plainmode)
1243 1243 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1244 1244 if msg:
1245 1245 ph.setmessage(msg)
1246 1246 if newuser:
1247 1247 ph.setuser(newuser)
1248 1248 if newdate:
1249 1249 ph.setdate(newdate)
1250 1250 ph.setparent(hex(patchparent))
1251 1251
1252 1252 # only commit new patch when write is complete
1253 1253 patchf = self.opener(patchfn, 'w', atomictemp=True)
1254 1254
1255 1255 comments = str(ph)
1256 1256 if comments:
1257 1257 patchf.write(comments)
1258 1258
1259 1259 # update the dirstate in place, strip off the qtip commit
1260 1260 # and then commit.
1261 1261 #
1262 1262 # this should really read:
1263 1263 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1264 1264 # but we do it backwards to take advantage of manifest/chlog
1265 1265 # caching against the next repo.status call
1266 1266 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1267 1267 changes = repo.changelog.read(top)
1268 1268 man = repo.manifest.read(changes[0])
1269 1269 aaa = aa[:]
1270 1270 matchfn = cmdutil.match(repo, pats, opts)
1271 1271 # in short mode, we only diff the files included in the
1272 1272 # patch already plus specified files
1273 1273 if opts.get('short'):
1274 1274 # if amending a patch, we start with existing
1275 1275 # files plus specified files - unfiltered
1276 1276 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1277 1277 # filter with inc/exl options
1278 1278 matchfn = cmdutil.match(repo, opts=opts)
1279 1279 else:
1280 1280 match = cmdutil.matchall(repo)
1281 1281 m, a, r, d = repo.status(match=match)[:4]
1282 1282
1283 1283 # we might end up with files that were added between
1284 1284 # qtip and the dirstate parent, but then changed in the
1285 1285 # local dirstate. in this case, we want them to only
1286 1286 # show up in the added section
1287 1287 for x in m:
1288 1288 if x not in aa:
1289 1289 mm.append(x)
1290 1290 # we might end up with files added by the local dirstate that
1291 1291 # were deleted by the patch. In this case, they should only
1292 1292 # show up in the changed section.
1293 1293 for x in a:
1294 1294 if x in dd:
1295 1295 del dd[dd.index(x)]
1296 1296 mm.append(x)
1297 1297 else:
1298 1298 aa.append(x)
1299 1299 # make sure any files deleted in the local dirstate
1300 1300 # are not in the add or change column of the patch
1301 1301 forget = []
1302 1302 for x in d + r:
1303 1303 if x in aa:
1304 1304 del aa[aa.index(x)]
1305 1305 forget.append(x)
1306 1306 continue
1307 1307 elif x in mm:
1308 1308 del mm[mm.index(x)]
1309 1309 dd.append(x)
1310 1310
1311 1311 m = list(set(mm))
1312 1312 r = list(set(dd))
1313 1313 a = list(set(aa))
1314 1314 c = [filter(matchfn, l) for l in (m, a, r)]
1315 1315 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1316 1316 chunks = patch.diff(repo, patchparent, match=match,
1317 1317 changes=c, opts=diffopts)
1318 1318 for chunk in chunks:
1319 1319 patchf.write(chunk)
1320 1320
1321 1321 try:
1322 1322 if diffopts.git or diffopts.upgrade:
1323 1323 copies = {}
1324 1324 for dst in a:
1325 1325 src = repo.dirstate.copied(dst)
1326 1326 # during qfold, the source file for copies may
1327 1327 # be removed. Treat this as a simple add.
1328 1328 if src is not None and src in repo.dirstate:
1329 1329 copies.setdefault(src, []).append(dst)
1330 1330 repo.dirstate.add(dst)
1331 1331 # remember the copies between patchparent and qtip
1332 1332 for dst in aaa:
1333 1333 f = repo.file(dst)
1334 1334 src = f.renamed(man[dst])
1335 1335 if src:
1336 1336 copies.setdefault(src[0], []).extend(
1337 1337 copies.get(dst, []))
1338 1338 if dst in a:
1339 1339 copies[src[0]].append(dst)
1340 1340 # we can't copy a file created by the patch itself
1341 1341 if dst in copies:
1342 1342 del copies[dst]
1343 1343 for src, dsts in copies.iteritems():
1344 1344 for dst in dsts:
1345 1345 repo.dirstate.copy(src, dst)
1346 1346 else:
1347 1347 for dst in a:
1348 1348 repo.dirstate.add(dst)
1349 1349 # Drop useless copy information
1350 1350 for f in list(repo.dirstate.copies()):
1351 1351 repo.dirstate.copy(None, f)
1352 1352 for f in r:
1353 1353 repo.dirstate.remove(f)
1354 1354 # if the patch excludes a modified file, mark that
1355 1355 # file with mtime=0 so status can see it.
1356 1356 mm = []
1357 1357 for i in xrange(len(m)-1, -1, -1):
1358 1358 if not matchfn(m[i]):
1359 1359 mm.append(m[i])
1360 1360 del m[i]
1361 1361 for f in m:
1362 1362 repo.dirstate.normal(f)
1363 1363 for f in mm:
1364 1364 repo.dirstate.normallookup(f)
1365 1365 for f in forget:
1366 1366 repo.dirstate.forget(f)
1367 1367
1368 1368 if not msg:
1369 1369 if not ph.message:
1370 1370 message = "[mq]: %s\n" % patchfn
1371 1371 else:
1372 1372 message = "\n".join(ph.message)
1373 1373 else:
1374 1374 message = msg
1375 1375
1376 1376 user = ph.user or changes[1]
1377 1377
1378 1378 # assumes strip can roll itself back if interrupted
1379 1379 repo.dirstate.setparents(*cparents)
1380 1380 self.applied.pop()
1381 1381 self.applied_dirty = 1
1382 1382 self.strip(repo, [top], update=False,
1383 1383 backup='strip')
1384 1384 except:
1385 1385 repo.dirstate.invalidate()
1386 1386 raise
1387 1387
1388 1388 try:
1389 1389 # might be nice to attempt to roll back strip after this
1390 1390 patchf.rename()
1391 1391 n = repo.commit(message, user, ph.date, match=match,
1392 1392 force=True)
1393 1393 self.applied.append(statusentry(n, patchfn))
1394 1394 except:
1395 1395 ctx = repo[cparents[0]]
1396 1396 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1397 1397 self.save_dirty()
1398 1398 self.ui.warn(_('refresh interrupted while patch was popped! '
1399 1399 '(revert --all, qpush to recover)\n'))
1400 1400 raise
1401 1401 finally:
1402 1402 wlock.release()
1403 1403 self.removeundo(repo)
1404 1404
1405 1405 def init(self, repo, create=False):
1406 1406 if not create and os.path.isdir(self.path):
1407 1407 raise util.Abort(_("patch queue directory already exists"))
1408 1408 try:
1409 1409 os.mkdir(self.path)
1410 1410 except OSError, inst:
1411 1411 if inst.errno != errno.EEXIST or not create:
1412 1412 raise
1413 1413 if create:
1414 1414 return self.qrepo(create=True)
1415 1415
1416 1416 def unapplied(self, repo, patch=None):
1417 1417 if patch and patch not in self.series:
1418 1418 raise util.Abort(_("patch %s is not in series file") % patch)
1419 1419 if not patch:
1420 1420 start = self.series_end()
1421 1421 else:
1422 1422 start = self.series.index(patch) + 1
1423 1423 unapplied = []
1424 1424 for i in xrange(start, len(self.series)):
1425 1425 pushable, reason = self.pushable(i)
1426 1426 if pushable:
1427 1427 unapplied.append((i, self.series[i]))
1428 1428 self.explain_pushable(i)
1429 1429 return unapplied
1430 1430
1431 1431 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1432 1432 summary=False):
1433 1433 def displayname(pfx, patchname, state):
1434 1434 if pfx:
1435 1435 self.ui.write(pfx)
1436 1436 if summary:
1437 1437 ph = patchheader(self.join(patchname), self.plainmode)
1438 1438 msg = ph.message and ph.message[0] or ''
1439 1439 if self.ui.formatted():
1440 width = util.termwidth() - len(pfx) - len(patchname) - 2
1440 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1441 1441 if width > 0:
1442 1442 msg = util.ellipsis(msg, width)
1443 1443 else:
1444 1444 msg = ''
1445 1445 self.ui.write(patchname, label='qseries.' + state)
1446 1446 self.ui.write(': ')
1447 1447 self.ui.write(msg, label='qseries.message.' + state)
1448 1448 else:
1449 1449 self.ui.write(patchname, label='qseries.' + state)
1450 1450 self.ui.write('\n')
1451 1451
1452 1452 applied = set([p.name for p in self.applied])
1453 1453 if length is None:
1454 1454 length = len(self.series) - start
1455 1455 if not missing:
1456 1456 if self.ui.verbose:
1457 1457 idxwidth = len(str(start + length - 1))
1458 1458 for i in xrange(start, start + length):
1459 1459 patch = self.series[i]
1460 1460 if patch in applied:
1461 1461 char, state = 'A', 'applied'
1462 1462 elif self.pushable(i)[0]:
1463 1463 char, state = 'U', 'unapplied'
1464 1464 else:
1465 1465 char, state = 'G', 'guarded'
1466 1466 pfx = ''
1467 1467 if self.ui.verbose:
1468 1468 pfx = '%*d %s ' % (idxwidth, i, char)
1469 1469 elif status and status != char:
1470 1470 continue
1471 1471 displayname(pfx, patch, state)
1472 1472 else:
1473 1473 msng_list = []
1474 1474 for root, dirs, files in os.walk(self.path):
1475 1475 d = root[len(self.path) + 1:]
1476 1476 for f in files:
1477 1477 fl = os.path.join(d, f)
1478 1478 if (fl not in self.series and
1479 1479 fl not in (self.status_path, self.series_path,
1480 1480 self.guards_path)
1481 1481 and not fl.startswith('.')):
1482 1482 msng_list.append(fl)
1483 1483 for x in sorted(msng_list):
1484 1484 pfx = self.ui.verbose and ('D ') or ''
1485 1485 displayname(pfx, x, 'missing')
1486 1486
1487 1487 def issaveline(self, l):
1488 1488 if l.name == '.hg.patches.save.line':
1489 1489 return True
1490 1490
1491 1491 def qrepo(self, create=False):
1492 1492 ui = self.ui.copy()
1493 1493 ui.setconfig('paths', 'default', '', overlay=False)
1494 1494 ui.setconfig('paths', 'default-push', '', overlay=False)
1495 1495 if create or os.path.isdir(self.join(".hg")):
1496 1496 return hg.repository(ui, path=self.path, create=create)
1497 1497
1498 1498 def restore(self, repo, rev, delete=None, qupdate=None):
1499 1499 desc = repo[rev].description().strip()
1500 1500 lines = desc.splitlines()
1501 1501 i = 0
1502 1502 datastart = None
1503 1503 series = []
1504 1504 applied = []
1505 1505 qpp = None
1506 1506 for i, line in enumerate(lines):
1507 1507 if line == 'Patch Data:':
1508 1508 datastart = i + 1
1509 1509 elif line.startswith('Dirstate:'):
1510 1510 l = line.rstrip()
1511 1511 l = l[10:].split(' ')
1512 1512 qpp = [bin(x) for x in l]
1513 1513 elif datastart != None:
1514 1514 l = line.rstrip()
1515 1515 n, name = l.split(':', 1)
1516 1516 if n:
1517 1517 applied.append(statusentry(bin(n), name))
1518 1518 else:
1519 1519 series.append(l)
1520 1520 if datastart is None:
1521 1521 self.ui.warn(_("No saved patch data found\n"))
1522 1522 return 1
1523 1523 self.ui.warn(_("restoring status: %s\n") % lines[0])
1524 1524 self.full_series = series
1525 1525 self.applied = applied
1526 1526 self.parse_series()
1527 1527 self.series_dirty = 1
1528 1528 self.applied_dirty = 1
1529 1529 heads = repo.changelog.heads()
1530 1530 if delete:
1531 1531 if rev not in heads:
1532 1532 self.ui.warn(_("save entry has children, leaving it alone\n"))
1533 1533 else:
1534 1534 self.ui.warn(_("removing save entry %s\n") % short(rev))
1535 1535 pp = repo.dirstate.parents()
1536 1536 if rev in pp:
1537 1537 update = True
1538 1538 else:
1539 1539 update = False
1540 1540 self.strip(repo, [rev], update=update, backup='strip')
1541 1541 if qpp:
1542 1542 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1543 1543 (short(qpp[0]), short(qpp[1])))
1544 1544 if qupdate:
1545 1545 self.ui.status(_("queue directory updating\n"))
1546 1546 r = self.qrepo()
1547 1547 if not r:
1548 1548 self.ui.warn(_("Unable to load queue repository\n"))
1549 1549 return 1
1550 1550 hg.clean(r, qpp[0])
1551 1551
1552 1552 def save(self, repo, msg=None):
1553 1553 if not self.applied:
1554 1554 self.ui.warn(_("save: no patches applied, exiting\n"))
1555 1555 return 1
1556 1556 if self.issaveline(self.applied[-1]):
1557 1557 self.ui.warn(_("status is already saved\n"))
1558 1558 return 1
1559 1559
1560 1560 if not msg:
1561 1561 msg = _("hg patches saved state")
1562 1562 else:
1563 1563 msg = "hg patches: " + msg.rstrip('\r\n')
1564 1564 r = self.qrepo()
1565 1565 if r:
1566 1566 pp = r.dirstate.parents()
1567 1567 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1568 1568 msg += "\n\nPatch Data:\n"
1569 1569 msg += ''.join('%s\n' % x for x in self.applied)
1570 1570 msg += ''.join(':%s\n' % x for x in self.full_series)
1571 1571 n = repo.commit(msg, force=True)
1572 1572 if not n:
1573 1573 self.ui.warn(_("repo commit failed\n"))
1574 1574 return 1
1575 1575 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1576 1576 self.applied_dirty = 1
1577 1577 self.removeundo(repo)
1578 1578
1579 1579 def full_series_end(self):
1580 1580 if self.applied:
1581 1581 p = self.applied[-1].name
1582 1582 end = self.find_series(p)
1583 1583 if end is None:
1584 1584 return len(self.full_series)
1585 1585 return end + 1
1586 1586 return 0
1587 1587
1588 1588 def series_end(self, all_patches=False):
1589 1589 """If all_patches is False, return the index of the next pushable patch
1590 1590 in the series, or the series length. If all_patches is True, return the
1591 1591 index of the first patch past the last applied one.
1592 1592 """
1593 1593 end = 0
1594 1594 def next(start):
1595 1595 if all_patches or start >= len(self.series):
1596 1596 return start
1597 1597 for i in xrange(start, len(self.series)):
1598 1598 p, reason = self.pushable(i)
1599 1599 if p:
1600 1600 break
1601 1601 self.explain_pushable(i)
1602 1602 return i
1603 1603 if self.applied:
1604 1604 p = self.applied[-1].name
1605 1605 try:
1606 1606 end = self.series.index(p)
1607 1607 except ValueError:
1608 1608 return 0
1609 1609 return next(end + 1)
1610 1610 return next(end)
1611 1611
1612 1612 def appliedname(self, index):
1613 1613 pname = self.applied[index].name
1614 1614 if not self.ui.verbose:
1615 1615 p = pname
1616 1616 else:
1617 1617 p = str(self.series.index(pname)) + " " + pname
1618 1618 return p
1619 1619
1620 1620 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1621 1621 force=None, git=False):
1622 1622 def checkseries(patchname):
1623 1623 if patchname in self.series:
1624 1624 raise util.Abort(_('patch %s is already in the series file')
1625 1625 % patchname)
1626 1626 def checkfile(patchname):
1627 1627 if not force and os.path.exists(self.join(patchname)):
1628 1628 raise util.Abort(_('patch "%s" already exists')
1629 1629 % patchname)
1630 1630
1631 1631 if rev:
1632 1632 if files:
1633 1633 raise util.Abort(_('option "-r" not valid when importing '
1634 1634 'files'))
1635 1635 rev = cmdutil.revrange(repo, rev)
1636 1636 rev.sort(reverse=True)
1637 1637 if (len(files) > 1 or len(rev) > 1) and patchname:
1638 1638 raise util.Abort(_('option "-n" not valid when importing multiple '
1639 1639 'patches'))
1640 1640 if rev:
1641 1641 # If mq patches are applied, we can only import revisions
1642 1642 # that form a linear path to qbase.
1643 1643 # Otherwise, they should form a linear path to a head.
1644 1644 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1645 1645 if len(heads) > 1:
1646 1646 raise util.Abort(_('revision %d is the root of more than one '
1647 1647 'branch') % rev[-1])
1648 1648 if self.applied:
1649 1649 base = repo.changelog.node(rev[0])
1650 1650 if base in [n.node for n in self.applied]:
1651 1651 raise util.Abort(_('revision %d is already managed')
1652 1652 % rev[0])
1653 1653 if heads != [self.applied[-1].node]:
1654 1654 raise util.Abort(_('revision %d is not the parent of '
1655 1655 'the queue') % rev[0])
1656 1656 base = repo.changelog.rev(self.applied[0].node)
1657 1657 lastparent = repo.changelog.parentrevs(base)[0]
1658 1658 else:
1659 1659 if heads != [repo.changelog.node(rev[0])]:
1660 1660 raise util.Abort(_('revision %d has unmanaged children')
1661 1661 % rev[0])
1662 1662 lastparent = None
1663 1663
1664 1664 diffopts = self.diffopts({'git': git})
1665 1665 for r in rev:
1666 1666 p1, p2 = repo.changelog.parentrevs(r)
1667 1667 n = repo.changelog.node(r)
1668 1668 if p2 != nullrev:
1669 1669 raise util.Abort(_('cannot import merge revision %d') % r)
1670 1670 if lastparent and lastparent != r:
1671 1671 raise util.Abort(_('revision %d is not the parent of %d')
1672 1672 % (r, lastparent))
1673 1673 lastparent = p1
1674 1674
1675 1675 if not patchname:
1676 1676 patchname = normname('%d.diff' % r)
1677 1677 self.check_reserved_name(patchname)
1678 1678 checkseries(patchname)
1679 1679 checkfile(patchname)
1680 1680 self.full_series.insert(0, patchname)
1681 1681
1682 1682 patchf = self.opener(patchname, "w")
1683 1683 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1684 1684 patchf.close()
1685 1685
1686 1686 se = statusentry(n, patchname)
1687 1687 self.applied.insert(0, se)
1688 1688
1689 1689 self.added.append(patchname)
1690 1690 patchname = None
1691 1691 self.parse_series()
1692 1692 self.applied_dirty = 1
1693 1693 self.series_dirty = True
1694 1694
1695 1695 for i, filename in enumerate(files):
1696 1696 if existing:
1697 1697 if filename == '-':
1698 1698 raise util.Abort(_('-e is incompatible with import from -'))
1699 1699 filename = normname(filename)
1700 1700 self.check_reserved_name(filename)
1701 1701 originpath = self.join(filename)
1702 1702 if not os.path.isfile(originpath):
1703 1703 raise util.Abort(_("patch %s does not exist") % filename)
1704 1704
1705 1705 if patchname:
1706 1706 self.check_reserved_name(patchname)
1707 1707 checkfile(patchname)
1708 1708
1709 1709 self.ui.write(_('renaming %s to %s\n')
1710 1710 % (filename, patchname))
1711 1711 util.rename(originpath, self.join(patchname))
1712 1712 else:
1713 1713 patchname = filename
1714 1714
1715 1715 else:
1716 1716 try:
1717 1717 if filename == '-':
1718 1718 if not patchname:
1719 1719 raise util.Abort(
1720 1720 _('need --name to import a patch from -'))
1721 1721 text = sys.stdin.read()
1722 1722 else:
1723 1723 text = url.open(self.ui, filename).read()
1724 1724 except (OSError, IOError):
1725 1725 raise util.Abort(_("unable to read file %s") % filename)
1726 1726 if not patchname:
1727 1727 patchname = normname(os.path.basename(filename))
1728 1728 self.check_reserved_name(patchname)
1729 1729 checkfile(patchname)
1730 1730 patchf = self.opener(patchname, "w")
1731 1731 patchf.write(text)
1732 1732 if not force:
1733 1733 checkseries(patchname)
1734 1734 if patchname not in self.series:
1735 1735 index = self.full_series_end() + i
1736 1736 self.full_series[index:index] = [patchname]
1737 1737 self.parse_series()
1738 1738 self.series_dirty = True
1739 1739 self.ui.warn(_("adding %s to series file\n") % patchname)
1740 1740 self.added.append(patchname)
1741 1741 patchname = None
1742 1742
1743 1743 def delete(ui, repo, *patches, **opts):
1744 1744 """remove patches from queue
1745 1745
1746 1746 The patches must not be applied, and at least one patch is required. With
1747 1747 -k/--keep, the patch files are preserved in the patch directory.
1748 1748
1749 1749 To stop managing a patch and move it into permanent history,
1750 1750 use the :hg:`qfinish` command."""
1751 1751 q = repo.mq
1752 1752 q.delete(repo, patches, opts)
1753 1753 q.save_dirty()
1754 1754 return 0
1755 1755
1756 1756 def applied(ui, repo, patch=None, **opts):
1757 1757 """print the patches already applied
1758 1758
1759 1759 Returns 0 on success."""
1760 1760
1761 1761 q = repo.mq
1762 1762
1763 1763 if patch:
1764 1764 if patch not in q.series:
1765 1765 raise util.Abort(_("patch %s is not in series file") % patch)
1766 1766 end = q.series.index(patch) + 1
1767 1767 else:
1768 1768 end = q.series_end(True)
1769 1769
1770 1770 if opts.get('last') and not end:
1771 1771 ui.write(_("no patches applied\n"))
1772 1772 return 1
1773 1773 elif opts.get('last') and end == 1:
1774 1774 ui.write(_("only one patch applied\n"))
1775 1775 return 1
1776 1776 elif opts.get('last'):
1777 1777 start = end - 2
1778 1778 end = 1
1779 1779 else:
1780 1780 start = 0
1781 1781
1782 1782 q.qseries(repo, length=end, start=start, status='A',
1783 1783 summary=opts.get('summary'))
1784 1784
1785 1785
1786 1786 def unapplied(ui, repo, patch=None, **opts):
1787 1787 """print the patches not yet applied
1788 1788
1789 1789 Returns 0 on success."""
1790 1790
1791 1791 q = repo.mq
1792 1792 if patch:
1793 1793 if patch not in q.series:
1794 1794 raise util.Abort(_("patch %s is not in series file") % patch)
1795 1795 start = q.series.index(patch) + 1
1796 1796 else:
1797 1797 start = q.series_end(True)
1798 1798
1799 1799 if start == len(q.series) and opts.get('first'):
1800 1800 ui.write(_("all patches applied\n"))
1801 1801 return 1
1802 1802
1803 1803 length = opts.get('first') and 1 or None
1804 1804 q.qseries(repo, start=start, length=length, status='U',
1805 1805 summary=opts.get('summary'))
1806 1806
1807 1807 def qimport(ui, repo, *filename, **opts):
1808 1808 """import a patch
1809 1809
1810 1810 The patch is inserted into the series after the last applied
1811 1811 patch. If no patches have been applied, qimport prepends the patch
1812 1812 to the series.
1813 1813
1814 1814 The patch will have the same name as its source file unless you
1815 1815 give it a new one with -n/--name.
1816 1816
1817 1817 You can register an existing patch inside the patch directory with
1818 1818 the -e/--existing flag.
1819 1819
1820 1820 With -f/--force, an existing patch of the same name will be
1821 1821 overwritten.
1822 1822
1823 1823 An existing changeset may be placed under mq control with -r/--rev
1824 1824 (e.g. qimport --rev tip -n patch will place tip under mq control).
1825 1825 With -g/--git, patches imported with --rev will use the git diff
1826 1826 format. See the diffs help topic for information on why this is
1827 1827 important for preserving rename/copy information and permission
1828 1828 changes.
1829 1829
1830 1830 To import a patch from standard input, pass - as the patch file.
1831 1831 When importing from standard input, a patch name must be specified
1832 1832 using the --name flag.
1833 1833
1834 1834 To import an existing patch while renaming it::
1835 1835
1836 1836 hg qimport -e existing-patch -n new-name
1837 1837
1838 1838 Returns 0 if import succeeded.
1839 1839 """
1840 1840 q = repo.mq
1841 1841 try:
1842 1842 q.qimport(repo, filename, patchname=opts.get('name'),
1843 1843 existing=opts.get('existing'), force=opts.get('force'),
1844 1844 rev=opts.get('rev'), git=opts.get('git'))
1845 1845 finally:
1846 1846 q.save_dirty()
1847 1847
1848 1848 if opts.get('push') and not opts.get('rev'):
1849 1849 return q.push(repo, None)
1850 1850 return 0
1851 1851
1852 1852 def qinit(ui, repo, create):
1853 1853 """initialize a new queue repository
1854 1854
1855 1855 This command also creates a series file for ordering patches, and
1856 1856 an mq-specific .hgignore file in the queue repository, to exclude
1857 1857 the status and guards files (these contain mostly transient state).
1858 1858
1859 1859 Returns 0 if initialization succeeded."""
1860 1860 q = repo.mq
1861 1861 r = q.init(repo, create)
1862 1862 q.save_dirty()
1863 1863 if r:
1864 1864 if not os.path.exists(r.wjoin('.hgignore')):
1865 1865 fp = r.wopener('.hgignore', 'w')
1866 1866 fp.write('^\\.hg\n')
1867 1867 fp.write('^\\.mq\n')
1868 1868 fp.write('syntax: glob\n')
1869 1869 fp.write('status\n')
1870 1870 fp.write('guards\n')
1871 1871 fp.close()
1872 1872 if not os.path.exists(r.wjoin('series')):
1873 1873 r.wopener('series', 'w').close()
1874 1874 r[None].add(['.hgignore', 'series'])
1875 1875 commands.add(ui, r)
1876 1876 return 0
1877 1877
1878 1878 def init(ui, repo, **opts):
1879 1879 """init a new queue repository (DEPRECATED)
1880 1880
1881 1881 The queue repository is unversioned by default. If
1882 1882 -c/--create-repo is specified, qinit will create a separate nested
1883 1883 repository for patches (qinit -c may also be run later to convert
1884 1884 an unversioned patch repository into a versioned one). You can use
1885 1885 qcommit to commit changes to this queue repository.
1886 1886
1887 1887 This command is deprecated. Without -c, it's implied by other relevant
1888 1888 commands. With -c, use :hg:`init --mq` instead."""
1889 1889 return qinit(ui, repo, create=opts.get('create_repo'))
1890 1890
1891 1891 def clone(ui, source, dest=None, **opts):
1892 1892 '''clone main and patch repository at same time
1893 1893
1894 1894 If source is local, destination will have no patches applied. If
1895 1895 source is remote, this command can not check if patches are
1896 1896 applied in source, so cannot guarantee that patches are not
1897 1897 applied in destination. If you clone remote repository, be sure
1898 1898 before that it has no patches applied.
1899 1899
1900 1900 Source patch repository is looked for in <src>/.hg/patches by
1901 1901 default. Use -p <url> to change.
1902 1902
1903 1903 The patch directory must be a nested Mercurial repository, as
1904 1904 would be created by :hg:`init --mq`.
1905 1905
1906 1906 Return 0 on success.
1907 1907 '''
1908 1908 def patchdir(repo):
1909 1909 url = repo.url()
1910 1910 if url.endswith('/'):
1911 1911 url = url[:-1]
1912 1912 return url + '/.hg/patches'
1913 1913 if dest is None:
1914 1914 dest = hg.defaultdest(source)
1915 1915 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
1916 1916 if opts.get('patches'):
1917 1917 patchespath = ui.expandpath(opts.get('patches'))
1918 1918 else:
1919 1919 patchespath = patchdir(sr)
1920 1920 try:
1921 1921 hg.repository(ui, patchespath)
1922 1922 except error.RepoError:
1923 1923 raise util.Abort(_('versioned patch repository not found'
1924 1924 ' (see init --mq)'))
1925 1925 qbase, destrev = None, None
1926 1926 if sr.local():
1927 1927 if sr.mq.applied:
1928 1928 qbase = sr.mq.applied[0].node
1929 1929 if not hg.islocal(dest):
1930 1930 heads = set(sr.heads())
1931 1931 destrev = list(heads.difference(sr.heads(qbase)))
1932 1932 destrev.append(sr.changelog.parents(qbase)[0])
1933 1933 elif sr.capable('lookup'):
1934 1934 try:
1935 1935 qbase = sr.lookup('qbase')
1936 1936 except error.RepoError:
1937 1937 pass
1938 1938 ui.note(_('cloning main repository\n'))
1939 1939 sr, dr = hg.clone(ui, sr.url(), dest,
1940 1940 pull=opts.get('pull'),
1941 1941 rev=destrev,
1942 1942 update=False,
1943 1943 stream=opts.get('uncompressed'))
1944 1944 ui.note(_('cloning patch repository\n'))
1945 1945 hg.clone(ui, opts.get('patches') or patchdir(sr), patchdir(dr),
1946 1946 pull=opts.get('pull'), update=not opts.get('noupdate'),
1947 1947 stream=opts.get('uncompressed'))
1948 1948 if dr.local():
1949 1949 if qbase:
1950 1950 ui.note(_('stripping applied patches from destination '
1951 1951 'repository\n'))
1952 1952 dr.mq.strip(dr, [qbase], update=False, backup=None)
1953 1953 if not opts.get('noupdate'):
1954 1954 ui.note(_('updating destination repository\n'))
1955 1955 hg.update(dr, dr.changelog.tip())
1956 1956
1957 1957 def commit(ui, repo, *pats, **opts):
1958 1958 """commit changes in the queue repository (DEPRECATED)
1959 1959
1960 1960 This command is deprecated; use :hg:`commit --mq` instead."""
1961 1961 q = repo.mq
1962 1962 r = q.qrepo()
1963 1963 if not r:
1964 1964 raise util.Abort('no queue repository')
1965 1965 commands.commit(r.ui, r, *pats, **opts)
1966 1966
1967 1967 def series(ui, repo, **opts):
1968 1968 """print the entire series file
1969 1969
1970 1970 Returns 0 on success."""
1971 1971 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
1972 1972 return 0
1973 1973
1974 1974 def top(ui, repo, **opts):
1975 1975 """print the name of the current patch
1976 1976
1977 1977 Returns 0 on success."""
1978 1978 q = repo.mq
1979 1979 t = q.applied and q.series_end(True) or 0
1980 1980 if t:
1981 1981 q.qseries(repo, start=t - 1, length=1, status='A',
1982 1982 summary=opts.get('summary'))
1983 1983 else:
1984 1984 ui.write(_("no patches applied\n"))
1985 1985 return 1
1986 1986
1987 1987 def next(ui, repo, **opts):
1988 1988 """print the name of the next patch
1989 1989
1990 1990 Returns 0 on success."""
1991 1991 q = repo.mq
1992 1992 end = q.series_end()
1993 1993 if end == len(q.series):
1994 1994 ui.write(_("all patches applied\n"))
1995 1995 return 1
1996 1996 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1997 1997
1998 1998 def prev(ui, repo, **opts):
1999 1999 """print the name of the previous patch
2000 2000
2001 2001 Returns 0 on success."""
2002 2002 q = repo.mq
2003 2003 l = len(q.applied)
2004 2004 if l == 1:
2005 2005 ui.write(_("only one patch applied\n"))
2006 2006 return 1
2007 2007 if not l:
2008 2008 ui.write(_("no patches applied\n"))
2009 2009 return 1
2010 2010 q.qseries(repo, start=l - 2, length=1, status='A',
2011 2011 summary=opts.get('summary'))
2012 2012
2013 2013 def setupheaderopts(ui, opts):
2014 2014 if not opts.get('user') and opts.get('currentuser'):
2015 2015 opts['user'] = ui.username()
2016 2016 if not opts.get('date') and opts.get('currentdate'):
2017 2017 opts['date'] = "%d %d" % util.makedate()
2018 2018
2019 2019 def new(ui, repo, patch, *args, **opts):
2020 2020 """create a new patch
2021 2021
2022 2022 qnew creates a new patch on top of the currently-applied patch (if
2023 2023 any). The patch will be initialized with any outstanding changes
2024 2024 in the working directory. You may also use -I/--include,
2025 2025 -X/--exclude, and/or a list of files after the patch name to add
2026 2026 only changes to matching files to the new patch, leaving the rest
2027 2027 as uncommitted modifications.
2028 2028
2029 2029 -u/--user and -d/--date can be used to set the (given) user and
2030 2030 date, respectively. -U/--currentuser and -D/--currentdate set user
2031 2031 to current user and date to current date.
2032 2032
2033 2033 -e/--edit, -m/--message or -l/--logfile set the patch header as
2034 2034 well as the commit message. If none is specified, the header is
2035 2035 empty and the commit message is '[mq]: PATCH'.
2036 2036
2037 2037 Use the -g/--git option to keep the patch in the git extended diff
2038 2038 format. Read the diffs help topic for more information on why this
2039 2039 is important for preserving permission changes and copy/rename
2040 2040 information.
2041 2041
2042 2042 Returns 0 on successful creation of a new patch.
2043 2043 """
2044 2044 msg = cmdutil.logmessage(opts)
2045 2045 def getmsg():
2046 2046 return ui.edit(msg, opts.get('user') or ui.username())
2047 2047 q = repo.mq
2048 2048 opts['msg'] = msg
2049 2049 if opts.get('edit'):
2050 2050 opts['msg'] = getmsg
2051 2051 else:
2052 2052 opts['msg'] = msg
2053 2053 setupheaderopts(ui, opts)
2054 2054 q.new(repo, patch, *args, **opts)
2055 2055 q.save_dirty()
2056 2056 return 0
2057 2057
2058 2058 def refresh(ui, repo, *pats, **opts):
2059 2059 """update the current patch
2060 2060
2061 2061 If any file patterns are provided, the refreshed patch will
2062 2062 contain only the modifications that match those patterns; the
2063 2063 remaining modifications will remain in the working directory.
2064 2064
2065 2065 If -s/--short is specified, files currently included in the patch
2066 2066 will be refreshed just like matched files and remain in the patch.
2067 2067
2068 2068 If -e/--edit is specified, Mercurial will start your configured editor for
2069 2069 you to enter a message. In case qrefresh fails, you will find a backup of
2070 2070 your message in ``.hg/last-message.txt``.
2071 2071
2072 2072 hg add/remove/copy/rename work as usual, though you might want to
2073 2073 use git-style patches (-g/--git or [diff] git=1) to track copies
2074 2074 and renames. See the diffs help topic for more information on the
2075 2075 git diff format.
2076 2076
2077 2077 Returns 0 on success.
2078 2078 """
2079 2079 q = repo.mq
2080 2080 message = cmdutil.logmessage(opts)
2081 2081 if opts.get('edit'):
2082 2082 if not q.applied:
2083 2083 ui.write(_("no patches applied\n"))
2084 2084 return 1
2085 2085 if message:
2086 2086 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2087 2087 patch = q.applied[-1].name
2088 2088 ph = patchheader(q.join(patch), q.plainmode)
2089 2089 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2090 2090 # We don't want to lose the patch message if qrefresh fails (issue2062)
2091 2091 msgfile = repo.opener('last-message.txt', 'wb')
2092 2092 msgfile.write(message)
2093 2093 msgfile.close()
2094 2094 setupheaderopts(ui, opts)
2095 2095 ret = q.refresh(repo, pats, msg=message, **opts)
2096 2096 q.save_dirty()
2097 2097 return ret
2098 2098
2099 2099 def diff(ui, repo, *pats, **opts):
2100 2100 """diff of the current patch and subsequent modifications
2101 2101
2102 2102 Shows a diff which includes the current patch as well as any
2103 2103 changes which have been made in the working directory since the
2104 2104 last refresh (thus showing what the current patch would become
2105 2105 after a qrefresh).
2106 2106
2107 2107 Use :hg:`diff` if you only want to see the changes made since the
2108 2108 last qrefresh, or :hg:`export qtip` if you want to see changes
2109 2109 made by the current patch without including changes made since the
2110 2110 qrefresh.
2111 2111
2112 2112 Returns 0 on success.
2113 2113 """
2114 2114 repo.mq.diff(repo, pats, opts)
2115 2115 return 0
2116 2116
2117 2117 def fold(ui, repo, *files, **opts):
2118 2118 """fold the named patches into the current patch
2119 2119
2120 2120 Patches must not yet be applied. Each patch will be successively
2121 2121 applied to the current patch in the order given. If all the
2122 2122 patches apply successfully, the current patch will be refreshed
2123 2123 with the new cumulative patch, and the folded patches will be
2124 2124 deleted. With -k/--keep, the folded patch files will not be
2125 2125 removed afterwards.
2126 2126
2127 2127 The header for each folded patch will be concatenated with the
2128 2128 current patch header, separated by a line of '* * *'.
2129 2129
2130 2130 Returns 0 on success."""
2131 2131
2132 2132 q = repo.mq
2133 2133
2134 2134 if not files:
2135 2135 raise util.Abort(_('qfold requires at least one patch name'))
2136 2136 if not q.check_toppatch(repo)[0]:
2137 2137 raise util.Abort(_('no patches applied'))
2138 2138 q.check_localchanges(repo)
2139 2139
2140 2140 message = cmdutil.logmessage(opts)
2141 2141 if opts.get('edit'):
2142 2142 if message:
2143 2143 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2144 2144
2145 2145 parent = q.lookup('qtip')
2146 2146 patches = []
2147 2147 messages = []
2148 2148 for f in files:
2149 2149 p = q.lookup(f)
2150 2150 if p in patches or p == parent:
2151 2151 ui.warn(_('Skipping already folded patch %s\n') % p)
2152 2152 if q.isapplied(p):
2153 2153 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2154 2154 patches.append(p)
2155 2155
2156 2156 for p in patches:
2157 2157 if not message:
2158 2158 ph = patchheader(q.join(p), q.plainmode)
2159 2159 if ph.message:
2160 2160 messages.append(ph.message)
2161 2161 pf = q.join(p)
2162 2162 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2163 2163 if not patchsuccess:
2164 2164 raise util.Abort(_('error folding patch %s') % p)
2165 2165 cmdutil.updatedir(ui, repo, files)
2166 2166
2167 2167 if not message:
2168 2168 ph = patchheader(q.join(parent), q.plainmode)
2169 2169 message, user = ph.message, ph.user
2170 2170 for msg in messages:
2171 2171 message.append('* * *')
2172 2172 message.extend(msg)
2173 2173 message = '\n'.join(message)
2174 2174
2175 2175 if opts.get('edit'):
2176 2176 message = ui.edit(message, user or ui.username())
2177 2177
2178 2178 diffopts = q.patchopts(q.diffopts(), *patches)
2179 2179 q.refresh(repo, msg=message, git=diffopts.git)
2180 2180 q.delete(repo, patches, opts)
2181 2181 q.save_dirty()
2182 2182
2183 2183 def goto(ui, repo, patch, **opts):
2184 2184 '''push or pop patches until named patch is at top of stack
2185 2185
2186 2186 Returns 0 on success.'''
2187 2187 q = repo.mq
2188 2188 patch = q.lookup(patch)
2189 2189 if q.isapplied(patch):
2190 2190 ret = q.pop(repo, patch, force=opts.get('force'))
2191 2191 else:
2192 2192 ret = q.push(repo, patch, force=opts.get('force'))
2193 2193 q.save_dirty()
2194 2194 return ret
2195 2195
2196 2196 def guard(ui, repo, *args, **opts):
2197 2197 '''set or print guards for a patch
2198 2198
2199 2199 Guards control whether a patch can be pushed. A patch with no
2200 2200 guards is always pushed. A patch with a positive guard ("+foo") is
2201 2201 pushed only if the :hg:`qselect` command has activated it. A patch with
2202 2202 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2203 2203 has activated it.
2204 2204
2205 2205 With no arguments, print the currently active guards.
2206 2206 With arguments, set guards for the named patch.
2207 2207
2208 2208 .. note::
2209 2209 Specifying negative guards now requires '--'.
2210 2210
2211 2211 To set guards on another patch::
2212 2212
2213 2213 hg qguard other.patch -- +2.6.17 -stable
2214 2214
2215 2215 Returns 0 on success.
2216 2216 '''
2217 2217 def status(idx):
2218 2218 guards = q.series_guards[idx] or ['unguarded']
2219 2219 if q.series[idx] in applied:
2220 2220 state = 'applied'
2221 2221 elif q.pushable(idx)[0]:
2222 2222 state = 'unapplied'
2223 2223 else:
2224 2224 state = 'guarded'
2225 2225 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2226 2226 ui.write('%s: ' % ui.label(q.series[idx], label))
2227 2227
2228 2228 for i, guard in enumerate(guards):
2229 2229 if guard.startswith('+'):
2230 2230 ui.write(guard, label='qguard.positive')
2231 2231 elif guard.startswith('-'):
2232 2232 ui.write(guard, label='qguard.negative')
2233 2233 else:
2234 2234 ui.write(guard, label='qguard.unguarded')
2235 2235 if i != len(guards) - 1:
2236 2236 ui.write(' ')
2237 2237 ui.write('\n')
2238 2238 q = repo.mq
2239 2239 applied = set(p.name for p in q.applied)
2240 2240 patch = None
2241 2241 args = list(args)
2242 2242 if opts.get('list'):
2243 2243 if args or opts.get('none'):
2244 2244 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2245 2245 for i in xrange(len(q.series)):
2246 2246 status(i)
2247 2247 return
2248 2248 if not args or args[0][0:1] in '-+':
2249 2249 if not q.applied:
2250 2250 raise util.Abort(_('no patches applied'))
2251 2251 patch = q.applied[-1].name
2252 2252 if patch is None and args[0][0:1] not in '-+':
2253 2253 patch = args.pop(0)
2254 2254 if patch is None:
2255 2255 raise util.Abort(_('no patch to work with'))
2256 2256 if args or opts.get('none'):
2257 2257 idx = q.find_series(patch)
2258 2258 if idx is None:
2259 2259 raise util.Abort(_('no patch named %s') % patch)
2260 2260 q.set_guards(idx, args)
2261 2261 q.save_dirty()
2262 2262 else:
2263 2263 status(q.series.index(q.lookup(patch)))
2264 2264
2265 2265 def header(ui, repo, patch=None):
2266 2266 """print the header of the topmost or specified patch
2267 2267
2268 2268 Returns 0 on success."""
2269 2269 q = repo.mq
2270 2270
2271 2271 if patch:
2272 2272 patch = q.lookup(patch)
2273 2273 else:
2274 2274 if not q.applied:
2275 2275 ui.write(_('no patches applied\n'))
2276 2276 return 1
2277 2277 patch = q.lookup('qtip')
2278 2278 ph = patchheader(q.join(patch), q.plainmode)
2279 2279
2280 2280 ui.write('\n'.join(ph.message) + '\n')
2281 2281
2282 2282 def lastsavename(path):
2283 2283 (directory, base) = os.path.split(path)
2284 2284 names = os.listdir(directory)
2285 2285 namere = re.compile("%s.([0-9]+)" % base)
2286 2286 maxindex = None
2287 2287 maxname = None
2288 2288 for f in names:
2289 2289 m = namere.match(f)
2290 2290 if m:
2291 2291 index = int(m.group(1))
2292 2292 if maxindex is None or index > maxindex:
2293 2293 maxindex = index
2294 2294 maxname = f
2295 2295 if maxname:
2296 2296 return (os.path.join(directory, maxname), maxindex)
2297 2297 return (None, None)
2298 2298
2299 2299 def savename(path):
2300 2300 (last, index) = lastsavename(path)
2301 2301 if last is None:
2302 2302 index = 0
2303 2303 newpath = path + ".%d" % (index + 1)
2304 2304 return newpath
2305 2305
2306 2306 def push(ui, repo, patch=None, **opts):
2307 2307 """push the next patch onto the stack
2308 2308
2309 2309 When -f/--force is applied, all local changes in patched files
2310 2310 will be lost.
2311 2311
2312 2312 Return 0 on succces.
2313 2313 """
2314 2314 q = repo.mq
2315 2315 mergeq = None
2316 2316
2317 2317 if opts.get('merge'):
2318 2318 if opts.get('name'):
2319 2319 newpath = repo.join(opts.get('name'))
2320 2320 else:
2321 2321 newpath, i = lastsavename(q.path)
2322 2322 if not newpath:
2323 2323 ui.warn(_("no saved queues found, please use -n\n"))
2324 2324 return 1
2325 2325 mergeq = queue(ui, repo.join(""), newpath)
2326 2326 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2327 2327 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2328 2328 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'))
2329 2329 return ret
2330 2330
2331 2331 def pop(ui, repo, patch=None, **opts):
2332 2332 """pop the current patch off the stack
2333 2333
2334 2334 By default, pops off the top of the patch stack. If given a patch
2335 2335 name, keeps popping off patches until the named patch is at the
2336 2336 top of the stack.
2337 2337
2338 2338 Return 0 on success.
2339 2339 """
2340 2340 localupdate = True
2341 2341 if opts.get('name'):
2342 2342 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2343 2343 ui.warn(_('using patch queue: %s\n') % q.path)
2344 2344 localupdate = False
2345 2345 else:
2346 2346 q = repo.mq
2347 2347 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2348 2348 all=opts.get('all'))
2349 2349 q.save_dirty()
2350 2350 return ret
2351 2351
2352 2352 def rename(ui, repo, patch, name=None, **opts):
2353 2353 """rename a patch
2354 2354
2355 2355 With one argument, renames the current patch to PATCH1.
2356 2356 With two arguments, renames PATCH1 to PATCH2.
2357 2357
2358 2358 Returns 0 on success."""
2359 2359
2360 2360 q = repo.mq
2361 2361
2362 2362 if not name:
2363 2363 name = patch
2364 2364 patch = None
2365 2365
2366 2366 if patch:
2367 2367 patch = q.lookup(patch)
2368 2368 else:
2369 2369 if not q.applied:
2370 2370 ui.write(_('no patches applied\n'))
2371 2371 return
2372 2372 patch = q.lookup('qtip')
2373 2373 absdest = q.join(name)
2374 2374 if os.path.isdir(absdest):
2375 2375 name = normname(os.path.join(name, os.path.basename(patch)))
2376 2376 absdest = q.join(name)
2377 2377 if os.path.exists(absdest):
2378 2378 raise util.Abort(_('%s already exists') % absdest)
2379 2379
2380 2380 if name in q.series:
2381 2381 raise util.Abort(
2382 2382 _('A patch named %s already exists in the series file') % name)
2383 2383
2384 2384 ui.note(_('renaming %s to %s\n') % (patch, name))
2385 2385 i = q.find_series(patch)
2386 2386 guards = q.guard_re.findall(q.full_series[i])
2387 2387 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2388 2388 q.parse_series()
2389 2389 q.series_dirty = 1
2390 2390
2391 2391 info = q.isapplied(patch)
2392 2392 if info:
2393 2393 q.applied[info[0]] = statusentry(info[1], name)
2394 2394 q.applied_dirty = 1
2395 2395
2396 2396 destdir = os.path.dirname(absdest)
2397 2397 if not os.path.isdir(destdir):
2398 2398 os.makedirs(destdir)
2399 2399 util.rename(q.join(patch), absdest)
2400 2400 r = q.qrepo()
2401 2401 if r:
2402 2402 wctx = r[None]
2403 2403 wlock = r.wlock()
2404 2404 try:
2405 2405 if r.dirstate[patch] == 'a':
2406 2406 r.dirstate.forget(patch)
2407 2407 r.dirstate.add(name)
2408 2408 else:
2409 2409 if r.dirstate[name] == 'r':
2410 2410 wctx.undelete([name])
2411 2411 wctx.copy(patch, name)
2412 2412 wctx.remove([patch], False)
2413 2413 finally:
2414 2414 wlock.release()
2415 2415
2416 2416 q.save_dirty()
2417 2417
2418 2418 def restore(ui, repo, rev, **opts):
2419 2419 """restore the queue state saved by a revision (DEPRECATED)
2420 2420
2421 2421 This command is deprecated, use :hg:`rebase` instead."""
2422 2422 rev = repo.lookup(rev)
2423 2423 q = repo.mq
2424 2424 q.restore(repo, rev, delete=opts.get('delete'),
2425 2425 qupdate=opts.get('update'))
2426 2426 q.save_dirty()
2427 2427 return 0
2428 2428
2429 2429 def save(ui, repo, **opts):
2430 2430 """save current queue state (DEPRECATED)
2431 2431
2432 2432 This command is deprecated, use :hg:`rebase` instead."""
2433 2433 q = repo.mq
2434 2434 message = cmdutil.logmessage(opts)
2435 2435 ret = q.save(repo, msg=message)
2436 2436 if ret:
2437 2437 return ret
2438 2438 q.save_dirty()
2439 2439 if opts.get('copy'):
2440 2440 path = q.path
2441 2441 if opts.get('name'):
2442 2442 newpath = os.path.join(q.basepath, opts.get('name'))
2443 2443 if os.path.exists(newpath):
2444 2444 if not os.path.isdir(newpath):
2445 2445 raise util.Abort(_('destination %s exists and is not '
2446 2446 'a directory') % newpath)
2447 2447 if not opts.get('force'):
2448 2448 raise util.Abort(_('destination %s exists, '
2449 2449 'use -f to force') % newpath)
2450 2450 else:
2451 2451 newpath = savename(path)
2452 2452 ui.warn(_("copy %s to %s\n") % (path, newpath))
2453 2453 util.copyfiles(path, newpath)
2454 2454 if opts.get('empty'):
2455 2455 try:
2456 2456 os.unlink(q.join(q.status_path))
2457 2457 except:
2458 2458 pass
2459 2459 return 0
2460 2460
2461 2461 def strip(ui, repo, *revs, **opts):
2462 2462 """strip changesets and all their descendants from the repository
2463 2463
2464 2464 The strip command removes the specified changesets and all their
2465 2465 descendants. If the working directory has uncommitted changes,
2466 2466 the operation is aborted unless the --force flag is supplied.
2467 2467
2468 2468 If a parent of the working directory is stripped, then the working
2469 2469 directory will automatically be updated to the most recent
2470 2470 available ancestor of the stripped parent after the operation
2471 2471 completes.
2472 2472
2473 2473 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2474 2474 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2475 2475 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2476 2476 where BUNDLE is the bundle file created by the strip. Note that
2477 2477 the local revision numbers will in general be different after the
2478 2478 restore.
2479 2479
2480 2480 Use the --no-backup option to discard the backup bundle once the
2481 2481 operation completes.
2482 2482
2483 2483 Return 0 on success.
2484 2484 """
2485 2485 backup = 'all'
2486 2486 if opts.get('backup'):
2487 2487 backup = 'strip'
2488 2488 elif opts.get('no-backup') or opts.get('nobackup'):
2489 2489 backup = 'none'
2490 2490
2491 2491 cl = repo.changelog
2492 2492 revs = set(cl.rev(repo.lookup(r)) for r in revs)
2493 2493
2494 2494 descendants = set(cl.descendants(*revs))
2495 2495 strippedrevs = revs.union(descendants)
2496 2496 roots = revs.difference(descendants)
2497 2497
2498 2498 update = False
2499 2499 # if one of the wdir parent is stripped we'll need
2500 2500 # to update away to an earlier revision
2501 2501 for p in repo.dirstate.parents():
2502 2502 if p != nullid and cl.rev(p) in strippedrevs:
2503 2503 update = True
2504 2504 break
2505 2505
2506 2506 rootnodes = set(cl.node(r) for r in roots)
2507 2507
2508 2508 q = repo.mq
2509 2509 if q.applied:
2510 2510 # refresh queue state if we're about to strip
2511 2511 # applied patches
2512 2512 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2513 2513 q.applied_dirty = True
2514 2514 start = 0
2515 2515 end = len(q.applied)
2516 2516 for i, statusentry in enumerate(q.applied):
2517 2517 if statusentry.node in rootnodes:
2518 2518 # if one of the stripped roots is an applied
2519 2519 # patch, only part of the queue is stripped
2520 2520 start = i
2521 2521 break
2522 2522 del q.applied[start:end]
2523 2523 q.save_dirty()
2524 2524
2525 2525 revs = list(rootnodes)
2526 2526 if update and opts.get('keep'):
2527 2527 wlock = repo.wlock()
2528 2528 try:
2529 2529 urev = repo.mq.qparents(repo, revs[0])
2530 2530 repo.dirstate.rebuild(urev, repo[urev].manifest())
2531 2531 repo.dirstate.write()
2532 2532 update = False
2533 2533 finally:
2534 2534 wlock.release()
2535 2535
2536 2536 repo.mq.strip(repo, revs, backup=backup, update=update,
2537 2537 force=opts.get('force'))
2538 2538 return 0
2539 2539
2540 2540 def select(ui, repo, *args, **opts):
2541 2541 '''set or print guarded patches to push
2542 2542
2543 2543 Use the :hg:`qguard` command to set or print guards on patch, then use
2544 2544 qselect to tell mq which guards to use. A patch will be pushed if
2545 2545 it has no guards or any positive guards match the currently
2546 2546 selected guard, but will not be pushed if any negative guards
2547 2547 match the current guard. For example::
2548 2548
2549 2549 qguard foo.patch -stable (negative guard)
2550 2550 qguard bar.patch +stable (positive guard)
2551 2551 qselect stable
2552 2552
2553 2553 This activates the "stable" guard. mq will skip foo.patch (because
2554 2554 it has a negative match) but push bar.patch (because it has a
2555 2555 positive match).
2556 2556
2557 2557 With no arguments, prints the currently active guards.
2558 2558 With one argument, sets the active guard.
2559 2559
2560 2560 Use -n/--none to deactivate guards (no other arguments needed).
2561 2561 When no guards are active, patches with positive guards are
2562 2562 skipped and patches with negative guards are pushed.
2563 2563
2564 2564 qselect can change the guards on applied patches. It does not pop
2565 2565 guarded patches by default. Use --pop to pop back to the last
2566 2566 applied patch that is not guarded. Use --reapply (which implies
2567 2567 --pop) to push back to the current patch afterwards, but skip
2568 2568 guarded patches.
2569 2569
2570 2570 Use -s/--series to print a list of all guards in the series file
2571 2571 (no other arguments needed). Use -v for more information.
2572 2572
2573 2573 Returns 0 on success.'''
2574 2574
2575 2575 q = repo.mq
2576 2576 guards = q.active()
2577 2577 if args or opts.get('none'):
2578 2578 old_unapplied = q.unapplied(repo)
2579 2579 old_guarded = [i for i in xrange(len(q.applied)) if
2580 2580 not q.pushable(i)[0]]
2581 2581 q.set_active(args)
2582 2582 q.save_dirty()
2583 2583 if not args:
2584 2584 ui.status(_('guards deactivated\n'))
2585 2585 if not opts.get('pop') and not opts.get('reapply'):
2586 2586 unapplied = q.unapplied(repo)
2587 2587 guarded = [i for i in xrange(len(q.applied))
2588 2588 if not q.pushable(i)[0]]
2589 2589 if len(unapplied) != len(old_unapplied):
2590 2590 ui.status(_('number of unguarded, unapplied patches has '
2591 2591 'changed from %d to %d\n') %
2592 2592 (len(old_unapplied), len(unapplied)))
2593 2593 if len(guarded) != len(old_guarded):
2594 2594 ui.status(_('number of guarded, applied patches has changed '
2595 2595 'from %d to %d\n') %
2596 2596 (len(old_guarded), len(guarded)))
2597 2597 elif opts.get('series'):
2598 2598 guards = {}
2599 2599 noguards = 0
2600 2600 for gs in q.series_guards:
2601 2601 if not gs:
2602 2602 noguards += 1
2603 2603 for g in gs:
2604 2604 guards.setdefault(g, 0)
2605 2605 guards[g] += 1
2606 2606 if ui.verbose:
2607 2607 guards['NONE'] = noguards
2608 2608 guards = guards.items()
2609 2609 guards.sort(key=lambda x: x[0][1:])
2610 2610 if guards:
2611 2611 ui.note(_('guards in series file:\n'))
2612 2612 for guard, count in guards:
2613 2613 ui.note('%2d ' % count)
2614 2614 ui.write(guard, '\n')
2615 2615 else:
2616 2616 ui.note(_('no guards in series file\n'))
2617 2617 else:
2618 2618 if guards:
2619 2619 ui.note(_('active guards:\n'))
2620 2620 for g in guards:
2621 2621 ui.write(g, '\n')
2622 2622 else:
2623 2623 ui.write(_('no active guards\n'))
2624 2624 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2625 2625 popped = False
2626 2626 if opts.get('pop') or opts.get('reapply'):
2627 2627 for i in xrange(len(q.applied)):
2628 2628 pushable, reason = q.pushable(i)
2629 2629 if not pushable:
2630 2630 ui.status(_('popping guarded patches\n'))
2631 2631 popped = True
2632 2632 if i == 0:
2633 2633 q.pop(repo, all=True)
2634 2634 else:
2635 2635 q.pop(repo, i - 1)
2636 2636 break
2637 2637 if popped:
2638 2638 try:
2639 2639 if reapply:
2640 2640 ui.status(_('reapplying unguarded patches\n'))
2641 2641 q.push(repo, reapply)
2642 2642 finally:
2643 2643 q.save_dirty()
2644 2644
2645 2645 def finish(ui, repo, *revrange, **opts):
2646 2646 """move applied patches into repository history
2647 2647
2648 2648 Finishes the specified revisions (corresponding to applied
2649 2649 patches) by moving them out of mq control into regular repository
2650 2650 history.
2651 2651
2652 2652 Accepts a revision range or the -a/--applied option. If --applied
2653 2653 is specified, all applied mq revisions are removed from mq
2654 2654 control. Otherwise, the given revisions must be at the base of the
2655 2655 stack of applied patches.
2656 2656
2657 2657 This can be especially useful if your changes have been applied to
2658 2658 an upstream repository, or if you are about to push your changes
2659 2659 to upstream.
2660 2660
2661 2661 Returns 0 on success.
2662 2662 """
2663 2663 if not opts.get('applied') and not revrange:
2664 2664 raise util.Abort(_('no revisions specified'))
2665 2665 elif opts.get('applied'):
2666 2666 revrange = ('qbase::qtip',) + revrange
2667 2667
2668 2668 q = repo.mq
2669 2669 if not q.applied:
2670 2670 ui.status(_('no patches applied\n'))
2671 2671 return 0
2672 2672
2673 2673 revs = cmdutil.revrange(repo, revrange)
2674 2674 q.finish(repo, revs)
2675 2675 q.save_dirty()
2676 2676 return 0
2677 2677
2678 2678 def qqueue(ui, repo, name=None, **opts):
2679 2679 '''manage multiple patch queues
2680 2680
2681 2681 Supports switching between different patch queues, as well as creating
2682 2682 new patch queues and deleting existing ones.
2683 2683
2684 2684 Omitting a queue name or specifying -l/--list will show you the registered
2685 2685 queues - by default the "normal" patches queue is registered. The currently
2686 2686 active queue will be marked with "(active)".
2687 2687
2688 2688 To create a new queue, use -c/--create. The queue is automatically made
2689 2689 active, except in the case where there are applied patches from the
2690 2690 currently active queue in the repository. Then the queue will only be
2691 2691 created and switching will fail.
2692 2692
2693 2693 To delete an existing queue, use --delete. You cannot delete the currently
2694 2694 active queue.
2695 2695
2696 2696 Returns 0 on success.
2697 2697 '''
2698 2698
2699 2699 q = repo.mq
2700 2700
2701 2701 _defaultqueue = 'patches'
2702 2702 _allqueues = 'patches.queues'
2703 2703 _activequeue = 'patches.queue'
2704 2704
2705 2705 def _getcurrent():
2706 2706 cur = os.path.basename(q.path)
2707 2707 if cur.startswith('patches-'):
2708 2708 cur = cur[8:]
2709 2709 return cur
2710 2710
2711 2711 def _noqueues():
2712 2712 try:
2713 2713 fh = repo.opener(_allqueues, 'r')
2714 2714 fh.close()
2715 2715 except IOError:
2716 2716 return True
2717 2717
2718 2718 return False
2719 2719
2720 2720 def _getqueues():
2721 2721 current = _getcurrent()
2722 2722
2723 2723 try:
2724 2724 fh = repo.opener(_allqueues, 'r')
2725 2725 queues = [queue.strip() for queue in fh if queue.strip()]
2726 2726 if current not in queues:
2727 2727 queues.append(current)
2728 2728 except IOError:
2729 2729 queues = [_defaultqueue]
2730 2730
2731 2731 return sorted(queues)
2732 2732
2733 2733 def _setactive(name):
2734 2734 if q.applied:
2735 2735 raise util.Abort(_('patches applied - cannot set new queue active'))
2736 2736 _setactivenocheck(name)
2737 2737
2738 2738 def _setactivenocheck(name):
2739 2739 fh = repo.opener(_activequeue, 'w')
2740 2740 if name != 'patches':
2741 2741 fh.write(name)
2742 2742 fh.close()
2743 2743
2744 2744 def _addqueue(name):
2745 2745 fh = repo.opener(_allqueues, 'a')
2746 2746 fh.write('%s\n' % (name,))
2747 2747 fh.close()
2748 2748
2749 2749 def _queuedir(name):
2750 2750 if name == 'patches':
2751 2751 return repo.join('patches')
2752 2752 else:
2753 2753 return repo.join('patches-' + name)
2754 2754
2755 2755 def _validname(name):
2756 2756 for n in name:
2757 2757 if n in ':\\/.':
2758 2758 return False
2759 2759 return True
2760 2760
2761 2761 def _delete(name):
2762 2762 if name not in existing:
2763 2763 raise util.Abort(_('cannot delete queue that does not exist'))
2764 2764
2765 2765 current = _getcurrent()
2766 2766
2767 2767 if name == current:
2768 2768 raise util.Abort(_('cannot delete currently active queue'))
2769 2769
2770 2770 fh = repo.opener('patches.queues.new', 'w')
2771 2771 for queue in existing:
2772 2772 if queue == name:
2773 2773 continue
2774 2774 fh.write('%s\n' % (queue,))
2775 2775 fh.close()
2776 2776 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2777 2777
2778 2778 if not name or opts.get('list'):
2779 2779 current = _getcurrent()
2780 2780 for queue in _getqueues():
2781 2781 ui.write('%s' % (queue,))
2782 2782 if queue == current and not ui.quiet:
2783 2783 ui.write(_(' (active)\n'))
2784 2784 else:
2785 2785 ui.write('\n')
2786 2786 return
2787 2787
2788 2788 if not _validname(name):
2789 2789 raise util.Abort(
2790 2790 _('invalid queue name, may not contain the characters ":\\/."'))
2791 2791
2792 2792 existing = _getqueues()
2793 2793
2794 2794 if opts.get('create'):
2795 2795 if name in existing:
2796 2796 raise util.Abort(_('queue "%s" already exists') % name)
2797 2797 if _noqueues():
2798 2798 _addqueue(_defaultqueue)
2799 2799 _addqueue(name)
2800 2800 _setactive(name)
2801 2801 elif opts.get('rename'):
2802 2802 current = _getcurrent()
2803 2803 if name == current:
2804 2804 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
2805 2805 if name in existing:
2806 2806 raise util.Abort(_('queue "%s" already exists') % name)
2807 2807
2808 2808 olddir = _queuedir(current)
2809 2809 newdir = _queuedir(name)
2810 2810
2811 2811 if os.path.exists(newdir):
2812 2812 raise util.Abort(_('non-queue directory "%s" already exists') %
2813 2813 newdir)
2814 2814
2815 2815 fh = repo.opener('patches.queues.new', 'w')
2816 2816 for queue in existing:
2817 2817 if queue == current:
2818 2818 fh.write('%s\n' % (name,))
2819 2819 if os.path.exists(olddir):
2820 2820 util.rename(olddir, newdir)
2821 2821 else:
2822 2822 fh.write('%s\n' % (queue,))
2823 2823 fh.close()
2824 2824 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2825 2825 _setactivenocheck(name)
2826 2826 elif opts.get('delete'):
2827 2827 _delete(name)
2828 2828 elif opts.get('purge'):
2829 2829 if name in existing:
2830 2830 _delete(name)
2831 2831 qdir = _queuedir(name)
2832 2832 if os.path.exists(qdir):
2833 2833 shutil.rmtree(qdir)
2834 2834 else:
2835 2835 if name not in existing:
2836 2836 raise util.Abort(_('use --create to create a new queue'))
2837 2837 _setactive(name)
2838 2838
2839 2839 def reposetup(ui, repo):
2840 2840 class mqrepo(repo.__class__):
2841 2841 @util.propertycache
2842 2842 def mq(self):
2843 2843 return queue(self.ui, self.join(""))
2844 2844
2845 2845 def abort_if_wdir_patched(self, errmsg, force=False):
2846 2846 if self.mq.applied and not force:
2847 2847 parent = self.dirstate.parents()[0]
2848 2848 if parent in [s.node for s in self.mq.applied]:
2849 2849 raise util.Abort(errmsg)
2850 2850
2851 2851 def commit(self, text="", user=None, date=None, match=None,
2852 2852 force=False, editor=False, extra={}):
2853 2853 self.abort_if_wdir_patched(
2854 2854 _('cannot commit over an applied mq patch'),
2855 2855 force)
2856 2856
2857 2857 return super(mqrepo, self).commit(text, user, date, match, force,
2858 2858 editor, extra)
2859 2859
2860 2860 def push(self, remote, force=False, revs=None, newbranch=False):
2861 2861 if self.mq.applied and not force:
2862 2862 haspatches = True
2863 2863 if revs:
2864 2864 # Assume applied patches have no non-patch descendants
2865 2865 # and are not on remote already. If they appear in the
2866 2866 # set of resolved 'revs', bail out.
2867 2867 applied = set(e.node for e in self.mq.applied)
2868 2868 haspatches = bool([n for n in revs if n in applied])
2869 2869 if haspatches:
2870 2870 raise util.Abort(_('source has mq patches applied'))
2871 2871 return super(mqrepo, self).push(remote, force, revs, newbranch)
2872 2872
2873 2873 def _findtags(self):
2874 2874 '''augment tags from base class with patch tags'''
2875 2875 result = super(mqrepo, self)._findtags()
2876 2876
2877 2877 q = self.mq
2878 2878 if not q.applied:
2879 2879 return result
2880 2880
2881 2881 mqtags = [(patch.node, patch.name) for patch in q.applied]
2882 2882
2883 2883 if mqtags[-1][0] not in self.changelog.nodemap:
2884 2884 self.ui.warn(_('mq status file refers to unknown node %s\n')
2885 2885 % short(mqtags[-1][0]))
2886 2886 return result
2887 2887
2888 2888 mqtags.append((mqtags[-1][0], 'qtip'))
2889 2889 mqtags.append((mqtags[0][0], 'qbase'))
2890 2890 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2891 2891 tags = result[0]
2892 2892 for patch in mqtags:
2893 2893 if patch[1] in tags:
2894 2894 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2895 2895 % patch[1])
2896 2896 else:
2897 2897 tags[patch[1]] = patch[0]
2898 2898
2899 2899 return result
2900 2900
2901 2901 def _branchtags(self, partial, lrev):
2902 2902 q = self.mq
2903 2903 if not q.applied:
2904 2904 return super(mqrepo, self)._branchtags(partial, lrev)
2905 2905
2906 2906 cl = self.changelog
2907 2907 qbasenode = q.applied[0].node
2908 2908 if qbasenode not in cl.nodemap:
2909 2909 self.ui.warn(_('mq status file refers to unknown node %s\n')
2910 2910 % short(qbasenode))
2911 2911 return super(mqrepo, self)._branchtags(partial, lrev)
2912 2912
2913 2913 qbase = cl.rev(qbasenode)
2914 2914 start = lrev + 1
2915 2915 if start < qbase:
2916 2916 # update the cache (excluding the patches) and save it
2917 2917 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
2918 2918 self._updatebranchcache(partial, ctxgen)
2919 2919 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2920 2920 start = qbase
2921 2921 # if start = qbase, the cache is as updated as it should be.
2922 2922 # if start > qbase, the cache includes (part of) the patches.
2923 2923 # we might as well use it, but we won't save it.
2924 2924
2925 2925 # update the cache up to the tip
2926 2926 ctxgen = (self[r] for r in xrange(start, len(cl)))
2927 2927 self._updatebranchcache(partial, ctxgen)
2928 2928
2929 2929 return partial
2930 2930
2931 2931 if repo.local():
2932 2932 repo.__class__ = mqrepo
2933 2933
2934 2934 def mqimport(orig, ui, repo, *args, **kwargs):
2935 2935 if (hasattr(repo, 'abort_if_wdir_patched')
2936 2936 and not kwargs.get('no_commit', False)):
2937 2937 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2938 2938 kwargs.get('force'))
2939 2939 return orig(ui, repo, *args, **kwargs)
2940 2940
2941 2941 def mqinit(orig, ui, *args, **kwargs):
2942 2942 mq = kwargs.pop('mq', None)
2943 2943
2944 2944 if not mq:
2945 2945 return orig(ui, *args, **kwargs)
2946 2946
2947 2947 if args:
2948 2948 repopath = args[0]
2949 2949 if not hg.islocal(repopath):
2950 2950 raise util.Abort(_('only a local queue repository '
2951 2951 'may be initialized'))
2952 2952 else:
2953 2953 repopath = cmdutil.findrepo(os.getcwd())
2954 2954 if not repopath:
2955 2955 raise util.Abort(_('there is no Mercurial repository here '
2956 2956 '(.hg not found)'))
2957 2957 repo = hg.repository(ui, repopath)
2958 2958 return qinit(ui, repo, True)
2959 2959
2960 2960 def mqcommand(orig, ui, repo, *args, **kwargs):
2961 2961 """Add --mq option to operate on patch repository instead of main"""
2962 2962
2963 2963 # some commands do not like getting unknown options
2964 2964 mq = kwargs.pop('mq', None)
2965 2965
2966 2966 if not mq:
2967 2967 return orig(ui, repo, *args, **kwargs)
2968 2968
2969 2969 q = repo.mq
2970 2970 r = q.qrepo()
2971 2971 if not r:
2972 2972 raise util.Abort(_('no queue repository'))
2973 2973 return orig(r.ui, r, *args, **kwargs)
2974 2974
2975 2975 def summary(orig, ui, repo, *args, **kwargs):
2976 2976 r = orig(ui, repo, *args, **kwargs)
2977 2977 q = repo.mq
2978 2978 m = []
2979 2979 a, u = len(q.applied), len(q.unapplied(repo))
2980 2980 if a:
2981 2981 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
2982 2982 if u:
2983 2983 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
2984 2984 if m:
2985 2985 ui.write("mq: %s\n" % ', '.join(m))
2986 2986 else:
2987 2987 ui.note(_("mq: (empty queue)\n"))
2988 2988 return r
2989 2989
2990 2990 def uisetup(ui):
2991 2991 mqopt = [('', 'mq', None, _("operate on patch repository"))]
2992 2992
2993 2993 extensions.wrapcommand(commands.table, 'import', mqimport)
2994 2994 extensions.wrapcommand(commands.table, 'summary', summary)
2995 2995
2996 2996 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2997 2997 entry[1].extend(mqopt)
2998 2998
2999 2999 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
3000 3000
3001 3001 def dotable(cmdtable):
3002 3002 for cmd in cmdtable.keys():
3003 3003 cmd = cmdutil.parsealiases(cmd)[0]
3004 3004 if cmd in nowrap:
3005 3005 continue
3006 3006 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3007 3007 entry[1].extend(mqopt)
3008 3008
3009 3009 dotable(commands.table)
3010 3010
3011 3011 for extname, extmodule in extensions.extensions():
3012 3012 if extmodule.__file__ != __file__:
3013 3013 dotable(getattr(extmodule, 'cmdtable', {}))
3014 3014
3015 3015 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
3016 3016
3017 3017 cmdtable = {
3018 3018 "qapplied":
3019 3019 (applied,
3020 3020 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
3021 3021 _('hg qapplied [-1] [-s] [PATCH]')),
3022 3022 "qclone":
3023 3023 (clone,
3024 3024 [('', 'pull', None, _('use pull protocol to copy metadata')),
3025 3025 ('U', 'noupdate', None, _('do not update the new working directories')),
3026 3026 ('', 'uncompressed', None,
3027 3027 _('use uncompressed transfer (fast over LAN)')),
3028 3028 ('p', 'patches', '',
3029 3029 _('location of source patch repository'), _('REPO')),
3030 3030 ] + commands.remoteopts,
3031 3031 _('hg qclone [OPTION]... SOURCE [DEST]')),
3032 3032 "qcommit|qci":
3033 3033 (commit,
3034 3034 commands.table["^commit|ci"][1],
3035 3035 _('hg qcommit [OPTION]... [FILE]...')),
3036 3036 "^qdiff":
3037 3037 (diff,
3038 3038 commands.diffopts + commands.diffopts2 + commands.walkopts,
3039 3039 _('hg qdiff [OPTION]... [FILE]...')),
3040 3040 "qdelete|qremove|qrm":
3041 3041 (delete,
3042 3042 [('k', 'keep', None, _('keep patch file')),
3043 3043 ('r', 'rev', [],
3044 3044 _('stop managing a revision (DEPRECATED)'), _('REV'))],
3045 3045 _('hg qdelete [-k] [PATCH]...')),
3046 3046 'qfold':
3047 3047 (fold,
3048 3048 [('e', 'edit', None, _('edit patch header')),
3049 3049 ('k', 'keep', None, _('keep folded patch files')),
3050 3050 ] + commands.commitopts,
3051 3051 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
3052 3052 'qgoto':
3053 3053 (goto,
3054 3054 [('f', 'force', None, _('overwrite any local changes'))],
3055 3055 _('hg qgoto [OPTION]... PATCH')),
3056 3056 'qguard':
3057 3057 (guard,
3058 3058 [('l', 'list', None, _('list all patches and guards')),
3059 3059 ('n', 'none', None, _('drop all guards'))],
3060 3060 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
3061 3061 'qheader': (header, [], _('hg qheader [PATCH]')),
3062 3062 "qimport":
3063 3063 (qimport,
3064 3064 [('e', 'existing', None, _('import file in patch directory')),
3065 3065 ('n', 'name', '',
3066 3066 _('name of patch file'), _('NAME')),
3067 3067 ('f', 'force', None, _('overwrite existing files')),
3068 3068 ('r', 'rev', [],
3069 3069 _('place existing revisions under mq control'), _('REV')),
3070 3070 ('g', 'git', None, _('use git extended diff format')),
3071 3071 ('P', 'push', None, _('qpush after importing'))],
3072 3072 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
3073 3073 "^qinit":
3074 3074 (init,
3075 3075 [('c', 'create-repo', None, _('create queue repository'))],
3076 3076 _('hg qinit [-c]')),
3077 3077 "^qnew":
3078 3078 (new,
3079 3079 [('e', 'edit', None, _('edit commit message')),
3080 3080 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
3081 3081 ('g', 'git', None, _('use git extended diff format')),
3082 3082 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
3083 3083 ('u', 'user', '',
3084 3084 _('add "From: <USER>" to patch'), _('USER')),
3085 3085 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
3086 3086 ('d', 'date', '',
3087 3087 _('add "Date: <DATE>" to patch'), _('DATE'))
3088 3088 ] + commands.walkopts + commands.commitopts,
3089 3089 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
3090 3090 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
3091 3091 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
3092 3092 "^qpop":
3093 3093 (pop,
3094 3094 [('a', 'all', None, _('pop all patches')),
3095 3095 ('n', 'name', '',
3096 3096 _('queue name to pop (DEPRECATED)'), _('NAME')),
3097 3097 ('f', 'force', None, _('forget any local changes to patched files'))],
3098 3098 _('hg qpop [-a] [-f] [PATCH | INDEX]')),
3099 3099 "^qpush":
3100 3100 (push,
3101 3101 [('f', 'force', None, _('apply on top of local changes')),
3102 3102 ('l', 'list', None, _('list patch name in commit text')),
3103 3103 ('a', 'all', None, _('apply all patches')),
3104 3104 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
3105 3105 ('n', 'name', '',
3106 3106 _('merge queue name (DEPRECATED)'), _('NAME')),
3107 3107 ('', 'move', None, _('reorder patch series and apply only the patch'))],
3108 3108 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]')),
3109 3109 "^qrefresh":
3110 3110 (refresh,
3111 3111 [('e', 'edit', None, _('edit commit message')),
3112 3112 ('g', 'git', None, _('use git extended diff format')),
3113 3113 ('s', 'short', None,
3114 3114 _('refresh only files already in the patch and specified files')),
3115 3115 ('U', 'currentuser', None,
3116 3116 _('add/update author field in patch with current user')),
3117 3117 ('u', 'user', '',
3118 3118 _('add/update author field in patch with given user'), _('USER')),
3119 3119 ('D', 'currentdate', None,
3120 3120 _('add/update date field in patch with current date')),
3121 3121 ('d', 'date', '',
3122 3122 _('add/update date field in patch with given date'), _('DATE'))
3123 3123 ] + commands.walkopts + commands.commitopts,
3124 3124 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
3125 3125 'qrename|qmv':
3126 3126 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
3127 3127 "qrestore":
3128 3128 (restore,
3129 3129 [('d', 'delete', None, _('delete save entry')),
3130 3130 ('u', 'update', None, _('update queue working directory'))],
3131 3131 _('hg qrestore [-d] [-u] REV')),
3132 3132 "qsave":
3133 3133 (save,
3134 3134 [('c', 'copy', None, _('copy patch directory')),
3135 3135 ('n', 'name', '',
3136 3136 _('copy directory name'), _('NAME')),
3137 3137 ('e', 'empty', None, _('clear queue status file')),
3138 3138 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3139 3139 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
3140 3140 "qselect":
3141 3141 (select,
3142 3142 [('n', 'none', None, _('disable all guards')),
3143 3143 ('s', 'series', None, _('list all guards in series file')),
3144 3144 ('', 'pop', None, _('pop to before first guarded applied patch')),
3145 3145 ('', 'reapply', None, _('pop, then reapply patches'))],
3146 3146 _('hg qselect [OPTION]... [GUARD]...')),
3147 3147 "qseries":
3148 3148 (series,
3149 3149 [('m', 'missing', None, _('print patches not in series')),
3150 3150 ] + seriesopts,
3151 3151 _('hg qseries [-ms]')),
3152 3152 "strip":
3153 3153 (strip,
3154 3154 [('f', 'force', None, _('force removal of changesets even if the '
3155 3155 'working directory has uncommitted changes')),
3156 3156 ('b', 'backup', None, _('bundle only changesets with local revision'
3157 3157 ' number greater than REV which are not'
3158 3158 ' descendants of REV (DEPRECATED)')),
3159 3159 ('n', 'no-backup', None, _('no backups')),
3160 3160 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
3161 3161 ('k', 'keep', None, _("do not modify working copy during strip"))],
3162 3162 _('hg strip [-k] [-f] [-n] REV...')),
3163 3163 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
3164 3164 "qunapplied":
3165 3165 (unapplied,
3166 3166 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
3167 3167 _('hg qunapplied [-1] [-s] [PATCH]')),
3168 3168 "qfinish":
3169 3169 (finish,
3170 3170 [('a', 'applied', None, _('finish all applied changesets'))],
3171 3171 _('hg qfinish [-a] [REV]...')),
3172 3172 'qqueue':
3173 3173 (qqueue,
3174 3174 [
3175 3175 ('l', 'list', False, _('list all available queues')),
3176 3176 ('c', 'create', False, _('create new queue')),
3177 3177 ('', 'rename', False, _('rename active queue')),
3178 3178 ('', 'delete', False, _('delete reference to queue')),
3179 3179 ('', 'purge', False, _('delete queue, and remove patch dir')),
3180 3180 ],
3181 3181 _('[OPTION] [QUEUE]')),
3182 3182 }
3183 3183
3184 3184 colortable = {'qguard.negative': 'red',
3185 3185 'qguard.positive': 'yellow',
3186 3186 'qguard.unguarded': 'green',
3187 3187 'qseries.applied': 'blue bold underline',
3188 3188 'qseries.guarded': 'black bold',
3189 3189 'qseries.missing': 'red bold',
3190 3190 'qseries.unapplied': 'black bold'}
@@ -1,206 +1,206
1 1 # progress.py show progress bars for some actions
2 2 #
3 3 # Copyright (C) 2010 Augie Fackler <durin42@gmail.com>
4 4 #
5 5 # This program is free software; you can redistribute it and/or modify it
6 6 # under the terms of the GNU General Public License as published by the
7 7 # Free Software Foundation; either version 2 of the License, or (at your
8 8 # option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful, but
11 11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 13 # Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License along
16 16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 18
19 19 """show progress bars for some actions
20 20
21 21 This extension uses the progress information logged by hg commands
22 22 to draw progress bars that are as informative as possible. Some progress
23 23 bars only offer indeterminate information, while others have a definite
24 24 end point.
25 25
26 26 The following settings are available::
27 27
28 28 [progress]
29 29 delay = 3 # number of seconds (float) before showing the progress bar
30 30 refresh = 0.1 # time in seconds between refreshes of the progress bar
31 31 format = topic bar number # format of the progress bar
32 32 width = <none> # if set, the maximum width of the progress information
33 33 # (that is, min(width, term width) will be used)
34 34 clear-complete = True # clear the progress bar after it's done
35 35 disable = False # if true, don't show a progress bar
36 36 assume-tty = False # if true, ALWAYS show a progress bar, unless
37 37 # disable is given
38 38
39 39 Valid entries for the format field are topic, bar, number, unit, and
40 40 item. item defaults to the last 20 characters of the item, but this
41 41 can be changed by adding either ``-<num>`` which would take the last
42 42 num characters, or ``+<num>`` for the first num characters.
43 43 """
44 44
45 45 import sys
46 46 import time
47 47
48 48 from mercurial import util
49 49
50 50 def spacejoin(*args):
51 51 return ' '.join(s for s in args if s)
52 52
53 53 def shouldprint(ui):
54 54 return (getattr(sys.stderr, 'isatty', None) and
55 55 (sys.stderr.isatty() or ui.configbool('progress', 'assume-tty')))
56 56
57 57 class progbar(object):
58 58 def __init__(self, ui):
59 59 self.ui = ui
60 60 self.resetstate()
61 61
62 62 def resetstate(self):
63 63 self.topics = []
64 64 self.printed = False
65 65 self.lastprint = time.time() + float(self.ui.config(
66 66 'progress', 'delay', default=3))
67 67 self.indetcount = 0
68 68 self.refresh = float(self.ui.config(
69 69 'progress', 'refresh', default=0.1))
70 70 self.order = self.ui.configlist(
71 71 'progress', 'format',
72 72 default=['topic', 'bar', 'number'])
73 73
74 74 def show(self, topic, pos, item, unit, total):
75 75 if not shouldprint(self.ui):
76 76 return
77 77 termwidth = self.width()
78 78 self.printed = True
79 79 head = ''
80 80 needprogress = False
81 81 tail = ''
82 82 for indicator in self.order:
83 83 add = ''
84 84 if indicator == 'topic':
85 85 add = topic
86 86 elif indicator == 'number':
87 87 if total:
88 88 add = ('% ' + str(len(str(total))) +
89 89 's/%s') % (pos, total)
90 90 else:
91 91 add = str(pos)
92 92 elif indicator.startswith('item') and item:
93 93 slice = 'end'
94 94 if '-' in indicator:
95 95 wid = int(indicator.split('-')[1])
96 96 elif '+' in indicator:
97 97 slice = 'beginning'
98 98 wid = int(indicator.split('+')[1])
99 99 else:
100 100 wid = 20
101 101 if slice == 'end':
102 102 add = item[-wid:]
103 103 else:
104 104 add = item[:wid]
105 105 add += (wid - len(add)) * ' '
106 106 elif indicator == 'bar':
107 107 add = ''
108 108 needprogress = True
109 109 elif indicator == 'unit' and unit:
110 110 add = unit
111 111 if not needprogress:
112 112 head = spacejoin(head, add)
113 113 else:
114 114 tail = spacejoin(add, tail)
115 115 if needprogress:
116 116 used = 0
117 117 if head:
118 118 used += len(head) + 1
119 119 if tail:
120 120 used += len(tail) + 1
121 121 progwidth = termwidth - used - 3
122 122 if total and pos <= total:
123 123 amt = pos * progwidth // total
124 124 bar = '=' * (amt - 1)
125 125 if amt > 0:
126 126 bar += '>'
127 127 bar += ' ' * (progwidth - amt)
128 128 else:
129 129 progwidth -= 3
130 130 self.indetcount += 1
131 131 # mod the count by twice the width so we can make the
132 132 # cursor bounce between the right and left sides
133 133 amt = self.indetcount % (2 * progwidth)
134 134 amt -= progwidth
135 135 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
136 136 ' ' * int(abs(amt)))
137 137 prog = ''.join(('[', bar , ']'))
138 138 out = spacejoin(head, prog, tail)
139 139 else:
140 140 out = spacejoin(head, tail)
141 141 sys.stderr.write('\r' + out[:termwidth])
142 142 sys.stderr.flush()
143 143
144 144 def clear(self):
145 145 if not shouldprint(self.ui):
146 146 return
147 147 sys.stderr.write('\r%s\r' % (' ' * self.width()))
148 148
149 149 def complete(self):
150 150 if not shouldprint(self.ui):
151 151 return
152 152 if self.ui.configbool('progress', 'clear-complete', default=True):
153 153 self.clear()
154 154 else:
155 155 sys.stderr.write('\n')
156 156 sys.stderr.flush()
157 157
158 158 def width(self):
159 tw = util.termwidth()
159 tw = self.ui.termwidth()
160 160 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
161 161
162 162 def progress(self, topic, pos, item='', unit='', total=None):
163 163 if pos is None:
164 164 if self.topics and self.topics[-1] == topic and self.printed:
165 165 self.complete()
166 166 self.resetstate()
167 167 else:
168 168 if topic not in self.topics:
169 169 self.topics.append(topic)
170 170 now = time.time()
171 171 if (now - self.lastprint >= self.refresh
172 172 and topic == self.topics[-1]):
173 173 self.lastprint = now
174 174 self.show(topic, pos, item, unit, total)
175 175
176 176 def uisetup(ui):
177 177 class progressui(ui.__class__):
178 178 _progbar = None
179 179
180 180 def progress(self, *args, **opts):
181 181 self._progbar.progress(*args, **opts)
182 182 return super(progressui, self).progress(*args, **opts)
183 183
184 184 def write(self, *args, **opts):
185 185 if self._progbar.printed:
186 186 self._progbar.clear()
187 187 return super(progressui, self).write(*args, **opts)
188 188
189 189 def write_err(self, *args, **opts):
190 190 if self._progbar.printed:
191 191 self._progbar.clear()
192 192 return super(progressui, self).write_err(*args, **opts)
193 193
194 194 # Apps that derive a class from ui.ui() can use
195 195 # setconfig('progress', 'disable', 'True') to disable this extension
196 196 if ui.configbool('progress', 'disable'):
197 197 return
198 198 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
199 199 ui.__class__ = progressui
200 200 # we instantiate one globally shared progress bar to avoid
201 201 # competing progress bars when multiple UI objects get created
202 202 if not progressui._progbar:
203 203 progressui._progbar = progbar(ui)
204 204
205 205 def reposetup(ui, repo):
206 206 uisetup(repo.ui)
@@ -1,1360 +1,1360
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, templater, patch, error, encoding, 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.parents()[1] != 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 94 message = open(logfile).read()
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.parents()[0], None
126 126
127 127 l = revrange(repo, revs)
128 128
129 129 if len(l) == 0:
130 130 return repo.dirstate.parents()[0], 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 revrangesep in spec:
151 151 start, end = spec.split(revrangesep, 1)
152 152 start = revfix(repo, start, 0)
153 153 end = revfix(repo, end, len(repo) - 1)
154 154 step = start > end and -1 or 1
155 155 for rev in xrange(start, end + step, step):
156 156 if rev in seen:
157 157 continue
158 158 seen.add(rev)
159 159 l.append(rev)
160 160 continue
161 161 elif spec and spec in repo: # single unquoted rev
162 162 rev = revfix(repo, spec, None)
163 163 if rev in seen:
164 164 continue
165 165 seen.add(rev)
166 166 l.append(rev)
167 167 continue
168 168 except error.RepoLookupError:
169 169 pass
170 170
171 171 # fall through to new-style queries if old-style fails
172 172 m = revset.match(spec)
173 173 for r in m(repo, range(len(repo))):
174 174 if r not in seen:
175 175 l.append(r)
176 176 seen.update(l)
177 177
178 178 return l
179 179
180 180 def make_filename(repo, pat, node,
181 181 total=None, seqno=None, revwidth=None, pathname=None):
182 182 node_expander = {
183 183 'H': lambda: hex(node),
184 184 'R': lambda: str(repo.changelog.rev(node)),
185 185 'h': lambda: short(node),
186 186 }
187 187 expander = {
188 188 '%': lambda: '%',
189 189 'b': lambda: os.path.basename(repo.root),
190 190 }
191 191
192 192 try:
193 193 if node:
194 194 expander.update(node_expander)
195 195 if node:
196 196 expander['r'] = (lambda:
197 197 str(repo.changelog.rev(node)).zfill(revwidth or 0))
198 198 if total is not None:
199 199 expander['N'] = lambda: str(total)
200 200 if seqno is not None:
201 201 expander['n'] = lambda: str(seqno)
202 202 if total is not None and seqno is not None:
203 203 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
204 204 if pathname is not None:
205 205 expander['s'] = lambda: os.path.basename(pathname)
206 206 expander['d'] = lambda: os.path.dirname(pathname) or '.'
207 207 expander['p'] = lambda: pathname
208 208
209 209 newname = []
210 210 patlen = len(pat)
211 211 i = 0
212 212 while i < patlen:
213 213 c = pat[i]
214 214 if c == '%':
215 215 i += 1
216 216 c = pat[i]
217 217 c = expander[c]()
218 218 newname.append(c)
219 219 i += 1
220 220 return ''.join(newname)
221 221 except KeyError, inst:
222 222 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
223 223 inst.args[0])
224 224
225 225 def make_file(repo, pat, node=None,
226 226 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
227 227
228 228 writable = 'w' in mode or 'a' in mode
229 229
230 230 if not pat or pat == '-':
231 231 return writable and sys.stdout or sys.stdin
232 232 if hasattr(pat, 'write') and writable:
233 233 return pat
234 234 if hasattr(pat, 'read') and 'r' in mode:
235 235 return pat
236 236 return open(make_filename(repo, pat, node, total, seqno, revwidth,
237 237 pathname),
238 238 mode)
239 239
240 240 def expandpats(pats):
241 241 if not util.expandglobs:
242 242 return list(pats)
243 243 ret = []
244 244 for p in pats:
245 245 kind, name = matchmod._patsplit(p, None)
246 246 if kind is None:
247 247 try:
248 248 globbed = glob.glob(name)
249 249 except re.error:
250 250 globbed = [name]
251 251 if globbed:
252 252 ret.extend(globbed)
253 253 continue
254 254 ret.append(p)
255 255 return ret
256 256
257 257 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
258 258 if not globbed and default == 'relpath':
259 259 pats = expandpats(pats or [])
260 260 m = matchmod.match(repo.root, repo.getcwd(), pats,
261 261 opts.get('include'), opts.get('exclude'), default,
262 262 auditor=repo.auditor)
263 263 def badfn(f, msg):
264 264 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
265 265 m.bad = badfn
266 266 return m
267 267
268 268 def matchall(repo):
269 269 return matchmod.always(repo.root, repo.getcwd())
270 270
271 271 def matchfiles(repo, files):
272 272 return matchmod.exact(repo.root, repo.getcwd(), files)
273 273
274 274 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
275 275 if dry_run is None:
276 276 dry_run = opts.get('dry_run')
277 277 if similarity is None:
278 278 similarity = float(opts.get('similarity') or 0)
279 279 # we'd use status here, except handling of symlinks and ignore is tricky
280 280 added, unknown, deleted, removed = [], [], [], []
281 281 audit_path = util.path_auditor(repo.root)
282 282 m = match(repo, pats, opts)
283 283 for abs in repo.walk(m):
284 284 target = repo.wjoin(abs)
285 285 good = True
286 286 try:
287 287 audit_path(abs)
288 288 except:
289 289 good = False
290 290 rel = m.rel(abs)
291 291 exact = m.exact(abs)
292 292 if good and abs not in repo.dirstate:
293 293 unknown.append(abs)
294 294 if repo.ui.verbose or not exact:
295 295 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
296 296 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
297 297 or (os.path.isdir(target) and not os.path.islink(target))):
298 298 deleted.append(abs)
299 299 if repo.ui.verbose or not exact:
300 300 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
301 301 # for finding renames
302 302 elif repo.dirstate[abs] == 'r':
303 303 removed.append(abs)
304 304 elif repo.dirstate[abs] == 'a':
305 305 added.append(abs)
306 306 copies = {}
307 307 if similarity > 0:
308 308 for old, new, score in similar.findrenames(repo,
309 309 added + unknown, removed + deleted, similarity):
310 310 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
311 311 repo.ui.status(_('recording removal of %s as rename to %s '
312 312 '(%d%% similar)\n') %
313 313 (m.rel(old), m.rel(new), score * 100))
314 314 copies[new] = old
315 315
316 316 if not dry_run:
317 317 wctx = repo[None]
318 318 wlock = repo.wlock()
319 319 try:
320 320 wctx.remove(deleted)
321 321 wctx.add(unknown)
322 322 for new, old in copies.iteritems():
323 323 wctx.copy(old, new)
324 324 finally:
325 325 wlock.release()
326 326
327 327 def updatedir(ui, repo, patches, similarity=0):
328 328 '''Update dirstate after patch application according to metadata'''
329 329 if not patches:
330 330 return
331 331 copies = []
332 332 removes = set()
333 333 cfiles = patches.keys()
334 334 cwd = repo.getcwd()
335 335 if cwd:
336 336 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
337 337 for f in patches:
338 338 gp = patches[f]
339 339 if not gp:
340 340 continue
341 341 if gp.op == 'RENAME':
342 342 copies.append((gp.oldpath, gp.path))
343 343 removes.add(gp.oldpath)
344 344 elif gp.op == 'COPY':
345 345 copies.append((gp.oldpath, gp.path))
346 346 elif gp.op == 'DELETE':
347 347 removes.add(gp.path)
348 348
349 349 wctx = repo[None]
350 350 for src, dst in copies:
351 351 wctx.copy(src, dst)
352 352 if (not similarity) and removes:
353 353 wctx.remove(sorted(removes), True)
354 354
355 355 for f in patches:
356 356 gp = patches[f]
357 357 if gp and gp.mode:
358 358 islink, isexec = gp.mode
359 359 dst = repo.wjoin(gp.path)
360 360 # patch won't create empty files
361 361 if gp.op == 'ADD' and not os.path.lexists(dst):
362 362 flags = (isexec and 'x' or '') + (islink and 'l' or '')
363 363 repo.wwrite(gp.path, '', flags)
364 364 util.set_flags(dst, islink, isexec)
365 365 addremove(repo, cfiles, similarity=similarity)
366 366 files = patches.keys()
367 367 files.extend([r for r in removes if r not in files])
368 368 return sorted(files)
369 369
370 370 def copy(ui, repo, pats, opts, rename=False):
371 371 # called with the repo lock held
372 372 #
373 373 # hgsep => pathname that uses "/" to separate directories
374 374 # ossep => pathname that uses os.sep to separate directories
375 375 cwd = repo.getcwd()
376 376 targets = {}
377 377 after = opts.get("after")
378 378 dryrun = opts.get("dry_run")
379 379 wctx = repo[None]
380 380
381 381 def walkpat(pat):
382 382 srcs = []
383 383 badstates = after and '?' or '?r'
384 384 m = match(repo, [pat], opts, globbed=True)
385 385 for abs in repo.walk(m):
386 386 state = repo.dirstate[abs]
387 387 rel = m.rel(abs)
388 388 exact = m.exact(abs)
389 389 if state in badstates:
390 390 if exact and state == '?':
391 391 ui.warn(_('%s: not copying - file is not managed\n') % rel)
392 392 if exact and state == 'r':
393 393 ui.warn(_('%s: not copying - file has been marked for'
394 394 ' remove\n') % rel)
395 395 continue
396 396 # abs: hgsep
397 397 # rel: ossep
398 398 srcs.append((abs, rel, exact))
399 399 return srcs
400 400
401 401 # abssrc: hgsep
402 402 # relsrc: ossep
403 403 # otarget: ossep
404 404 def copyfile(abssrc, relsrc, otarget, exact):
405 405 abstarget = util.canonpath(repo.root, cwd, otarget)
406 406 reltarget = repo.pathto(abstarget, cwd)
407 407 target = repo.wjoin(abstarget)
408 408 src = repo.wjoin(abssrc)
409 409 state = repo.dirstate[abstarget]
410 410
411 411 # check for collisions
412 412 prevsrc = targets.get(abstarget)
413 413 if prevsrc is not None:
414 414 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
415 415 (reltarget, repo.pathto(abssrc, cwd),
416 416 repo.pathto(prevsrc, cwd)))
417 417 return
418 418
419 419 # check for overwrites
420 420 exists = os.path.lexists(target)
421 421 if not after and exists or after and state in 'mn':
422 422 if not opts['force']:
423 423 ui.warn(_('%s: not overwriting - file exists\n') %
424 424 reltarget)
425 425 return
426 426
427 427 if after:
428 428 if not exists:
429 429 if rename:
430 430 ui.warn(_('%s: not recording move - %s does not exist\n') %
431 431 (relsrc, reltarget))
432 432 else:
433 433 ui.warn(_('%s: not recording copy - %s does not exist\n') %
434 434 (relsrc, reltarget))
435 435 return
436 436 elif not dryrun:
437 437 try:
438 438 if exists:
439 439 os.unlink(target)
440 440 targetdir = os.path.dirname(target) or '.'
441 441 if not os.path.isdir(targetdir):
442 442 os.makedirs(targetdir)
443 443 util.copyfile(src, target)
444 444 except IOError, inst:
445 445 if inst.errno == errno.ENOENT:
446 446 ui.warn(_('%s: deleted in working copy\n') % relsrc)
447 447 else:
448 448 ui.warn(_('%s: cannot copy - %s\n') %
449 449 (relsrc, inst.strerror))
450 450 return True # report a failure
451 451
452 452 if ui.verbose or not exact:
453 453 if rename:
454 454 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
455 455 else:
456 456 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
457 457
458 458 targets[abstarget] = abssrc
459 459
460 460 # fix up dirstate
461 461 origsrc = repo.dirstate.copied(abssrc) or abssrc
462 462 if abstarget == origsrc: # copying back a copy?
463 463 if state not in 'mn' and not dryrun:
464 464 repo.dirstate.normallookup(abstarget)
465 465 else:
466 466 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
467 467 if not ui.quiet:
468 468 ui.warn(_("%s has not been committed yet, so no copy "
469 469 "data will be stored for %s.\n")
470 470 % (repo.pathto(origsrc, cwd), reltarget))
471 471 if repo.dirstate[abstarget] in '?r' and not dryrun:
472 472 wctx.add([abstarget])
473 473 elif not dryrun:
474 474 wctx.copy(origsrc, abstarget)
475 475
476 476 if rename and not dryrun:
477 477 wctx.remove([abssrc], not after)
478 478
479 479 # pat: ossep
480 480 # dest ossep
481 481 # srcs: list of (hgsep, hgsep, ossep, bool)
482 482 # return: function that takes hgsep and returns ossep
483 483 def targetpathfn(pat, dest, srcs):
484 484 if os.path.isdir(pat):
485 485 abspfx = util.canonpath(repo.root, cwd, pat)
486 486 abspfx = util.localpath(abspfx)
487 487 if destdirexists:
488 488 striplen = len(os.path.split(abspfx)[0])
489 489 else:
490 490 striplen = len(abspfx)
491 491 if striplen:
492 492 striplen += len(os.sep)
493 493 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
494 494 elif destdirexists:
495 495 res = lambda p: os.path.join(dest,
496 496 os.path.basename(util.localpath(p)))
497 497 else:
498 498 res = lambda p: dest
499 499 return res
500 500
501 501 # pat: ossep
502 502 # dest ossep
503 503 # srcs: list of (hgsep, hgsep, ossep, bool)
504 504 # return: function that takes hgsep and returns ossep
505 505 def targetpathafterfn(pat, dest, srcs):
506 506 if matchmod.patkind(pat):
507 507 # a mercurial pattern
508 508 res = lambda p: os.path.join(dest,
509 509 os.path.basename(util.localpath(p)))
510 510 else:
511 511 abspfx = util.canonpath(repo.root, cwd, pat)
512 512 if len(abspfx) < len(srcs[0][0]):
513 513 # A directory. Either the target path contains the last
514 514 # component of the source path or it does not.
515 515 def evalpath(striplen):
516 516 score = 0
517 517 for s in srcs:
518 518 t = os.path.join(dest, util.localpath(s[0])[striplen:])
519 519 if os.path.lexists(t):
520 520 score += 1
521 521 return score
522 522
523 523 abspfx = util.localpath(abspfx)
524 524 striplen = len(abspfx)
525 525 if striplen:
526 526 striplen += len(os.sep)
527 527 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
528 528 score = evalpath(striplen)
529 529 striplen1 = len(os.path.split(abspfx)[0])
530 530 if striplen1:
531 531 striplen1 += len(os.sep)
532 532 if evalpath(striplen1) > score:
533 533 striplen = striplen1
534 534 res = lambda p: os.path.join(dest,
535 535 util.localpath(p)[striplen:])
536 536 else:
537 537 # a file
538 538 if destdirexists:
539 539 res = lambda p: os.path.join(dest,
540 540 os.path.basename(util.localpath(p)))
541 541 else:
542 542 res = lambda p: dest
543 543 return res
544 544
545 545
546 546 pats = expandpats(pats)
547 547 if not pats:
548 548 raise util.Abort(_('no source or destination specified'))
549 549 if len(pats) == 1:
550 550 raise util.Abort(_('no destination specified'))
551 551 dest = pats.pop()
552 552 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
553 553 if not destdirexists:
554 554 if len(pats) > 1 or matchmod.patkind(pats[0]):
555 555 raise util.Abort(_('with multiple sources, destination must be an '
556 556 'existing directory'))
557 557 if util.endswithsep(dest):
558 558 raise util.Abort(_('destination %s is not a directory') % dest)
559 559
560 560 tfn = targetpathfn
561 561 if after:
562 562 tfn = targetpathafterfn
563 563 copylist = []
564 564 for pat in pats:
565 565 srcs = walkpat(pat)
566 566 if not srcs:
567 567 continue
568 568 copylist.append((tfn(pat, dest, srcs), srcs))
569 569 if not copylist:
570 570 raise util.Abort(_('no files to copy'))
571 571
572 572 errors = 0
573 573 for targetpath, srcs in copylist:
574 574 for abssrc, relsrc, exact in srcs:
575 575 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
576 576 errors += 1
577 577
578 578 if errors:
579 579 ui.warn(_('(consider using --after)\n'))
580 580
581 581 return errors != 0
582 582
583 583 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
584 584 runargs=None, appendpid=False):
585 585 '''Run a command as a service.'''
586 586
587 587 if opts['daemon'] and not opts['daemon_pipefds']:
588 588 # Signal child process startup with file removal
589 589 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
590 590 os.close(lockfd)
591 591 try:
592 592 if not runargs:
593 593 runargs = util.hgcmd() + sys.argv[1:]
594 594 runargs.append('--daemon-pipefds=%s' % lockpath)
595 595 # Don't pass --cwd to the child process, because we've already
596 596 # changed directory.
597 597 for i in xrange(1, len(runargs)):
598 598 if runargs[i].startswith('--cwd='):
599 599 del runargs[i]
600 600 break
601 601 elif runargs[i].startswith('--cwd'):
602 602 del runargs[i:i + 2]
603 603 break
604 604 def condfn():
605 605 return not os.path.exists(lockpath)
606 606 pid = util.rundetached(runargs, condfn)
607 607 if pid < 0:
608 608 raise util.Abort(_('child process failed to start'))
609 609 finally:
610 610 try:
611 611 os.unlink(lockpath)
612 612 except OSError, e:
613 613 if e.errno != errno.ENOENT:
614 614 raise
615 615 if parentfn:
616 616 return parentfn(pid)
617 617 else:
618 618 return
619 619
620 620 if initfn:
621 621 initfn()
622 622
623 623 if opts['pid_file']:
624 624 mode = appendpid and 'a' or 'w'
625 625 fp = open(opts['pid_file'], mode)
626 626 fp.write(str(os.getpid()) + '\n')
627 627 fp.close()
628 628
629 629 if opts['daemon_pipefds']:
630 630 lockpath = opts['daemon_pipefds']
631 631 try:
632 632 os.setsid()
633 633 except AttributeError:
634 634 pass
635 635 os.unlink(lockpath)
636 636 util.hidewindow()
637 637 sys.stdout.flush()
638 638 sys.stderr.flush()
639 639
640 640 nullfd = os.open(util.nulldev, os.O_RDWR)
641 641 logfilefd = nullfd
642 642 if logfile:
643 643 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
644 644 os.dup2(nullfd, 0)
645 645 os.dup2(logfilefd, 1)
646 646 os.dup2(logfilefd, 2)
647 647 if nullfd not in (0, 1, 2):
648 648 os.close(nullfd)
649 649 if logfile and logfilefd not in (0, 1, 2):
650 650 os.close(logfilefd)
651 651
652 652 if runfn:
653 653 return runfn()
654 654
655 655 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
656 656 opts=None):
657 657 '''export changesets as hg patches.'''
658 658
659 659 total = len(revs)
660 660 revwidth = max([len(str(rev)) for rev in revs])
661 661
662 662 def single(rev, seqno, fp):
663 663 ctx = repo[rev]
664 664 node = ctx.node()
665 665 parents = [p.node() for p in ctx.parents() if p]
666 666 branch = ctx.branch()
667 667 if switch_parent:
668 668 parents.reverse()
669 669 prev = (parents and parents[0]) or nullid
670 670
671 671 if not fp:
672 672 fp = make_file(repo, template, node, total=total, seqno=seqno,
673 673 revwidth=revwidth, mode='ab')
674 674 if fp != sys.stdout and hasattr(fp, 'name'):
675 675 repo.ui.note("%s\n" % fp.name)
676 676
677 677 fp.write("# HG changeset patch\n")
678 678 fp.write("# User %s\n" % ctx.user())
679 679 fp.write("# Date %d %d\n" % ctx.date())
680 680 if branch and branch != 'default':
681 681 fp.write("# Branch %s\n" % branch)
682 682 fp.write("# Node ID %s\n" % hex(node))
683 683 fp.write("# Parent %s\n" % hex(prev))
684 684 if len(parents) > 1:
685 685 fp.write("# Parent %s\n" % hex(parents[1]))
686 686 fp.write(ctx.description().rstrip())
687 687 fp.write("\n\n")
688 688
689 689 for chunk in patch.diff(repo, prev, node, opts=opts):
690 690 fp.write(chunk)
691 691
692 692 for seqno, rev in enumerate(revs):
693 693 single(rev, seqno + 1, fp)
694 694
695 695 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
696 696 changes=None, stat=False, fp=None, prefix='',
697 697 listsubrepos=False):
698 698 '''show diff or diffstat.'''
699 699 if fp is None:
700 700 write = ui.write
701 701 else:
702 702 def write(s, **kw):
703 703 fp.write(s)
704 704
705 705 if stat:
706 706 diffopts = diffopts.copy(context=0)
707 707 width = 80
708 708 if not ui.plain():
709 width = util.termwidth()
709 width = ui.termwidth()
710 710 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
711 711 prefix=prefix)
712 712 for chunk, label in patch.diffstatui(util.iterlines(chunks),
713 713 width=width,
714 714 git=diffopts.git):
715 715 write(chunk, label=label)
716 716 else:
717 717 for chunk, label in patch.diffui(repo, node1, node2, match,
718 718 changes, diffopts, prefix=prefix):
719 719 write(chunk, label=label)
720 720
721 721 if listsubrepos:
722 722 ctx1 = repo[node1]
723 723 ctx2 = repo[node2]
724 724 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
725 725 if node2 is not None:
726 726 node2 = ctx2.substate[subpath][1]
727 727 submatch = matchmod.narrowmatcher(subpath, match)
728 728 sub.diff(diffopts, node2, submatch, changes=changes,
729 729 stat=stat, fp=fp, prefix=prefix)
730 730
731 731 class changeset_printer(object):
732 732 '''show changeset information when templating not requested.'''
733 733
734 734 def __init__(self, ui, repo, patch, diffopts, buffered):
735 735 self.ui = ui
736 736 self.repo = repo
737 737 self.buffered = buffered
738 738 self.patch = patch
739 739 self.diffopts = diffopts
740 740 self.header = {}
741 741 self.hunk = {}
742 742 self.lastheader = None
743 743 self.footer = None
744 744
745 745 def flush(self, rev):
746 746 if rev in self.header:
747 747 h = self.header[rev]
748 748 if h != self.lastheader:
749 749 self.lastheader = h
750 750 self.ui.write(h)
751 751 del self.header[rev]
752 752 if rev in self.hunk:
753 753 self.ui.write(self.hunk[rev])
754 754 del self.hunk[rev]
755 755 return 1
756 756 return 0
757 757
758 758 def close(self):
759 759 if self.footer:
760 760 self.ui.write(self.footer)
761 761
762 762 def show(self, ctx, copies=None, matchfn=None, **props):
763 763 if self.buffered:
764 764 self.ui.pushbuffer()
765 765 self._show(ctx, copies, matchfn, props)
766 766 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
767 767 else:
768 768 self._show(ctx, copies, matchfn, props)
769 769
770 770 def _show(self, ctx, copies, matchfn, props):
771 771 '''show a single changeset or file revision'''
772 772 changenode = ctx.node()
773 773 rev = ctx.rev()
774 774
775 775 if self.ui.quiet:
776 776 self.ui.write("%d:%s\n" % (rev, short(changenode)),
777 777 label='log.node')
778 778 return
779 779
780 780 log = self.repo.changelog
781 781 date = util.datestr(ctx.date())
782 782
783 783 hexfunc = self.ui.debugflag and hex or short
784 784
785 785 parents = [(p, hexfunc(log.node(p)))
786 786 for p in self._meaningful_parentrevs(log, rev)]
787 787
788 788 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
789 789 label='log.changeset')
790 790
791 791 branch = ctx.branch()
792 792 # don't show the default branch name
793 793 if branch != 'default':
794 794 branch = encoding.tolocal(branch)
795 795 self.ui.write(_("branch: %s\n") % branch,
796 796 label='log.branch')
797 797 for tag in self.repo.nodetags(changenode):
798 798 self.ui.write(_("tag: %s\n") % tag,
799 799 label='log.tag')
800 800 for parent in parents:
801 801 self.ui.write(_("parent: %d:%s\n") % parent,
802 802 label='log.parent')
803 803
804 804 if self.ui.debugflag:
805 805 mnode = ctx.manifestnode()
806 806 self.ui.write(_("manifest: %d:%s\n") %
807 807 (self.repo.manifest.rev(mnode), hex(mnode)),
808 808 label='ui.debug log.manifest')
809 809 self.ui.write(_("user: %s\n") % ctx.user(),
810 810 label='log.user')
811 811 self.ui.write(_("date: %s\n") % date,
812 812 label='log.date')
813 813
814 814 if self.ui.debugflag:
815 815 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
816 816 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
817 817 files):
818 818 if value:
819 819 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
820 820 label='ui.debug log.files')
821 821 elif ctx.files() and self.ui.verbose:
822 822 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
823 823 label='ui.note log.files')
824 824 if copies and self.ui.verbose:
825 825 copies = ['%s (%s)' % c for c in copies]
826 826 self.ui.write(_("copies: %s\n") % ' '.join(copies),
827 827 label='ui.note log.copies')
828 828
829 829 extra = ctx.extra()
830 830 if extra and self.ui.debugflag:
831 831 for key, value in sorted(extra.items()):
832 832 self.ui.write(_("extra: %s=%s\n")
833 833 % (key, value.encode('string_escape')),
834 834 label='ui.debug log.extra')
835 835
836 836 description = ctx.description().strip()
837 837 if description:
838 838 if self.ui.verbose:
839 839 self.ui.write(_("description:\n"),
840 840 label='ui.note log.description')
841 841 self.ui.write(description,
842 842 label='ui.note log.description')
843 843 self.ui.write("\n\n")
844 844 else:
845 845 self.ui.write(_("summary: %s\n") %
846 846 description.splitlines()[0],
847 847 label='log.summary')
848 848 self.ui.write("\n")
849 849
850 850 self.showpatch(changenode, matchfn)
851 851
852 852 def showpatch(self, node, matchfn):
853 853 if not matchfn:
854 854 matchfn = self.patch
855 855 if matchfn:
856 856 stat = self.diffopts.get('stat')
857 857 diff = self.diffopts.get('patch')
858 858 diffopts = patch.diffopts(self.ui, self.diffopts)
859 859 prev = self.repo.changelog.parents(node)[0]
860 860 if stat:
861 861 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
862 862 match=matchfn, stat=True)
863 863 if diff:
864 864 if stat:
865 865 self.ui.write("\n")
866 866 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
867 867 match=matchfn, stat=False)
868 868 self.ui.write("\n")
869 869
870 870 def _meaningful_parentrevs(self, log, rev):
871 871 """Return list of meaningful (or all if debug) parentrevs for rev.
872 872
873 873 For merges (two non-nullrev revisions) both parents are meaningful.
874 874 Otherwise the first parent revision is considered meaningful if it
875 875 is not the preceding revision.
876 876 """
877 877 parents = log.parentrevs(rev)
878 878 if not self.ui.debugflag and parents[1] == nullrev:
879 879 if parents[0] >= rev - 1:
880 880 parents = []
881 881 else:
882 882 parents = [parents[0]]
883 883 return parents
884 884
885 885
886 886 class changeset_templater(changeset_printer):
887 887 '''format changeset information.'''
888 888
889 889 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
890 890 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
891 891 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
892 892 defaulttempl = {
893 893 'parent': '{rev}:{node|formatnode} ',
894 894 'manifest': '{rev}:{node|formatnode}',
895 895 'file_copy': '{name} ({source})',
896 896 'extra': '{key}={value|stringescape}'
897 897 }
898 898 # filecopy is preserved for compatibility reasons
899 899 defaulttempl['filecopy'] = defaulttempl['file_copy']
900 900 self.t = templater.templater(mapfile, {'formatnode': formatnode},
901 901 cache=defaulttempl)
902 902 self.cache = {}
903 903
904 904 def use_template(self, t):
905 905 '''set template string to use'''
906 906 self.t.cache['changeset'] = t
907 907
908 908 def _meaningful_parentrevs(self, ctx):
909 909 """Return list of meaningful (or all if debug) parentrevs for rev.
910 910 """
911 911 parents = ctx.parents()
912 912 if len(parents) > 1:
913 913 return parents
914 914 if self.ui.debugflag:
915 915 return [parents[0], self.repo['null']]
916 916 if parents[0].rev() >= ctx.rev() - 1:
917 917 return []
918 918 return parents
919 919
920 920 def _show(self, ctx, copies, matchfn, props):
921 921 '''show a single changeset or file revision'''
922 922
923 923 showlist = templatekw.showlist
924 924
925 925 # showparents() behaviour depends on ui trace level which
926 926 # causes unexpected behaviours at templating level and makes
927 927 # it harder to extract it in a standalone function. Its
928 928 # behaviour cannot be changed so leave it here for now.
929 929 def showparents(**args):
930 930 ctx = args['ctx']
931 931 parents = [[('rev', p.rev()), ('node', p.hex())]
932 932 for p in self._meaningful_parentrevs(ctx)]
933 933 return showlist('parent', parents, **args)
934 934
935 935 props = props.copy()
936 936 props.update(templatekw.keywords)
937 937 props['parents'] = showparents
938 938 props['templ'] = self.t
939 939 props['ctx'] = ctx
940 940 props['repo'] = self.repo
941 941 props['revcache'] = {'copies': copies}
942 942 props['cache'] = self.cache
943 943
944 944 # find correct templates for current mode
945 945
946 946 tmplmodes = [
947 947 (True, None),
948 948 (self.ui.verbose, 'verbose'),
949 949 (self.ui.quiet, 'quiet'),
950 950 (self.ui.debugflag, 'debug'),
951 951 ]
952 952
953 953 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
954 954 for mode, postfix in tmplmodes:
955 955 for type in types:
956 956 cur = postfix and ('%s_%s' % (type, postfix)) or type
957 957 if mode and cur in self.t:
958 958 types[type] = cur
959 959
960 960 try:
961 961
962 962 # write header
963 963 if types['header']:
964 964 h = templater.stringify(self.t(types['header'], **props))
965 965 if self.buffered:
966 966 self.header[ctx.rev()] = h
967 967 else:
968 968 if self.lastheader != h:
969 969 self.lastheader = h
970 970 self.ui.write(h)
971 971
972 972 # write changeset metadata, then patch if requested
973 973 key = types['changeset']
974 974 self.ui.write(templater.stringify(self.t(key, **props)))
975 975 self.showpatch(ctx.node(), matchfn)
976 976
977 977 if types['footer']:
978 978 if not self.footer:
979 979 self.footer = templater.stringify(self.t(types['footer'],
980 980 **props))
981 981
982 982 except KeyError, inst:
983 983 msg = _("%s: no key named '%s'")
984 984 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
985 985 except SyntaxError, inst:
986 986 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
987 987
988 988 def show_changeset(ui, repo, opts, buffered=False):
989 989 """show one changeset using template or regular display.
990 990
991 991 Display format will be the first non-empty hit of:
992 992 1. option 'template'
993 993 2. option 'style'
994 994 3. [ui] setting 'logtemplate'
995 995 4. [ui] setting 'style'
996 996 If all of these values are either the unset or the empty string,
997 997 regular display via changeset_printer() is done.
998 998 """
999 999 # options
1000 1000 patch = False
1001 1001 if opts.get('patch') or opts.get('stat'):
1002 1002 patch = matchall(repo)
1003 1003
1004 1004 tmpl = opts.get('template')
1005 1005 style = None
1006 1006 if tmpl:
1007 1007 tmpl = templater.parsestring(tmpl, quoted=False)
1008 1008 else:
1009 1009 style = opts.get('style')
1010 1010
1011 1011 # ui settings
1012 1012 if not (tmpl or style):
1013 1013 tmpl = ui.config('ui', 'logtemplate')
1014 1014 if tmpl:
1015 1015 tmpl = templater.parsestring(tmpl)
1016 1016 else:
1017 1017 style = util.expandpath(ui.config('ui', 'style', ''))
1018 1018
1019 1019 if not (tmpl or style):
1020 1020 return changeset_printer(ui, repo, patch, opts, buffered)
1021 1021
1022 1022 mapfile = None
1023 1023 if style and not tmpl:
1024 1024 mapfile = style
1025 1025 if not os.path.split(mapfile)[0]:
1026 1026 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1027 1027 or templater.templatepath(mapfile))
1028 1028 if mapname:
1029 1029 mapfile = mapname
1030 1030
1031 1031 try:
1032 1032 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1033 1033 except SyntaxError, inst:
1034 1034 raise util.Abort(inst.args[0])
1035 1035 if tmpl:
1036 1036 t.use_template(tmpl)
1037 1037 return t
1038 1038
1039 1039 def finddate(ui, repo, date):
1040 1040 """Find the tipmost changeset that matches the given date spec"""
1041 1041
1042 1042 df = util.matchdate(date)
1043 1043 m = matchall(repo)
1044 1044 results = {}
1045 1045
1046 1046 def prep(ctx, fns):
1047 1047 d = ctx.date()
1048 1048 if df(d[0]):
1049 1049 results[ctx.rev()] = d
1050 1050
1051 1051 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1052 1052 rev = ctx.rev()
1053 1053 if rev in results:
1054 1054 ui.status(_("Found revision %s from %s\n") %
1055 1055 (rev, util.datestr(results[rev])))
1056 1056 return str(rev)
1057 1057
1058 1058 raise util.Abort(_("revision matching date not found"))
1059 1059
1060 1060 def walkchangerevs(repo, match, opts, prepare):
1061 1061 '''Iterate over files and the revs in which they changed.
1062 1062
1063 1063 Callers most commonly need to iterate backwards over the history
1064 1064 in which they are interested. Doing so has awful (quadratic-looking)
1065 1065 performance, so we use iterators in a "windowed" way.
1066 1066
1067 1067 We walk a window of revisions in the desired order. Within the
1068 1068 window, we first walk forwards to gather data, then in the desired
1069 1069 order (usually backwards) to display it.
1070 1070
1071 1071 This function returns an iterator yielding contexts. Before
1072 1072 yielding each context, the iterator will first call the prepare
1073 1073 function on each context in the window in forward order.'''
1074 1074
1075 1075 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1076 1076 if start < end:
1077 1077 while start < end:
1078 1078 yield start, min(windowsize, end - start)
1079 1079 start += windowsize
1080 1080 if windowsize < sizelimit:
1081 1081 windowsize *= 2
1082 1082 else:
1083 1083 while start > end:
1084 1084 yield start, min(windowsize, start - end - 1)
1085 1085 start -= windowsize
1086 1086 if windowsize < sizelimit:
1087 1087 windowsize *= 2
1088 1088
1089 1089 follow = opts.get('follow') or opts.get('follow_first')
1090 1090
1091 1091 if not len(repo):
1092 1092 return []
1093 1093
1094 1094 if follow:
1095 1095 defrange = '%s:0' % repo['.'].rev()
1096 1096 else:
1097 1097 defrange = '-1:0'
1098 1098 revs = revrange(repo, opts['rev'] or [defrange])
1099 1099 if not revs:
1100 1100 return []
1101 1101 wanted = set()
1102 1102 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1103 1103 fncache = {}
1104 1104 change = util.cachefunc(repo.changectx)
1105 1105
1106 1106 # First step is to fill wanted, the set of revisions that we want to yield.
1107 1107 # When it does not induce extra cost, we also fill fncache for revisions in
1108 1108 # wanted: a cache of filenames that were changed (ctx.files()) and that
1109 1109 # match the file filtering conditions.
1110 1110
1111 1111 if not slowpath and not match.files():
1112 1112 # No files, no patterns. Display all revs.
1113 1113 wanted = set(revs)
1114 1114 copies = []
1115 1115
1116 1116 if not slowpath:
1117 1117 # We only have to read through the filelog to find wanted revisions
1118 1118
1119 1119 minrev, maxrev = min(revs), max(revs)
1120 1120 def filerevgen(filelog, last):
1121 1121 """
1122 1122 Only files, no patterns. Check the history of each file.
1123 1123
1124 1124 Examines filelog entries within minrev, maxrev linkrev range
1125 1125 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1126 1126 tuples in backwards order
1127 1127 """
1128 1128 cl_count = len(repo)
1129 1129 revs = []
1130 1130 for j in xrange(0, last + 1):
1131 1131 linkrev = filelog.linkrev(j)
1132 1132 if linkrev < minrev:
1133 1133 continue
1134 1134 # only yield rev for which we have the changelog, it can
1135 1135 # happen while doing "hg log" during a pull or commit
1136 1136 if linkrev > maxrev or linkrev >= cl_count:
1137 1137 break
1138 1138
1139 1139 parentlinkrevs = []
1140 1140 for p in filelog.parentrevs(j):
1141 1141 if p != nullrev:
1142 1142 parentlinkrevs.append(filelog.linkrev(p))
1143 1143 n = filelog.node(j)
1144 1144 revs.append((linkrev, parentlinkrevs,
1145 1145 follow and filelog.renamed(n)))
1146 1146
1147 1147 return reversed(revs)
1148 1148 def iterfiles():
1149 1149 for filename in match.files():
1150 1150 yield filename, None
1151 1151 for filename_node in copies:
1152 1152 yield filename_node
1153 1153 for file_, node in iterfiles():
1154 1154 filelog = repo.file(file_)
1155 1155 if not len(filelog):
1156 1156 if node is None:
1157 1157 # A zero count may be a directory or deleted file, so
1158 1158 # try to find matching entries on the slow path.
1159 1159 if follow:
1160 1160 raise util.Abort(
1161 1161 _('cannot follow nonexistent file: "%s"') % file_)
1162 1162 slowpath = True
1163 1163 break
1164 1164 else:
1165 1165 continue
1166 1166
1167 1167 if node is None:
1168 1168 last = len(filelog) - 1
1169 1169 else:
1170 1170 last = filelog.rev(node)
1171 1171
1172 1172
1173 1173 # keep track of all ancestors of the file
1174 1174 ancestors = set([filelog.linkrev(last)])
1175 1175
1176 1176 # iterate from latest to oldest revision
1177 1177 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1178 1178 if rev not in ancestors:
1179 1179 continue
1180 1180 # XXX insert 1327 fix here
1181 1181 if flparentlinkrevs:
1182 1182 ancestors.update(flparentlinkrevs)
1183 1183
1184 1184 fncache.setdefault(rev, []).append(file_)
1185 1185 wanted.add(rev)
1186 1186 if copied:
1187 1187 copies.append(copied)
1188 1188 if slowpath:
1189 1189 # We have to read the changelog to match filenames against
1190 1190 # changed files
1191 1191
1192 1192 if follow:
1193 1193 raise util.Abort(_('can only follow copies/renames for explicit '
1194 1194 'filenames'))
1195 1195
1196 1196 # The slow path checks files modified in every changeset.
1197 1197 for i in sorted(revs):
1198 1198 ctx = change(i)
1199 1199 matches = filter(match, ctx.files())
1200 1200 if matches:
1201 1201 fncache[i] = matches
1202 1202 wanted.add(i)
1203 1203
1204 1204 class followfilter(object):
1205 1205 def __init__(self, onlyfirst=False):
1206 1206 self.startrev = nullrev
1207 1207 self.roots = set()
1208 1208 self.onlyfirst = onlyfirst
1209 1209
1210 1210 def match(self, rev):
1211 1211 def realparents(rev):
1212 1212 if self.onlyfirst:
1213 1213 return repo.changelog.parentrevs(rev)[0:1]
1214 1214 else:
1215 1215 return filter(lambda x: x != nullrev,
1216 1216 repo.changelog.parentrevs(rev))
1217 1217
1218 1218 if self.startrev == nullrev:
1219 1219 self.startrev = rev
1220 1220 return True
1221 1221
1222 1222 if rev > self.startrev:
1223 1223 # forward: all descendants
1224 1224 if not self.roots:
1225 1225 self.roots.add(self.startrev)
1226 1226 for parent in realparents(rev):
1227 1227 if parent in self.roots:
1228 1228 self.roots.add(rev)
1229 1229 return True
1230 1230 else:
1231 1231 # backwards: all parents
1232 1232 if not self.roots:
1233 1233 self.roots.update(realparents(self.startrev))
1234 1234 if rev in self.roots:
1235 1235 self.roots.remove(rev)
1236 1236 self.roots.update(realparents(rev))
1237 1237 return True
1238 1238
1239 1239 return False
1240 1240
1241 1241 # it might be worthwhile to do this in the iterator if the rev range
1242 1242 # is descending and the prune args are all within that range
1243 1243 for rev in opts.get('prune', ()):
1244 1244 rev = repo.changelog.rev(repo.lookup(rev))
1245 1245 ff = followfilter()
1246 1246 stop = min(revs[0], revs[-1])
1247 1247 for x in xrange(rev, stop - 1, -1):
1248 1248 if ff.match(x):
1249 1249 wanted.discard(x)
1250 1250
1251 1251 # Now that wanted is correctly initialized, we can iterate over the
1252 1252 # revision range, yielding only revisions in wanted.
1253 1253 def iterate():
1254 1254 if follow and not match.files():
1255 1255 ff = followfilter(onlyfirst=opts.get('follow_first'))
1256 1256 def want(rev):
1257 1257 return ff.match(rev) and rev in wanted
1258 1258 else:
1259 1259 def want(rev):
1260 1260 return rev in wanted
1261 1261
1262 1262 for i, window in increasing_windows(0, len(revs)):
1263 1263 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1264 1264 for rev in sorted(nrevs):
1265 1265 fns = fncache.get(rev)
1266 1266 ctx = change(rev)
1267 1267 if not fns:
1268 1268 def fns_generator():
1269 1269 for f in ctx.files():
1270 1270 if match(f):
1271 1271 yield f
1272 1272 fns = fns_generator()
1273 1273 prepare(ctx, fns)
1274 1274 for rev in nrevs:
1275 1275 yield change(rev)
1276 1276 return iterate()
1277 1277
1278 1278 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1279 1279 join = lambda f: os.path.join(prefix, f)
1280 1280 bad = []
1281 1281 oldbad = match.bad
1282 1282 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1283 1283 names = []
1284 1284 wctx = repo[None]
1285 1285 for f in repo.walk(match):
1286 1286 exact = match.exact(f)
1287 1287 if exact or f not in repo.dirstate:
1288 1288 names.append(f)
1289 1289 if ui.verbose or not exact:
1290 1290 ui.status(_('adding %s\n') % match.rel(join(f)))
1291 1291
1292 1292 if listsubrepos:
1293 1293 for subpath in wctx.substate:
1294 1294 sub = wctx.sub(subpath)
1295 1295 try:
1296 1296 submatch = matchmod.narrowmatcher(subpath, match)
1297 1297 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1298 1298 except error.LookupError:
1299 1299 ui.status(_("skipping missing subrepository: %s\n")
1300 1300 % join(subpath))
1301 1301
1302 1302 if not dryrun:
1303 1303 rejected = wctx.add(names, prefix)
1304 1304 bad.extend(f for f in rejected if f in match.files())
1305 1305 return bad
1306 1306
1307 1307 def commit(ui, repo, commitfunc, pats, opts):
1308 1308 '''commit the specified files or all outstanding changes'''
1309 1309 date = opts.get('date')
1310 1310 if date:
1311 1311 opts['date'] = util.parsedate(date)
1312 1312 message = logmessage(opts)
1313 1313
1314 1314 # extract addremove carefully -- this function can be called from a command
1315 1315 # that doesn't support addremove
1316 1316 if opts.get('addremove'):
1317 1317 addremove(repo, pats, opts)
1318 1318
1319 1319 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1320 1320
1321 1321 def commiteditor(repo, ctx, subs):
1322 1322 if ctx.description():
1323 1323 return ctx.description()
1324 1324 return commitforceeditor(repo, ctx, subs)
1325 1325
1326 1326 def commitforceeditor(repo, ctx, subs):
1327 1327 edittext = []
1328 1328 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1329 1329 if ctx.description():
1330 1330 edittext.append(ctx.description())
1331 1331 edittext.append("")
1332 1332 edittext.append("") # Empty line between message and comments.
1333 1333 edittext.append(_("HG: Enter commit message."
1334 1334 " Lines beginning with 'HG:' are removed."))
1335 1335 edittext.append(_("HG: Leave message empty to abort commit."))
1336 1336 edittext.append("HG: --")
1337 1337 edittext.append(_("HG: user: %s") % ctx.user())
1338 1338 if ctx.p2():
1339 1339 edittext.append(_("HG: branch merge"))
1340 1340 if ctx.branch():
1341 1341 edittext.append(_("HG: branch '%s'")
1342 1342 % encoding.tolocal(ctx.branch()))
1343 1343 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1344 1344 edittext.extend([_("HG: added %s") % f for f in added])
1345 1345 edittext.extend([_("HG: changed %s") % f for f in modified])
1346 1346 edittext.extend([_("HG: removed %s") % f for f in removed])
1347 1347 if not added and not modified and not removed:
1348 1348 edittext.append(_("HG: no files changed"))
1349 1349 edittext.append("")
1350 1350 # run editor in the repository root
1351 1351 olddir = os.getcwd()
1352 1352 os.chdir(repo.root)
1353 1353 text = repo.ui.edit("\n".join(edittext), ctx.user())
1354 1354 text = re.sub("(?m)^HG:.*\n", "", text)
1355 1355 os.chdir(olddir)
1356 1356
1357 1357 if not text.strip():
1358 1358 raise util.Abort(_("empty commit message"))
1359 1359
1360 1360 return text
@@ -1,4449 +1,4449
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, 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, util, revlog, extensions, copies, error
13 13 import patch, help, mdiff, 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
17 17 import dagparser
18 18
19 19 # Commands start here, listed alphabetically
20 20
21 21 def add(ui, repo, *pats, **opts):
22 22 """add the specified files on the next commit
23 23
24 24 Schedule files to be version controlled and added to the
25 25 repository.
26 26
27 27 The files will be added to the repository at the next commit. To
28 28 undo an add before that, see :hg:`forget`.
29 29
30 30 If no names are given, add all files to the repository.
31 31
32 32 .. container:: verbose
33 33
34 34 An example showing how new (unknown) files are added
35 35 automatically by :hg:`add`::
36 36
37 37 $ ls
38 38 foo.c
39 39 $ hg status
40 40 ? foo.c
41 41 $ hg add
42 42 adding foo.c
43 43 $ hg status
44 44 A foo.c
45 45
46 46 Returns 0 if all files are successfully added.
47 47 """
48 48
49 49 m = cmdutil.match(repo, pats, opts)
50 50 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
51 51 opts.get('subrepos'), prefix="")
52 52 return rejected and 1 or 0
53 53
54 54 def addremove(ui, repo, *pats, **opts):
55 55 """add all new files, delete all missing files
56 56
57 57 Add all new files and remove all missing files from the
58 58 repository.
59 59
60 60 New files are ignored if they match any of the patterns in
61 61 .hgignore. As with add, these changes take effect at the next
62 62 commit.
63 63
64 64 Use the -s/--similarity option to detect renamed files. With a
65 65 parameter greater than 0, this compares every removed file with
66 66 every added file and records those similar enough as renames. This
67 67 option takes a percentage between 0 (disabled) and 100 (files must
68 68 be identical) as its parameter. Detecting renamed files this way
69 69 can be expensive. After using this option, :hg:`status -C` can be
70 70 used to check which files were identified as moved or renamed.
71 71
72 72 Returns 0 if all files are successfully added.
73 73 """
74 74 try:
75 75 sim = float(opts.get('similarity') or 100)
76 76 except ValueError:
77 77 raise util.Abort(_('similarity must be a number'))
78 78 if sim < 0 or sim > 100:
79 79 raise util.Abort(_('similarity must be between 0 and 100'))
80 80 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
81 81
82 82 def annotate(ui, repo, *pats, **opts):
83 83 """show changeset information by line for each file
84 84
85 85 List changes in files, showing the revision id responsible for
86 86 each line
87 87
88 88 This command is useful for discovering when a change was made and
89 89 by whom.
90 90
91 91 Without the -a/--text option, annotate will avoid processing files
92 92 it detects as binary. With -a, annotate will annotate the file
93 93 anyway, although the results will probably be neither useful
94 94 nor desirable.
95 95
96 96 Returns 0 on success.
97 97 """
98 98 if opts.get('follow'):
99 99 # --follow is deprecated and now just an alias for -f/--file
100 100 # to mimic the behavior of Mercurial before version 1.5
101 101 opts['file'] = 1
102 102
103 103 datefunc = ui.quiet and util.shortdate or util.datestr
104 104 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
105 105
106 106 if not pats:
107 107 raise util.Abort(_('at least one filename or pattern is required'))
108 108
109 109 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
110 110 ('number', lambda x: str(x[0].rev())),
111 111 ('changeset', lambda x: short(x[0].node())),
112 112 ('date', getdate),
113 113 ('file', lambda x: x[0].path()),
114 114 ]
115 115
116 116 if (not opts.get('user') and not opts.get('changeset')
117 117 and not opts.get('date') and not opts.get('file')):
118 118 opts['number'] = 1
119 119
120 120 linenumber = opts.get('line_number') is not None
121 121 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
122 122 raise util.Abort(_('at least one of -n/-c is required for -l'))
123 123
124 124 funcmap = [func for op, func in opmap if opts.get(op)]
125 125 if linenumber:
126 126 lastfunc = funcmap[-1]
127 127 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
128 128
129 129 ctx = repo[opts.get('rev')]
130 130 m = cmdutil.match(repo, pats, opts)
131 131 follow = not opts.get('no_follow')
132 132 for abs in ctx.walk(m):
133 133 fctx = ctx[abs]
134 134 if not opts.get('text') and util.binary(fctx.data()):
135 135 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
136 136 continue
137 137
138 138 lines = fctx.annotate(follow=follow, linenumber=linenumber)
139 139 pieces = []
140 140
141 141 for f in funcmap:
142 142 l = [f(n) for n, dummy in lines]
143 143 if l:
144 144 sized = [(x, encoding.colwidth(x)) for x in l]
145 145 ml = max([w for x, w in sized])
146 146 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
147 147
148 148 if pieces:
149 149 for p, l in zip(zip(*pieces), lines):
150 150 ui.write("%s: %s" % (" ".join(p), l[1]))
151 151
152 152 def archive(ui, repo, dest, **opts):
153 153 '''create an unversioned archive of a repository revision
154 154
155 155 By default, the revision used is the parent of the working
156 156 directory; use -r/--rev to specify a different revision.
157 157
158 158 The archive type is automatically detected based on file
159 159 extension (or override using -t/--type).
160 160
161 161 Valid types are:
162 162
163 163 :``files``: a directory full of files (default)
164 164 :``tar``: tar archive, uncompressed
165 165 :``tbz2``: tar archive, compressed using bzip2
166 166 :``tgz``: tar archive, compressed using gzip
167 167 :``uzip``: zip archive, uncompressed
168 168 :``zip``: zip archive, compressed using deflate
169 169
170 170 The exact name of the destination archive or directory is given
171 171 using a format string; see :hg:`help export` for details.
172 172
173 173 Each member added to an archive file has a directory prefix
174 174 prepended. Use -p/--prefix to specify a format string for the
175 175 prefix. The default is the basename of the archive, with suffixes
176 176 removed.
177 177
178 178 Returns 0 on success.
179 179 '''
180 180
181 181 ctx = repo[opts.get('rev')]
182 182 if not ctx:
183 183 raise util.Abort(_('no working directory: please specify a revision'))
184 184 node = ctx.node()
185 185 dest = cmdutil.make_filename(repo, dest, node)
186 186 if os.path.realpath(dest) == repo.root:
187 187 raise util.Abort(_('repository root cannot be destination'))
188 188
189 189 kind = opts.get('type') or archival.guesskind(dest) or 'files'
190 190 prefix = opts.get('prefix')
191 191
192 192 if dest == '-':
193 193 if kind == 'files':
194 194 raise util.Abort(_('cannot archive plain files to stdout'))
195 195 dest = sys.stdout
196 196 if not prefix:
197 197 prefix = os.path.basename(repo.root) + '-%h'
198 198
199 199 prefix = cmdutil.make_filename(repo, prefix, node)
200 200 matchfn = cmdutil.match(repo, [], opts)
201 201 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
202 202 matchfn, prefix, subrepos=opts.get('subrepos'))
203 203
204 204 def backout(ui, repo, node=None, rev=None, **opts):
205 205 '''reverse effect of earlier changeset
206 206
207 207 Commit the backed out changes as a new changeset. The new
208 208 changeset is a child of the backed out changeset.
209 209
210 210 If you backout a changeset other than the tip, a new head is
211 211 created. This head will be the new tip and you should merge this
212 212 backout changeset with another head.
213 213
214 214 The --merge option remembers the parent of the working directory
215 215 before starting the backout, then merges the new head with that
216 216 changeset afterwards. This saves you from doing the merge by hand.
217 217 The result of this merge is not committed, as with a normal merge.
218 218
219 219 See :hg:`help dates` for a list of formats valid for -d/--date.
220 220
221 221 Returns 0 on success.
222 222 '''
223 223 if rev and node:
224 224 raise util.Abort(_("please specify just one revision"))
225 225
226 226 if not rev:
227 227 rev = node
228 228
229 229 if not rev:
230 230 raise util.Abort(_("please specify a revision to backout"))
231 231
232 232 date = opts.get('date')
233 233 if date:
234 234 opts['date'] = util.parsedate(date)
235 235
236 236 cmdutil.bail_if_changed(repo)
237 237 node = repo.lookup(rev)
238 238
239 239 op1, op2 = repo.dirstate.parents()
240 240 a = repo.changelog.ancestor(op1, node)
241 241 if a != node:
242 242 raise util.Abort(_('cannot backout change on a different branch'))
243 243
244 244 p1, p2 = repo.changelog.parents(node)
245 245 if p1 == nullid:
246 246 raise util.Abort(_('cannot backout a change with no parents'))
247 247 if p2 != nullid:
248 248 if not opts.get('parent'):
249 249 raise util.Abort(_('cannot backout a merge changeset without '
250 250 '--parent'))
251 251 p = repo.lookup(opts['parent'])
252 252 if p not in (p1, p2):
253 253 raise util.Abort(_('%s is not a parent of %s') %
254 254 (short(p), short(node)))
255 255 parent = p
256 256 else:
257 257 if opts.get('parent'):
258 258 raise util.Abort(_('cannot use --parent on non-merge changeset'))
259 259 parent = p1
260 260
261 261 # the backout should appear on the same branch
262 262 branch = repo.dirstate.branch()
263 263 hg.clean(repo, node, show_stats=False)
264 264 repo.dirstate.setbranch(branch)
265 265 revert_opts = opts.copy()
266 266 revert_opts['date'] = None
267 267 revert_opts['all'] = True
268 268 revert_opts['rev'] = hex(parent)
269 269 revert_opts['no_backup'] = None
270 270 revert(ui, repo, **revert_opts)
271 271 commit_opts = opts.copy()
272 272 commit_opts['addremove'] = False
273 273 if not commit_opts['message'] and not commit_opts['logfile']:
274 274 # we don't translate commit messages
275 275 commit_opts['message'] = "Backed out changeset %s" % short(node)
276 276 commit_opts['force_editor'] = True
277 277 commit(ui, repo, **commit_opts)
278 278 def nice(node):
279 279 return '%d:%s' % (repo.changelog.rev(node), short(node))
280 280 ui.status(_('changeset %s backs out changeset %s\n') %
281 281 (nice(repo.changelog.tip()), nice(node)))
282 282 if op1 != node:
283 283 hg.clean(repo, op1, show_stats=False)
284 284 if opts.get('merge'):
285 285 ui.status(_('merging with changeset %s\n')
286 286 % nice(repo.changelog.tip()))
287 287 hg.merge(repo, hex(repo.changelog.tip()))
288 288 else:
289 289 ui.status(_('the backout changeset is a new head - '
290 290 'do not forget to merge\n'))
291 291 ui.status(_('(use "backout --merge" '
292 292 'if you want to auto-merge)\n'))
293 293
294 294 def bisect(ui, repo, rev=None, extra=None, command=None,
295 295 reset=None, good=None, bad=None, skip=None, noupdate=None):
296 296 """subdivision search of changesets
297 297
298 298 This command helps to find changesets which introduce problems. To
299 299 use, mark the earliest changeset you know exhibits the problem as
300 300 bad, then mark the latest changeset which is free from the problem
301 301 as good. Bisect will update your working directory to a revision
302 302 for testing (unless the -U/--noupdate option is specified). Once
303 303 you have performed tests, mark the working directory as good or
304 304 bad, and bisect will either update to another candidate changeset
305 305 or announce that it has found the bad revision.
306 306
307 307 As a shortcut, you can also use the revision argument to mark a
308 308 revision as good or bad without checking it out first.
309 309
310 310 If you supply a command, it will be used for automatic bisection.
311 311 Its exit status will be used to mark revisions as good or bad:
312 312 status 0 means good, 125 means to skip the revision, 127
313 313 (command not found) will abort the bisection, and any other
314 314 non-zero exit status means the revision is bad.
315 315
316 316 Returns 0 on success.
317 317 """
318 318 def print_result(nodes, good):
319 319 displayer = cmdutil.show_changeset(ui, repo, {})
320 320 if len(nodes) == 1:
321 321 # narrowed it down to a single revision
322 322 if good:
323 323 ui.write(_("The first good revision is:\n"))
324 324 else:
325 325 ui.write(_("The first bad revision is:\n"))
326 326 displayer.show(repo[nodes[0]])
327 327 parents = repo[nodes[0]].parents()
328 328 if len(parents) > 1:
329 329 side = good and state['bad'] or state['good']
330 330 num = len(set(i.node() for i in parents) & set(side))
331 331 if num == 1:
332 332 common = parents[0].ancestor(parents[1])
333 333 ui.write(_('Not all ancestors of this changeset have been'
334 334 ' checked.\nTo check the other ancestors, start'
335 335 ' from the common ancestor, %s.\n' % common))
336 336 else:
337 337 # multiple possible revisions
338 338 if good:
339 339 ui.write(_("Due to skipped revisions, the first "
340 340 "good revision could be any of:\n"))
341 341 else:
342 342 ui.write(_("Due to skipped revisions, the first "
343 343 "bad revision could be any of:\n"))
344 344 for n in nodes:
345 345 displayer.show(repo[n])
346 346 displayer.close()
347 347
348 348 def check_state(state, interactive=True):
349 349 if not state['good'] or not state['bad']:
350 350 if (good or bad or skip or reset) and interactive:
351 351 return
352 352 if not state['good']:
353 353 raise util.Abort(_('cannot bisect (no known good revisions)'))
354 354 else:
355 355 raise util.Abort(_('cannot bisect (no known bad revisions)'))
356 356 return True
357 357
358 358 # backward compatibility
359 359 if rev in "good bad reset init".split():
360 360 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
361 361 cmd, rev, extra = rev, extra, None
362 362 if cmd == "good":
363 363 good = True
364 364 elif cmd == "bad":
365 365 bad = True
366 366 else:
367 367 reset = True
368 368 elif extra or good + bad + skip + reset + bool(command) > 1:
369 369 raise util.Abort(_('incompatible arguments'))
370 370
371 371 if reset:
372 372 p = repo.join("bisect.state")
373 373 if os.path.exists(p):
374 374 os.unlink(p)
375 375 return
376 376
377 377 state = hbisect.load_state(repo)
378 378
379 379 if command:
380 380 changesets = 1
381 381 try:
382 382 while changesets:
383 383 # update state
384 384 status = util.system(command)
385 385 if status == 125:
386 386 transition = "skip"
387 387 elif status == 0:
388 388 transition = "good"
389 389 # status < 0 means process was killed
390 390 elif status == 127:
391 391 raise util.Abort(_("failed to execute %s") % command)
392 392 elif status < 0:
393 393 raise util.Abort(_("%s killed") % command)
394 394 else:
395 395 transition = "bad"
396 396 ctx = repo[rev or '.']
397 397 state[transition].append(ctx.node())
398 398 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
399 399 check_state(state, interactive=False)
400 400 # bisect
401 401 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
402 402 # update to next check
403 403 cmdutil.bail_if_changed(repo)
404 404 hg.clean(repo, nodes[0], show_stats=False)
405 405 finally:
406 406 hbisect.save_state(repo, state)
407 407 print_result(nodes, good)
408 408 return
409 409
410 410 # update state
411 411
412 412 if rev:
413 413 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
414 414 else:
415 415 nodes = [repo.lookup('.')]
416 416
417 417 if good or bad or skip:
418 418 if good:
419 419 state['good'] += nodes
420 420 elif bad:
421 421 state['bad'] += nodes
422 422 elif skip:
423 423 state['skip'] += nodes
424 424 hbisect.save_state(repo, state)
425 425
426 426 if not check_state(state):
427 427 return
428 428
429 429 # actually bisect
430 430 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
431 431 if changesets == 0:
432 432 print_result(nodes, good)
433 433 else:
434 434 assert len(nodes) == 1 # only a single node can be tested next
435 435 node = nodes[0]
436 436 # compute the approximate number of remaining tests
437 437 tests, size = 0, 2
438 438 while size <= changesets:
439 439 tests, size = tests + 1, size * 2
440 440 rev = repo.changelog.rev(node)
441 441 ui.write(_("Testing changeset %d:%s "
442 442 "(%d changesets remaining, ~%d tests)\n")
443 443 % (rev, short(node), changesets, tests))
444 444 if not noupdate:
445 445 cmdutil.bail_if_changed(repo)
446 446 return hg.clean(repo, node)
447 447
448 448 def branch(ui, repo, label=None, **opts):
449 449 """set or show the current branch name
450 450
451 451 With no argument, show the current branch name. With one argument,
452 452 set the working directory branch name (the branch will not exist
453 453 in the repository until the next commit). Standard practice
454 454 recommends that primary development take place on the 'default'
455 455 branch.
456 456
457 457 Unless -f/--force is specified, branch will not let you set a
458 458 branch name that already exists, even if it's inactive.
459 459
460 460 Use -C/--clean to reset the working directory branch to that of
461 461 the parent of the working directory, negating a previous branch
462 462 change.
463 463
464 464 Use the command :hg:`update` to switch to an existing branch. Use
465 465 :hg:`commit --close-branch` to mark this branch as closed.
466 466
467 467 Returns 0 on success.
468 468 """
469 469
470 470 if opts.get('clean'):
471 471 label = repo[None].parents()[0].branch()
472 472 repo.dirstate.setbranch(label)
473 473 ui.status(_('reset working directory to branch %s\n') % label)
474 474 elif label:
475 475 utflabel = encoding.fromlocal(label)
476 476 if not opts.get('force') and utflabel in repo.branchtags():
477 477 if label not in [p.branch() for p in repo.parents()]:
478 478 raise util.Abort(_('a branch of the same name already exists'
479 479 " (use 'hg update' to switch to it)"))
480 480 repo.dirstate.setbranch(utflabel)
481 481 ui.status(_('marked working directory as branch %s\n') % label)
482 482 else:
483 483 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
484 484
485 485 def branches(ui, repo, active=False, closed=False):
486 486 """list repository named branches
487 487
488 488 List the repository's named branches, indicating which ones are
489 489 inactive. If -c/--closed is specified, also list branches which have
490 490 been marked closed (see :hg:`commit --close-branch`).
491 491
492 492 If -a/--active is specified, only show active branches. A branch
493 493 is considered active if it contains repository heads.
494 494
495 495 Use the command :hg:`update` to switch to an existing branch.
496 496
497 497 Returns 0.
498 498 """
499 499
500 500 hexfunc = ui.debugflag and hex or short
501 501 activebranches = [repo[n].branch() for n in repo.heads()]
502 502 def testactive(tag, node):
503 503 realhead = tag in activebranches
504 504 open = node in repo.branchheads(tag, closed=False)
505 505 return realhead and open
506 506 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
507 507 for tag, node in repo.branchtags().items()],
508 508 reverse=True)
509 509
510 510 for isactive, node, tag in branches:
511 511 if (not active) or isactive:
512 512 encodedtag = encoding.tolocal(tag)
513 513 if ui.quiet:
514 514 ui.write("%s\n" % encodedtag)
515 515 else:
516 516 hn = repo.lookup(node)
517 517 if isactive:
518 518 label = 'branches.active'
519 519 notice = ''
520 520 elif hn not in repo.branchheads(tag, closed=False):
521 521 if not closed:
522 522 continue
523 523 label = 'branches.closed'
524 524 notice = _(' (closed)')
525 525 else:
526 526 label = 'branches.inactive'
527 527 notice = _(' (inactive)')
528 528 if tag == repo.dirstate.branch():
529 529 label = 'branches.current'
530 530 rev = str(node).rjust(31 - encoding.colwidth(encodedtag))
531 531 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
532 532 encodedtag = ui.label(encodedtag, label)
533 533 ui.write("%s %s%s\n" % (encodedtag, rev, notice))
534 534
535 535 def bundle(ui, repo, fname, dest=None, **opts):
536 536 """create a changegroup file
537 537
538 538 Generate a compressed changegroup file collecting changesets not
539 539 known to be in another repository.
540 540
541 541 If you omit the destination repository, then hg assumes the
542 542 destination will have all the nodes you specify with --base
543 543 parameters. To create a bundle containing all changesets, use
544 544 -a/--all (or --base null).
545 545
546 546 You can change compression method with the -t/--type option.
547 547 The available compression methods are: none, bzip2, and
548 548 gzip (by default, bundles are compressed using bzip2).
549 549
550 550 The bundle file can then be transferred using conventional means
551 551 and applied to another repository with the unbundle or pull
552 552 command. This is useful when direct push and pull are not
553 553 available or when exporting an entire repository is undesirable.
554 554
555 555 Applying bundles preserves all changeset contents including
556 556 permissions, copy/rename information, and revision history.
557 557
558 558 Returns 0 on success, 1 if no changes found.
559 559 """
560 560 revs = opts.get('rev') or None
561 561 if opts.get('all'):
562 562 base = ['null']
563 563 else:
564 564 base = opts.get('base')
565 565 if base:
566 566 if dest:
567 567 raise util.Abort(_("--base is incompatible with specifying "
568 568 "a destination"))
569 569 base = [repo.lookup(rev) for rev in base]
570 570 # create the right base
571 571 # XXX: nodesbetween / changegroup* should be "fixed" instead
572 572 o = []
573 573 has = set((nullid,))
574 574 for n in base:
575 575 has.update(repo.changelog.reachable(n))
576 576 if revs:
577 577 revs = [repo.lookup(rev) for rev in revs]
578 578 visit = revs[:]
579 579 has.difference_update(visit)
580 580 else:
581 581 visit = repo.changelog.heads()
582 582 seen = {}
583 583 while visit:
584 584 n = visit.pop(0)
585 585 parents = [p for p in repo.changelog.parents(n) if p not in has]
586 586 if len(parents) == 0:
587 587 if n not in has:
588 588 o.append(n)
589 589 else:
590 590 for p in parents:
591 591 if p not in seen:
592 592 seen[p] = 1
593 593 visit.append(p)
594 594 else:
595 595 dest = ui.expandpath(dest or 'default-push', dest or 'default')
596 596 dest, branches = hg.parseurl(dest, opts.get('branch'))
597 597 other = hg.repository(hg.remoteui(repo, opts), dest)
598 598 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
599 599 if revs:
600 600 revs = [repo.lookup(rev) for rev in revs]
601 601 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
602 602
603 603 if not o:
604 604 ui.status(_("no changes found\n"))
605 605 return 1
606 606
607 607 if revs:
608 608 cg = repo.changegroupsubset(o, revs, 'bundle')
609 609 else:
610 610 cg = repo.changegroup(o, 'bundle')
611 611
612 612 bundletype = opts.get('type', 'bzip2').lower()
613 613 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
614 614 bundletype = btypes.get(bundletype)
615 615 if bundletype not in changegroup.bundletypes:
616 616 raise util.Abort(_('unknown bundle type specified with --type'))
617 617
618 618 changegroup.writebundle(cg, fname, bundletype)
619 619
620 620 def cat(ui, repo, file1, *pats, **opts):
621 621 """output the current or given revision of files
622 622
623 623 Print the specified files as they were at the given revision. If
624 624 no revision is given, the parent of the working directory is used,
625 625 or tip if no revision is checked out.
626 626
627 627 Output may be to a file, in which case the name of the file is
628 628 given using a format string. The formatting rules are the same as
629 629 for the export command, with the following additions:
630 630
631 631 :``%s``: basename of file being printed
632 632 :``%d``: dirname of file being printed, or '.' if in repository root
633 633 :``%p``: root-relative path name of file being printed
634 634
635 635 Returns 0 on success.
636 636 """
637 637 ctx = cmdutil.revsingle(repo, opts.get('rev'))
638 638 err = 1
639 639 m = cmdutil.match(repo, (file1,) + pats, opts)
640 640 for abs in ctx.walk(m):
641 641 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
642 642 data = ctx[abs].data()
643 643 if opts.get('decode'):
644 644 data = repo.wwritedata(abs, data)
645 645 fp.write(data)
646 646 err = 0
647 647 return err
648 648
649 649 def clone(ui, source, dest=None, **opts):
650 650 """make a copy of an existing repository
651 651
652 652 Create a copy of an existing repository in a new directory.
653 653
654 654 If no destination directory name is specified, it defaults to the
655 655 basename of the source.
656 656
657 657 The location of the source is added to the new repository's
658 658 .hg/hgrc file, as the default to be used for future pulls.
659 659
660 660 See :hg:`help urls` for valid source format details.
661 661
662 662 It is possible to specify an ``ssh://`` URL as the destination, but no
663 663 .hg/hgrc and working directory will be created on the remote side.
664 664 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
665 665
666 666 A set of changesets (tags, or branch names) to pull may be specified
667 667 by listing each changeset (tag, or branch name) with -r/--rev.
668 668 If -r/--rev is used, the cloned repository will contain only a subset
669 669 of the changesets of the source repository. Only the set of changesets
670 670 defined by all -r/--rev options (including all their ancestors)
671 671 will be pulled into the destination repository.
672 672 No subsequent changesets (including subsequent tags) will be present
673 673 in the destination.
674 674
675 675 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
676 676 local source repositories.
677 677
678 678 For efficiency, hardlinks are used for cloning whenever the source
679 679 and destination are on the same filesystem (note this applies only
680 680 to the repository data, not to the working directory). Some
681 681 filesystems, such as AFS, implement hardlinking incorrectly, but
682 682 do not report errors. In these cases, use the --pull option to
683 683 avoid hardlinking.
684 684
685 685 In some cases, you can clone repositories and the working directory
686 686 using full hardlinks with ::
687 687
688 688 $ cp -al REPO REPOCLONE
689 689
690 690 This is the fastest way to clone, but it is not always safe. The
691 691 operation is not atomic (making sure REPO is not modified during
692 692 the operation is up to you) and you have to make sure your editor
693 693 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
694 694 this is not compatible with certain extensions that place their
695 695 metadata under the .hg directory, such as mq.
696 696
697 697 Mercurial will update the working directory to the first applicable
698 698 revision from this list:
699 699
700 700 a) null if -U or the source repository has no changesets
701 701 b) if -u . and the source repository is local, the first parent of
702 702 the source repository's working directory
703 703 c) the changeset specified with -u (if a branch name, this means the
704 704 latest head of that branch)
705 705 d) the changeset specified with -r
706 706 e) the tipmost head specified with -b
707 707 f) the tipmost head specified with the url#branch source syntax
708 708 g) the tipmost head of the default branch
709 709 h) tip
710 710
711 711 Returns 0 on success.
712 712 """
713 713 if opts.get('noupdate') and opts.get('updaterev'):
714 714 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
715 715
716 716 r = hg.clone(hg.remoteui(ui, opts), source, dest,
717 717 pull=opts.get('pull'),
718 718 stream=opts.get('uncompressed'),
719 719 rev=opts.get('rev'),
720 720 update=opts.get('updaterev') or not opts.get('noupdate'),
721 721 branch=opts.get('branch'))
722 722
723 723 return r is None
724 724
725 725 def commit(ui, repo, *pats, **opts):
726 726 """commit the specified files or all outstanding changes
727 727
728 728 Commit changes to the given files into the repository. Unlike a
729 729 centralized RCS, this operation is a local operation. See
730 730 :hg:`push` for a way to actively distribute your changes.
731 731
732 732 If a list of files is omitted, all changes reported by :hg:`status`
733 733 will be committed.
734 734
735 735 If you are committing the result of a merge, do not provide any
736 736 filenames or -I/-X filters.
737 737
738 738 If no commit message is specified, Mercurial starts your
739 739 configured editor where you can enter a message. In case your
740 740 commit fails, you will find a backup of your message in
741 741 ``.hg/last-message.txt``.
742 742
743 743 See :hg:`help dates` for a list of formats valid for -d/--date.
744 744
745 745 Returns 0 on success, 1 if nothing changed.
746 746 """
747 747 extra = {}
748 748 if opts.get('close_branch'):
749 749 if repo['.'].node() not in repo.branchheads():
750 750 # The topo heads set is included in the branch heads set of the
751 751 # current branch, so it's sufficient to test branchheads
752 752 raise util.Abort(_('can only close branch heads'))
753 753 extra['close'] = 1
754 754 e = cmdutil.commiteditor
755 755 if opts.get('force_editor'):
756 756 e = cmdutil.commitforceeditor
757 757
758 758 def commitfunc(ui, repo, message, match, opts):
759 759 return repo.commit(message, opts.get('user'), opts.get('date'), match,
760 760 editor=e, extra=extra)
761 761
762 762 branch = repo[None].branch()
763 763 bheads = repo.branchheads(branch)
764 764
765 765 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
766 766 if not node:
767 767 ui.status(_("nothing changed\n"))
768 768 return 1
769 769
770 770 ctx = repo[node]
771 771 parents = ctx.parents()
772 772
773 773 if bheads and not [x for x in parents
774 774 if x.node() in bheads and x.branch() == branch]:
775 775 ui.status(_('created new head\n'))
776 776 # The message is not printed for initial roots. For the other
777 777 # changesets, it is printed in the following situations:
778 778 #
779 779 # Par column: for the 2 parents with ...
780 780 # N: null or no parent
781 781 # B: parent is on another named branch
782 782 # C: parent is a regular non head changeset
783 783 # H: parent was a branch head of the current branch
784 784 # Msg column: whether we print "created new head" message
785 785 # In the following, it is assumed that there already exists some
786 786 # initial branch heads of the current branch, otherwise nothing is
787 787 # printed anyway.
788 788 #
789 789 # Par Msg Comment
790 790 # NN y additional topo root
791 791 #
792 792 # BN y additional branch root
793 793 # CN y additional topo head
794 794 # HN n usual case
795 795 #
796 796 # BB y weird additional branch root
797 797 # CB y branch merge
798 798 # HB n merge with named branch
799 799 #
800 800 # CC y additional head from merge
801 801 # CH n merge with a head
802 802 #
803 803 # HH n head merge: head count decreases
804 804
805 805 if not opts.get('close_branch'):
806 806 for r in parents:
807 807 if r.extra().get('close') and r.branch() == branch:
808 808 ui.status(_('reopening closed branch head %d\n') % r)
809 809
810 810 if ui.debugflag:
811 811 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
812 812 elif ui.verbose:
813 813 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
814 814
815 815 def copy(ui, repo, *pats, **opts):
816 816 """mark files as copied for the next commit
817 817
818 818 Mark dest as having copies of source files. If dest is a
819 819 directory, copies are put in that directory. If dest is a file,
820 820 the source must be a single file.
821 821
822 822 By default, this command copies the contents of files as they
823 823 exist in the working directory. If invoked with -A/--after, the
824 824 operation is recorded, but no copying is performed.
825 825
826 826 This command takes effect with the next commit. To undo a copy
827 827 before that, see :hg:`revert`.
828 828
829 829 Returns 0 on success, 1 if errors are encountered.
830 830 """
831 831 wlock = repo.wlock(False)
832 832 try:
833 833 return cmdutil.copy(ui, repo, pats, opts)
834 834 finally:
835 835 wlock.release()
836 836
837 837 def debugancestor(ui, repo, *args):
838 838 """find the ancestor revision of two revisions in a given index"""
839 839 if len(args) == 3:
840 840 index, rev1, rev2 = args
841 841 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
842 842 lookup = r.lookup
843 843 elif len(args) == 2:
844 844 if not repo:
845 845 raise util.Abort(_("there is no Mercurial repository here "
846 846 "(.hg not found)"))
847 847 rev1, rev2 = args
848 848 r = repo.changelog
849 849 lookup = repo.lookup
850 850 else:
851 851 raise util.Abort(_('either two or three arguments required'))
852 852 a = r.ancestor(lookup(rev1), lookup(rev2))
853 853 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
854 854
855 855 def debugbuilddag(ui, repo, text,
856 856 mergeable_file=False,
857 857 appended_file=False,
858 858 overwritten_file=False,
859 859 new_file=False):
860 860 """builds a repo with a given dag from scratch in the current empty repo
861 861
862 862 Elements:
863 863
864 864 - "+n" is a linear run of n nodes based on the current default parent
865 865 - "." is a single node based on the current default parent
866 866 - "$" resets the default parent to null (implied at the start);
867 867 otherwise the default parent is always the last node created
868 868 - "<p" sets the default parent to the backref p
869 869 - "*p" is a fork at parent p, which is a backref
870 870 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
871 871 - "/p2" is a merge of the preceding node and p2
872 872 - ":tag" defines a local tag for the preceding node
873 873 - "@branch" sets the named branch for subsequent nodes
874 874 - "!command" runs the command using your shell
875 875 - "!!my command\\n" is like "!", but to the end of the line
876 876 - "#...\\n" is a comment up to the end of the line
877 877
878 878 Whitespace between the above elements is ignored.
879 879
880 880 A backref is either
881 881
882 882 - a number n, which references the node curr-n, where curr is the current
883 883 node, or
884 884 - the name of a local tag you placed earlier using ":tag", or
885 885 - empty to denote the default parent.
886 886
887 887 All string valued-elements are either strictly alphanumeric, or must
888 888 be enclosed in double quotes ("..."), with "\\" as escape character.
889 889
890 890 Note that the --overwritten-file and --appended-file options imply the
891 891 use of "HGMERGE=internal:local" during DAG buildup.
892 892 """
893 893
894 894 if not (mergeable_file or appended_file or overwritten_file or new_file):
895 895 raise util.Abort(_('need at least one of -m, -a, -o, -n'))
896 896
897 897 if len(repo.changelog) > 0:
898 898 raise util.Abort(_('repository is not empty'))
899 899
900 900 if overwritten_file or appended_file:
901 901 # we don't want to fail in merges during buildup
902 902 os.environ['HGMERGE'] = 'internal:local'
903 903
904 904 def writefile(fname, text, fmode="wb"):
905 905 f = open(fname, fmode)
906 906 try:
907 907 f.write(text)
908 908 finally:
909 909 f.close()
910 910
911 911 if mergeable_file:
912 912 linesperrev = 2
913 913 # determine number of revs in DAG
914 914 n = 0
915 915 for type, data in dagparser.parsedag(text):
916 916 if type == 'n':
917 917 n += 1
918 918 # make a file with k lines per rev
919 919 writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
920 920 + "\n")
921 921
922 922 at = -1
923 923 atbranch = 'default'
924 924 for type, data in dagparser.parsedag(text):
925 925 if type == 'n':
926 926 ui.status('node %s\n' % str(data))
927 927 id, ps = data
928 928 p1 = ps[0]
929 929 if p1 != at:
930 930 update(ui, repo, node=p1, clean=True)
931 931 at = p1
932 932 if repo.dirstate.branch() != atbranch:
933 933 branch(ui, repo, atbranch, force=True)
934 934 if len(ps) > 1:
935 935 p2 = ps[1]
936 936 merge(ui, repo, node=p2)
937 937
938 938 if mergeable_file:
939 939 f = open("mf", "rb+")
940 940 try:
941 941 lines = f.read().split("\n")
942 942 lines[id * linesperrev] += " r%i" % id
943 943 f.seek(0)
944 944 f.write("\n".join(lines))
945 945 finally:
946 946 f.close()
947 947
948 948 if appended_file:
949 949 writefile("af", "r%i\n" % id, "ab")
950 950
951 951 if overwritten_file:
952 952 writefile("of", "r%i\n" % id)
953 953
954 954 if new_file:
955 955 writefile("nf%i" % id, "r%i\n" % id)
956 956
957 957 commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
958 958 at = id
959 959 elif type == 'l':
960 960 id, name = data
961 961 ui.status('tag %s\n' % name)
962 962 tag(ui, repo, name, local=True)
963 963 elif type == 'a':
964 964 ui.status('branch %s\n' % data)
965 965 atbranch = data
966 966 elif type in 'cC':
967 967 r = util.system(data, cwd=repo.root)
968 968 if r:
969 969 desc, r = util.explain_exit(r)
970 970 raise util.Abort(_('%s command %s') % (data, desc))
971 971
972 972 def debugcommands(ui, cmd='', *args):
973 973 """list all available commands and options"""
974 974 for cmd, vals in sorted(table.iteritems()):
975 975 cmd = cmd.split('|')[0].strip('^')
976 976 opts = ', '.join([i[1] for i in vals[1]])
977 977 ui.write('%s: %s\n' % (cmd, opts))
978 978
979 979 def debugcomplete(ui, cmd='', **opts):
980 980 """returns the completion list associated with the given command"""
981 981
982 982 if opts.get('options'):
983 983 options = []
984 984 otables = [globalopts]
985 985 if cmd:
986 986 aliases, entry = cmdutil.findcmd(cmd, table, False)
987 987 otables.append(entry[1])
988 988 for t in otables:
989 989 for o in t:
990 990 if "(DEPRECATED)" in o[3]:
991 991 continue
992 992 if o[0]:
993 993 options.append('-%s' % o[0])
994 994 options.append('--%s' % o[1])
995 995 ui.write("%s\n" % "\n".join(options))
996 996 return
997 997
998 998 cmdlist = cmdutil.findpossible(cmd, table)
999 999 if ui.verbose:
1000 1000 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1001 1001 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1002 1002
1003 1003 def debugfsinfo(ui, path = "."):
1004 1004 """show information detected about current filesystem"""
1005 1005 open('.debugfsinfo', 'w').write('')
1006 1006 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1007 1007 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1008 1008 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1009 1009 and 'yes' or 'no'))
1010 1010 os.unlink('.debugfsinfo')
1011 1011
1012 1012 def debugrebuildstate(ui, repo, rev="tip"):
1013 1013 """rebuild the dirstate as it would look like for the given revision"""
1014 1014 ctx = repo[rev]
1015 1015 wlock = repo.wlock()
1016 1016 try:
1017 1017 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1018 1018 finally:
1019 1019 wlock.release()
1020 1020
1021 1021 def debugcheckstate(ui, repo):
1022 1022 """validate the correctness of the current dirstate"""
1023 1023 parent1, parent2 = repo.dirstate.parents()
1024 1024 m1 = repo[parent1].manifest()
1025 1025 m2 = repo[parent2].manifest()
1026 1026 errors = 0
1027 1027 for f in repo.dirstate:
1028 1028 state = repo.dirstate[f]
1029 1029 if state in "nr" and f not in m1:
1030 1030 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1031 1031 errors += 1
1032 1032 if state in "a" and f in m1:
1033 1033 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1034 1034 errors += 1
1035 1035 if state in "m" and f not in m1 and f not in m2:
1036 1036 ui.warn(_("%s in state %s, but not in either manifest\n") %
1037 1037 (f, state))
1038 1038 errors += 1
1039 1039 for f in m1:
1040 1040 state = repo.dirstate[f]
1041 1041 if state not in "nrm":
1042 1042 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1043 1043 errors += 1
1044 1044 if errors:
1045 1045 error = _(".hg/dirstate inconsistent with current parent's manifest")
1046 1046 raise util.Abort(error)
1047 1047
1048 1048 def showconfig(ui, repo, *values, **opts):
1049 1049 """show combined config settings from all hgrc files
1050 1050
1051 1051 With no arguments, print names and values of all config items.
1052 1052
1053 1053 With one argument of the form section.name, print just the value
1054 1054 of that config item.
1055 1055
1056 1056 With multiple arguments, print names and values of all config
1057 1057 items with matching section names.
1058 1058
1059 1059 With --debug, the source (filename and line number) is printed
1060 1060 for each config item.
1061 1061
1062 1062 Returns 0 on success.
1063 1063 """
1064 1064
1065 1065 for f in util.rcpath():
1066 1066 ui.debug(_('read config from: %s\n') % f)
1067 1067 untrusted = bool(opts.get('untrusted'))
1068 1068 if values:
1069 1069 if len([v for v in values if '.' in v]) > 1:
1070 1070 raise util.Abort(_('only one config item permitted'))
1071 1071 for section, name, value in ui.walkconfig(untrusted=untrusted):
1072 1072 sectname = section + '.' + name
1073 1073 if values:
1074 1074 for v in values:
1075 1075 if v == section:
1076 1076 ui.debug('%s: ' %
1077 1077 ui.configsource(section, name, untrusted))
1078 1078 ui.write('%s=%s\n' % (sectname, value))
1079 1079 elif v == sectname:
1080 1080 ui.debug('%s: ' %
1081 1081 ui.configsource(section, name, untrusted))
1082 1082 ui.write(value, '\n')
1083 1083 else:
1084 1084 ui.debug('%s: ' %
1085 1085 ui.configsource(section, name, untrusted))
1086 1086 ui.write('%s=%s\n' % (sectname, value))
1087 1087
1088 1088 def debugpushkey(ui, repopath, namespace, *keyinfo):
1089 1089 '''access the pushkey key/value protocol
1090 1090
1091 1091 With two args, list the keys in the given namespace.
1092 1092
1093 1093 With five args, set a key to new if it currently is set to old.
1094 1094 Reports success or failure.
1095 1095 '''
1096 1096
1097 1097 target = hg.repository(ui, repopath)
1098 1098 if keyinfo:
1099 1099 key, old, new = keyinfo
1100 1100 r = target.pushkey(namespace, key, old, new)
1101 1101 ui.status(str(r) + '\n')
1102 1102 return not(r)
1103 1103 else:
1104 1104 for k, v in target.listkeys(namespace).iteritems():
1105 1105 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1106 1106 v.encode('string-escape')))
1107 1107
1108 1108 def debugrevspec(ui, repo, expr):
1109 1109 '''parse and apply a revision specification'''
1110 1110 if ui.verbose:
1111 1111 tree = revset.parse(expr)
1112 1112 ui.note(tree, "\n")
1113 1113 func = revset.match(expr)
1114 1114 for c in func(repo, range(len(repo))):
1115 1115 ui.write("%s\n" % c)
1116 1116
1117 1117 def debugsetparents(ui, repo, rev1, rev2=None):
1118 1118 """manually set the parents of the current working directory
1119 1119
1120 1120 This is useful for writing repository conversion tools, but should
1121 1121 be used with care.
1122 1122
1123 1123 Returns 0 on success.
1124 1124 """
1125 1125
1126 1126 if not rev2:
1127 1127 rev2 = hex(nullid)
1128 1128
1129 1129 wlock = repo.wlock()
1130 1130 try:
1131 1131 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1132 1132 finally:
1133 1133 wlock.release()
1134 1134
1135 1135 def debugstate(ui, repo, nodates=None):
1136 1136 """show the contents of the current dirstate"""
1137 1137 timestr = ""
1138 1138 showdate = not nodates
1139 1139 for file_, ent in sorted(repo.dirstate._map.iteritems()):
1140 1140 if showdate:
1141 1141 if ent[3] == -1:
1142 1142 # Pad or slice to locale representation
1143 1143 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1144 1144 time.localtime(0)))
1145 1145 timestr = 'unset'
1146 1146 timestr = (timestr[:locale_len] +
1147 1147 ' ' * (locale_len - len(timestr)))
1148 1148 else:
1149 1149 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1150 1150 time.localtime(ent[3]))
1151 1151 if ent[1] & 020000:
1152 1152 mode = 'lnk'
1153 1153 else:
1154 1154 mode = '%3o' % (ent[1] & 0777)
1155 1155 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1156 1156 for f in repo.dirstate.copies():
1157 1157 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1158 1158
1159 1159 def debugsub(ui, repo, rev=None):
1160 1160 if rev == '':
1161 1161 rev = None
1162 1162 for k, v in sorted(repo[rev].substate.items()):
1163 1163 ui.write('path %s\n' % k)
1164 1164 ui.write(' source %s\n' % v[0])
1165 1165 ui.write(' revision %s\n' % v[1])
1166 1166
1167 1167 def debugdag(ui, repo, file_=None, *revs, **opts):
1168 1168 """format the changelog or an index DAG as a concise textual description
1169 1169
1170 1170 If you pass a revlog index, the revlog's DAG is emitted. If you list
1171 1171 revision numbers, they get labelled in the output as rN.
1172 1172
1173 1173 Otherwise, the changelog DAG of the current repo is emitted.
1174 1174 """
1175 1175 spaces = opts.get('spaces')
1176 1176 dots = opts.get('dots')
1177 1177 if file_:
1178 1178 rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1179 1179 revs = set((int(r) for r in revs))
1180 1180 def events():
1181 1181 for r in rlog:
1182 1182 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1183 1183 if r in revs:
1184 1184 yield 'l', (r, "r%i" % r)
1185 1185 elif repo:
1186 1186 cl = repo.changelog
1187 1187 tags = opts.get('tags')
1188 1188 branches = opts.get('branches')
1189 1189 if tags:
1190 1190 labels = {}
1191 1191 for l, n in repo.tags().items():
1192 1192 labels.setdefault(cl.rev(n), []).append(l)
1193 1193 def events():
1194 1194 b = "default"
1195 1195 for r in cl:
1196 1196 if branches:
1197 1197 newb = cl.read(cl.node(r))[5]['branch']
1198 1198 if newb != b:
1199 1199 yield 'a', newb
1200 1200 b = newb
1201 1201 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1202 1202 if tags:
1203 1203 ls = labels.get(r)
1204 1204 if ls:
1205 1205 for l in ls:
1206 1206 yield 'l', (r, l)
1207 1207 else:
1208 1208 raise util.Abort(_('need repo for changelog dag'))
1209 1209
1210 1210 for line in dagparser.dagtextlines(events(),
1211 1211 addspaces=spaces,
1212 1212 wraplabels=True,
1213 1213 wrapannotations=True,
1214 1214 wrapnonlinear=dots,
1215 1215 usedots=dots,
1216 1216 maxlinewidth=70):
1217 1217 ui.write(line)
1218 1218 ui.write("\n")
1219 1219
1220 1220 def debugdata(ui, repo, file_, rev):
1221 1221 """dump the contents of a data file revision"""
1222 1222 r = None
1223 1223 if repo:
1224 1224 filelog = repo.file(file_)
1225 1225 if len(filelog):
1226 1226 r = filelog
1227 1227 if not r:
1228 1228 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1229 1229 try:
1230 1230 ui.write(r.revision(r.lookup(rev)))
1231 1231 except KeyError:
1232 1232 raise util.Abort(_('invalid revision identifier %s') % rev)
1233 1233
1234 1234 def debugdate(ui, date, range=None, **opts):
1235 1235 """parse and display a date"""
1236 1236 if opts["extended"]:
1237 1237 d = util.parsedate(date, util.extendeddateformats)
1238 1238 else:
1239 1239 d = util.parsedate(date)
1240 1240 ui.write("internal: %s %s\n" % d)
1241 1241 ui.write("standard: %s\n" % util.datestr(d))
1242 1242 if range:
1243 1243 m = util.matchdate(range)
1244 1244 ui.write("match: %s\n" % m(d[0]))
1245 1245
1246 1246 def debugindex(ui, repo, file_):
1247 1247 """dump the contents of an index file"""
1248 1248 r = None
1249 1249 if repo:
1250 1250 filelog = repo.file(file_)
1251 1251 if len(filelog):
1252 1252 r = filelog
1253 1253 if not r:
1254 1254 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1255 1255 ui.write(" rev offset length base linkrev"
1256 1256 " nodeid p1 p2\n")
1257 1257 for i in r:
1258 1258 node = r.node(i)
1259 1259 try:
1260 1260 pp = r.parents(node)
1261 1261 except:
1262 1262 pp = [nullid, nullid]
1263 1263 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1264 1264 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1265 1265 short(node), short(pp[0]), short(pp[1])))
1266 1266
1267 1267 def debugindexdot(ui, repo, file_):
1268 1268 """dump an index DAG as a graphviz dot file"""
1269 1269 r = None
1270 1270 if repo:
1271 1271 filelog = repo.file(file_)
1272 1272 if len(filelog):
1273 1273 r = filelog
1274 1274 if not r:
1275 1275 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1276 1276 ui.write("digraph G {\n")
1277 1277 for i in r:
1278 1278 node = r.node(i)
1279 1279 pp = r.parents(node)
1280 1280 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1281 1281 if pp[1] != nullid:
1282 1282 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1283 1283 ui.write("}\n")
1284 1284
1285 1285 def debuginstall(ui):
1286 1286 '''test Mercurial installation
1287 1287
1288 1288 Returns 0 on success.
1289 1289 '''
1290 1290
1291 1291 def writetemp(contents):
1292 1292 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1293 1293 f = os.fdopen(fd, "wb")
1294 1294 f.write(contents)
1295 1295 f.close()
1296 1296 return name
1297 1297
1298 1298 problems = 0
1299 1299
1300 1300 # encoding
1301 1301 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1302 1302 try:
1303 1303 encoding.fromlocal("test")
1304 1304 except util.Abort, inst:
1305 1305 ui.write(" %s\n" % inst)
1306 1306 ui.write(_(" (check that your locale is properly set)\n"))
1307 1307 problems += 1
1308 1308
1309 1309 # compiled modules
1310 1310 ui.status(_("Checking installed modules (%s)...\n")
1311 1311 % os.path.dirname(__file__))
1312 1312 try:
1313 1313 import bdiff, mpatch, base85, osutil
1314 1314 except Exception, inst:
1315 1315 ui.write(" %s\n" % inst)
1316 1316 ui.write(_(" One or more extensions could not be found"))
1317 1317 ui.write(_(" (check that you compiled the extensions)\n"))
1318 1318 problems += 1
1319 1319
1320 1320 # templates
1321 1321 ui.status(_("Checking templates...\n"))
1322 1322 try:
1323 1323 import templater
1324 1324 templater.templater(templater.templatepath("map-cmdline.default"))
1325 1325 except Exception, inst:
1326 1326 ui.write(" %s\n" % inst)
1327 1327 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1328 1328 problems += 1
1329 1329
1330 1330 # patch
1331 1331 ui.status(_("Checking patch...\n"))
1332 1332 patchproblems = 0
1333 1333 a = "1\n2\n3\n4\n"
1334 1334 b = "1\n2\n3\ninsert\n4\n"
1335 1335 fa = writetemp(a)
1336 1336 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1337 1337 os.path.basename(fa))
1338 1338 fd = writetemp(d)
1339 1339
1340 1340 files = {}
1341 1341 try:
1342 1342 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1343 1343 except util.Abort, e:
1344 1344 ui.write(_(" patch call failed:\n"))
1345 1345 ui.write(" " + str(e) + "\n")
1346 1346 patchproblems += 1
1347 1347 else:
1348 1348 if list(files) != [os.path.basename(fa)]:
1349 1349 ui.write(_(" unexpected patch output!\n"))
1350 1350 patchproblems += 1
1351 1351 a = open(fa).read()
1352 1352 if a != b:
1353 1353 ui.write(_(" patch test failed!\n"))
1354 1354 patchproblems += 1
1355 1355
1356 1356 if patchproblems:
1357 1357 if ui.config('ui', 'patch'):
1358 1358 ui.write(_(" (Current patch tool may be incompatible with patch,"
1359 1359 " or misconfigured. Please check your configuration"
1360 1360 " file)\n"))
1361 1361 else:
1362 1362 ui.write(_(" Internal patcher failure, please report this error"
1363 1363 " to http://mercurial.selenic.com/bts/\n"))
1364 1364 problems += patchproblems
1365 1365
1366 1366 os.unlink(fa)
1367 1367 os.unlink(fd)
1368 1368
1369 1369 # editor
1370 1370 ui.status(_("Checking commit editor...\n"))
1371 1371 editor = ui.geteditor()
1372 1372 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1373 1373 if not cmdpath:
1374 1374 if editor == 'vi':
1375 1375 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1376 1376 ui.write(_(" (specify a commit editor in your configuration"
1377 1377 " file)\n"))
1378 1378 else:
1379 1379 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1380 1380 ui.write(_(" (specify a commit editor in your configuration"
1381 1381 " file)\n"))
1382 1382 problems += 1
1383 1383
1384 1384 # check username
1385 1385 ui.status(_("Checking username...\n"))
1386 1386 try:
1387 1387 ui.username()
1388 1388 except util.Abort, e:
1389 1389 ui.write(" %s\n" % e)
1390 1390 ui.write(_(" (specify a username in your configuration file)\n"))
1391 1391 problems += 1
1392 1392
1393 1393 if not problems:
1394 1394 ui.status(_("No problems detected\n"))
1395 1395 else:
1396 1396 ui.write(_("%s problems detected,"
1397 1397 " please check your install!\n") % problems)
1398 1398
1399 1399 return problems
1400 1400
1401 1401 def debugrename(ui, repo, file1, *pats, **opts):
1402 1402 """dump rename information"""
1403 1403
1404 1404 ctx = repo[opts.get('rev')]
1405 1405 m = cmdutil.match(repo, (file1,) + pats, opts)
1406 1406 for abs in ctx.walk(m):
1407 1407 fctx = ctx[abs]
1408 1408 o = fctx.filelog().renamed(fctx.filenode())
1409 1409 rel = m.rel(abs)
1410 1410 if o:
1411 1411 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1412 1412 else:
1413 1413 ui.write(_("%s not renamed\n") % rel)
1414 1414
1415 1415 def debugwalk(ui, repo, *pats, **opts):
1416 1416 """show how files match on given patterns"""
1417 1417 m = cmdutil.match(repo, pats, opts)
1418 1418 items = list(repo.walk(m))
1419 1419 if not items:
1420 1420 return
1421 1421 fmt = 'f %%-%ds %%-%ds %%s' % (
1422 1422 max([len(abs) for abs in items]),
1423 1423 max([len(m.rel(abs)) for abs in items]))
1424 1424 for abs in items:
1425 1425 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1426 1426 ui.write("%s\n" % line.rstrip())
1427 1427
1428 1428 def diff(ui, repo, *pats, **opts):
1429 1429 """diff repository (or selected files)
1430 1430
1431 1431 Show differences between revisions for the specified files.
1432 1432
1433 1433 Differences between files are shown using the unified diff format.
1434 1434
1435 1435 .. note::
1436 1436 diff may generate unexpected results for merges, as it will
1437 1437 default to comparing against the working directory's first
1438 1438 parent changeset if no revisions are specified.
1439 1439
1440 1440 When two revision arguments are given, then changes are shown
1441 1441 between those revisions. If only one revision is specified then
1442 1442 that revision is compared to the working directory, and, when no
1443 1443 revisions are specified, the working directory files are compared
1444 1444 to its parent.
1445 1445
1446 1446 Alternatively you can specify -c/--change with a revision to see
1447 1447 the changes in that changeset relative to its first parent.
1448 1448
1449 1449 Without the -a/--text option, diff will avoid generating diffs of
1450 1450 files it detects as binary. With -a, diff will generate a diff
1451 1451 anyway, probably with undesirable results.
1452 1452
1453 1453 Use the -g/--git option to generate diffs in the git extended diff
1454 1454 format. For more information, read :hg:`help diffs`.
1455 1455
1456 1456 Returns 0 on success.
1457 1457 """
1458 1458
1459 1459 revs = opts.get('rev')
1460 1460 change = opts.get('change')
1461 1461 stat = opts.get('stat')
1462 1462 reverse = opts.get('reverse')
1463 1463
1464 1464 if revs and change:
1465 1465 msg = _('cannot specify --rev and --change at the same time')
1466 1466 raise util.Abort(msg)
1467 1467 elif change:
1468 1468 node2 = repo.lookup(change)
1469 1469 node1 = repo[node2].parents()[0].node()
1470 1470 else:
1471 1471 node1, node2 = cmdutil.revpair(repo, revs)
1472 1472
1473 1473 if reverse:
1474 1474 node1, node2 = node2, node1
1475 1475
1476 1476 diffopts = patch.diffopts(ui, opts)
1477 1477 m = cmdutil.match(repo, pats, opts)
1478 1478 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1479 1479 listsubrepos=opts.get('subrepos'))
1480 1480
1481 1481 def export(ui, repo, *changesets, **opts):
1482 1482 """dump the header and diffs for one or more changesets
1483 1483
1484 1484 Print the changeset header and diffs for one or more revisions.
1485 1485
1486 1486 The information shown in the changeset header is: author, date,
1487 1487 branch name (if non-default), changeset hash, parent(s) and commit
1488 1488 comment.
1489 1489
1490 1490 .. note::
1491 1491 export may generate unexpected diff output for merge
1492 1492 changesets, as it will compare the merge changeset against its
1493 1493 first parent only.
1494 1494
1495 1495 Output may be to a file, in which case the name of the file is
1496 1496 given using a format string. The formatting rules are as follows:
1497 1497
1498 1498 :``%%``: literal "%" character
1499 1499 :``%H``: changeset hash (40 hexadecimal digits)
1500 1500 :``%N``: number of patches being generated
1501 1501 :``%R``: changeset revision number
1502 1502 :``%b``: basename of the exporting repository
1503 1503 :``%h``: short-form changeset hash (12 hexadecimal digits)
1504 1504 :``%n``: zero-padded sequence number, starting at 1
1505 1505 :``%r``: zero-padded changeset revision number
1506 1506
1507 1507 Without the -a/--text option, export will avoid generating diffs
1508 1508 of files it detects as binary. With -a, export will generate a
1509 1509 diff anyway, probably with undesirable results.
1510 1510
1511 1511 Use the -g/--git option to generate diffs in the git extended diff
1512 1512 format. See :hg:`help diffs` for more information.
1513 1513
1514 1514 With the --switch-parent option, the diff will be against the
1515 1515 second parent. It can be useful to review a merge.
1516 1516
1517 1517 Returns 0 on success.
1518 1518 """
1519 1519 changesets += tuple(opts.get('rev', []))
1520 1520 if not changesets:
1521 1521 raise util.Abort(_("export requires at least one changeset"))
1522 1522 revs = cmdutil.revrange(repo, changesets)
1523 1523 if len(revs) > 1:
1524 1524 ui.note(_('exporting patches:\n'))
1525 1525 else:
1526 1526 ui.note(_('exporting patch:\n'))
1527 1527 cmdutil.export(repo, revs, template=opts.get('output'),
1528 1528 switch_parent=opts.get('switch_parent'),
1529 1529 opts=patch.diffopts(ui, opts))
1530 1530
1531 1531 def forget(ui, repo, *pats, **opts):
1532 1532 """forget the specified files on the next commit
1533 1533
1534 1534 Mark the specified files so they will no longer be tracked
1535 1535 after the next commit.
1536 1536
1537 1537 This only removes files from the current branch, not from the
1538 1538 entire project history, and it does not delete them from the
1539 1539 working directory.
1540 1540
1541 1541 To undo a forget before the next commit, see :hg:`add`.
1542 1542
1543 1543 Returns 0 on success.
1544 1544 """
1545 1545
1546 1546 if not pats:
1547 1547 raise util.Abort(_('no files specified'))
1548 1548
1549 1549 m = cmdutil.match(repo, pats, opts)
1550 1550 s = repo.status(match=m, clean=True)
1551 1551 forget = sorted(s[0] + s[1] + s[3] + s[6])
1552 1552 errs = 0
1553 1553
1554 1554 for f in m.files():
1555 1555 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1556 1556 ui.warn(_('not removing %s: file is already untracked\n')
1557 1557 % m.rel(f))
1558 1558 errs = 1
1559 1559
1560 1560 for f in forget:
1561 1561 if ui.verbose or not m.exact(f):
1562 1562 ui.status(_('removing %s\n') % m.rel(f))
1563 1563
1564 1564 repo[None].remove(forget, unlink=False)
1565 1565 return errs
1566 1566
1567 1567 def grep(ui, repo, pattern, *pats, **opts):
1568 1568 """search for a pattern in specified files and revisions
1569 1569
1570 1570 Search revisions of files for a regular expression.
1571 1571
1572 1572 This command behaves differently than Unix grep. It only accepts
1573 1573 Python/Perl regexps. It searches repository history, not the
1574 1574 working directory. It always prints the revision number in which a
1575 1575 match appears.
1576 1576
1577 1577 By default, grep only prints output for the first revision of a
1578 1578 file in which it finds a match. To get it to print every revision
1579 1579 that contains a change in match status ("-" for a match that
1580 1580 becomes a non-match, or "+" for a non-match that becomes a match),
1581 1581 use the --all flag.
1582 1582
1583 1583 Returns 0 if a match is found, 1 otherwise.
1584 1584 """
1585 1585 reflags = 0
1586 1586 if opts.get('ignore_case'):
1587 1587 reflags |= re.I
1588 1588 try:
1589 1589 regexp = re.compile(pattern, reflags)
1590 1590 except re.error, inst:
1591 1591 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1592 1592 return 1
1593 1593 sep, eol = ':', '\n'
1594 1594 if opts.get('print0'):
1595 1595 sep = eol = '\0'
1596 1596
1597 1597 getfile = util.lrucachefunc(repo.file)
1598 1598
1599 1599 def matchlines(body):
1600 1600 begin = 0
1601 1601 linenum = 0
1602 1602 while True:
1603 1603 match = regexp.search(body, begin)
1604 1604 if not match:
1605 1605 break
1606 1606 mstart, mend = match.span()
1607 1607 linenum += body.count('\n', begin, mstart) + 1
1608 1608 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1609 1609 begin = body.find('\n', mend) + 1 or len(body)
1610 1610 lend = begin - 1
1611 1611 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1612 1612
1613 1613 class linestate(object):
1614 1614 def __init__(self, line, linenum, colstart, colend):
1615 1615 self.line = line
1616 1616 self.linenum = linenum
1617 1617 self.colstart = colstart
1618 1618 self.colend = colend
1619 1619
1620 1620 def __hash__(self):
1621 1621 return hash((self.linenum, self.line))
1622 1622
1623 1623 def __eq__(self, other):
1624 1624 return self.line == other.line
1625 1625
1626 1626 matches = {}
1627 1627 copies = {}
1628 1628 def grepbody(fn, rev, body):
1629 1629 matches[rev].setdefault(fn, [])
1630 1630 m = matches[rev][fn]
1631 1631 for lnum, cstart, cend, line in matchlines(body):
1632 1632 s = linestate(line, lnum, cstart, cend)
1633 1633 m.append(s)
1634 1634
1635 1635 def difflinestates(a, b):
1636 1636 sm = difflib.SequenceMatcher(None, a, b)
1637 1637 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1638 1638 if tag == 'insert':
1639 1639 for i in xrange(blo, bhi):
1640 1640 yield ('+', b[i])
1641 1641 elif tag == 'delete':
1642 1642 for i in xrange(alo, ahi):
1643 1643 yield ('-', a[i])
1644 1644 elif tag == 'replace':
1645 1645 for i in xrange(alo, ahi):
1646 1646 yield ('-', a[i])
1647 1647 for i in xrange(blo, bhi):
1648 1648 yield ('+', b[i])
1649 1649
1650 1650 def display(fn, ctx, pstates, states):
1651 1651 rev = ctx.rev()
1652 1652 datefunc = ui.quiet and util.shortdate or util.datestr
1653 1653 found = False
1654 1654 filerevmatches = {}
1655 1655 if opts.get('all'):
1656 1656 iter = difflinestates(pstates, states)
1657 1657 else:
1658 1658 iter = [('', l) for l in states]
1659 1659 for change, l in iter:
1660 1660 cols = [fn, str(rev)]
1661 1661 before, match, after = None, None, None
1662 1662 if opts.get('line_number'):
1663 1663 cols.append(str(l.linenum))
1664 1664 if opts.get('all'):
1665 1665 cols.append(change)
1666 1666 if opts.get('user'):
1667 1667 cols.append(ui.shortuser(ctx.user()))
1668 1668 if opts.get('date'):
1669 1669 cols.append(datefunc(ctx.date()))
1670 1670 if opts.get('files_with_matches'):
1671 1671 c = (fn, rev)
1672 1672 if c in filerevmatches:
1673 1673 continue
1674 1674 filerevmatches[c] = 1
1675 1675 else:
1676 1676 before = l.line[:l.colstart]
1677 1677 match = l.line[l.colstart:l.colend]
1678 1678 after = l.line[l.colend:]
1679 1679 ui.write(sep.join(cols))
1680 1680 if before is not None:
1681 1681 ui.write(sep + before)
1682 1682 ui.write(match, label='grep.match')
1683 1683 ui.write(after)
1684 1684 ui.write(eol)
1685 1685 found = True
1686 1686 return found
1687 1687
1688 1688 skip = {}
1689 1689 revfiles = {}
1690 1690 matchfn = cmdutil.match(repo, pats, opts)
1691 1691 found = False
1692 1692 follow = opts.get('follow')
1693 1693
1694 1694 def prep(ctx, fns):
1695 1695 rev = ctx.rev()
1696 1696 pctx = ctx.parents()[0]
1697 1697 parent = pctx.rev()
1698 1698 matches.setdefault(rev, {})
1699 1699 matches.setdefault(parent, {})
1700 1700 files = revfiles.setdefault(rev, [])
1701 1701 for fn in fns:
1702 1702 flog = getfile(fn)
1703 1703 try:
1704 1704 fnode = ctx.filenode(fn)
1705 1705 except error.LookupError:
1706 1706 continue
1707 1707
1708 1708 copied = flog.renamed(fnode)
1709 1709 copy = follow and copied and copied[0]
1710 1710 if copy:
1711 1711 copies.setdefault(rev, {})[fn] = copy
1712 1712 if fn in skip:
1713 1713 if copy:
1714 1714 skip[copy] = True
1715 1715 continue
1716 1716 files.append(fn)
1717 1717
1718 1718 if fn not in matches[rev]:
1719 1719 grepbody(fn, rev, flog.read(fnode))
1720 1720
1721 1721 pfn = copy or fn
1722 1722 if pfn not in matches[parent]:
1723 1723 try:
1724 1724 fnode = pctx.filenode(pfn)
1725 1725 grepbody(pfn, parent, flog.read(fnode))
1726 1726 except error.LookupError:
1727 1727 pass
1728 1728
1729 1729 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1730 1730 rev = ctx.rev()
1731 1731 parent = ctx.parents()[0].rev()
1732 1732 for fn in sorted(revfiles.get(rev, [])):
1733 1733 states = matches[rev][fn]
1734 1734 copy = copies.get(rev, {}).get(fn)
1735 1735 if fn in skip:
1736 1736 if copy:
1737 1737 skip[copy] = True
1738 1738 continue
1739 1739 pstates = matches.get(parent, {}).get(copy or fn, [])
1740 1740 if pstates or states:
1741 1741 r = display(fn, ctx, pstates, states)
1742 1742 found = found or r
1743 1743 if r and not opts.get('all'):
1744 1744 skip[fn] = True
1745 1745 if copy:
1746 1746 skip[copy] = True
1747 1747 del matches[rev]
1748 1748 del revfiles[rev]
1749 1749
1750 1750 return not found
1751 1751
1752 1752 def heads(ui, repo, *branchrevs, **opts):
1753 1753 """show current repository heads or show branch heads
1754 1754
1755 1755 With no arguments, show all repository branch heads.
1756 1756
1757 1757 Repository "heads" are changesets with no child changesets. They are
1758 1758 where development generally takes place and are the usual targets
1759 1759 for update and merge operations. Branch heads are changesets that have
1760 1760 no child changeset on the same branch.
1761 1761
1762 1762 If one or more REVs are given, only branch heads on the branches
1763 1763 associated with the specified changesets are shown.
1764 1764
1765 1765 If -c/--closed is specified, also show branch heads marked closed
1766 1766 (see :hg:`commit --close-branch`).
1767 1767
1768 1768 If STARTREV is specified, only those heads that are descendants of
1769 1769 STARTREV will be displayed.
1770 1770
1771 1771 If -t/--topo is specified, named branch mechanics will be ignored and only
1772 1772 changesets without children will be shown.
1773 1773
1774 1774 Returns 0 if matching heads are found, 1 if not.
1775 1775 """
1776 1776
1777 1777 if opts.get('rev'):
1778 1778 start = repo.lookup(opts['rev'])
1779 1779 else:
1780 1780 start = None
1781 1781
1782 1782 if opts.get('topo'):
1783 1783 heads = [repo[h] for h in repo.heads(start)]
1784 1784 else:
1785 1785 heads = []
1786 1786 for b, ls in repo.branchmap().iteritems():
1787 1787 if start is None:
1788 1788 heads += [repo[h] for h in ls]
1789 1789 continue
1790 1790 startrev = repo.changelog.rev(start)
1791 1791 descendants = set(repo.changelog.descendants(startrev))
1792 1792 descendants.add(startrev)
1793 1793 rev = repo.changelog.rev
1794 1794 heads += [repo[h] for h in ls if rev(h) in descendants]
1795 1795
1796 1796 if branchrevs:
1797 1797 decode, encode = encoding.fromlocal, encoding.tolocal
1798 1798 branches = set(repo[decode(br)].branch() for br in branchrevs)
1799 1799 heads = [h for h in heads if h.branch() in branches]
1800 1800
1801 1801 if not opts.get('closed'):
1802 1802 heads = [h for h in heads if not h.extra().get('close')]
1803 1803
1804 1804 if opts.get('active') and branchrevs:
1805 1805 dagheads = repo.heads(start)
1806 1806 heads = [h for h in heads if h.node() in dagheads]
1807 1807
1808 1808 if branchrevs:
1809 1809 haveheads = set(h.branch() for h in heads)
1810 1810 if branches - haveheads:
1811 1811 headless = ', '.join(encode(b) for b in branches - haveheads)
1812 1812 msg = _('no open branch heads found on branches %s')
1813 1813 if opts.get('rev'):
1814 1814 msg += _(' (started at %s)' % opts['rev'])
1815 1815 ui.warn((msg + '\n') % headless)
1816 1816
1817 1817 if not heads:
1818 1818 return 1
1819 1819
1820 1820 heads = sorted(heads, key=lambda x: -x.rev())
1821 1821 displayer = cmdutil.show_changeset(ui, repo, opts)
1822 1822 for ctx in heads:
1823 1823 displayer.show(ctx)
1824 1824 displayer.close()
1825 1825
1826 1826 def help_(ui, name=None, with_version=False, unknowncmd=False):
1827 1827 """show help for a given topic or a help overview
1828 1828
1829 1829 With no arguments, print a list of commands with short help messages.
1830 1830
1831 1831 Given a topic, extension, or command name, print help for that
1832 1832 topic.
1833 1833
1834 1834 Returns 0 if successful.
1835 1835 """
1836 1836 option_lists = []
1837 textwidth = util.termwidth() - 2
1837 textwidth = ui.termwidth() - 2
1838 1838
1839 1839 def addglobalopts(aliases):
1840 1840 if ui.verbose:
1841 1841 option_lists.append((_("global options:"), globalopts))
1842 1842 if name == 'shortlist':
1843 1843 option_lists.append((_('use "hg help" for the full list '
1844 1844 'of commands'), ()))
1845 1845 else:
1846 1846 if name == 'shortlist':
1847 1847 msg = _('use "hg help" for the full list of commands '
1848 1848 'or "hg -v" for details')
1849 1849 elif aliases:
1850 1850 msg = _('use "hg -v help%s" to show aliases and '
1851 1851 'global options') % (name and " " + name or "")
1852 1852 else:
1853 1853 msg = _('use "hg -v help %s" to show global options') % name
1854 1854 option_lists.append((msg, ()))
1855 1855
1856 1856 def helpcmd(name):
1857 1857 if with_version:
1858 1858 version_(ui)
1859 1859 ui.write('\n')
1860 1860
1861 1861 try:
1862 1862 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1863 1863 except error.AmbiguousCommand, inst:
1864 1864 # py3k fix: except vars can't be used outside the scope of the
1865 1865 # except block, nor can be used inside a lambda. python issue4617
1866 1866 prefix = inst.args[0]
1867 1867 select = lambda c: c.lstrip('^').startswith(prefix)
1868 1868 helplist(_('list of commands:\n\n'), select)
1869 1869 return
1870 1870
1871 1871 # check if it's an invalid alias and display its error if it is
1872 1872 if getattr(entry[0], 'badalias', False):
1873 1873 if not unknowncmd:
1874 1874 entry[0](ui)
1875 1875 return
1876 1876
1877 1877 # synopsis
1878 1878 if len(entry) > 2:
1879 1879 if entry[2].startswith('hg'):
1880 1880 ui.write("%s\n" % entry[2])
1881 1881 else:
1882 1882 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
1883 1883 else:
1884 1884 ui.write('hg %s\n' % aliases[0])
1885 1885
1886 1886 # aliases
1887 1887 if not ui.quiet and len(aliases) > 1:
1888 1888 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1889 1889
1890 1890 # description
1891 1891 doc = gettext(entry[0].__doc__)
1892 1892 if not doc:
1893 1893 doc = _("(no help text available)")
1894 1894 if hasattr(entry[0], 'definition'): # aliased command
1895 1895 if entry[0].definition.startswith('!'): # shell alias
1896 1896 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
1897 1897 else:
1898 1898 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
1899 1899 if ui.quiet:
1900 1900 doc = doc.splitlines()[0]
1901 1901 keep = ui.verbose and ['verbose'] or []
1902 1902 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
1903 1903 ui.write("\n%s\n" % formatted)
1904 1904 if pruned:
1905 1905 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
1906 1906
1907 1907 if not ui.quiet:
1908 1908 # options
1909 1909 if entry[1]:
1910 1910 option_lists.append((_("options:\n"), entry[1]))
1911 1911
1912 1912 addglobalopts(False)
1913 1913
1914 1914 def helplist(header, select=None):
1915 1915 h = {}
1916 1916 cmds = {}
1917 1917 for c, e in table.iteritems():
1918 1918 f = c.split("|", 1)[0]
1919 1919 if select and not select(f):
1920 1920 continue
1921 1921 if (not select and name != 'shortlist' and
1922 1922 e[0].__module__ != __name__):
1923 1923 continue
1924 1924 if name == "shortlist" and not f.startswith("^"):
1925 1925 continue
1926 1926 f = f.lstrip("^")
1927 1927 if not ui.debugflag and f.startswith("debug"):
1928 1928 continue
1929 1929 doc = e[0].__doc__
1930 1930 if doc and 'DEPRECATED' in doc and not ui.verbose:
1931 1931 continue
1932 1932 doc = gettext(doc)
1933 1933 if not doc:
1934 1934 doc = _("(no help text available)")
1935 1935 h[f] = doc.splitlines()[0].rstrip()
1936 1936 cmds[f] = c.lstrip("^")
1937 1937
1938 1938 if not h:
1939 1939 ui.status(_('no commands defined\n'))
1940 1940 return
1941 1941
1942 1942 ui.status(header)
1943 1943 fns = sorted(h)
1944 1944 m = max(map(len, fns))
1945 1945 for f in fns:
1946 1946 if ui.verbose:
1947 1947 commands = cmds[f].replace("|",", ")
1948 1948 ui.write(" %s:\n %s\n"%(commands, h[f]))
1949 1949 else:
1950 1950 ui.write('%s\n' % (util.wrap(h[f],
1951 1951 initindent=' %-*s ' % (m, f),
1952 1952 hangindent=' ' * (m + 4))))
1953 1953
1954 1954 if not ui.quiet:
1955 1955 addglobalopts(True)
1956 1956
1957 1957 def helptopic(name):
1958 1958 for names, header, doc in help.helptable:
1959 1959 if name in names:
1960 1960 break
1961 1961 else:
1962 1962 raise error.UnknownCommand(name)
1963 1963
1964 1964 # description
1965 1965 if not doc:
1966 1966 doc = _("(no help text available)")
1967 1967 if hasattr(doc, '__call__'):
1968 1968 doc = doc()
1969 1969
1970 1970 ui.write("%s\n\n" % header)
1971 1971 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
1972 1972
1973 1973 def helpext(name):
1974 1974 try:
1975 1975 mod = extensions.find(name)
1976 1976 doc = gettext(mod.__doc__) or _('no help text available')
1977 1977 except KeyError:
1978 1978 mod = None
1979 1979 doc = extensions.disabledext(name)
1980 1980 if not doc:
1981 1981 raise error.UnknownCommand(name)
1982 1982
1983 1983 if '\n' not in doc:
1984 1984 head, tail = doc, ""
1985 1985 else:
1986 1986 head, tail = doc.split('\n', 1)
1987 1987 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1988 1988 if tail:
1989 1989 ui.write(minirst.format(tail, textwidth))
1990 1990 ui.status('\n\n')
1991 1991
1992 1992 if mod:
1993 1993 try:
1994 1994 ct = mod.cmdtable
1995 1995 except AttributeError:
1996 1996 ct = {}
1997 1997 modcmds = set([c.split('|', 1)[0] for c in ct])
1998 1998 helplist(_('list of commands:\n\n'), modcmds.__contains__)
1999 1999 else:
2000 2000 ui.write(_('use "hg help extensions" for information on enabling '
2001 2001 'extensions\n'))
2002 2002
2003 2003 def helpextcmd(name):
2004 2004 cmd, ext, mod = extensions.disabledcmd(name, ui.config('ui', 'strict'))
2005 2005 doc = gettext(mod.__doc__).splitlines()[0]
2006 2006
2007 2007 msg = help.listexts(_("'%s' is provided by the following "
2008 2008 "extension:") % cmd, {ext: doc}, len(ext),
2009 2009 indent=4)
2010 2010 ui.write(minirst.format(msg, textwidth))
2011 2011 ui.write('\n\n')
2012 2012 ui.write(_('use "hg help extensions" for information on enabling '
2013 2013 'extensions\n'))
2014 2014
2015 2015 if name and name != 'shortlist':
2016 2016 i = None
2017 2017 if unknowncmd:
2018 2018 queries = (helpextcmd,)
2019 2019 else:
2020 2020 queries = (helptopic, helpcmd, helpext, helpextcmd)
2021 2021 for f in queries:
2022 2022 try:
2023 2023 f(name)
2024 2024 i = None
2025 2025 break
2026 2026 except error.UnknownCommand, inst:
2027 2027 i = inst
2028 2028 if i:
2029 2029 raise i
2030 2030
2031 2031 else:
2032 2032 # program name
2033 2033 if ui.verbose or with_version:
2034 2034 version_(ui)
2035 2035 else:
2036 2036 ui.status(_("Mercurial Distributed SCM\n"))
2037 2037 ui.status('\n')
2038 2038
2039 2039 # list of commands
2040 2040 if name == "shortlist":
2041 2041 header = _('basic commands:\n\n')
2042 2042 else:
2043 2043 header = _('list of commands:\n\n')
2044 2044
2045 2045 helplist(header)
2046 2046 if name != 'shortlist':
2047 2047 exts, maxlength = extensions.enabled()
2048 2048 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2049 2049 if text:
2050 2050 ui.write("\n%s\n" % minirst.format(text, textwidth))
2051 2051
2052 2052 # list all option lists
2053 2053 opt_output = []
2054 2054 multioccur = False
2055 2055 for title, options in option_lists:
2056 2056 opt_output.append(("\n%s" % title, None))
2057 2057 for option in options:
2058 2058 if len(option) == 5:
2059 2059 shortopt, longopt, default, desc, optlabel = option
2060 2060 else:
2061 2061 shortopt, longopt, default, desc = option
2062 2062 optlabel = _("VALUE") # default label
2063 2063
2064 2064 if _("DEPRECATED") in desc and not ui.verbose:
2065 2065 continue
2066 2066 if isinstance(default, list):
2067 2067 numqualifier = " %s [+]" % optlabel
2068 2068 multioccur = True
2069 2069 elif (default is not None) and not isinstance(default, bool):
2070 2070 numqualifier = " %s" % optlabel
2071 2071 else:
2072 2072 numqualifier = ""
2073 2073 opt_output.append(("%2s%s" %
2074 2074 (shortopt and "-%s" % shortopt,
2075 2075 longopt and " --%s%s" %
2076 2076 (longopt, numqualifier)),
2077 2077 "%s%s" % (desc,
2078 2078 default
2079 2079 and _(" (default: %s)") % default
2080 2080 or "")))
2081 2081 if multioccur:
2082 2082 msg = _("\n[+] marked option can be specified multiple times")
2083 2083 if ui.verbose and name != 'shortlist':
2084 2084 opt_output.append((msg, None))
2085 2085 else:
2086 2086 opt_output.insert(-1, (msg, None))
2087 2087
2088 2088 if not name:
2089 2089 ui.write(_("\nadditional help topics:\n\n"))
2090 2090 topics = []
2091 2091 for names, header, doc in help.helptable:
2092 2092 topics.append((sorted(names, key=len, reverse=True)[0], header))
2093 2093 topics_len = max([len(s[0]) for s in topics])
2094 2094 for t, desc in topics:
2095 2095 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2096 2096
2097 2097 if opt_output:
2098 2098 colwidth = encoding.colwidth
2099 2099 # normalize: (opt or message, desc or None, width of opt)
2100 2100 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2101 2101 for opt, desc in opt_output]
2102 2102 hanging = max([e[2] for e in entries])
2103 2103 for opt, desc, width in entries:
2104 2104 if desc:
2105 2105 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2106 2106 hangindent = ' ' * (hanging + 3)
2107 2107 ui.write('%s\n' % (util.wrap(desc,
2108 2108 initindent=initindent,
2109 2109 hangindent=hangindent)))
2110 2110 else:
2111 2111 ui.write("%s\n" % opt)
2112 2112
2113 2113 def identify(ui, repo, source=None,
2114 2114 rev=None, num=None, id=None, branch=None, tags=None):
2115 2115 """identify the working copy or specified revision
2116 2116
2117 2117 With no revision, print a summary of the current state of the
2118 2118 repository.
2119 2119
2120 2120 Specifying a path to a repository root or Mercurial bundle will
2121 2121 cause lookup to operate on that repository/bundle.
2122 2122
2123 2123 This summary identifies the repository state using one or two
2124 2124 parent hash identifiers, followed by a "+" if there are
2125 2125 uncommitted changes in the working directory, a list of tags for
2126 2126 this revision and a branch name for non-default branches.
2127 2127
2128 2128 Returns 0 if successful.
2129 2129 """
2130 2130
2131 2131 if not repo and not source:
2132 2132 raise util.Abort(_("there is no Mercurial repository here "
2133 2133 "(.hg not found)"))
2134 2134
2135 2135 hexfunc = ui.debugflag and hex or short
2136 2136 default = not (num or id or branch or tags)
2137 2137 output = []
2138 2138
2139 2139 revs = []
2140 2140 if source:
2141 2141 source, branches = hg.parseurl(ui.expandpath(source))
2142 2142 repo = hg.repository(ui, source)
2143 2143 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2144 2144
2145 2145 if not repo.local():
2146 2146 if not rev and revs:
2147 2147 rev = revs[0]
2148 2148 if not rev:
2149 2149 rev = "tip"
2150 2150 if num or branch or tags:
2151 2151 raise util.Abort(
2152 2152 "can't query remote revision number, branch, or tags")
2153 2153 output = [hexfunc(repo.lookup(rev))]
2154 2154 elif not rev:
2155 2155 ctx = repo[None]
2156 2156 parents = ctx.parents()
2157 2157 changed = False
2158 2158 if default or id or num:
2159 2159 changed = util.any(repo.status())
2160 2160 if default or id:
2161 2161 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
2162 2162 (changed) and "+" or "")]
2163 2163 if num:
2164 2164 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
2165 2165 (changed) and "+" or ""))
2166 2166 else:
2167 2167 ctx = repo[rev]
2168 2168 if default or id:
2169 2169 output = [hexfunc(ctx.node())]
2170 2170 if num:
2171 2171 output.append(str(ctx.rev()))
2172 2172
2173 2173 if repo.local() and default and not ui.quiet:
2174 2174 b = encoding.tolocal(ctx.branch())
2175 2175 if b != 'default':
2176 2176 output.append("(%s)" % b)
2177 2177
2178 2178 # multiple tags for a single parent separated by '/'
2179 2179 t = "/".join(ctx.tags())
2180 2180 if t:
2181 2181 output.append(t)
2182 2182
2183 2183 if branch:
2184 2184 output.append(encoding.tolocal(ctx.branch()))
2185 2185
2186 2186 if tags:
2187 2187 output.extend(ctx.tags())
2188 2188
2189 2189 ui.write("%s\n" % ' '.join(output))
2190 2190
2191 2191 def import_(ui, repo, patch1, *patches, **opts):
2192 2192 """import an ordered set of patches
2193 2193
2194 2194 Import a list of patches and commit them individually (unless
2195 2195 --no-commit is specified).
2196 2196
2197 2197 If there are outstanding changes in the working directory, import
2198 2198 will abort unless given the -f/--force flag.
2199 2199
2200 2200 You can import a patch straight from a mail message. Even patches
2201 2201 as attachments work (to use the body part, it must have type
2202 2202 text/plain or text/x-patch). From and Subject headers of email
2203 2203 message are used as default committer and commit message. All
2204 2204 text/plain body parts before first diff are added to commit
2205 2205 message.
2206 2206
2207 2207 If the imported patch was generated by :hg:`export`, user and
2208 2208 description from patch override values from message headers and
2209 2209 body. Values given on command line with -m/--message and -u/--user
2210 2210 override these.
2211 2211
2212 2212 If --exact is specified, import will set the working directory to
2213 2213 the parent of each patch before applying it, and will abort if the
2214 2214 resulting changeset has a different ID than the one recorded in
2215 2215 the patch. This may happen due to character set problems or other
2216 2216 deficiencies in the text patch format.
2217 2217
2218 2218 With -s/--similarity, hg will attempt to discover renames and
2219 2219 copies in the patch in the same way as 'addremove'.
2220 2220
2221 2221 To read a patch from standard input, use "-" as the patch name. If
2222 2222 a URL is specified, the patch will be downloaded from it.
2223 2223 See :hg:`help dates` for a list of formats valid for -d/--date.
2224 2224
2225 2225 Returns 0 on success.
2226 2226 """
2227 2227 patches = (patch1,) + patches
2228 2228
2229 2229 date = opts.get('date')
2230 2230 if date:
2231 2231 opts['date'] = util.parsedate(date)
2232 2232
2233 2233 try:
2234 2234 sim = float(opts.get('similarity') or 0)
2235 2235 except ValueError:
2236 2236 raise util.Abort(_('similarity must be a number'))
2237 2237 if sim < 0 or sim > 100:
2238 2238 raise util.Abort(_('similarity must be between 0 and 100'))
2239 2239
2240 2240 if opts.get('exact') or not opts.get('force'):
2241 2241 cmdutil.bail_if_changed(repo)
2242 2242
2243 2243 d = opts["base"]
2244 2244 strip = opts["strip"]
2245 2245 wlock = lock = None
2246 2246
2247 2247 def tryone(ui, hunk):
2248 2248 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2249 2249 patch.extract(ui, hunk)
2250 2250
2251 2251 if not tmpname:
2252 2252 return None
2253 2253 commitid = _('to working directory')
2254 2254
2255 2255 try:
2256 2256 cmdline_message = cmdutil.logmessage(opts)
2257 2257 if cmdline_message:
2258 2258 # pickup the cmdline msg
2259 2259 message = cmdline_message
2260 2260 elif message:
2261 2261 # pickup the patch msg
2262 2262 message = message.strip()
2263 2263 else:
2264 2264 # launch the editor
2265 2265 message = None
2266 2266 ui.debug('message:\n%s\n' % message)
2267 2267
2268 2268 wp = repo.parents()
2269 2269 if opts.get('exact'):
2270 2270 if not nodeid or not p1:
2271 2271 raise util.Abort(_('not a Mercurial patch'))
2272 2272 p1 = repo.lookup(p1)
2273 2273 p2 = repo.lookup(p2 or hex(nullid))
2274 2274
2275 2275 if p1 != wp[0].node():
2276 2276 hg.clean(repo, p1)
2277 2277 repo.dirstate.setparents(p1, p2)
2278 2278 elif p2:
2279 2279 try:
2280 2280 p1 = repo.lookup(p1)
2281 2281 p2 = repo.lookup(p2)
2282 2282 if p1 == wp[0].node():
2283 2283 repo.dirstate.setparents(p1, p2)
2284 2284 except error.RepoError:
2285 2285 pass
2286 2286 if opts.get('exact') or opts.get('import_branch'):
2287 2287 repo.dirstate.setbranch(branch or 'default')
2288 2288
2289 2289 files = {}
2290 2290 try:
2291 2291 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2292 2292 files=files, eolmode=None)
2293 2293 finally:
2294 2294 files = cmdutil.updatedir(ui, repo, files,
2295 2295 similarity=sim / 100.0)
2296 2296 if not opts.get('no_commit'):
2297 2297 if opts.get('exact'):
2298 2298 m = None
2299 2299 else:
2300 2300 m = cmdutil.matchfiles(repo, files or [])
2301 2301 n = repo.commit(message, opts.get('user') or user,
2302 2302 opts.get('date') or date, match=m,
2303 2303 editor=cmdutil.commiteditor)
2304 2304 if opts.get('exact'):
2305 2305 if hex(n) != nodeid:
2306 2306 repo.rollback()
2307 2307 raise util.Abort(_('patch is damaged'
2308 2308 ' or loses information'))
2309 2309 # Force a dirstate write so that the next transaction
2310 2310 # backups an up-do-date file.
2311 2311 repo.dirstate.write()
2312 2312 if n:
2313 2313 commitid = short(n)
2314 2314
2315 2315 return commitid
2316 2316 finally:
2317 2317 os.unlink(tmpname)
2318 2318
2319 2319 try:
2320 2320 wlock = repo.wlock()
2321 2321 lock = repo.lock()
2322 2322 lastcommit = None
2323 2323 for p in patches:
2324 2324 pf = os.path.join(d, p)
2325 2325
2326 2326 if pf == '-':
2327 2327 ui.status(_("applying patch from stdin\n"))
2328 2328 pf = sys.stdin
2329 2329 else:
2330 2330 ui.status(_("applying %s\n") % p)
2331 2331 pf = url.open(ui, pf)
2332 2332
2333 2333 haspatch = False
2334 2334 for hunk in patch.split(pf):
2335 2335 commitid = tryone(ui, hunk)
2336 2336 if commitid:
2337 2337 haspatch = True
2338 2338 if lastcommit:
2339 2339 ui.status(_('applied %s\n') % lastcommit)
2340 2340 lastcommit = commitid
2341 2341
2342 2342 if not haspatch:
2343 2343 raise util.Abort(_('no diffs found'))
2344 2344
2345 2345 finally:
2346 2346 release(lock, wlock)
2347 2347
2348 2348 def incoming(ui, repo, source="default", **opts):
2349 2349 """show new changesets found in source
2350 2350
2351 2351 Show new changesets found in the specified path/URL or the default
2352 2352 pull location. These are the changesets that would have been pulled
2353 2353 if a pull at the time you issued this command.
2354 2354
2355 2355 For remote repository, using --bundle avoids downloading the
2356 2356 changesets twice if the incoming is followed by a pull.
2357 2357
2358 2358 See pull for valid source format details.
2359 2359
2360 2360 Returns 0 if there are incoming changes, 1 otherwise.
2361 2361 """
2362 2362 if opts.get('bundle') and opts.get('subrepos'):
2363 2363 raise util.Abort(_('cannot combine --bundle and --subrepos'))
2364 2364
2365 2365 ret = hg.incoming(ui, repo, source, opts)
2366 2366 return ret
2367 2367
2368 2368 def init(ui, dest=".", **opts):
2369 2369 """create a new repository in the given directory
2370 2370
2371 2371 Initialize a new repository in the given directory. If the given
2372 2372 directory does not exist, it will be created.
2373 2373
2374 2374 If no directory is given, the current directory is used.
2375 2375
2376 2376 It is possible to specify an ``ssh://`` URL as the destination.
2377 2377 See :hg:`help urls` for more information.
2378 2378
2379 2379 Returns 0 on success.
2380 2380 """
2381 2381 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=1)
2382 2382
2383 2383 def locate(ui, repo, *pats, **opts):
2384 2384 """locate files matching specific patterns
2385 2385
2386 2386 Print files under Mercurial control in the working directory whose
2387 2387 names match the given patterns.
2388 2388
2389 2389 By default, this command searches all directories in the working
2390 2390 directory. To search just the current directory and its
2391 2391 subdirectories, use "--include .".
2392 2392
2393 2393 If no patterns are given to match, this command prints the names
2394 2394 of all files under Mercurial control in the working directory.
2395 2395
2396 2396 If you want to feed the output of this command into the "xargs"
2397 2397 command, use the -0 option to both this command and "xargs". This
2398 2398 will avoid the problem of "xargs" treating single filenames that
2399 2399 contain whitespace as multiple filenames.
2400 2400
2401 2401 Returns 0 if a match is found, 1 otherwise.
2402 2402 """
2403 2403 end = opts.get('print0') and '\0' or '\n'
2404 2404 rev = opts.get('rev') or None
2405 2405
2406 2406 ret = 1
2407 2407 m = cmdutil.match(repo, pats, opts, default='relglob')
2408 2408 m.bad = lambda x, y: False
2409 2409 for abs in repo[rev].walk(m):
2410 2410 if not rev and abs not in repo.dirstate:
2411 2411 continue
2412 2412 if opts.get('fullpath'):
2413 2413 ui.write(repo.wjoin(abs), end)
2414 2414 else:
2415 2415 ui.write(((pats and m.rel(abs)) or abs), end)
2416 2416 ret = 0
2417 2417
2418 2418 return ret
2419 2419
2420 2420 def log(ui, repo, *pats, **opts):
2421 2421 """show revision history of entire repository or files
2422 2422
2423 2423 Print the revision history of the specified files or the entire
2424 2424 project.
2425 2425
2426 2426 File history is shown without following rename or copy history of
2427 2427 files. Use -f/--follow with a filename to follow history across
2428 2428 renames and copies. --follow without a filename will only show
2429 2429 ancestors or descendants of the starting revision. --follow-first
2430 2430 only follows the first parent of merge revisions.
2431 2431
2432 2432 If no revision range is specified, the default is tip:0 unless
2433 2433 --follow is set, in which case the working directory parent is
2434 2434 used as the starting revision. You can specify a revision set for
2435 2435 log, see :hg:`help revsets` for more information.
2436 2436
2437 2437 See :hg:`help dates` for a list of formats valid for -d/--date.
2438 2438
2439 2439 By default this command prints revision number and changeset id,
2440 2440 tags, non-trivial parents, user, date and time, and a summary for
2441 2441 each commit. When the -v/--verbose switch is used, the list of
2442 2442 changed files and full commit message are shown.
2443 2443
2444 2444 .. note::
2445 2445 log -p/--patch may generate unexpected diff output for merge
2446 2446 changesets, as it will only compare the merge changeset against
2447 2447 its first parent. Also, only files different from BOTH parents
2448 2448 will appear in files:.
2449 2449
2450 2450 Returns 0 on success.
2451 2451 """
2452 2452
2453 2453 matchfn = cmdutil.match(repo, pats, opts)
2454 2454 limit = cmdutil.loglimit(opts)
2455 2455 count = 0
2456 2456
2457 2457 endrev = None
2458 2458 if opts.get('copies') and opts.get('rev'):
2459 2459 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2460 2460
2461 2461 df = False
2462 2462 if opts["date"]:
2463 2463 df = util.matchdate(opts["date"])
2464 2464
2465 2465 branches = opts.get('branch', []) + opts.get('only_branch', [])
2466 2466 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2467 2467
2468 2468 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2469 2469 def prep(ctx, fns):
2470 2470 rev = ctx.rev()
2471 2471 parents = [p for p in repo.changelog.parentrevs(rev)
2472 2472 if p != nullrev]
2473 2473 if opts.get('no_merges') and len(parents) == 2:
2474 2474 return
2475 2475 if opts.get('only_merges') and len(parents) != 2:
2476 2476 return
2477 2477 if opts.get('branch') and ctx.branch() not in opts['branch']:
2478 2478 return
2479 2479 if df and not df(ctx.date()[0]):
2480 2480 return
2481 2481 if opts['user'] and not [k for k in opts['user'] if k in ctx.user()]:
2482 2482 return
2483 2483 if opts.get('keyword'):
2484 2484 for k in [kw.lower() for kw in opts['keyword']]:
2485 2485 if (k in ctx.user().lower() or
2486 2486 k in ctx.description().lower() or
2487 2487 k in " ".join(ctx.files()).lower()):
2488 2488 break
2489 2489 else:
2490 2490 return
2491 2491
2492 2492 copies = None
2493 2493 if opts.get('copies') and rev:
2494 2494 copies = []
2495 2495 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2496 2496 for fn in ctx.files():
2497 2497 rename = getrenamed(fn, rev)
2498 2498 if rename:
2499 2499 copies.append((fn, rename[0]))
2500 2500
2501 2501 revmatchfn = None
2502 2502 if opts.get('patch') or opts.get('stat'):
2503 2503 if opts.get('follow') or opts.get('follow_first'):
2504 2504 # note: this might be wrong when following through merges
2505 2505 revmatchfn = cmdutil.match(repo, fns, default='path')
2506 2506 else:
2507 2507 revmatchfn = matchfn
2508 2508
2509 2509 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2510 2510
2511 2511 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2512 2512 if count == limit:
2513 2513 break
2514 2514 if displayer.flush(ctx.rev()):
2515 2515 count += 1
2516 2516 displayer.close()
2517 2517
2518 2518 def manifest(ui, repo, node=None, rev=None):
2519 2519 """output the current or given revision of the project manifest
2520 2520
2521 2521 Print a list of version controlled files for the given revision.
2522 2522 If no revision is given, the first parent of the working directory
2523 2523 is used, or the null revision if no revision is checked out.
2524 2524
2525 2525 With -v, print file permissions, symlink and executable bits.
2526 2526 With --debug, print file revision hashes.
2527 2527
2528 2528 Returns 0 on success.
2529 2529 """
2530 2530
2531 2531 if rev and node:
2532 2532 raise util.Abort(_("please specify just one revision"))
2533 2533
2534 2534 if not node:
2535 2535 node = rev
2536 2536
2537 2537 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2538 2538 ctx = repo[node]
2539 2539 for f in ctx:
2540 2540 if ui.debugflag:
2541 2541 ui.write("%40s " % hex(ctx.manifest()[f]))
2542 2542 if ui.verbose:
2543 2543 ui.write(decor[ctx.flags(f)])
2544 2544 ui.write("%s\n" % f)
2545 2545
2546 2546 def merge(ui, repo, node=None, **opts):
2547 2547 """merge working directory with another revision
2548 2548
2549 2549 The current working directory is updated with all changes made in
2550 2550 the requested revision since the last common predecessor revision.
2551 2551
2552 2552 Files that changed between either parent are marked as changed for
2553 2553 the next commit and a commit must be performed before any further
2554 2554 updates to the repository are allowed. The next commit will have
2555 2555 two parents.
2556 2556
2557 2557 If no revision is specified, the working directory's parent is a
2558 2558 head revision, and the current branch contains exactly one other
2559 2559 head, the other head is merged with by default. Otherwise, an
2560 2560 explicit revision with which to merge with must be provided.
2561 2561
2562 2562 To undo an uncommitted merge, use :hg:`update --clean .` which
2563 2563 will check out a clean copy of the original merge parent, losing
2564 2564 all changes.
2565 2565
2566 2566 Returns 0 on success, 1 if there are unresolved files.
2567 2567 """
2568 2568
2569 2569 if opts.get('rev') and node:
2570 2570 raise util.Abort(_("please specify just one revision"))
2571 2571 if not node:
2572 2572 node = opts.get('rev')
2573 2573
2574 2574 if not node:
2575 2575 branch = repo.changectx(None).branch()
2576 2576 bheads = repo.branchheads(branch)
2577 2577 if len(bheads) > 2:
2578 2578 raise util.Abort(_(
2579 2579 'branch \'%s\' has %d heads - '
2580 2580 'please merge with an explicit rev\n'
2581 2581 '(run \'hg heads .\' to see heads)')
2582 2582 % (branch, len(bheads)))
2583 2583
2584 2584 parent = repo.dirstate.parents()[0]
2585 2585 if len(bheads) == 1:
2586 2586 if len(repo.heads()) > 1:
2587 2587 raise util.Abort(_(
2588 2588 'branch \'%s\' has one head - '
2589 2589 'please merge with an explicit rev\n'
2590 2590 '(run \'hg heads\' to see all heads)')
2591 2591 % branch)
2592 2592 msg = _('there is nothing to merge')
2593 2593 if parent != repo.lookup(repo[None].branch()):
2594 2594 msg = _('%s - use "hg update" instead') % msg
2595 2595 raise util.Abort(msg)
2596 2596
2597 2597 if parent not in bheads:
2598 2598 raise util.Abort(_('working dir not at a head rev - '
2599 2599 'use "hg update" or merge with an explicit rev'))
2600 2600 node = parent == bheads[0] and bheads[-1] or bheads[0]
2601 2601
2602 2602 if opts.get('preview'):
2603 2603 # find nodes that are ancestors of p2 but not of p1
2604 2604 p1 = repo.lookup('.')
2605 2605 p2 = repo.lookup(node)
2606 2606 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2607 2607
2608 2608 displayer = cmdutil.show_changeset(ui, repo, opts)
2609 2609 for node in nodes:
2610 2610 displayer.show(repo[node])
2611 2611 displayer.close()
2612 2612 return 0
2613 2613
2614 2614 return hg.merge(repo, node, force=opts.get('force'))
2615 2615
2616 2616 def outgoing(ui, repo, dest=None, **opts):
2617 2617 """show changesets not found in the destination
2618 2618
2619 2619 Show changesets not found in the specified destination repository
2620 2620 or the default push location. These are the changesets that would
2621 2621 be pushed if a push was requested.
2622 2622
2623 2623 See pull for details of valid destination formats.
2624 2624
2625 2625 Returns 0 if there are outgoing changes, 1 otherwise.
2626 2626 """
2627 2627 ret = hg.outgoing(ui, repo, dest, opts)
2628 2628 return ret
2629 2629
2630 2630 def parents(ui, repo, file_=None, **opts):
2631 2631 """show the parents of the working directory or revision
2632 2632
2633 2633 Print the working directory's parent revisions. If a revision is
2634 2634 given via -r/--rev, the parent of that revision will be printed.
2635 2635 If a file argument is given, the revision in which the file was
2636 2636 last changed (before the working directory revision or the
2637 2637 argument to --rev if given) is printed.
2638 2638
2639 2639 Returns 0 on success.
2640 2640 """
2641 2641 rev = opts.get('rev')
2642 2642 if rev:
2643 2643 ctx = repo[rev]
2644 2644 else:
2645 2645 ctx = repo[None]
2646 2646
2647 2647 if file_:
2648 2648 m = cmdutil.match(repo, (file_,), opts)
2649 2649 if m.anypats() or len(m.files()) != 1:
2650 2650 raise util.Abort(_('can only specify an explicit filename'))
2651 2651 file_ = m.files()[0]
2652 2652 filenodes = []
2653 2653 for cp in ctx.parents():
2654 2654 if not cp:
2655 2655 continue
2656 2656 try:
2657 2657 filenodes.append(cp.filenode(file_))
2658 2658 except error.LookupError:
2659 2659 pass
2660 2660 if not filenodes:
2661 2661 raise util.Abort(_("'%s' not found in manifest!") % file_)
2662 2662 fl = repo.file(file_)
2663 2663 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2664 2664 else:
2665 2665 p = [cp.node() for cp in ctx.parents()]
2666 2666
2667 2667 displayer = cmdutil.show_changeset(ui, repo, opts)
2668 2668 for n in p:
2669 2669 if n != nullid:
2670 2670 displayer.show(repo[n])
2671 2671 displayer.close()
2672 2672
2673 2673 def paths(ui, repo, search=None):
2674 2674 """show aliases for remote repositories
2675 2675
2676 2676 Show definition of symbolic path name NAME. If no name is given,
2677 2677 show definition of all available names.
2678 2678
2679 2679 Path names are defined in the [paths] section of your
2680 2680 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
2681 2681 repository, ``.hg/hgrc`` is used, too.
2682 2682
2683 2683 The path names ``default`` and ``default-push`` have a special
2684 2684 meaning. When performing a push or pull operation, they are used
2685 2685 as fallbacks if no location is specified on the command-line.
2686 2686 When ``default-push`` is set, it will be used for push and
2687 2687 ``default`` will be used for pull; otherwise ``default`` is used
2688 2688 as the fallback for both. When cloning a repository, the clone
2689 2689 source is written as ``default`` in ``.hg/hgrc``. Note that
2690 2690 ``default`` and ``default-push`` apply to all inbound (e.g.
2691 2691 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2692 2692 :hg:`bundle`) operations.
2693 2693
2694 2694 See :hg:`help urls` for more information.
2695 2695
2696 2696 Returns 0 on success.
2697 2697 """
2698 2698 if search:
2699 2699 for name, path in ui.configitems("paths"):
2700 2700 if name == search:
2701 2701 ui.write("%s\n" % url.hidepassword(path))
2702 2702 return
2703 2703 ui.warn(_("not found!\n"))
2704 2704 return 1
2705 2705 else:
2706 2706 for name, path in ui.configitems("paths"):
2707 2707 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2708 2708
2709 2709 def postincoming(ui, repo, modheads, optupdate, checkout):
2710 2710 if modheads == 0:
2711 2711 return
2712 2712 if optupdate:
2713 2713 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2714 2714 return hg.update(repo, checkout)
2715 2715 else:
2716 2716 ui.status(_("not updating, since new heads added\n"))
2717 2717 if modheads > 1:
2718 2718 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2719 2719 else:
2720 2720 ui.status(_("(run 'hg update' to get a working copy)\n"))
2721 2721
2722 2722 def pull(ui, repo, source="default", **opts):
2723 2723 """pull changes from the specified source
2724 2724
2725 2725 Pull changes from a remote repository to a local one.
2726 2726
2727 2727 This finds all changes from the repository at the specified path
2728 2728 or URL and adds them to a local repository (the current one unless
2729 2729 -R is specified). By default, this does not update the copy of the
2730 2730 project in the working directory.
2731 2731
2732 2732 Use :hg:`incoming` if you want to see what would have been added
2733 2733 by a pull at the time you issued this command. If you then decide
2734 2734 to add those changes to the repository, you should use :hg:`pull
2735 2735 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
2736 2736
2737 2737 If SOURCE is omitted, the 'default' path will be used.
2738 2738 See :hg:`help urls` for more information.
2739 2739
2740 2740 Returns 0 on success, 1 if an update had unresolved files.
2741 2741 """
2742 2742 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2743 2743 other = hg.repository(hg.remoteui(repo, opts), source)
2744 2744 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2745 2745 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2746 2746 if revs:
2747 2747 try:
2748 2748 revs = [other.lookup(rev) for rev in revs]
2749 2749 except error.CapabilityError:
2750 2750 err = _("other repository doesn't support revision lookup, "
2751 2751 "so a rev cannot be specified.")
2752 2752 raise util.Abort(err)
2753 2753
2754 2754 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2755 2755 if checkout:
2756 2756 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2757 2757 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2758 2758
2759 2759 def push(ui, repo, dest=None, **opts):
2760 2760 """push changes to the specified destination
2761 2761
2762 2762 Push changesets from the local repository to the specified
2763 2763 destination.
2764 2764
2765 2765 This operation is symmetrical to pull: it is identical to a pull
2766 2766 in the destination repository from the current one.
2767 2767
2768 2768 By default, push will not allow creation of new heads at the
2769 2769 destination, since multiple heads would make it unclear which head
2770 2770 to use. In this situation, it is recommended to pull and merge
2771 2771 before pushing.
2772 2772
2773 2773 Use --new-branch if you want to allow push to create a new named
2774 2774 branch that is not present at the destination. This allows you to
2775 2775 only create a new branch without forcing other changes.
2776 2776
2777 2777 Use -f/--force to override the default behavior and push all
2778 2778 changesets on all branches.
2779 2779
2780 2780 If -r/--rev is used, the specified revision and all its ancestors
2781 2781 will be pushed to the remote repository.
2782 2782
2783 2783 Please see :hg:`help urls` for important details about ``ssh://``
2784 2784 URLs. If DESTINATION is omitted, a default path will be used.
2785 2785
2786 2786 Returns 0 if push was successful, 1 if nothing to push.
2787 2787 """
2788 2788 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2789 2789 dest, branches = hg.parseurl(dest, opts.get('branch'))
2790 2790 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2791 2791 other = hg.repository(hg.remoteui(repo, opts), dest)
2792 2792 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2793 2793 if revs:
2794 2794 revs = [repo.lookup(rev) for rev in revs]
2795 2795
2796 2796 # push subrepos depth-first for coherent ordering
2797 2797 c = repo['']
2798 2798 subs = c.substate # only repos that are committed
2799 2799 for s in sorted(subs):
2800 2800 if not c.sub(s).push(opts.get('force')):
2801 2801 return False
2802 2802
2803 2803 r = repo.push(other, opts.get('force'), revs=revs,
2804 2804 newbranch=opts.get('new_branch'))
2805 2805 return r == 0
2806 2806
2807 2807 def recover(ui, repo):
2808 2808 """roll back an interrupted transaction
2809 2809
2810 2810 Recover from an interrupted commit or pull.
2811 2811
2812 2812 This command tries to fix the repository status after an
2813 2813 interrupted operation. It should only be necessary when Mercurial
2814 2814 suggests it.
2815 2815
2816 2816 Returns 0 if successful, 1 if nothing to recover or verify fails.
2817 2817 """
2818 2818 if repo.recover():
2819 2819 return hg.verify(repo)
2820 2820 return 1
2821 2821
2822 2822 def remove(ui, repo, *pats, **opts):
2823 2823 """remove the specified files on the next commit
2824 2824
2825 2825 Schedule the indicated files for removal from the repository.
2826 2826
2827 2827 This only removes files from the current branch, not from the
2828 2828 entire project history. -A/--after can be used to remove only
2829 2829 files that have already been deleted, -f/--force can be used to
2830 2830 force deletion, and -Af can be used to remove files from the next
2831 2831 revision without deleting them from the working directory.
2832 2832
2833 2833 The following table details the behavior of remove for different
2834 2834 file states (columns) and option combinations (rows). The file
2835 2835 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
2836 2836 reported by :hg:`status`). The actions are Warn, Remove (from
2837 2837 branch) and Delete (from disk)::
2838 2838
2839 2839 A C M !
2840 2840 none W RD W R
2841 2841 -f R RD RD R
2842 2842 -A W W W R
2843 2843 -Af R R R R
2844 2844
2845 2845 This command schedules the files to be removed at the next commit.
2846 2846 To undo a remove before that, see :hg:`revert`.
2847 2847
2848 2848 Returns 0 on success, 1 if any warnings encountered.
2849 2849 """
2850 2850
2851 2851 ret = 0
2852 2852 after, force = opts.get('after'), opts.get('force')
2853 2853 if not pats and not after:
2854 2854 raise util.Abort(_('no files specified'))
2855 2855
2856 2856 m = cmdutil.match(repo, pats, opts)
2857 2857 s = repo.status(match=m, clean=True)
2858 2858 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2859 2859
2860 2860 for f in m.files():
2861 2861 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2862 2862 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2863 2863 ret = 1
2864 2864
2865 2865 if force:
2866 2866 remove, forget = modified + deleted + clean, added
2867 2867 elif after:
2868 2868 remove, forget = deleted, []
2869 2869 for f in modified + added + clean:
2870 2870 ui.warn(_('not removing %s: file still exists (use -f'
2871 2871 ' to force removal)\n') % m.rel(f))
2872 2872 ret = 1
2873 2873 else:
2874 2874 remove, forget = deleted + clean, []
2875 2875 for f in modified:
2876 2876 ui.warn(_('not removing %s: file is modified (use -f'
2877 2877 ' to force removal)\n') % m.rel(f))
2878 2878 ret = 1
2879 2879 for f in added:
2880 2880 ui.warn(_('not removing %s: file has been marked for add (use -f'
2881 2881 ' to force removal)\n') % m.rel(f))
2882 2882 ret = 1
2883 2883
2884 2884 for f in sorted(remove + forget):
2885 2885 if ui.verbose or not m.exact(f):
2886 2886 ui.status(_('removing %s\n') % m.rel(f))
2887 2887
2888 2888 repo[None].forget(forget)
2889 2889 repo[None].remove(remove, unlink=not after)
2890 2890 return ret
2891 2891
2892 2892 def rename(ui, repo, *pats, **opts):
2893 2893 """rename files; equivalent of copy + remove
2894 2894
2895 2895 Mark dest as copies of sources; mark sources for deletion. If dest
2896 2896 is a directory, copies are put in that directory. If dest is a
2897 2897 file, there can only be one source.
2898 2898
2899 2899 By default, this command copies the contents of files as they
2900 2900 exist in the working directory. If invoked with -A/--after, the
2901 2901 operation is recorded, but no copying is performed.
2902 2902
2903 2903 This command takes effect at the next commit. To undo a rename
2904 2904 before that, see :hg:`revert`.
2905 2905
2906 2906 Returns 0 on success, 1 if errors are encountered.
2907 2907 """
2908 2908 wlock = repo.wlock(False)
2909 2909 try:
2910 2910 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2911 2911 finally:
2912 2912 wlock.release()
2913 2913
2914 2914 def resolve(ui, repo, *pats, **opts):
2915 2915 """redo merges or set/view the merge status of files
2916 2916
2917 2917 Merges with unresolved conflicts are often the result of
2918 2918 non-interactive merging using the ``internal:merge`` configuration
2919 2919 setting, or a command-line merge tool like ``diff3``. The resolve
2920 2920 command is used to manage the files involved in a merge, after
2921 2921 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
2922 2922 working directory must have two parents).
2923 2923
2924 2924 The resolve command can be used in the following ways:
2925 2925
2926 2926 - :hg:`resolve FILE...`: attempt to re-merge the specified files,
2927 2927 discarding any previous merge attempts. Re-merging is not
2928 2928 performed for files already marked as resolved. Use ``--all/-a``
2929 2929 to selects all unresolved files.
2930 2930
2931 2931 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
2932 2932 (e.g. after having manually fixed-up the files). The default is
2933 2933 to mark all unresolved files.
2934 2934
2935 2935 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
2936 2936 default is to mark all resolved files.
2937 2937
2938 2938 - :hg:`resolve -l`: list files which had or still have conflicts.
2939 2939 In the printed list, ``U`` = unresolved and ``R`` = resolved.
2940 2940
2941 2941 Note that Mercurial will not let you commit files with unresolved
2942 2942 merge conflicts. You must use :hg:`resolve -m ...` before you can
2943 2943 commit after a conflicting merge.
2944 2944
2945 2945 Returns 0 on success, 1 if any files fail a resolve attempt.
2946 2946 """
2947 2947
2948 2948 all, mark, unmark, show, nostatus = \
2949 2949 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
2950 2950
2951 2951 if (show and (mark or unmark)) or (mark and unmark):
2952 2952 raise util.Abort(_("too many options specified"))
2953 2953 if pats and all:
2954 2954 raise util.Abort(_("can't specify --all and patterns"))
2955 2955 if not (all or pats or show or mark or unmark):
2956 2956 raise util.Abort(_('no files or directories specified; '
2957 2957 'use --all to remerge all files'))
2958 2958
2959 2959 ms = mergemod.mergestate(repo)
2960 2960 m = cmdutil.match(repo, pats, opts)
2961 2961 ret = 0
2962 2962
2963 2963 for f in ms:
2964 2964 if m(f):
2965 2965 if show:
2966 2966 if nostatus:
2967 2967 ui.write("%s\n" % f)
2968 2968 else:
2969 2969 ui.write("%s %s\n" % (ms[f].upper(), f),
2970 2970 label='resolve.' +
2971 2971 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
2972 2972 elif mark:
2973 2973 ms.mark(f, "r")
2974 2974 elif unmark:
2975 2975 ms.mark(f, "u")
2976 2976 else:
2977 2977 wctx = repo[None]
2978 2978 mctx = wctx.parents()[-1]
2979 2979
2980 2980 # backup pre-resolve (merge uses .orig for its own purposes)
2981 2981 a = repo.wjoin(f)
2982 2982 util.copyfile(a, a + ".resolve")
2983 2983
2984 2984 # resolve file
2985 2985 if ms.resolve(f, wctx, mctx):
2986 2986 ret = 1
2987 2987
2988 2988 # replace filemerge's .orig file with our resolve file
2989 2989 util.rename(a + ".resolve", a + ".orig")
2990 2990
2991 2991 ms.commit()
2992 2992 return ret
2993 2993
2994 2994 def revert(ui, repo, *pats, **opts):
2995 2995 """restore individual files or directories to an earlier state
2996 2996
2997 2997 .. note::
2998 2998 This command is most likely not what you are looking for.
2999 2999 revert will partially overwrite content in the working
3000 3000 directory without changing the working directory parents. Use
3001 3001 :hg:`update -r rev` to check out earlier revisions, or
3002 3002 :hg:`update --clean .` to undo a merge which has added another
3003 3003 parent.
3004 3004
3005 3005 With no revision specified, revert the named files or directories
3006 3006 to the contents they had in the parent of the working directory.
3007 3007 This restores the contents of the affected files to an unmodified
3008 3008 state and unschedules adds, removes, copies, and renames. If the
3009 3009 working directory has two parents, you must explicitly specify a
3010 3010 revision.
3011 3011
3012 3012 Using the -r/--rev option, revert the given files or directories
3013 3013 to their contents as of a specific revision. This can be helpful
3014 3014 to "roll back" some or all of an earlier change. See :hg:`help
3015 3015 dates` for a list of formats valid for -d/--date.
3016 3016
3017 3017 Revert modifies the working directory. It does not commit any
3018 3018 changes, or change the parent of the working directory. If you
3019 3019 revert to a revision other than the parent of the working
3020 3020 directory, the reverted files will thus appear modified
3021 3021 afterwards.
3022 3022
3023 3023 If a file has been deleted, it is restored. If the executable mode
3024 3024 of a file was changed, it is reset.
3025 3025
3026 3026 If names are given, all files matching the names are reverted.
3027 3027 If no arguments are given, no files are reverted.
3028 3028
3029 3029 Modified files are saved with a .orig suffix before reverting.
3030 3030 To disable these backups, use --no-backup.
3031 3031
3032 3032 Returns 0 on success.
3033 3033 """
3034 3034
3035 3035 if opts.get("date"):
3036 3036 if opts.get("rev"):
3037 3037 raise util.Abort(_("you can't specify a revision and a date"))
3038 3038 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3039 3039
3040 3040 if not pats and not opts.get('all'):
3041 3041 raise util.Abort(_('no files or directories specified; '
3042 3042 'use --all to revert the whole repo'))
3043 3043
3044 3044 parent, p2 = repo.dirstate.parents()
3045 3045 if not opts.get('rev') and p2 != nullid:
3046 3046 raise util.Abort(_('uncommitted merge - please provide a '
3047 3047 'specific revision'))
3048 3048 ctx = repo[opts.get('rev')]
3049 3049 node = ctx.node()
3050 3050 mf = ctx.manifest()
3051 3051 if node == parent:
3052 3052 pmf = mf
3053 3053 else:
3054 3054 pmf = None
3055 3055
3056 3056 # need all matching names in dirstate and manifest of target rev,
3057 3057 # so have to walk both. do not print errors if files exist in one
3058 3058 # but not other.
3059 3059
3060 3060 names = {}
3061 3061
3062 3062 wlock = repo.wlock()
3063 3063 try:
3064 3064 # walk dirstate.
3065 3065
3066 3066 m = cmdutil.match(repo, pats, opts)
3067 3067 m.bad = lambda x, y: False
3068 3068 for abs in repo.walk(m):
3069 3069 names[abs] = m.rel(abs), m.exact(abs)
3070 3070
3071 3071 # walk target manifest.
3072 3072
3073 3073 def badfn(path, msg):
3074 3074 if path in names:
3075 3075 return
3076 3076 path_ = path + '/'
3077 3077 for f in names:
3078 3078 if f.startswith(path_):
3079 3079 return
3080 3080 ui.warn("%s: %s\n" % (m.rel(path), msg))
3081 3081
3082 3082 m = cmdutil.match(repo, pats, opts)
3083 3083 m.bad = badfn
3084 3084 for abs in repo[node].walk(m):
3085 3085 if abs not in names:
3086 3086 names[abs] = m.rel(abs), m.exact(abs)
3087 3087
3088 3088 m = cmdutil.matchfiles(repo, names)
3089 3089 changes = repo.status(match=m)[:4]
3090 3090 modified, added, removed, deleted = map(set, changes)
3091 3091
3092 3092 # if f is a rename, also revert the source
3093 3093 cwd = repo.getcwd()
3094 3094 for f in added:
3095 3095 src = repo.dirstate.copied(f)
3096 3096 if src and src not in names and repo.dirstate[src] == 'r':
3097 3097 removed.add(src)
3098 3098 names[src] = (repo.pathto(src, cwd), True)
3099 3099
3100 3100 def removeforget(abs):
3101 3101 if repo.dirstate[abs] == 'a':
3102 3102 return _('forgetting %s\n')
3103 3103 return _('removing %s\n')
3104 3104
3105 3105 revert = ([], _('reverting %s\n'))
3106 3106 add = ([], _('adding %s\n'))
3107 3107 remove = ([], removeforget)
3108 3108 undelete = ([], _('undeleting %s\n'))
3109 3109
3110 3110 disptable = (
3111 3111 # dispatch table:
3112 3112 # file state
3113 3113 # action if in target manifest
3114 3114 # action if not in target manifest
3115 3115 # make backup if in target manifest
3116 3116 # make backup if not in target manifest
3117 3117 (modified, revert, remove, True, True),
3118 3118 (added, revert, remove, True, False),
3119 3119 (removed, undelete, None, False, False),
3120 3120 (deleted, revert, remove, False, False),
3121 3121 )
3122 3122
3123 3123 for abs, (rel, exact) in sorted(names.items()):
3124 3124 mfentry = mf.get(abs)
3125 3125 target = repo.wjoin(abs)
3126 3126 def handle(xlist, dobackup):
3127 3127 xlist[0].append(abs)
3128 3128 if (dobackup and not opts.get('no_backup') and
3129 3129 os.path.lexists(target)):
3130 3130 bakname = "%s.orig" % rel
3131 3131 ui.note(_('saving current version of %s as %s\n') %
3132 3132 (rel, bakname))
3133 3133 if not opts.get('dry_run'):
3134 3134 util.rename(target, bakname)
3135 3135 if ui.verbose or not exact:
3136 3136 msg = xlist[1]
3137 3137 if not isinstance(msg, basestring):
3138 3138 msg = msg(abs)
3139 3139 ui.status(msg % rel)
3140 3140 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3141 3141 if abs not in table:
3142 3142 continue
3143 3143 # file has changed in dirstate
3144 3144 if mfentry:
3145 3145 handle(hitlist, backuphit)
3146 3146 elif misslist is not None:
3147 3147 handle(misslist, backupmiss)
3148 3148 break
3149 3149 else:
3150 3150 if abs not in repo.dirstate:
3151 3151 if mfentry:
3152 3152 handle(add, True)
3153 3153 elif exact:
3154 3154 ui.warn(_('file not managed: %s\n') % rel)
3155 3155 continue
3156 3156 # file has not changed in dirstate
3157 3157 if node == parent:
3158 3158 if exact:
3159 3159 ui.warn(_('no changes needed to %s\n') % rel)
3160 3160 continue
3161 3161 if pmf is None:
3162 3162 # only need parent manifest in this unlikely case,
3163 3163 # so do not read by default
3164 3164 pmf = repo[parent].manifest()
3165 3165 if abs in pmf:
3166 3166 if mfentry:
3167 3167 # if version of file is same in parent and target
3168 3168 # manifests, do nothing
3169 3169 if (pmf[abs] != mfentry or
3170 3170 pmf.flags(abs) != mf.flags(abs)):
3171 3171 handle(revert, False)
3172 3172 else:
3173 3173 handle(remove, False)
3174 3174
3175 3175 if not opts.get('dry_run'):
3176 3176 def checkout(f):
3177 3177 fc = ctx[f]
3178 3178 repo.wwrite(f, fc.data(), fc.flags())
3179 3179
3180 3180 audit_path = util.path_auditor(repo.root)
3181 3181 for f in remove[0]:
3182 3182 if repo.dirstate[f] == 'a':
3183 3183 repo.dirstate.forget(f)
3184 3184 continue
3185 3185 audit_path(f)
3186 3186 try:
3187 3187 util.unlink(repo.wjoin(f))
3188 3188 except OSError:
3189 3189 pass
3190 3190 repo.dirstate.remove(f)
3191 3191
3192 3192 normal = None
3193 3193 if node == parent:
3194 3194 # We're reverting to our parent. If possible, we'd like status
3195 3195 # to report the file as clean. We have to use normallookup for
3196 3196 # merges to avoid losing information about merged/dirty files.
3197 3197 if p2 != nullid:
3198 3198 normal = repo.dirstate.normallookup
3199 3199 else:
3200 3200 normal = repo.dirstate.normal
3201 3201 for f in revert[0]:
3202 3202 checkout(f)
3203 3203 if normal:
3204 3204 normal(f)
3205 3205
3206 3206 for f in add[0]:
3207 3207 checkout(f)
3208 3208 repo.dirstate.add(f)
3209 3209
3210 3210 normal = repo.dirstate.normallookup
3211 3211 if node == parent and p2 == nullid:
3212 3212 normal = repo.dirstate.normal
3213 3213 for f in undelete[0]:
3214 3214 checkout(f)
3215 3215 normal(f)
3216 3216
3217 3217 finally:
3218 3218 wlock.release()
3219 3219
3220 3220 def rollback(ui, repo, **opts):
3221 3221 """roll back the last transaction (dangerous)
3222 3222
3223 3223 This command should be used with care. There is only one level of
3224 3224 rollback, and there is no way to undo a rollback. It will also
3225 3225 restore the dirstate at the time of the last transaction, losing
3226 3226 any dirstate changes since that time. This command does not alter
3227 3227 the working directory.
3228 3228
3229 3229 Transactions are used to encapsulate the effects of all commands
3230 3230 that create new changesets or propagate existing changesets into a
3231 3231 repository. For example, the following commands are transactional,
3232 3232 and their effects can be rolled back:
3233 3233
3234 3234 - commit
3235 3235 - import
3236 3236 - pull
3237 3237 - push (with this repository as the destination)
3238 3238 - unbundle
3239 3239
3240 3240 This command is not intended for use on public repositories. Once
3241 3241 changes are visible for pull by other users, rolling a transaction
3242 3242 back locally is ineffective (someone else may already have pulled
3243 3243 the changes). Furthermore, a race is possible with readers of the
3244 3244 repository; for example an in-progress pull from the repository
3245 3245 may fail if a rollback is performed.
3246 3246
3247 3247 Returns 0 on success, 1 if no rollback data is available.
3248 3248 """
3249 3249 return repo.rollback(opts.get('dry_run'))
3250 3250
3251 3251 def root(ui, repo):
3252 3252 """print the root (top) of the current working directory
3253 3253
3254 3254 Print the root directory of the current repository.
3255 3255
3256 3256 Returns 0 on success.
3257 3257 """
3258 3258 ui.write(repo.root + "\n")
3259 3259
3260 3260 def serve(ui, repo, **opts):
3261 3261 """start stand-alone webserver
3262 3262
3263 3263 Start a local HTTP repository browser and pull server. You can use
3264 3264 this for ad-hoc sharing and browing of repositories. It is
3265 3265 recommended to use a real web server to serve a repository for
3266 3266 longer periods of time.
3267 3267
3268 3268 Please note that the server does not implement access control.
3269 3269 This means that, by default, anybody can read from the server and
3270 3270 nobody can write to it by default. Set the ``web.allow_push``
3271 3271 option to ``*`` to allow everybody to push to the server. You
3272 3272 should use a real web server if you need to authenticate users.
3273 3273
3274 3274 By default, the server logs accesses to stdout and errors to
3275 3275 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3276 3276 files.
3277 3277
3278 3278 To have the server choose a free port number to listen on, specify
3279 3279 a port number of 0; in this case, the server will print the port
3280 3280 number it uses.
3281 3281
3282 3282 Returns 0 on success.
3283 3283 """
3284 3284
3285 3285 if opts["stdio"]:
3286 3286 if repo is None:
3287 3287 raise error.RepoError(_("There is no Mercurial repository here"
3288 3288 " (.hg not found)"))
3289 3289 s = sshserver.sshserver(ui, repo)
3290 3290 s.serve_forever()
3291 3291
3292 3292 # this way we can check if something was given in the command-line
3293 3293 if opts.get('port'):
3294 3294 opts['port'] = util.getport(opts.get('port'))
3295 3295
3296 3296 baseui = repo and repo.baseui or ui
3297 3297 optlist = ("name templates style address port prefix ipv6"
3298 3298 " accesslog errorlog certificate encoding")
3299 3299 for o in optlist.split():
3300 3300 val = opts.get(o, '')
3301 3301 if val in (None, ''): # should check against default options instead
3302 3302 continue
3303 3303 baseui.setconfig("web", o, val)
3304 3304 if repo and repo.ui != baseui:
3305 3305 repo.ui.setconfig("web", o, val)
3306 3306
3307 3307 o = opts.get('web_conf') or opts.get('webdir_conf')
3308 3308 if not o:
3309 3309 if not repo:
3310 3310 raise error.RepoError(_("There is no Mercurial repository"
3311 3311 " here (.hg not found)"))
3312 3312 o = repo.root
3313 3313
3314 3314 app = hgweb.hgweb(o, baseui=ui)
3315 3315
3316 3316 class service(object):
3317 3317 def init(self):
3318 3318 util.set_signal_handler()
3319 3319 self.httpd = hgweb.server.create_server(ui, app)
3320 3320
3321 3321 if opts['port'] and not ui.verbose:
3322 3322 return
3323 3323
3324 3324 if self.httpd.prefix:
3325 3325 prefix = self.httpd.prefix.strip('/') + '/'
3326 3326 else:
3327 3327 prefix = ''
3328 3328
3329 3329 port = ':%d' % self.httpd.port
3330 3330 if port == ':80':
3331 3331 port = ''
3332 3332
3333 3333 bindaddr = self.httpd.addr
3334 3334 if bindaddr == '0.0.0.0':
3335 3335 bindaddr = '*'
3336 3336 elif ':' in bindaddr: # IPv6
3337 3337 bindaddr = '[%s]' % bindaddr
3338 3338
3339 3339 fqaddr = self.httpd.fqaddr
3340 3340 if ':' in fqaddr:
3341 3341 fqaddr = '[%s]' % fqaddr
3342 3342 if opts['port']:
3343 3343 write = ui.status
3344 3344 else:
3345 3345 write = ui.write
3346 3346 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3347 3347 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3348 3348
3349 3349 def run(self):
3350 3350 self.httpd.serve_forever()
3351 3351
3352 3352 service = service()
3353 3353
3354 3354 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3355 3355
3356 3356 def status(ui, repo, *pats, **opts):
3357 3357 """show changed files in the working directory
3358 3358
3359 3359 Show status of files in the repository. If names are given, only
3360 3360 files that match are shown. Files that are clean or ignored or
3361 3361 the source of a copy/move operation, are not listed unless
3362 3362 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3363 3363 Unless options described with "show only ..." are given, the
3364 3364 options -mardu are used.
3365 3365
3366 3366 Option -q/--quiet hides untracked (unknown and ignored) files
3367 3367 unless explicitly requested with -u/--unknown or -i/--ignored.
3368 3368
3369 3369 .. note::
3370 3370 status may appear to disagree with diff if permissions have
3371 3371 changed or a merge has occurred. The standard diff format does
3372 3372 not report permission changes and diff only reports changes
3373 3373 relative to one merge parent.
3374 3374
3375 3375 If one revision is given, it is used as the base revision.
3376 3376 If two revisions are given, the differences between them are
3377 3377 shown. The --change option can also be used as a shortcut to list
3378 3378 the changed files of a revision from its first parent.
3379 3379
3380 3380 The codes used to show the status of files are::
3381 3381
3382 3382 M = modified
3383 3383 A = added
3384 3384 R = removed
3385 3385 C = clean
3386 3386 ! = missing (deleted by non-hg command, but still tracked)
3387 3387 ? = not tracked
3388 3388 I = ignored
3389 3389 = origin of the previous file listed as A (added)
3390 3390
3391 3391 Returns 0 on success.
3392 3392 """
3393 3393
3394 3394 revs = opts.get('rev')
3395 3395 change = opts.get('change')
3396 3396
3397 3397 if revs and change:
3398 3398 msg = _('cannot specify --rev and --change at the same time')
3399 3399 raise util.Abort(msg)
3400 3400 elif change:
3401 3401 node2 = repo.lookup(change)
3402 3402 node1 = repo[node2].parents()[0].node()
3403 3403 else:
3404 3404 node1, node2 = cmdutil.revpair(repo, revs)
3405 3405
3406 3406 cwd = (pats and repo.getcwd()) or ''
3407 3407 end = opts.get('print0') and '\0' or '\n'
3408 3408 copy = {}
3409 3409 states = 'modified added removed deleted unknown ignored clean'.split()
3410 3410 show = [k for k in states if opts.get(k)]
3411 3411 if opts.get('all'):
3412 3412 show += ui.quiet and (states[:4] + ['clean']) or states
3413 3413 if not show:
3414 3414 show = ui.quiet and states[:4] or states[:5]
3415 3415
3416 3416 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3417 3417 'ignored' in show, 'clean' in show, 'unknown' in show,
3418 3418 opts.get('subrepos'))
3419 3419 changestates = zip(states, 'MAR!?IC', stat)
3420 3420
3421 3421 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3422 3422 ctxn = repo[nullid]
3423 3423 ctx1 = repo[node1]
3424 3424 ctx2 = repo[node2]
3425 3425 added = stat[1]
3426 3426 if node2 is None:
3427 3427 added = stat[0] + stat[1] # merged?
3428 3428
3429 3429 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3430 3430 if k in added:
3431 3431 copy[k] = v
3432 3432 elif v in added:
3433 3433 copy[v] = k
3434 3434
3435 3435 for state, char, files in changestates:
3436 3436 if state in show:
3437 3437 format = "%s %%s%s" % (char, end)
3438 3438 if opts.get('no_status'):
3439 3439 format = "%%s%s" % end
3440 3440
3441 3441 for f in files:
3442 3442 ui.write(format % repo.pathto(f, cwd),
3443 3443 label='status.' + state)
3444 3444 if f in copy:
3445 3445 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3446 3446 label='status.copied')
3447 3447
3448 3448 def summary(ui, repo, **opts):
3449 3449 """summarize working directory state
3450 3450
3451 3451 This generates a brief summary of the working directory state,
3452 3452 including parents, branch, commit status, and available updates.
3453 3453
3454 3454 With the --remote option, this will check the default paths for
3455 3455 incoming and outgoing changes. This can be time-consuming.
3456 3456
3457 3457 Returns 0 on success.
3458 3458 """
3459 3459
3460 3460 ctx = repo[None]
3461 3461 parents = ctx.parents()
3462 3462 pnode = parents[0].node()
3463 3463
3464 3464 for p in parents:
3465 3465 # label with log.changeset (instead of log.parent) since this
3466 3466 # shows a working directory parent *changeset*:
3467 3467 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3468 3468 label='log.changeset')
3469 3469 ui.write(' '.join(p.tags()), label='log.tag')
3470 3470 if p.rev() == -1:
3471 3471 if not len(repo):
3472 3472 ui.write(_(' (empty repository)'))
3473 3473 else:
3474 3474 ui.write(_(' (no revision checked out)'))
3475 3475 ui.write('\n')
3476 3476 if p.description():
3477 3477 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3478 3478 label='log.summary')
3479 3479
3480 3480 branch = ctx.branch()
3481 3481 bheads = repo.branchheads(branch)
3482 3482 m = _('branch: %s\n') % branch
3483 3483 if branch != 'default':
3484 3484 ui.write(m, label='log.branch')
3485 3485 else:
3486 3486 ui.status(m, label='log.branch')
3487 3487
3488 3488 st = list(repo.status(unknown=True))[:6]
3489 3489
3490 3490 c = repo.dirstate.copies()
3491 3491 copied, renamed = [], []
3492 3492 for d, s in c.iteritems():
3493 3493 if s in st[2]:
3494 3494 st[2].remove(s)
3495 3495 renamed.append(d)
3496 3496 else:
3497 3497 copied.append(d)
3498 3498 if d in st[1]:
3499 3499 st[1].remove(d)
3500 3500 st.insert(3, renamed)
3501 3501 st.insert(4, copied)
3502 3502
3503 3503 ms = mergemod.mergestate(repo)
3504 3504 st.append([f for f in ms if ms[f] == 'u'])
3505 3505
3506 3506 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3507 3507 st.append(subs)
3508 3508
3509 3509 labels = [ui.label(_('%d modified'), 'status.modified'),
3510 3510 ui.label(_('%d added'), 'status.added'),
3511 3511 ui.label(_('%d removed'), 'status.removed'),
3512 3512 ui.label(_('%d renamed'), 'status.copied'),
3513 3513 ui.label(_('%d copied'), 'status.copied'),
3514 3514 ui.label(_('%d deleted'), 'status.deleted'),
3515 3515 ui.label(_('%d unknown'), 'status.unknown'),
3516 3516 ui.label(_('%d ignored'), 'status.ignored'),
3517 3517 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3518 3518 ui.label(_('%d subrepos'), 'status.modified')]
3519 3519 t = []
3520 3520 for s, l in zip(st, labels):
3521 3521 if s:
3522 3522 t.append(l % len(s))
3523 3523
3524 3524 t = ', '.join(t)
3525 3525 cleanworkdir = False
3526 3526
3527 3527 if len(parents) > 1:
3528 3528 t += _(' (merge)')
3529 3529 elif branch != parents[0].branch():
3530 3530 t += _(' (new branch)')
3531 3531 elif (parents[0].extra().get('close') and
3532 3532 pnode in repo.branchheads(branch, closed=True)):
3533 3533 t += _(' (head closed)')
3534 3534 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3535 3535 t += _(' (clean)')
3536 3536 cleanworkdir = True
3537 3537 elif pnode not in bheads:
3538 3538 t += _(' (new branch head)')
3539 3539
3540 3540 if cleanworkdir:
3541 3541 ui.status(_('commit: %s\n') % t.strip())
3542 3542 else:
3543 3543 ui.write(_('commit: %s\n') % t.strip())
3544 3544
3545 3545 # all ancestors of branch heads - all ancestors of parent = new csets
3546 3546 new = [0] * len(repo)
3547 3547 cl = repo.changelog
3548 3548 for a in [cl.rev(n) for n in bheads]:
3549 3549 new[a] = 1
3550 3550 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3551 3551 new[a] = 1
3552 3552 for a in [p.rev() for p in parents]:
3553 3553 if a >= 0:
3554 3554 new[a] = 0
3555 3555 for a in cl.ancestors(*[p.rev() for p in parents]):
3556 3556 new[a] = 0
3557 3557 new = sum(new)
3558 3558
3559 3559 if new == 0:
3560 3560 ui.status(_('update: (current)\n'))
3561 3561 elif pnode not in bheads:
3562 3562 ui.write(_('update: %d new changesets (update)\n') % new)
3563 3563 else:
3564 3564 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3565 3565 (new, len(bheads)))
3566 3566
3567 3567 if opts.get('remote'):
3568 3568 t = []
3569 3569 source, branches = hg.parseurl(ui.expandpath('default'))
3570 3570 other = hg.repository(hg.remoteui(repo, {}), source)
3571 3571 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3572 3572 ui.debug('comparing with %s\n' % url.hidepassword(source))
3573 3573 repo.ui.pushbuffer()
3574 3574 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3575 3575 repo.ui.popbuffer()
3576 3576 if incoming:
3577 3577 t.append(_('1 or more incoming'))
3578 3578
3579 3579 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3580 3580 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3581 3581 other = hg.repository(hg.remoteui(repo, {}), dest)
3582 3582 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3583 3583 repo.ui.pushbuffer()
3584 3584 o = discovery.findoutgoing(repo, other)
3585 3585 repo.ui.popbuffer()
3586 3586 o = repo.changelog.nodesbetween(o, None)[0]
3587 3587 if o:
3588 3588 t.append(_('%d outgoing') % len(o))
3589 3589
3590 3590 if t:
3591 3591 ui.write(_('remote: %s\n') % (', '.join(t)))
3592 3592 else:
3593 3593 ui.status(_('remote: (synced)\n'))
3594 3594
3595 3595 def tag(ui, repo, name1, *names, **opts):
3596 3596 """add one or more tags for the current or given revision
3597 3597
3598 3598 Name a particular revision using <name>.
3599 3599
3600 3600 Tags are used to name particular revisions of the repository and are
3601 3601 very useful to compare different revisions, to go back to significant
3602 3602 earlier versions or to mark branch points as releases, etc.
3603 3603
3604 3604 If no revision is given, the parent of the working directory is
3605 3605 used, or tip if no revision is checked out.
3606 3606
3607 3607 To facilitate version control, distribution, and merging of tags,
3608 3608 they are stored as a file named ".hgtags" which is managed
3609 3609 similarly to other project files and can be hand-edited if
3610 3610 necessary. The file '.hg/localtags' is used for local tags (not
3611 3611 shared among repositories).
3612 3612
3613 3613 See :hg:`help dates` for a list of formats valid for -d/--date.
3614 3614
3615 3615 Since tag names have priority over branch names during revision
3616 3616 lookup, using an existing branch name as a tag name is discouraged.
3617 3617
3618 3618 Returns 0 on success.
3619 3619 """
3620 3620
3621 3621 rev_ = "."
3622 3622 names = [t.strip() for t in (name1,) + names]
3623 3623 if len(names) != len(set(names)):
3624 3624 raise util.Abort(_('tag names must be unique'))
3625 3625 for n in names:
3626 3626 if n in ['tip', '.', 'null']:
3627 3627 raise util.Abort(_('the name \'%s\' is reserved') % n)
3628 3628 if not n:
3629 3629 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
3630 3630 if opts.get('rev') and opts.get('remove'):
3631 3631 raise util.Abort(_("--rev and --remove are incompatible"))
3632 3632 if opts.get('rev'):
3633 3633 rev_ = opts['rev']
3634 3634 message = opts.get('message')
3635 3635 if opts.get('remove'):
3636 3636 expectedtype = opts.get('local') and 'local' or 'global'
3637 3637 for n in names:
3638 3638 if not repo.tagtype(n):
3639 3639 raise util.Abort(_('tag \'%s\' does not exist') % n)
3640 3640 if repo.tagtype(n) != expectedtype:
3641 3641 if expectedtype == 'global':
3642 3642 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3643 3643 else:
3644 3644 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3645 3645 rev_ = nullid
3646 3646 if not message:
3647 3647 # we don't translate commit messages
3648 3648 message = 'Removed tag %s' % ', '.join(names)
3649 3649 elif not opts.get('force'):
3650 3650 for n in names:
3651 3651 if n in repo.tags():
3652 3652 raise util.Abort(_('tag \'%s\' already exists '
3653 3653 '(use -f to force)') % n)
3654 3654 if not rev_ and repo.dirstate.parents()[1] != nullid:
3655 3655 raise util.Abort(_('uncommitted merge - please provide a '
3656 3656 'specific revision'))
3657 3657 r = repo[rev_].node()
3658 3658
3659 3659 if not message:
3660 3660 # we don't translate commit messages
3661 3661 message = ('Added tag %s for changeset %s' %
3662 3662 (', '.join(names), short(r)))
3663 3663
3664 3664 date = opts.get('date')
3665 3665 if date:
3666 3666 date = util.parsedate(date)
3667 3667
3668 3668 if opts.get('edit'):
3669 3669 message = ui.edit(message, ui.username())
3670 3670
3671 3671 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3672 3672
3673 3673 def tags(ui, repo):
3674 3674 """list repository tags
3675 3675
3676 3676 This lists both regular and local tags. When the -v/--verbose
3677 3677 switch is used, a third column "local" is printed for local tags.
3678 3678
3679 3679 Returns 0 on success.
3680 3680 """
3681 3681
3682 3682 hexfunc = ui.debugflag and hex or short
3683 3683 tagtype = ""
3684 3684
3685 3685 for t, n in reversed(repo.tagslist()):
3686 3686 if ui.quiet:
3687 3687 ui.write("%s\n" % t)
3688 3688 continue
3689 3689
3690 3690 try:
3691 3691 hn = hexfunc(n)
3692 3692 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3693 3693 except error.LookupError:
3694 3694 r = " ?:%s" % hn
3695 3695 else:
3696 3696 spaces = " " * (30 - encoding.colwidth(t))
3697 3697 if ui.verbose:
3698 3698 if repo.tagtype(t) == 'local':
3699 3699 tagtype = " local"
3700 3700 else:
3701 3701 tagtype = ""
3702 3702 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3703 3703
3704 3704 def tip(ui, repo, **opts):
3705 3705 """show the tip revision
3706 3706
3707 3707 The tip revision (usually just called the tip) is the changeset
3708 3708 most recently added to the repository (and therefore the most
3709 3709 recently changed head).
3710 3710
3711 3711 If you have just made a commit, that commit will be the tip. If
3712 3712 you have just pulled changes from another repository, the tip of
3713 3713 that repository becomes the current tip. The "tip" tag is special
3714 3714 and cannot be renamed or assigned to a different changeset.
3715 3715
3716 3716 Returns 0 on success.
3717 3717 """
3718 3718 displayer = cmdutil.show_changeset(ui, repo, opts)
3719 3719 displayer.show(repo[len(repo) - 1])
3720 3720 displayer.close()
3721 3721
3722 3722 def unbundle(ui, repo, fname1, *fnames, **opts):
3723 3723 """apply one or more changegroup files
3724 3724
3725 3725 Apply one or more compressed changegroup files generated by the
3726 3726 bundle command.
3727 3727
3728 3728 Returns 0 on success, 1 if an update has unresolved files.
3729 3729 """
3730 3730 fnames = (fname1,) + fnames
3731 3731
3732 3732 lock = repo.lock()
3733 3733 try:
3734 3734 for fname in fnames:
3735 3735 f = url.open(ui, fname)
3736 3736 gen = changegroup.readbundle(f, fname)
3737 3737 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
3738 3738 lock=lock)
3739 3739 finally:
3740 3740 lock.release()
3741 3741
3742 3742 return postincoming(ui, repo, modheads, opts.get('update'), None)
3743 3743
3744 3744 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3745 3745 """update working directory (or switch revisions)
3746 3746
3747 3747 Update the repository's working directory to the specified
3748 3748 changeset. If no changeset is specified, update to the tip of the
3749 3749 current named branch.
3750 3750
3751 3751 If the changeset is not a descendant of the working directory's
3752 3752 parent, the update is aborted. With the -c/--check option, the
3753 3753 working directory is checked for uncommitted changes; if none are
3754 3754 found, the working directory is updated to the specified
3755 3755 changeset.
3756 3756
3757 3757 The following rules apply when the working directory contains
3758 3758 uncommitted changes:
3759 3759
3760 3760 1. If neither -c/--check nor -C/--clean is specified, and if
3761 3761 the requested changeset is an ancestor or descendant of
3762 3762 the working directory's parent, the uncommitted changes
3763 3763 are merged into the requested changeset and the merged
3764 3764 result is left uncommitted. If the requested changeset is
3765 3765 not an ancestor or descendant (that is, it is on another
3766 3766 branch), the update is aborted and the uncommitted changes
3767 3767 are preserved.
3768 3768
3769 3769 2. With the -c/--check option, the update is aborted and the
3770 3770 uncommitted changes are preserved.
3771 3771
3772 3772 3. With the -C/--clean option, uncommitted changes are discarded and
3773 3773 the working directory is updated to the requested changeset.
3774 3774
3775 3775 Use null as the changeset to remove the working directory (like
3776 3776 :hg:`clone -U`).
3777 3777
3778 3778 If you want to update just one file to an older changeset, use
3779 3779 :hg:`revert`.
3780 3780
3781 3781 See :hg:`help dates` for a list of formats valid for -d/--date.
3782 3782
3783 3783 Returns 0 on success, 1 if there are unresolved files.
3784 3784 """
3785 3785 if rev and node:
3786 3786 raise util.Abort(_("please specify just one revision"))
3787 3787
3788 3788 if not rev:
3789 3789 rev = node
3790 3790
3791 3791 if check and clean:
3792 3792 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
3793 3793
3794 3794 if check:
3795 3795 # we could use dirty() but we can ignore merge and branch trivia
3796 3796 c = repo[None]
3797 3797 if c.modified() or c.added() or c.removed():
3798 3798 raise util.Abort(_("uncommitted local changes"))
3799 3799
3800 3800 if date:
3801 3801 if rev:
3802 3802 raise util.Abort(_("you can't specify a revision and a date"))
3803 3803 rev = cmdutil.finddate(ui, repo, date)
3804 3804
3805 3805 if clean or check:
3806 3806 return hg.clean(repo, rev)
3807 3807 else:
3808 3808 return hg.update(repo, rev)
3809 3809
3810 3810 def verify(ui, repo):
3811 3811 """verify the integrity of the repository
3812 3812
3813 3813 Verify the integrity of the current repository.
3814 3814
3815 3815 This will perform an extensive check of the repository's
3816 3816 integrity, validating the hashes and checksums of each entry in
3817 3817 the changelog, manifest, and tracked files, as well as the
3818 3818 integrity of their crosslinks and indices.
3819 3819
3820 3820 Returns 0 on success, 1 if errors are encountered.
3821 3821 """
3822 3822 return hg.verify(repo)
3823 3823
3824 3824 def version_(ui):
3825 3825 """output version and copyright information"""
3826 3826 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3827 3827 % util.version())
3828 3828 ui.status(_(
3829 3829 "\nCopyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others\n"
3830 3830 "This is free software; see the source for copying conditions. "
3831 3831 "There is NO\nwarranty; "
3832 3832 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3833 3833 ))
3834 3834
3835 3835 # Command options and aliases are listed here, alphabetically
3836 3836
3837 3837 globalopts = [
3838 3838 ('R', 'repository', '',
3839 3839 _('repository root directory or name of overlay bundle file'),
3840 3840 _('REPO')),
3841 3841 ('', 'cwd', '',
3842 3842 _('change working directory'), _('DIR')),
3843 3843 ('y', 'noninteractive', None,
3844 3844 _('do not prompt, assume \'yes\' for any required answers')),
3845 3845 ('q', 'quiet', None, _('suppress output')),
3846 3846 ('v', 'verbose', None, _('enable additional output')),
3847 3847 ('', 'config', [],
3848 3848 _('set/override config option (use \'section.name=value\')'),
3849 3849 _('CONFIG')),
3850 3850 ('', 'debug', None, _('enable debugging output')),
3851 3851 ('', 'debugger', None, _('start debugger')),
3852 3852 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
3853 3853 _('ENCODE')),
3854 3854 ('', 'encodingmode', encoding.encodingmode,
3855 3855 _('set the charset encoding mode'), _('MODE')),
3856 3856 ('', 'traceback', None, _('always print a traceback on exception')),
3857 3857 ('', 'time', None, _('time how long the command takes')),
3858 3858 ('', 'profile', None, _('print command execution profile')),
3859 3859 ('', 'version', None, _('output version information and exit')),
3860 3860 ('h', 'help', None, _('display help and exit')),
3861 3861 ]
3862 3862
3863 3863 dryrunopts = [('n', 'dry-run', None,
3864 3864 _('do not perform actions, just print output'))]
3865 3865
3866 3866 remoteopts = [
3867 3867 ('e', 'ssh', '',
3868 3868 _('specify ssh command to use'), _('CMD')),
3869 3869 ('', 'remotecmd', '',
3870 3870 _('specify hg command to run on the remote side'), _('CMD')),
3871 3871 ]
3872 3872
3873 3873 walkopts = [
3874 3874 ('I', 'include', [],
3875 3875 _('include names matching the given patterns'), _('PATTERN')),
3876 3876 ('X', 'exclude', [],
3877 3877 _('exclude names matching the given patterns'), _('PATTERN')),
3878 3878 ]
3879 3879
3880 3880 commitopts = [
3881 3881 ('m', 'message', '',
3882 3882 _('use text as commit message'), _('TEXT')),
3883 3883 ('l', 'logfile', '',
3884 3884 _('read commit message from file'), _('FILE')),
3885 3885 ]
3886 3886
3887 3887 commitopts2 = [
3888 3888 ('d', 'date', '',
3889 3889 _('record datecode as commit date'), _('DATE')),
3890 3890 ('u', 'user', '',
3891 3891 _('record the specified user as committer'), _('USER')),
3892 3892 ]
3893 3893
3894 3894 templateopts = [
3895 3895 ('', 'style', '',
3896 3896 _('display using template map file'), _('STYLE')),
3897 3897 ('', 'template', '',
3898 3898 _('display with template'), _('TEMPLATE')),
3899 3899 ]
3900 3900
3901 3901 logopts = [
3902 3902 ('p', 'patch', None, _('show patch')),
3903 3903 ('g', 'git', None, _('use git extended diff format')),
3904 3904 ('l', 'limit', '',
3905 3905 _('limit number of changes displayed'), _('NUM')),
3906 3906 ('M', 'no-merges', None, _('do not show merges')),
3907 3907 ('', 'stat', None, _('output diffstat-style summary of changes')),
3908 3908 ] + templateopts
3909 3909
3910 3910 diffopts = [
3911 3911 ('a', 'text', None, _('treat all files as text')),
3912 3912 ('g', 'git', None, _('use git extended diff format')),
3913 3913 ('', 'nodates', None, _('omit dates from diff headers'))
3914 3914 ]
3915 3915
3916 3916 diffopts2 = [
3917 3917 ('p', 'show-function', None, _('show which function each change is in')),
3918 3918 ('', 'reverse', None, _('produce a diff that undoes the changes')),
3919 3919 ('w', 'ignore-all-space', None,
3920 3920 _('ignore white space when comparing lines')),
3921 3921 ('b', 'ignore-space-change', None,
3922 3922 _('ignore changes in the amount of white space')),
3923 3923 ('B', 'ignore-blank-lines', None,
3924 3924 _('ignore changes whose lines are all blank')),
3925 3925 ('U', 'unified', '',
3926 3926 _('number of lines of context to show'), _('NUM')),
3927 3927 ('', 'stat', None, _('output diffstat-style summary of changes')),
3928 3928 ]
3929 3929
3930 3930 similarityopts = [
3931 3931 ('s', 'similarity', '',
3932 3932 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
3933 3933 ]
3934 3934
3935 3935 subrepoopts = [
3936 3936 ('S', 'subrepos', None,
3937 3937 _('recurse into subrepositories'))
3938 3938 ]
3939 3939
3940 3940 table = {
3941 3941 "^add": (add, walkopts + subrepoopts + dryrunopts,
3942 3942 _('[OPTION]... [FILE]...')),
3943 3943 "addremove":
3944 3944 (addremove, similarityopts + walkopts + dryrunopts,
3945 3945 _('[OPTION]... [FILE]...')),
3946 3946 "^annotate|blame":
3947 3947 (annotate,
3948 3948 [('r', 'rev', '',
3949 3949 _('annotate the specified revision'), _('REV')),
3950 3950 ('', 'follow', None,
3951 3951 _('follow copies/renames and list the filename (DEPRECATED)')),
3952 3952 ('', 'no-follow', None, _("don't follow copies and renames")),
3953 3953 ('a', 'text', None, _('treat all files as text')),
3954 3954 ('u', 'user', None, _('list the author (long with -v)')),
3955 3955 ('f', 'file', None, _('list the filename')),
3956 3956 ('d', 'date', None, _('list the date (short with -q)')),
3957 3957 ('n', 'number', None, _('list the revision number (default)')),
3958 3958 ('c', 'changeset', None, _('list the changeset')),
3959 3959 ('l', 'line-number', None,
3960 3960 _('show line number at the first appearance'))
3961 3961 ] + walkopts,
3962 3962 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3963 3963 "archive":
3964 3964 (archive,
3965 3965 [('', 'no-decode', None, _('do not pass files through decoders')),
3966 3966 ('p', 'prefix', '',
3967 3967 _('directory prefix for files in archive'), _('PREFIX')),
3968 3968 ('r', 'rev', '',
3969 3969 _('revision to distribute'), _('REV')),
3970 3970 ('t', 'type', '',
3971 3971 _('type of distribution to create'), _('TYPE')),
3972 3972 ] + subrepoopts + walkopts,
3973 3973 _('[OPTION]... DEST')),
3974 3974 "backout":
3975 3975 (backout,
3976 3976 [('', 'merge', None,
3977 3977 _('merge with old dirstate parent after backout')),
3978 3978 ('', 'parent', '',
3979 3979 _('parent to choose when backing out merge'), _('REV')),
3980 3980 ('r', 'rev', '',
3981 3981 _('revision to backout'), _('REV')),
3982 3982 ] + walkopts + commitopts + commitopts2,
3983 3983 _('[OPTION]... [-r] REV')),
3984 3984 "bisect":
3985 3985 (bisect,
3986 3986 [('r', 'reset', False, _('reset bisect state')),
3987 3987 ('g', 'good', False, _('mark changeset good')),
3988 3988 ('b', 'bad', False, _('mark changeset bad')),
3989 3989 ('s', 'skip', False, _('skip testing changeset')),
3990 3990 ('c', 'command', '',
3991 3991 _('use command to check changeset state'), _('CMD')),
3992 3992 ('U', 'noupdate', False, _('do not update to target'))],
3993 3993 _("[-gbsr] [-U] [-c CMD] [REV]")),
3994 3994 "branch":
3995 3995 (branch,
3996 3996 [('f', 'force', None,
3997 3997 _('set branch name even if it shadows an existing branch')),
3998 3998 ('C', 'clean', None, _('reset branch name to parent branch name'))],
3999 3999 _('[-fC] [NAME]')),
4000 4000 "branches":
4001 4001 (branches,
4002 4002 [('a', 'active', False,
4003 4003 _('show only branches that have unmerged heads')),
4004 4004 ('c', 'closed', False,
4005 4005 _('show normal and closed branches'))],
4006 4006 _('[-ac]')),
4007 4007 "bundle":
4008 4008 (bundle,
4009 4009 [('f', 'force', None,
4010 4010 _('run even when the destination is unrelated')),
4011 4011 ('r', 'rev', [],
4012 4012 _('a changeset intended to be added to the destination'),
4013 4013 _('REV')),
4014 4014 ('b', 'branch', [],
4015 4015 _('a specific branch you would like to bundle'),
4016 4016 _('BRANCH')),
4017 4017 ('', 'base', [],
4018 4018 _('a base changeset assumed to be available at the destination'),
4019 4019 _('REV')),
4020 4020 ('a', 'all', None, _('bundle all changesets in the repository')),
4021 4021 ('t', 'type', 'bzip2',
4022 4022 _('bundle compression type to use'), _('TYPE')),
4023 4023 ] + remoteopts,
4024 4024 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4025 4025 "cat":
4026 4026 (cat,
4027 4027 [('o', 'output', '',
4028 4028 _('print output to file with formatted name'), _('FORMAT')),
4029 4029 ('r', 'rev', '',
4030 4030 _('print the given revision'), _('REV')),
4031 4031 ('', 'decode', None, _('apply any matching decode filter')),
4032 4032 ] + walkopts,
4033 4033 _('[OPTION]... FILE...')),
4034 4034 "^clone":
4035 4035 (clone,
4036 4036 [('U', 'noupdate', None,
4037 4037 _('the clone will include an empty working copy (only a repository)')),
4038 4038 ('u', 'updaterev', '',
4039 4039 _('revision, tag or branch to check out'), _('REV')),
4040 4040 ('r', 'rev', [],
4041 4041 _('include the specified changeset'), _('REV')),
4042 4042 ('b', 'branch', [],
4043 4043 _('clone only the specified branch'), _('BRANCH')),
4044 4044 ('', 'pull', None, _('use pull protocol to copy metadata')),
4045 4045 ('', 'uncompressed', None,
4046 4046 _('use uncompressed transfer (fast over LAN)')),
4047 4047 ] + remoteopts,
4048 4048 _('[OPTION]... SOURCE [DEST]')),
4049 4049 "^commit|ci":
4050 4050 (commit,
4051 4051 [('A', 'addremove', None,
4052 4052 _('mark new/missing files as added/removed before committing')),
4053 4053 ('', 'close-branch', None,
4054 4054 _('mark a branch as closed, hiding it from the branch list')),
4055 4055 ] + walkopts + commitopts + commitopts2,
4056 4056 _('[OPTION]... [FILE]...')),
4057 4057 "copy|cp":
4058 4058 (copy,
4059 4059 [('A', 'after', None, _('record a copy that has already occurred')),
4060 4060 ('f', 'force', None,
4061 4061 _('forcibly copy over an existing managed file')),
4062 4062 ] + walkopts + dryrunopts,
4063 4063 _('[OPTION]... [SOURCE]... DEST')),
4064 4064 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4065 4065 "debugbuilddag":
4066 4066 (debugbuilddag,
4067 4067 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4068 4068 ('a', 'appended-file', None, _('add single file all revs append to')),
4069 4069 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4070 4070 ('n', 'new-file', None, _('add new file at each rev')),
4071 4071 ],
4072 4072 _('[OPTION]... TEXT')),
4073 4073 "debugcheckstate": (debugcheckstate, [], ''),
4074 4074 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4075 4075 "debugcomplete":
4076 4076 (debugcomplete,
4077 4077 [('o', 'options', None, _('show the command options'))],
4078 4078 _('[-o] CMD')),
4079 4079 "debugdag":
4080 4080 (debugdag,
4081 4081 [('t', 'tags', None, _('use tags as labels')),
4082 4082 ('b', 'branches', None, _('annotate with branch names')),
4083 4083 ('', 'dots', None, _('use dots for runs')),
4084 4084 ('s', 'spaces', None, _('separate elements by spaces')),
4085 4085 ],
4086 4086 _('[OPTION]... [FILE [REV]...]')),
4087 4087 "debugdate":
4088 4088 (debugdate,
4089 4089 [('e', 'extended', None, _('try extended date formats'))],
4090 4090 _('[-e] DATE [RANGE]')),
4091 4091 "debugdata": (debugdata, [], _('FILE REV')),
4092 4092 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4093 4093 "debugindex": (debugindex, [], _('FILE')),
4094 4094 "debugindexdot": (debugindexdot, [], _('FILE')),
4095 4095 "debuginstall": (debuginstall, [], ''),
4096 4096 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4097 4097 "debugrebuildstate":
4098 4098 (debugrebuildstate,
4099 4099 [('r', 'rev', '',
4100 4100 _('revision to rebuild to'), _('REV'))],
4101 4101 _('[-r REV] [REV]')),
4102 4102 "debugrename":
4103 4103 (debugrename,
4104 4104 [('r', 'rev', '',
4105 4105 _('revision to debug'), _('REV'))],
4106 4106 _('[-r REV] FILE')),
4107 4107 "debugrevspec":
4108 4108 (debugrevspec, [], ('REVSPEC')),
4109 4109 "debugsetparents":
4110 4110 (debugsetparents, [], _('REV1 [REV2]')),
4111 4111 "debugstate":
4112 4112 (debugstate,
4113 4113 [('', 'nodates', None, _('do not display the saved mtime'))],
4114 4114 _('[OPTION]...')),
4115 4115 "debugsub":
4116 4116 (debugsub,
4117 4117 [('r', 'rev', '',
4118 4118 _('revision to check'), _('REV'))],
4119 4119 _('[-r REV] [REV]')),
4120 4120 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4121 4121 "^diff":
4122 4122 (diff,
4123 4123 [('r', 'rev', [],
4124 4124 _('revision'), _('REV')),
4125 4125 ('c', 'change', '',
4126 4126 _('change made by revision'), _('REV'))
4127 4127 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4128 4128 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4129 4129 "^export":
4130 4130 (export,
4131 4131 [('o', 'output', '',
4132 4132 _('print output to file with formatted name'), _('FORMAT')),
4133 4133 ('', 'switch-parent', None, _('diff against the second parent')),
4134 4134 ('r', 'rev', [],
4135 4135 _('revisions to export'), _('REV')),
4136 4136 ] + diffopts,
4137 4137 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4138 4138 "^forget":
4139 4139 (forget,
4140 4140 [] + walkopts,
4141 4141 _('[OPTION]... FILE...')),
4142 4142 "grep":
4143 4143 (grep,
4144 4144 [('0', 'print0', None, _('end fields with NUL')),
4145 4145 ('', 'all', None, _('print all revisions that match')),
4146 4146 ('f', 'follow', None,
4147 4147 _('follow changeset history,'
4148 4148 ' or file history across copies and renames')),
4149 4149 ('i', 'ignore-case', None, _('ignore case when matching')),
4150 4150 ('l', 'files-with-matches', None,
4151 4151 _('print only filenames and revisions that match')),
4152 4152 ('n', 'line-number', None, _('print matching line numbers')),
4153 4153 ('r', 'rev', [],
4154 4154 _('only search files changed within revision range'), _('REV')),
4155 4155 ('u', 'user', None, _('list the author (long with -v)')),
4156 4156 ('d', 'date', None, _('list the date (short with -q)')),
4157 4157 ] + walkopts,
4158 4158 _('[OPTION]... PATTERN [FILE]...')),
4159 4159 "heads":
4160 4160 (heads,
4161 4161 [('r', 'rev', '',
4162 4162 _('show only heads which are descendants of STARTREV'),
4163 4163 _('STARTREV')),
4164 4164 ('t', 'topo', False, _('show topological heads only')),
4165 4165 ('a', 'active', False,
4166 4166 _('show active branchheads only (DEPRECATED)')),
4167 4167 ('c', 'closed', False,
4168 4168 _('show normal and closed branch heads')),
4169 4169 ] + templateopts,
4170 4170 _('[-ac] [-r STARTREV] [REV]...')),
4171 4171 "help": (help_, [], _('[TOPIC]')),
4172 4172 "identify|id":
4173 4173 (identify,
4174 4174 [('r', 'rev', '',
4175 4175 _('identify the specified revision'), _('REV')),
4176 4176 ('n', 'num', None, _('show local revision number')),
4177 4177 ('i', 'id', None, _('show global revision id')),
4178 4178 ('b', 'branch', None, _('show branch')),
4179 4179 ('t', 'tags', None, _('show tags'))],
4180 4180 _('[-nibt] [-r REV] [SOURCE]')),
4181 4181 "import|patch":
4182 4182 (import_,
4183 4183 [('p', 'strip', 1,
4184 4184 _('directory strip option for patch. This has the same '
4185 4185 'meaning as the corresponding patch option'),
4186 4186 _('NUM')),
4187 4187 ('b', 'base', '',
4188 4188 _('base path'), _('PATH')),
4189 4189 ('f', 'force', None,
4190 4190 _('skip check for outstanding uncommitted changes')),
4191 4191 ('', 'no-commit', None,
4192 4192 _("don't commit, just update the working directory")),
4193 4193 ('', 'exact', None,
4194 4194 _('apply patch to the nodes from which it was generated')),
4195 4195 ('', 'import-branch', None,
4196 4196 _('use any branch information in patch (implied by --exact)'))] +
4197 4197 commitopts + commitopts2 + similarityopts,
4198 4198 _('[OPTION]... PATCH...')),
4199 4199 "incoming|in":
4200 4200 (incoming,
4201 4201 [('f', 'force', None,
4202 4202 _('run even if remote repository is unrelated')),
4203 4203 ('n', 'newest-first', None, _('show newest record first')),
4204 4204 ('', 'bundle', '',
4205 4205 _('file to store the bundles into'), _('FILE')),
4206 4206 ('r', 'rev', [],
4207 4207 _('a remote changeset intended to be added'), _('REV')),
4208 4208 ('b', 'branch', [],
4209 4209 _('a specific branch you would like to pull'), _('BRANCH')),
4210 4210 ] + logopts + remoteopts + subrepoopts,
4211 4211 _('[-p] [-n] [-M] [-f] [-r REV]...'
4212 4212 ' [--bundle FILENAME] [SOURCE]')),
4213 4213 "^init":
4214 4214 (init,
4215 4215 remoteopts,
4216 4216 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4217 4217 "locate":
4218 4218 (locate,
4219 4219 [('r', 'rev', '',
4220 4220 _('search the repository as it is in REV'), _('REV')),
4221 4221 ('0', 'print0', None,
4222 4222 _('end filenames with NUL, for use with xargs')),
4223 4223 ('f', 'fullpath', None,
4224 4224 _('print complete paths from the filesystem root')),
4225 4225 ] + walkopts,
4226 4226 _('[OPTION]... [PATTERN]...')),
4227 4227 "^log|history":
4228 4228 (log,
4229 4229 [('f', 'follow', None,
4230 4230 _('follow changeset history,'
4231 4231 ' or file history across copies and renames')),
4232 4232 ('', 'follow-first', None,
4233 4233 _('only follow the first parent of merge changesets')),
4234 4234 ('d', 'date', '',
4235 4235 _('show revisions matching date spec'), _('DATE')),
4236 4236 ('C', 'copies', None, _('show copied files')),
4237 4237 ('k', 'keyword', [],
4238 4238 _('do case-insensitive search for a given text'), _('TEXT')),
4239 4239 ('r', 'rev', [],
4240 4240 _('show the specified revision or range'), _('REV')),
4241 4241 ('', 'removed', None, _('include revisions where files were removed')),
4242 4242 ('m', 'only-merges', None, _('show only merges')),
4243 4243 ('u', 'user', [],
4244 4244 _('revisions committed by user'), _('USER')),
4245 4245 ('', 'only-branch', [],
4246 4246 _('show only changesets within the given named branch (DEPRECATED)'),
4247 4247 _('BRANCH')),
4248 4248 ('b', 'branch', [],
4249 4249 _('show changesets within the given named branch'), _('BRANCH')),
4250 4250 ('P', 'prune', [],
4251 4251 _('do not display revision or any of its ancestors'), _('REV')),
4252 4252 ] + logopts + walkopts,
4253 4253 _('[OPTION]... [FILE]')),
4254 4254 "manifest":
4255 4255 (manifest,
4256 4256 [('r', 'rev', '',
4257 4257 _('revision to display'), _('REV'))],
4258 4258 _('[-r REV]')),
4259 4259 "^merge":
4260 4260 (merge,
4261 4261 [('f', 'force', None, _('force a merge with outstanding changes')),
4262 4262 ('r', 'rev', '',
4263 4263 _('revision to merge'), _('REV')),
4264 4264 ('P', 'preview', None,
4265 4265 _('review revisions to merge (no merge is performed)'))],
4266 4266 _('[-P] [-f] [[-r] REV]')),
4267 4267 "outgoing|out":
4268 4268 (outgoing,
4269 4269 [('f', 'force', None,
4270 4270 _('run even when the destination is unrelated')),
4271 4271 ('r', 'rev', [],
4272 4272 _('a changeset intended to be included in the destination'),
4273 4273 _('REV')),
4274 4274 ('n', 'newest-first', None, _('show newest record first')),
4275 4275 ('b', 'branch', [],
4276 4276 _('a specific branch you would like to push'), _('BRANCH')),
4277 4277 ] + logopts + remoteopts + subrepoopts,
4278 4278 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4279 4279 "parents":
4280 4280 (parents,
4281 4281 [('r', 'rev', '',
4282 4282 _('show parents of the specified revision'), _('REV')),
4283 4283 ] + templateopts,
4284 4284 _('[-r REV] [FILE]')),
4285 4285 "paths": (paths, [], _('[NAME]')),
4286 4286 "^pull":
4287 4287 (pull,
4288 4288 [('u', 'update', None,
4289 4289 _('update to new branch head if changesets were pulled')),
4290 4290 ('f', 'force', None,
4291 4291 _('run even when remote repository is unrelated')),
4292 4292 ('r', 'rev', [],
4293 4293 _('a remote changeset intended to be added'), _('REV')),
4294 4294 ('b', 'branch', [],
4295 4295 _('a specific branch you would like to pull'), _('BRANCH')),
4296 4296 ] + remoteopts,
4297 4297 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4298 4298 "^push":
4299 4299 (push,
4300 4300 [('f', 'force', None, _('force push')),
4301 4301 ('r', 'rev', [],
4302 4302 _('a changeset intended to be included in the destination'),
4303 4303 _('REV')),
4304 4304 ('b', 'branch', [],
4305 4305 _('a specific branch you would like to push'), _('BRANCH')),
4306 4306 ('', 'new-branch', False, _('allow pushing a new branch')),
4307 4307 ] + remoteopts,
4308 4308 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4309 4309 "recover": (recover, []),
4310 4310 "^remove|rm":
4311 4311 (remove,
4312 4312 [('A', 'after', None, _('record delete for missing files')),
4313 4313 ('f', 'force', None,
4314 4314 _('remove (and delete) file even if added or modified')),
4315 4315 ] + walkopts,
4316 4316 _('[OPTION]... FILE...')),
4317 4317 "rename|mv":
4318 4318 (rename,
4319 4319 [('A', 'after', None, _('record a rename that has already occurred')),
4320 4320 ('f', 'force', None,
4321 4321 _('forcibly copy over an existing managed file')),
4322 4322 ] + walkopts + dryrunopts,
4323 4323 _('[OPTION]... SOURCE... DEST')),
4324 4324 "resolve":
4325 4325 (resolve,
4326 4326 [('a', 'all', None, _('select all unresolved files')),
4327 4327 ('l', 'list', None, _('list state of files needing merge')),
4328 4328 ('m', 'mark', None, _('mark files as resolved')),
4329 4329 ('u', 'unmark', None, _('mark files as unresolved')),
4330 4330 ('n', 'no-status', None, _('hide status prefix'))]
4331 4331 + walkopts,
4332 4332 _('[OPTION]... [FILE]...')),
4333 4333 "revert":
4334 4334 (revert,
4335 4335 [('a', 'all', None, _('revert all changes when no arguments given')),
4336 4336 ('d', 'date', '',
4337 4337 _('tipmost revision matching date'), _('DATE')),
4338 4338 ('r', 'rev', '',
4339 4339 _('revert to the specified revision'), _('REV')),
4340 4340 ('', 'no-backup', None, _('do not save backup copies of files')),
4341 4341 ] + walkopts + dryrunopts,
4342 4342 _('[OPTION]... [-r REV] [NAME]...')),
4343 4343 "rollback": (rollback, dryrunopts),
4344 4344 "root": (root, []),
4345 4345 "^serve":
4346 4346 (serve,
4347 4347 [('A', 'accesslog', '',
4348 4348 _('name of access log file to write to'), _('FILE')),
4349 4349 ('d', 'daemon', None, _('run server in background')),
4350 4350 ('', 'daemon-pipefds', '',
4351 4351 _('used internally by daemon mode'), _('NUM')),
4352 4352 ('E', 'errorlog', '',
4353 4353 _('name of error log file to write to'), _('FILE')),
4354 4354 # use string type, then we can check if something was passed
4355 4355 ('p', 'port', '',
4356 4356 _('port to listen on (default: 8000)'), _('PORT')),
4357 4357 ('a', 'address', '',
4358 4358 _('address to listen on (default: all interfaces)'), _('ADDR')),
4359 4359 ('', 'prefix', '',
4360 4360 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4361 4361 ('n', 'name', '',
4362 4362 _('name to show in web pages (default: working directory)'),
4363 4363 _('NAME')),
4364 4364 ('', 'web-conf', '',
4365 4365 _('name of the hgweb config file (see "hg help hgweb")'),
4366 4366 _('FILE')),
4367 4367 ('', 'webdir-conf', '',
4368 4368 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4369 4369 ('', 'pid-file', '',
4370 4370 _('name of file to write process ID to'), _('FILE')),
4371 4371 ('', 'stdio', None, _('for remote clients')),
4372 4372 ('t', 'templates', '',
4373 4373 _('web templates to use'), _('TEMPLATE')),
4374 4374 ('', 'style', '',
4375 4375 _('template style to use'), _('STYLE')),
4376 4376 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4377 4377 ('', 'certificate', '',
4378 4378 _('SSL certificate file'), _('FILE'))],
4379 4379 _('[OPTION]...')),
4380 4380 "showconfig|debugconfig":
4381 4381 (showconfig,
4382 4382 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4383 4383 _('[-u] [NAME]...')),
4384 4384 "^summary|sum":
4385 4385 (summary,
4386 4386 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4387 4387 "^status|st":
4388 4388 (status,
4389 4389 [('A', 'all', None, _('show status of all files')),
4390 4390 ('m', 'modified', None, _('show only modified files')),
4391 4391 ('a', 'added', None, _('show only added files')),
4392 4392 ('r', 'removed', None, _('show only removed files')),
4393 4393 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4394 4394 ('c', 'clean', None, _('show only files without changes')),
4395 4395 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4396 4396 ('i', 'ignored', None, _('show only ignored files')),
4397 4397 ('n', 'no-status', None, _('hide status prefix')),
4398 4398 ('C', 'copies', None, _('show source of copied files')),
4399 4399 ('0', 'print0', None,
4400 4400 _('end filenames with NUL, for use with xargs')),
4401 4401 ('', 'rev', [],
4402 4402 _('show difference from revision'), _('REV')),
4403 4403 ('', 'change', '',
4404 4404 _('list the changed files of a revision'), _('REV')),
4405 4405 ] + walkopts + subrepoopts,
4406 4406 _('[OPTION]... [FILE]...')),
4407 4407 "tag":
4408 4408 (tag,
4409 4409 [('f', 'force', None, _('replace existing tag')),
4410 4410 ('l', 'local', None, _('make the tag local')),
4411 4411 ('r', 'rev', '',
4412 4412 _('revision to tag'), _('REV')),
4413 4413 ('', 'remove', None, _('remove a tag')),
4414 4414 # -l/--local is already there, commitopts cannot be used
4415 4415 ('e', 'edit', None, _('edit commit message')),
4416 4416 ('m', 'message', '',
4417 4417 _('use <text> as commit message'), _('TEXT')),
4418 4418 ] + commitopts2,
4419 4419 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4420 4420 "tags": (tags, [], ''),
4421 4421 "tip":
4422 4422 (tip,
4423 4423 [('p', 'patch', None, _('show patch')),
4424 4424 ('g', 'git', None, _('use git extended diff format')),
4425 4425 ] + templateopts,
4426 4426 _('[-p] [-g]')),
4427 4427 "unbundle":
4428 4428 (unbundle,
4429 4429 [('u', 'update', None,
4430 4430 _('update to new branch head if changesets were unbundled'))],
4431 4431 _('[-u] FILE...')),
4432 4432 "^update|up|checkout|co":
4433 4433 (update,
4434 4434 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4435 4435 ('c', 'check', None,
4436 4436 _('update across branches if no uncommitted changes')),
4437 4437 ('d', 'date', '',
4438 4438 _('tipmost revision matching date'), _('DATE')),
4439 4439 ('r', 'rev', '',
4440 4440 _('revision'), _('REV'))],
4441 4441 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4442 4442 "verify": (verify, []),
4443 4443 "version": (version_, []),
4444 4444 }
4445 4445
4446 4446 norepo = ("clone init version help debugcommands debugcomplete"
4447 4447 " debugdate debuginstall debugfsinfo debugpushkey")
4448 4448 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4449 4449 " debugdata debugindex debugindexdot")
@@ -1,297 +1,297
1 1 # posix.py - Posix utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import osutil
10 10 import os, sys, errno, stat, getpass, pwd, grp
11 11
12 12 posixfile = open
13 13 nulldev = '/dev/null'
14 14 normpath = os.path.normpath
15 15 samestat = os.path.samestat
16 16 rename = os.rename
17 17 expandglobs = False
18 18
19 19 umask = os.umask(0)
20 20 os.umask(umask)
21 21
22 22 def openhardlinks():
23 23 '''return true if it is safe to hold open file handles to hardlinks'''
24 24 return True
25 25
26 26 def rcfiles(path):
27 27 rcs = [os.path.join(path, 'hgrc')]
28 28 rcdir = os.path.join(path, 'hgrc.d')
29 29 try:
30 30 rcs.extend([os.path.join(rcdir, f)
31 31 for f, kind in osutil.listdir(rcdir)
32 32 if f.endswith(".rc")])
33 33 except OSError:
34 34 pass
35 35 return rcs
36 36
37 37 def system_rcpath():
38 38 path = []
39 39 # old mod_python does not set sys.argv
40 40 if len(getattr(sys, 'argv', [])) > 0:
41 41 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
42 42 '/../etc/mercurial'))
43 43 path.extend(rcfiles('/etc/mercurial'))
44 44 return path
45 45
46 46 def user_rcpath():
47 47 return [os.path.expanduser('~/.hgrc')]
48 48
49 49 def parse_patch_output(output_line):
50 50 """parses the output produced by patch and returns the filename"""
51 51 pf = output_line[14:]
52 52 if os.sys.platform == 'OpenVMS':
53 53 if pf[0] == '`':
54 54 pf = pf[1:-1] # Remove the quotes
55 55 else:
56 56 if pf.startswith("'") and pf.endswith("'") and " " in pf:
57 57 pf = pf[1:-1] # Remove the quotes
58 58 return pf
59 59
60 60 def sshargs(sshcmd, host, user, port):
61 61 '''Build argument list for ssh'''
62 62 args = user and ("%s@%s" % (user, host)) or host
63 63 return port and ("%s -p %s" % (args, port)) or args
64 64
65 65 def is_exec(f):
66 66 """check whether a file is executable"""
67 67 return (os.lstat(f).st_mode & 0100 != 0)
68 68
69 69 def set_flags(f, l, x):
70 70 s = os.lstat(f).st_mode
71 71 if l:
72 72 if not stat.S_ISLNK(s):
73 73 # switch file to link
74 74 data = open(f).read()
75 75 os.unlink(f)
76 76 try:
77 77 os.symlink(data, f)
78 78 except:
79 79 # failed to make a link, rewrite file
80 80 open(f, "w").write(data)
81 81 # no chmod needed at this point
82 82 return
83 83 if stat.S_ISLNK(s):
84 84 # switch link to file
85 85 data = os.readlink(f)
86 86 os.unlink(f)
87 87 open(f, "w").write(data)
88 88 s = 0666 & ~umask # avoid restatting for chmod
89 89
90 90 sx = s & 0100
91 91 if x and not sx:
92 92 # Turn on +x for every +r bit when making a file executable
93 93 # and obey umask.
94 94 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
95 95 elif not x and sx:
96 96 # Turn off all +x bits
97 97 os.chmod(f, s & 0666)
98 98
99 99 def set_binary(fd):
100 100 pass
101 101
102 102 def pconvert(path):
103 103 return path
104 104
105 105 def localpath(path):
106 106 return path
107 107
108 108 def samefile(fpath1, fpath2):
109 109 """Returns whether path1 and path2 refer to the same file. This is only
110 110 guaranteed to work for files, not directories."""
111 111 return os.path.samefile(fpath1, fpath2)
112 112
113 113 def samedevice(fpath1, fpath2):
114 114 """Returns whether fpath1 and fpath2 are on the same device. This is only
115 115 guaranteed to work for files, not directories."""
116 116 st1 = os.lstat(fpath1)
117 117 st2 = os.lstat(fpath2)
118 118 return st1.st_dev == st2.st_dev
119 119
120 120 if sys.platform == 'darwin':
121 121 import fcntl # only needed on darwin, missing on jython
122 122 def realpath(path):
123 123 '''
124 124 Returns the true, canonical file system path equivalent to the given
125 125 path.
126 126
127 127 Equivalent means, in this case, resulting in the same, unique
128 128 file system link to the path. Every file system entry, whether a file,
129 129 directory, hard link or symbolic link or special, will have a single
130 130 path preferred by the system, but may allow multiple, differing path
131 131 lookups to point to it.
132 132
133 133 Most regular UNIX file systems only allow a file system entry to be
134 134 looked up by its distinct path. Obviously, this does not apply to case
135 135 insensitive file systems, whether case preserving or not. The most
136 136 complex issue to deal with is file systems transparently reencoding the
137 137 path, such as the non-standard Unicode normalisation required for HFS+
138 138 and HFSX.
139 139 '''
140 140 # Constants copied from /usr/include/sys/fcntl.h
141 141 F_GETPATH = 50
142 142 O_SYMLINK = 0x200000
143 143
144 144 try:
145 145 fd = os.open(path, O_SYMLINK)
146 146 except OSError, err:
147 147 if err.errno is errno.ENOENT:
148 148 return path
149 149 raise
150 150
151 151 try:
152 152 return fcntl.fcntl(fd, F_GETPATH, '\0' * 1024).rstrip('\0')
153 153 finally:
154 154 os.close(fd)
155 155 else:
156 156 # Fallback to the likely inadequate Python builtin function.
157 157 realpath = os.path.realpath
158 158
159 159 def shellquote(s):
160 160 if os.sys.platform == 'OpenVMS':
161 161 return '"%s"' % s
162 162 else:
163 163 return "'%s'" % s.replace("'", "'\\''")
164 164
165 165 def quotecommand(cmd):
166 166 return cmd
167 167
168 168 def popen(command, mode='r'):
169 169 return os.popen(command, mode)
170 170
171 171 def testpid(pid):
172 172 '''return False if pid dead, True if running or not sure'''
173 173 if os.sys.platform == 'OpenVMS':
174 174 return True
175 175 try:
176 176 os.kill(pid, 0)
177 177 return True
178 178 except OSError, inst:
179 179 return inst.errno != errno.ESRCH
180 180
181 181 def explain_exit(code):
182 182 """return a 2-tuple (desc, code) describing a subprocess status
183 183 (codes from kill are negative - not os.system/wait encoding)"""
184 184 if code >= 0:
185 185 return _("exited with status %d") % code, code
186 186 return _("killed by signal %d") % -code, -code
187 187
188 188 def isowner(st):
189 189 """Return True if the stat object st is from the current user."""
190 190 return st.st_uid == os.getuid()
191 191
192 192 def find_exe(command):
193 193 '''Find executable for command searching like which does.
194 194 If command is a basename then PATH is searched for command.
195 195 PATH isn't searched if command is an absolute or relative path.
196 196 If command isn't found None is returned.'''
197 197 if sys.platform == 'OpenVMS':
198 198 return command
199 199
200 200 def findexisting(executable):
201 201 'Will return executable if existing file'
202 202 if os.path.exists(executable):
203 203 return executable
204 204 return None
205 205
206 206 if os.sep in command:
207 207 return findexisting(command)
208 208
209 209 for path in os.environ.get('PATH', '').split(os.pathsep):
210 210 executable = findexisting(os.path.join(path, command))
211 211 if executable is not None:
212 212 return executable
213 213 return None
214 214
215 215 def set_signal_handler():
216 216 pass
217 217
218 218 def statfiles(files):
219 219 'Stat each file in files and yield stat or None if file does not exist.'
220 220 lstat = os.lstat
221 221 for nf in files:
222 222 try:
223 223 st = lstat(nf)
224 224 except OSError, err:
225 225 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
226 226 raise
227 227 st = None
228 228 yield st
229 229
230 230 def getuser():
231 231 '''return name of current user'''
232 232 return getpass.getuser()
233 233
234 234 def expand_glob(pats):
235 235 '''On Windows, expand the implicit globs in a list of patterns'''
236 236 return list(pats)
237 237
238 238 def username(uid=None):
239 239 """Return the name of the user with the given uid.
240 240
241 241 If uid is None, return the name of the current user."""
242 242
243 243 if uid is None:
244 244 uid = os.getuid()
245 245 try:
246 246 return pwd.getpwuid(uid)[0]
247 247 except KeyError:
248 248 return str(uid)
249 249
250 250 def groupname(gid=None):
251 251 """Return the name of the group with the given gid.
252 252
253 253 If gid is None, return the name of the current group."""
254 254
255 255 if gid is None:
256 256 gid = os.getgid()
257 257 try:
258 258 return grp.getgrgid(gid)[0]
259 259 except KeyError:
260 260 return str(gid)
261 261
262 262 def groupmembers(name):
263 263 """Return the list of members of the group with the given
264 264 name, KeyError if the group does not exist.
265 265 """
266 266 return list(grp.getgrnam(name).gr_mem)
267 267
268 268 def spawndetached(args):
269 269 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
270 270 args[0], args)
271 271
272 272 def gethgcmd():
273 273 return sys.argv[:1]
274 274
275 def termwidth_():
275 def termwidth():
276 276 try:
277 277 import termios, array, fcntl
278 278 for dev in (sys.stderr, sys.stdout, sys.stdin):
279 279 try:
280 280 try:
281 281 fd = dev.fileno()
282 282 except AttributeError:
283 283 continue
284 284 if not os.isatty(fd):
285 285 continue
286 286 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
287 287 return array.array('h', arri)[1]
288 288 except ValueError:
289 289 pass
290 290 except IOError, e:
291 291 if e[0] == errno.EINVAL:
292 292 pass
293 293 else:
294 294 raise
295 295 except ImportError:
296 296 pass
297 297 return 80
@@ -1,612 +1,622
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import errno, getpass, os, socket, sys, tempfile, traceback
10 10 import config, util, error
11 11
12 12 class ui(object):
13 13 def __init__(self, src=None):
14 14 self._buffers = []
15 15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
16 16 self._reportuntrusted = True
17 17 self._ocfg = config.config() # overlay
18 18 self._tcfg = config.config() # trusted
19 19 self._ucfg = config.config() # untrusted
20 20 self._trustusers = set()
21 21 self._trustgroups = set()
22 22
23 23 if src:
24 24 self._tcfg = src._tcfg.copy()
25 25 self._ucfg = src._ucfg.copy()
26 26 self._ocfg = src._ocfg.copy()
27 27 self._trustusers = src._trustusers.copy()
28 28 self._trustgroups = src._trustgroups.copy()
29 29 self.environ = src.environ
30 30 self.fixconfig()
31 31 else:
32 32 # shared read-only environment
33 33 self.environ = os.environ
34 34 # we always trust global config files
35 35 for f in util.rcpath():
36 36 self.readconfig(f, trust=True)
37 37
38 38 def copy(self):
39 39 return self.__class__(self)
40 40
41 41 def _is_trusted(self, fp, f):
42 42 st = util.fstat(fp)
43 43 if util.isowner(st):
44 44 return True
45 45
46 46 tusers, tgroups = self._trustusers, self._trustgroups
47 47 if '*' in tusers or '*' in tgroups:
48 48 return True
49 49
50 50 user = util.username(st.st_uid)
51 51 group = util.groupname(st.st_gid)
52 52 if user in tusers or group in tgroups or user == util.username():
53 53 return True
54 54
55 55 if self._reportuntrusted:
56 56 self.warn(_('Not trusting file %s from untrusted '
57 57 'user %s, group %s\n') % (f, user, group))
58 58 return False
59 59
60 60 def readconfig(self, filename, root=None, trust=False,
61 61 sections=None, remap=None):
62 62 try:
63 63 fp = open(filename)
64 64 except IOError:
65 65 if not sections: # ignore unless we were looking for something
66 66 return
67 67 raise
68 68
69 69 cfg = config.config()
70 70 trusted = sections or trust or self._is_trusted(fp, filename)
71 71
72 72 try:
73 73 cfg.read(filename, fp, sections=sections, remap=remap)
74 74 except error.ConfigError, inst:
75 75 if trusted:
76 76 raise
77 77 self.warn(_("Ignored: %s\n") % str(inst))
78 78
79 79 if self.plain():
80 80 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
81 81 'logtemplate', 'style',
82 82 'traceback', 'verbose'):
83 83 if k in cfg['ui']:
84 84 del cfg['ui'][k]
85 85 for k, v in cfg.items('alias'):
86 86 del cfg['alias'][k]
87 87 for k, v in cfg.items('defaults'):
88 88 del cfg['defaults'][k]
89 89
90 90 if trusted:
91 91 self._tcfg.update(cfg)
92 92 self._tcfg.update(self._ocfg)
93 93 self._ucfg.update(cfg)
94 94 self._ucfg.update(self._ocfg)
95 95
96 96 if root is None:
97 97 root = os.path.expanduser('~')
98 98 self.fixconfig(root=root)
99 99
100 100 def fixconfig(self, root=None):
101 101 # expand vars and ~
102 102 # translate paths relative to root (or home) into absolute paths
103 103 root = root or os.getcwd()
104 104 for c in self._tcfg, self._ucfg, self._ocfg:
105 105 for n, p in c.items('paths'):
106 106 if not p:
107 107 continue
108 108 if '%%' in p:
109 109 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
110 110 % (n, p, self.configsource('paths', n)))
111 111 p = p.replace('%%', '%')
112 112 p = util.expandpath(p)
113 113 if '://' not in p and not os.path.isabs(p):
114 114 p = os.path.normpath(os.path.join(root, p))
115 115 c.set("paths", n, p)
116 116
117 117 # update ui options
118 118 self.debugflag = self.configbool('ui', 'debug')
119 119 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
120 120 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
121 121 if self.verbose and self.quiet:
122 122 self.quiet = self.verbose = False
123 123 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
124 124 self.tracebackflag = self.configbool('ui', 'traceback', False)
125 125
126 126 # update trust information
127 127 self._trustusers.update(self.configlist('trusted', 'users'))
128 128 self._trustgroups.update(self.configlist('trusted', 'groups'))
129 129
130 130 def setconfig(self, section, name, value, overlay=True):
131 131 if overlay:
132 132 self._ocfg.set(section, name, value)
133 133 self._tcfg.set(section, name, value)
134 134 self._ucfg.set(section, name, value)
135 135 self.fixconfig()
136 136
137 137 def _data(self, untrusted):
138 138 return untrusted and self._ucfg or self._tcfg
139 139
140 140 def configsource(self, section, name, untrusted=False):
141 141 return self._data(untrusted).source(section, name) or 'none'
142 142
143 143 def config(self, section, name, default=None, untrusted=False):
144 144 value = self._data(untrusted).get(section, name, default)
145 145 if self.debugflag and not untrusted and self._reportuntrusted:
146 146 uvalue = self._ucfg.get(section, name)
147 147 if uvalue is not None and uvalue != value:
148 148 self.debug(_("ignoring untrusted configuration option "
149 149 "%s.%s = %s\n") % (section, name, uvalue))
150 150 return value
151 151
152 152 def configbool(self, section, name, default=False, untrusted=False):
153 153 v = self.config(section, name, None, untrusted)
154 154 if v is None:
155 155 return default
156 156 if isinstance(v, bool):
157 157 return v
158 158 b = util.parsebool(v)
159 159 if b is None:
160 160 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
161 161 % (section, name, v))
162 162 return b
163 163
164 164 def configlist(self, section, name, default=None, untrusted=False):
165 165 """Return a list of comma/space separated strings"""
166 166
167 167 def _parse_plain(parts, s, offset):
168 168 whitespace = False
169 169 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
170 170 whitespace = True
171 171 offset += 1
172 172 if offset >= len(s):
173 173 return None, parts, offset
174 174 if whitespace:
175 175 parts.append('')
176 176 if s[offset] == '"' and not parts[-1]:
177 177 return _parse_quote, parts, offset + 1
178 178 elif s[offset] == '"' and parts[-1][-1] == '\\':
179 179 parts[-1] = parts[-1][:-1] + s[offset]
180 180 return _parse_plain, parts, offset + 1
181 181 parts[-1] += s[offset]
182 182 return _parse_plain, parts, offset + 1
183 183
184 184 def _parse_quote(parts, s, offset):
185 185 if offset < len(s) and s[offset] == '"': # ""
186 186 parts.append('')
187 187 offset += 1
188 188 while offset < len(s) and (s[offset].isspace() or
189 189 s[offset] == ','):
190 190 offset += 1
191 191 return _parse_plain, parts, offset
192 192
193 193 while offset < len(s) and s[offset] != '"':
194 194 if (s[offset] == '\\' and offset + 1 < len(s)
195 195 and s[offset + 1] == '"'):
196 196 offset += 1
197 197 parts[-1] += '"'
198 198 else:
199 199 parts[-1] += s[offset]
200 200 offset += 1
201 201
202 202 if offset >= len(s):
203 203 real_parts = _configlist(parts[-1])
204 204 if not real_parts:
205 205 parts[-1] = '"'
206 206 else:
207 207 real_parts[0] = '"' + real_parts[0]
208 208 parts = parts[:-1]
209 209 parts.extend(real_parts)
210 210 return None, parts, offset
211 211
212 212 offset += 1
213 213 while offset < len(s) and s[offset] in [' ', ',']:
214 214 offset += 1
215 215
216 216 if offset < len(s):
217 217 if offset + 1 == len(s) and s[offset] == '"':
218 218 parts[-1] += '"'
219 219 offset += 1
220 220 else:
221 221 parts.append('')
222 222 else:
223 223 return None, parts, offset
224 224
225 225 return _parse_plain, parts, offset
226 226
227 227 def _configlist(s):
228 228 s = s.rstrip(' ,')
229 229 if not s:
230 230 return []
231 231 parser, parts, offset = _parse_plain, [''], 0
232 232 while parser:
233 233 parser, parts, offset = parser(parts, s, offset)
234 234 return parts
235 235
236 236 result = self.config(section, name, untrusted=untrusted)
237 237 if result is None:
238 238 result = default or []
239 239 if isinstance(result, basestring):
240 240 result = _configlist(result.lstrip(' ,\n'))
241 241 if result is None:
242 242 result = default or []
243 243 return result
244 244
245 245 def has_section(self, section, untrusted=False):
246 246 '''tell whether section exists in config.'''
247 247 return section in self._data(untrusted)
248 248
249 249 def configitems(self, section, untrusted=False):
250 250 items = self._data(untrusted).items(section)
251 251 if self.debugflag and not untrusted and self._reportuntrusted:
252 252 for k, v in self._ucfg.items(section):
253 253 if self._tcfg.get(section, k) != v:
254 254 self.debug(_("ignoring untrusted configuration option "
255 255 "%s.%s = %s\n") % (section, k, v))
256 256 return items
257 257
258 258 def walkconfig(self, untrusted=False):
259 259 cfg = self._data(untrusted)
260 260 for section in cfg.sections():
261 261 for name, value in self.configitems(section, untrusted):
262 262 yield section, name, str(value).replace('\n', '\\n')
263 263
264 264 def plain(self):
265 265 '''is plain mode active?
266 266
267 267 Plain mode means that all configuration variables which affect the
268 268 behavior and output of Mercurial should be ignored. Additionally, the
269 269 output should be stable, reproducible and suitable for use in scripts or
270 270 applications.
271 271
272 272 The only way to trigger plain mode is by setting the `HGPLAIN'
273 273 environment variable.
274 274 '''
275 275 return 'HGPLAIN' in os.environ
276 276
277 277 def username(self):
278 278 """Return default username to be used in commits.
279 279
280 280 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
281 281 and stop searching if one of these is set.
282 282 If not found and ui.askusername is True, ask the user, else use
283 283 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
284 284 """
285 285 user = os.environ.get("HGUSER")
286 286 if user is None:
287 287 user = self.config("ui", "username")
288 288 if user is not None:
289 289 user = os.path.expandvars(user)
290 290 if user is None:
291 291 user = os.environ.get("EMAIL")
292 292 if user is None and self.configbool("ui", "askusername"):
293 293 user = self.prompt(_("enter a commit username:"), default=None)
294 294 if user is None and not self.interactive():
295 295 try:
296 296 user = '%s@%s' % (util.getuser(), socket.getfqdn())
297 297 self.warn(_("No username found, using '%s' instead\n") % user)
298 298 except KeyError:
299 299 pass
300 300 if not user:
301 301 raise util.Abort(_('no username supplied (see "hg help config")'))
302 302 if "\n" in user:
303 303 raise util.Abort(_("username %s contains a newline\n") % repr(user))
304 304 return user
305 305
306 306 def shortuser(self, user):
307 307 """Return a short representation of a user name or email address."""
308 308 if not self.verbose:
309 309 user = util.shortuser(user)
310 310 return user
311 311
312 312 def expandpath(self, loc, default=None):
313 313 """Return repository location relative to cwd or from [paths]"""
314 314 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
315 315 return loc
316 316
317 317 path = self.config('paths', loc)
318 318 if not path and default is not None:
319 319 path = self.config('paths', default)
320 320 return path or loc
321 321
322 322 def pushbuffer(self):
323 323 self._buffers.append([])
324 324
325 325 def popbuffer(self, labeled=False):
326 326 '''pop the last buffer and return the buffered output
327 327
328 328 If labeled is True, any labels associated with buffered
329 329 output will be handled. By default, this has no effect
330 330 on the output returned, but extensions and GUI tools may
331 331 handle this argument and returned styled output. If output
332 332 is being buffered so it can be captured and parsed or
333 333 processed, labeled should not be set to True.
334 334 '''
335 335 return "".join(self._buffers.pop())
336 336
337 337 def write(self, *args, **opts):
338 338 '''write args to output
339 339
340 340 By default, this method simply writes to the buffer or stdout,
341 341 but extensions or GUI tools may override this method,
342 342 write_err(), popbuffer(), and label() to style output from
343 343 various parts of hg.
344 344
345 345 An optional keyword argument, "label", can be passed in.
346 346 This should be a string containing label names separated by
347 347 space. Label names take the form of "topic.type". For example,
348 348 ui.debug() issues a label of "ui.debug".
349 349
350 350 When labeling output for a specific command, a label of
351 351 "cmdname.type" is recommended. For example, status issues
352 352 a label of "status.modified" for modified files.
353 353 '''
354 354 if self._buffers:
355 355 self._buffers[-1].extend([str(a) for a in args])
356 356 else:
357 357 for a in args:
358 358 sys.stdout.write(str(a))
359 359
360 360 def write_err(self, *args, **opts):
361 361 try:
362 362 if not getattr(sys.stdout, 'closed', False):
363 363 sys.stdout.flush()
364 364 for a in args:
365 365 sys.stderr.write(str(a))
366 366 # stderr may be buffered under win32 when redirected to files,
367 367 # including stdout.
368 368 if not getattr(sys.stderr, 'closed', False):
369 369 sys.stderr.flush()
370 370 except IOError, inst:
371 371 if inst.errno not in (errno.EPIPE, errno.EIO):
372 372 raise
373 373
374 374 def flush(self):
375 375 try: sys.stdout.flush()
376 376 except: pass
377 377 try: sys.stderr.flush()
378 378 except: pass
379 379
380 380 def interactive(self):
381 381 '''is interactive input allowed?
382 382
383 383 An interactive session is a session where input can be reasonably read
384 384 from `sys.stdin'. If this function returns false, any attempt to read
385 385 from stdin should fail with an error, unless a sensible default has been
386 386 specified.
387 387
388 388 Interactiveness is triggered by the value of the `ui.interactive'
389 389 configuration variable or - if it is unset - when `sys.stdin' points
390 390 to a terminal device.
391 391
392 392 This function refers to input only; for output, see `ui.formatted()'.
393 393 '''
394 394 i = self.configbool("ui", "interactive", None)
395 395 if i is None:
396 396 try:
397 397 return sys.stdin.isatty()
398 398 except AttributeError:
399 399 # some environments replace stdin without implementing isatty
400 400 # usually those are non-interactive
401 401 return False
402 402
403 403 return i
404 404
405 def termwidth(self):
406 '''how wide is the terminal in columns?
407 '''
408 if 'COLUMNS' in os.environ:
409 try:
410 return int(os.environ['COLUMNS'])
411 except ValueError:
412 pass
413 return util.termwidth()
414
405 415 def formatted(self):
406 416 '''should formatted output be used?
407 417
408 418 It is often desirable to format the output to suite the output medium.
409 419 Examples of this are truncating long lines or colorizing messages.
410 420 However, this is not often not desirable when piping output into other
411 421 utilities, e.g. `grep'.
412 422
413 423 Formatted output is triggered by the value of the `ui.formatted'
414 424 configuration variable or - if it is unset - when `sys.stdout' points
415 425 to a terminal device. Please note that `ui.formatted' should be
416 426 considered an implementation detail; it is not intended for use outside
417 427 Mercurial or its extensions.
418 428
419 429 This function refers to output only; for input, see `ui.interactive()'.
420 430 This function always returns false when in plain mode, see `ui.plain()'.
421 431 '''
422 432 if self.plain():
423 433 return False
424 434
425 435 i = self.configbool("ui", "formatted", None)
426 436 if i is None:
427 437 try:
428 438 return sys.stdout.isatty()
429 439 except AttributeError:
430 440 # some environments replace stdout without implementing isatty
431 441 # usually those are non-interactive
432 442 return False
433 443
434 444 return i
435 445
436 446 def _readline(self, prompt=''):
437 447 if sys.stdin.isatty():
438 448 try:
439 449 # magically add command line editing support, where
440 450 # available
441 451 import readline
442 452 # force demandimport to really load the module
443 453 readline.read_history_file
444 454 # windows sometimes raises something other than ImportError
445 455 except Exception:
446 456 pass
447 457 line = raw_input(prompt)
448 458 # When stdin is in binary mode on Windows, it can cause
449 459 # raw_input() to emit an extra trailing carriage return
450 460 if os.linesep == '\r\n' and line and line[-1] == '\r':
451 461 line = line[:-1]
452 462 return line
453 463
454 464 def prompt(self, msg, default="y"):
455 465 """Prompt user with msg, read response.
456 466 If ui is not interactive, the default is returned.
457 467 """
458 468 if not self.interactive():
459 469 self.write(msg, ' ', default, "\n")
460 470 return default
461 471 try:
462 472 r = self._readline(msg + ' ')
463 473 if not r:
464 474 return default
465 475 return r
466 476 except EOFError:
467 477 raise util.Abort(_('response expected'))
468 478
469 479 def promptchoice(self, msg, choices, default=0):
470 480 """Prompt user with msg, read response, and ensure it matches
471 481 one of the provided choices. The index of the choice is returned.
472 482 choices is a sequence of acceptable responses with the format:
473 483 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
474 484 If ui is not interactive, the default is returned.
475 485 """
476 486 resps = [s[s.index('&')+1].lower() for s in choices]
477 487 while True:
478 488 r = self.prompt(msg, resps[default])
479 489 if r.lower() in resps:
480 490 return resps.index(r.lower())
481 491 self.write(_("unrecognized response\n"))
482 492
483 493 def getpass(self, prompt=None, default=None):
484 494 if not self.interactive():
485 495 return default
486 496 try:
487 497 return getpass.getpass(prompt or _('password: '))
488 498 except EOFError:
489 499 raise util.Abort(_('response expected'))
490 500 def status(self, *msg, **opts):
491 501 '''write status message to output (if ui.quiet is False)
492 502
493 503 This adds an output label of "ui.status".
494 504 '''
495 505 if not self.quiet:
496 506 opts['label'] = opts.get('label', '') + ' ui.status'
497 507 self.write(*msg, **opts)
498 508 def warn(self, *msg, **opts):
499 509 '''write warning message to output (stderr)
500 510
501 511 This adds an output label of "ui.warning".
502 512 '''
503 513 opts['label'] = opts.get('label', '') + ' ui.warning'
504 514 self.write_err(*msg, **opts)
505 515 def note(self, *msg, **opts):
506 516 '''write note to output (if ui.verbose is True)
507 517
508 518 This adds an output label of "ui.note".
509 519 '''
510 520 if self.verbose:
511 521 opts['label'] = opts.get('label', '') + ' ui.note'
512 522 self.write(*msg, **opts)
513 523 def debug(self, *msg, **opts):
514 524 '''write debug message to output (if ui.debugflag is True)
515 525
516 526 This adds an output label of "ui.debug".
517 527 '''
518 528 if self.debugflag:
519 529 opts['label'] = opts.get('label', '') + ' ui.debug'
520 530 self.write(*msg, **opts)
521 531 def edit(self, text, user):
522 532 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
523 533 text=True)
524 534 try:
525 535 f = os.fdopen(fd, "w")
526 536 f.write(text)
527 537 f.close()
528 538
529 539 editor = self.geteditor()
530 540
531 541 util.system("%s \"%s\"" % (editor, name),
532 542 environ={'HGUSER': user},
533 543 onerr=util.Abort, errprefix=_("edit failed"))
534 544
535 545 f = open(name)
536 546 t = f.read()
537 547 f.close()
538 548 finally:
539 549 os.unlink(name)
540 550
541 551 return t
542 552
543 553 def traceback(self, exc=None):
544 554 '''print exception traceback if traceback printing enabled.
545 555 only to call in exception handler. returns true if traceback
546 556 printed.'''
547 557 if self.tracebackflag:
548 558 if exc:
549 559 traceback.print_exception(exc[0], exc[1], exc[2])
550 560 else:
551 561 traceback.print_exc()
552 562 return self.tracebackflag
553 563
554 564 def geteditor(self):
555 565 '''return editor to use'''
556 566 return (os.environ.get("HGEDITOR") or
557 567 self.config("ui", "editor") or
558 568 os.environ.get("VISUAL") or
559 569 os.environ.get("EDITOR", "vi"))
560 570
561 571 def progress(self, topic, pos, item="", unit="", total=None):
562 572 '''show a progress message
563 573
564 574 With stock hg, this is simply a debug message that is hidden
565 575 by default, but with extensions or GUI tools it may be
566 576 visible. 'topic' is the current operation, 'item' is a
567 577 non-numeric marker of the current position (ie the currently
568 578 in-process file), 'pos' is the current numeric position (ie
569 579 revision, bytes, etc.), unit is a corresponding unit label,
570 580 and total is the highest expected pos.
571 581
572 582 Multiple nested topics may be active at a time.
573 583
574 584 All topics should be marked closed by setting pos to None at
575 585 termination.
576 586 '''
577 587
578 588 if pos == None or not self.debugflag:
579 589 return
580 590
581 591 if unit:
582 592 unit = ' ' + unit
583 593 if item:
584 594 item = ' ' + item
585 595
586 596 if total:
587 597 pct = 100.0 * pos / total
588 598 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
589 599 % (topic, item, pos, total, unit, pct))
590 600 else:
591 601 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
592 602
593 603 def log(self, service, message):
594 604 '''hook for logging facility extensions
595 605
596 606 service should be a readily-identifiable subsystem, which will
597 607 allow filtering.
598 608 message should be a newline-terminated string to log.
599 609 '''
600 610 pass
601 611
602 612 def label(self, msg, label):
603 613 '''style msg based on supplied label
604 614
605 615 Like ui.write(), this just returns msg unchanged, but extensions
606 616 and GUI tools can override it to allow styling output without
607 617 writing it.
608 618
609 619 ui.write(s, 'label') is equivalent to
610 620 ui.write(ui.label(s, 'label')).
611 621 '''
612 622 return msg
@@ -1,1444 +1,1436
1 1 # util.py - Mercurial utility functions and platform specfic implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specfic implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from i18n import _
17 17 import error, osutil, encoding
18 18 import errno, re, shutil, sys, tempfile, traceback
19 19 import os, stat, time, calendar, textwrap, unicodedata, signal
20 20 import imp, socket
21 21
22 22 # Python compatibility
23 23
24 24 def sha1(s):
25 25 return _fastsha1(s)
26 26
27 27 def _fastsha1(s):
28 28 # This function will import sha1 from hashlib or sha (whichever is
29 29 # available) and overwrite itself with it on the first call.
30 30 # Subsequent calls will go directly to the imported function.
31 31 if sys.version_info >= (2, 5):
32 32 from hashlib import sha1 as _sha1
33 33 else:
34 34 from sha import sha as _sha1
35 35 global _fastsha1, sha1
36 36 _fastsha1 = sha1 = _sha1
37 37 return _sha1(s)
38 38
39 39 import __builtin__
40 40
41 41 if sys.version_info[0] < 3:
42 42 def fakebuffer(sliceable, offset=0):
43 43 return sliceable[offset:]
44 44 else:
45 45 def fakebuffer(sliceable, offset=0):
46 46 return memoryview(sliceable)[offset:]
47 47 try:
48 48 buffer
49 49 except NameError:
50 50 __builtin__.buffer = fakebuffer
51 51
52 52 import subprocess
53 53 closefds = os.name == 'posix'
54 54
55 55 def popen2(cmd, env=None, newlines=False):
56 56 # Setting bufsize to -1 lets the system decide the buffer size.
57 57 # The default for bufsize is 0, meaning unbuffered. This leads to
58 58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 60 close_fds=closefds,
61 61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 62 universal_newlines=newlines,
63 63 env=env)
64 64 return p.stdin, p.stdout
65 65
66 66 def popen3(cmd, env=None, newlines=False):
67 67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 68 close_fds=closefds,
69 69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 70 stderr=subprocess.PIPE,
71 71 universal_newlines=newlines,
72 72 env=env)
73 73 return p.stdin, p.stdout, p.stderr
74 74
75 75 def version():
76 76 """Return version information if available."""
77 77 try:
78 78 import __version__
79 79 return __version__.version
80 80 except ImportError:
81 81 return 'unknown'
82 82
83 83 # used by parsedate
84 84 defaultdateformats = (
85 85 '%Y-%m-%d %H:%M:%S',
86 86 '%Y-%m-%d %I:%M:%S%p',
87 87 '%Y-%m-%d %H:%M',
88 88 '%Y-%m-%d %I:%M%p',
89 89 '%Y-%m-%d',
90 90 '%m-%d',
91 91 '%m/%d',
92 92 '%m/%d/%y',
93 93 '%m/%d/%Y',
94 94 '%a %b %d %H:%M:%S %Y',
95 95 '%a %b %d %I:%M:%S%p %Y',
96 96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 97 '%b %d %H:%M:%S %Y',
98 98 '%b %d %I:%M:%S%p %Y',
99 99 '%b %d %H:%M:%S',
100 100 '%b %d %I:%M:%S%p',
101 101 '%b %d %H:%M',
102 102 '%b %d %I:%M%p',
103 103 '%b %d %Y',
104 104 '%b %d',
105 105 '%H:%M:%S',
106 106 '%I:%M:%S%p',
107 107 '%H:%M',
108 108 '%I:%M%p',
109 109 )
110 110
111 111 extendeddateformats = defaultdateformats + (
112 112 "%Y",
113 113 "%Y-%m",
114 114 "%b",
115 115 "%b %Y",
116 116 )
117 117
118 118 def cachefunc(func):
119 119 '''cache the result of function calls'''
120 120 # XXX doesn't handle keywords args
121 121 cache = {}
122 122 if func.func_code.co_argcount == 1:
123 123 # we gain a small amount of time because
124 124 # we don't need to pack/unpack the list
125 125 def f(arg):
126 126 if arg not in cache:
127 127 cache[arg] = func(arg)
128 128 return cache[arg]
129 129 else:
130 130 def f(*args):
131 131 if args not in cache:
132 132 cache[args] = func(*args)
133 133 return cache[args]
134 134
135 135 return f
136 136
137 137 def lrucachefunc(func):
138 138 '''cache most recent results of function calls'''
139 139 cache = {}
140 140 order = []
141 141 if func.func_code.co_argcount == 1:
142 142 def f(arg):
143 143 if arg not in cache:
144 144 if len(cache) > 20:
145 145 del cache[order.pop(0)]
146 146 cache[arg] = func(arg)
147 147 else:
148 148 order.remove(arg)
149 149 order.append(arg)
150 150 return cache[arg]
151 151 else:
152 152 def f(*args):
153 153 if args not in cache:
154 154 if len(cache) > 20:
155 155 del cache[order.pop(0)]
156 156 cache[args] = func(*args)
157 157 else:
158 158 order.remove(args)
159 159 order.append(args)
160 160 return cache[args]
161 161
162 162 return f
163 163
164 164 class propertycache(object):
165 165 def __init__(self, func):
166 166 self.func = func
167 167 self.name = func.__name__
168 168 def __get__(self, obj, type=None):
169 169 result = self.func(obj)
170 170 setattr(obj, self.name, result)
171 171 return result
172 172
173 173 def pipefilter(s, cmd):
174 174 '''filter string S through command CMD, returning its output'''
175 175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 177 pout, perr = p.communicate(s)
178 178 return pout
179 179
180 180 def tempfilter(s, cmd):
181 181 '''filter string S through a pair of temporary files with CMD.
182 182 CMD is used as a template to create the real command to be run,
183 183 with the strings INFILE and OUTFILE replaced by the real names of
184 184 the temporary files generated.'''
185 185 inname, outname = None, None
186 186 try:
187 187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 188 fp = os.fdopen(infd, 'wb')
189 189 fp.write(s)
190 190 fp.close()
191 191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 192 os.close(outfd)
193 193 cmd = cmd.replace('INFILE', inname)
194 194 cmd = cmd.replace('OUTFILE', outname)
195 195 code = os.system(cmd)
196 196 if sys.platform == 'OpenVMS' and code & 1:
197 197 code = 0
198 198 if code:
199 199 raise Abort(_("command '%s' failed: %s") %
200 200 (cmd, explain_exit(code)))
201 201 return open(outname, 'rb').read()
202 202 finally:
203 203 try:
204 204 if inname:
205 205 os.unlink(inname)
206 206 except:
207 207 pass
208 208 try:
209 209 if outname:
210 210 os.unlink(outname)
211 211 except:
212 212 pass
213 213
214 214 filtertable = {
215 215 'tempfile:': tempfilter,
216 216 'pipe:': pipefilter,
217 217 }
218 218
219 219 def filter(s, cmd):
220 220 "filter a string through a command that transforms its input to its output"
221 221 for name, fn in filtertable.iteritems():
222 222 if cmd.startswith(name):
223 223 return fn(s, cmd[len(name):].lstrip())
224 224 return pipefilter(s, cmd)
225 225
226 226 def binary(s):
227 227 """return true if a string is binary data"""
228 228 return bool(s and '\0' in s)
229 229
230 230 def increasingchunks(source, min=1024, max=65536):
231 231 '''return no less than min bytes per chunk while data remains,
232 232 doubling min after each chunk until it reaches max'''
233 233 def log2(x):
234 234 if not x:
235 235 return 0
236 236 i = 0
237 237 while x:
238 238 x >>= 1
239 239 i += 1
240 240 return i - 1
241 241
242 242 buf = []
243 243 blen = 0
244 244 for chunk in source:
245 245 buf.append(chunk)
246 246 blen += len(chunk)
247 247 if blen >= min:
248 248 if min < max:
249 249 min = min << 1
250 250 nmin = 1 << log2(blen)
251 251 if nmin > min:
252 252 min = nmin
253 253 if min > max:
254 254 min = max
255 255 yield ''.join(buf)
256 256 blen = 0
257 257 buf = []
258 258 if buf:
259 259 yield ''.join(buf)
260 260
261 261 Abort = error.Abort
262 262
263 263 def always(fn):
264 264 return True
265 265
266 266 def never(fn):
267 267 return False
268 268
269 269 def pathto(root, n1, n2):
270 270 '''return the relative path from one place to another.
271 271 root should use os.sep to separate directories
272 272 n1 should use os.sep to separate directories
273 273 n2 should use "/" to separate directories
274 274 returns an os.sep-separated path.
275 275
276 276 If n1 is a relative path, it's assumed it's
277 277 relative to root.
278 278 n2 should always be relative to root.
279 279 '''
280 280 if not n1:
281 281 return localpath(n2)
282 282 if os.path.isabs(n1):
283 283 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
284 284 return os.path.join(root, localpath(n2))
285 285 n2 = '/'.join((pconvert(root), n2))
286 286 a, b = splitpath(n1), n2.split('/')
287 287 a.reverse()
288 288 b.reverse()
289 289 while a and b and a[-1] == b[-1]:
290 290 a.pop()
291 291 b.pop()
292 292 b.reverse()
293 293 return os.sep.join((['..'] * len(a)) + b) or '.'
294 294
295 295 def canonpath(root, cwd, myname, auditor=None):
296 296 """return the canonical path of myname, given cwd and root"""
297 297 if endswithsep(root):
298 298 rootsep = root
299 299 else:
300 300 rootsep = root + os.sep
301 301 name = myname
302 302 if not os.path.isabs(name):
303 303 name = os.path.join(root, cwd, name)
304 304 name = os.path.normpath(name)
305 305 if auditor is None:
306 306 auditor = path_auditor(root)
307 307 if name != rootsep and name.startswith(rootsep):
308 308 name = name[len(rootsep):]
309 309 auditor(name)
310 310 return pconvert(name)
311 311 elif name == root:
312 312 return ''
313 313 else:
314 314 # Determine whether `name' is in the hierarchy at or beneath `root',
315 315 # by iterating name=dirname(name) until that causes no change (can't
316 316 # check name == '/', because that doesn't work on windows). For each
317 317 # `name', compare dev/inode numbers. If they match, the list `rel'
318 318 # holds the reversed list of components making up the relative file
319 319 # name we want.
320 320 root_st = os.stat(root)
321 321 rel = []
322 322 while True:
323 323 try:
324 324 name_st = os.stat(name)
325 325 except OSError:
326 326 break
327 327 if samestat(name_st, root_st):
328 328 if not rel:
329 329 # name was actually the same as root (maybe a symlink)
330 330 return ''
331 331 rel.reverse()
332 332 name = os.path.join(*rel)
333 333 auditor(name)
334 334 return pconvert(name)
335 335 dirname, basename = os.path.split(name)
336 336 rel.append(basename)
337 337 if dirname == name:
338 338 break
339 339 name = dirname
340 340
341 341 raise Abort('%s not under root' % myname)
342 342
343 343 _hgexecutable = None
344 344
345 345 def main_is_frozen():
346 346 """return True if we are a frozen executable.
347 347
348 348 The code supports py2exe (most common, Windows only) and tools/freeze
349 349 (portable, not much used).
350 350 """
351 351 return (hasattr(sys, "frozen") or # new py2exe
352 352 hasattr(sys, "importers") or # old py2exe
353 353 imp.is_frozen("__main__")) # tools/freeze
354 354
355 355 def hgexecutable():
356 356 """return location of the 'hg' executable.
357 357
358 358 Defaults to $HG or 'hg' in the search path.
359 359 """
360 360 if _hgexecutable is None:
361 361 hg = os.environ.get('HG')
362 362 if hg:
363 363 set_hgexecutable(hg)
364 364 elif main_is_frozen():
365 365 set_hgexecutable(sys.executable)
366 366 else:
367 367 exe = find_exe('hg') or os.path.basename(sys.argv[0])
368 368 set_hgexecutable(exe)
369 369 return _hgexecutable
370 370
371 371 def set_hgexecutable(path):
372 372 """set location of the 'hg' executable"""
373 373 global _hgexecutable
374 374 _hgexecutable = path
375 375
376 376 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
377 377 '''enhanced shell command execution.
378 378 run with environment maybe modified, maybe in different dir.
379 379
380 380 if command fails and onerr is None, return status. if ui object,
381 381 print error message and return status, else raise onerr object as
382 382 exception.
383 383
384 384 if out is specified, it is assumed to be a file-like object that has a
385 385 write() method. stdout and stderr will be redirected to out.'''
386 386 def py2shell(val):
387 387 'convert python object into string that is useful to shell'
388 388 if val is None or val is False:
389 389 return '0'
390 390 if val is True:
391 391 return '1'
392 392 return str(val)
393 393 origcmd = cmd
394 394 if os.name == 'nt':
395 395 cmd = '"%s"' % cmd
396 396 env = dict(os.environ)
397 397 env.update((k, py2shell(v)) for k, v in environ.iteritems())
398 398 env['HG'] = hgexecutable()
399 399 if out is None:
400 400 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
401 401 env=env, cwd=cwd)
402 402 else:
403 403 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
404 404 env=env, cwd=cwd, stdout=subprocess.PIPE,
405 405 stderr=subprocess.STDOUT)
406 406 for line in proc.stdout:
407 407 out.write(line)
408 408 proc.wait()
409 409 rc = proc.returncode
410 410 if sys.platform == 'OpenVMS' and rc & 1:
411 411 rc = 0
412 412 if rc and onerr:
413 413 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
414 414 explain_exit(rc)[0])
415 415 if errprefix:
416 416 errmsg = '%s: %s' % (errprefix, errmsg)
417 417 try:
418 418 onerr.warn(errmsg + '\n')
419 419 except AttributeError:
420 420 raise onerr(errmsg)
421 421 return rc
422 422
423 423 def checksignature(func):
424 424 '''wrap a function with code to check for calling errors'''
425 425 def check(*args, **kwargs):
426 426 try:
427 427 return func(*args, **kwargs)
428 428 except TypeError:
429 429 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
430 430 raise error.SignatureError
431 431 raise
432 432
433 433 return check
434 434
435 435 def unlink(f):
436 436 """unlink and remove the directory if it is empty"""
437 437 os.unlink(f)
438 438 # try removing directories that might now be empty
439 439 try:
440 440 os.removedirs(os.path.dirname(f))
441 441 except OSError:
442 442 pass
443 443
444 444 def copyfile(src, dest):
445 445 "copy a file, preserving mode and atime/mtime"
446 446 if os.path.islink(src):
447 447 try:
448 448 os.unlink(dest)
449 449 except:
450 450 pass
451 451 os.symlink(os.readlink(src), dest)
452 452 else:
453 453 try:
454 454 shutil.copyfile(src, dest)
455 455 shutil.copystat(src, dest)
456 456 except shutil.Error, inst:
457 457 raise Abort(str(inst))
458 458
459 459 def copyfiles(src, dst, hardlink=None):
460 460 """Copy a directory tree using hardlinks if possible"""
461 461
462 462 if hardlink is None:
463 463 hardlink = (os.stat(src).st_dev ==
464 464 os.stat(os.path.dirname(dst)).st_dev)
465 465
466 466 num = 0
467 467 if os.path.isdir(src):
468 468 os.mkdir(dst)
469 469 for name, kind in osutil.listdir(src):
470 470 srcname = os.path.join(src, name)
471 471 dstname = os.path.join(dst, name)
472 472 hardlink, n = copyfiles(srcname, dstname, hardlink)
473 473 num += n
474 474 else:
475 475 if hardlink:
476 476 try:
477 477 os_link(src, dst)
478 478 except (IOError, OSError):
479 479 hardlink = False
480 480 shutil.copy(src, dst)
481 481 else:
482 482 shutil.copy(src, dst)
483 483 num += 1
484 484
485 485 return hardlink, num
486 486
487 487 class path_auditor(object):
488 488 '''ensure that a filesystem path contains no banned components.
489 489 the following properties of a path are checked:
490 490
491 491 - under top-level .hg
492 492 - starts at the root of a windows drive
493 493 - contains ".."
494 494 - traverses a symlink (e.g. a/symlink_here/b)
495 495 - inside a nested repository (a callback can be used to approve
496 496 some nested repositories, e.g., subrepositories)
497 497 '''
498 498
499 499 def __init__(self, root, callback=None):
500 500 self.audited = set()
501 501 self.auditeddir = set()
502 502 self.root = root
503 503 self.callback = callback
504 504
505 505 def __call__(self, path):
506 506 if path in self.audited:
507 507 return
508 508 normpath = os.path.normcase(path)
509 509 parts = splitpath(normpath)
510 510 if (os.path.splitdrive(path)[0]
511 511 or parts[0].lower() in ('.hg', '.hg.', '')
512 512 or os.pardir in parts):
513 513 raise Abort(_("path contains illegal component: %s") % path)
514 514 if '.hg' in path.lower():
515 515 lparts = [p.lower() for p in parts]
516 516 for p in '.hg', '.hg.':
517 517 if p in lparts[1:]:
518 518 pos = lparts.index(p)
519 519 base = os.path.join(*parts[:pos])
520 520 raise Abort(_('path %r is inside repo %r') % (path, base))
521 521 def check(prefix):
522 522 curpath = os.path.join(self.root, prefix)
523 523 try:
524 524 st = os.lstat(curpath)
525 525 except OSError, err:
526 526 # EINVAL can be raised as invalid path syntax under win32.
527 527 # They must be ignored for patterns can be checked too.
528 528 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
529 529 raise
530 530 else:
531 531 if stat.S_ISLNK(st.st_mode):
532 532 raise Abort(_('path %r traverses symbolic link %r') %
533 533 (path, prefix))
534 534 elif (stat.S_ISDIR(st.st_mode) and
535 535 os.path.isdir(os.path.join(curpath, '.hg'))):
536 536 if not self.callback or not self.callback(curpath):
537 537 raise Abort(_('path %r is inside repo %r') %
538 538 (path, prefix))
539 539 parts.pop()
540 540 prefixes = []
541 541 while parts:
542 542 prefix = os.sep.join(parts)
543 543 if prefix in self.auditeddir:
544 544 break
545 545 check(prefix)
546 546 prefixes.append(prefix)
547 547 parts.pop()
548 548
549 549 self.audited.add(path)
550 550 # only add prefixes to the cache after checking everything: we don't
551 551 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
552 552 self.auditeddir.update(prefixes)
553 553
554 554 def nlinks(pathname):
555 555 """Return number of hardlinks for the given file."""
556 556 return os.lstat(pathname).st_nlink
557 557
558 558 if hasattr(os, 'link'):
559 559 os_link = os.link
560 560 else:
561 561 def os_link(src, dst):
562 562 raise OSError(0, _("Hardlinks not supported"))
563 563
564 564 def lookup_reg(key, name=None, scope=None):
565 565 return None
566 566
567 567 def hidewindow():
568 568 """Hide current shell window.
569 569
570 570 Used to hide the window opened when starting asynchronous
571 571 child process under Windows, unneeded on other systems.
572 572 """
573 573 pass
574 574
575 575 if os.name == 'nt':
576 576 from windows import *
577 577 else:
578 578 from posix import *
579 579
580 580 def makelock(info, pathname):
581 581 try:
582 582 return os.symlink(info, pathname)
583 583 except OSError, why:
584 584 if why.errno == errno.EEXIST:
585 585 raise
586 586 except AttributeError: # no symlink in os
587 587 pass
588 588
589 589 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
590 590 os.write(ld, info)
591 591 os.close(ld)
592 592
593 593 def readlock(pathname):
594 594 try:
595 595 return os.readlink(pathname)
596 596 except OSError, why:
597 597 if why.errno not in (errno.EINVAL, errno.ENOSYS):
598 598 raise
599 599 except AttributeError: # no symlink in os
600 600 pass
601 601 return posixfile(pathname).read()
602 602
603 603 def fstat(fp):
604 604 '''stat file object that may not have fileno method.'''
605 605 try:
606 606 return os.fstat(fp.fileno())
607 607 except AttributeError:
608 608 return os.stat(fp.name)
609 609
610 610 # File system features
611 611
612 612 def checkcase(path):
613 613 """
614 614 Check whether the given path is on a case-sensitive filesystem
615 615
616 616 Requires a path (like /foo/.hg) ending with a foldable final
617 617 directory component.
618 618 """
619 619 s1 = os.stat(path)
620 620 d, b = os.path.split(path)
621 621 p2 = os.path.join(d, b.upper())
622 622 if path == p2:
623 623 p2 = os.path.join(d, b.lower())
624 624 try:
625 625 s2 = os.stat(p2)
626 626 if s2 == s1:
627 627 return False
628 628 return True
629 629 except:
630 630 return True
631 631
632 632 _fspathcache = {}
633 633 def fspath(name, root):
634 634 '''Get name in the case stored in the filesystem
635 635
636 636 The name is either relative to root, or it is an absolute path starting
637 637 with root. Note that this function is unnecessary, and should not be
638 638 called, for case-sensitive filesystems (simply because it's expensive).
639 639 '''
640 640 # If name is absolute, make it relative
641 641 if name.lower().startswith(root.lower()):
642 642 l = len(root)
643 643 if name[l] == os.sep or name[l] == os.altsep:
644 644 l = l + 1
645 645 name = name[l:]
646 646
647 647 if not os.path.lexists(os.path.join(root, name)):
648 648 return None
649 649
650 650 seps = os.sep
651 651 if os.altsep:
652 652 seps = seps + os.altsep
653 653 # Protect backslashes. This gets silly very quickly.
654 654 seps.replace('\\','\\\\')
655 655 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
656 656 dir = os.path.normcase(os.path.normpath(root))
657 657 result = []
658 658 for part, sep in pattern.findall(name):
659 659 if sep:
660 660 result.append(sep)
661 661 continue
662 662
663 663 if dir not in _fspathcache:
664 664 _fspathcache[dir] = os.listdir(dir)
665 665 contents = _fspathcache[dir]
666 666
667 667 lpart = part.lower()
668 668 lenp = len(part)
669 669 for n in contents:
670 670 if lenp == len(n) and n.lower() == lpart:
671 671 result.append(n)
672 672 break
673 673 else:
674 674 # Cannot happen, as the file exists!
675 675 result.append(part)
676 676 dir = os.path.join(dir, lpart)
677 677
678 678 return ''.join(result)
679 679
680 680 def checkexec(path):
681 681 """
682 682 Check whether the given path is on a filesystem with UNIX-like exec flags
683 683
684 684 Requires a directory (like /foo/.hg)
685 685 """
686 686
687 687 # VFAT on some Linux versions can flip mode but it doesn't persist
688 688 # a FS remount. Frequently we can detect it if files are created
689 689 # with exec bit on.
690 690
691 691 try:
692 692 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
693 693 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
694 694 try:
695 695 os.close(fh)
696 696 m = os.stat(fn).st_mode & 0777
697 697 new_file_has_exec = m & EXECFLAGS
698 698 os.chmod(fn, m ^ EXECFLAGS)
699 699 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
700 700 finally:
701 701 os.unlink(fn)
702 702 except (IOError, OSError):
703 703 # we don't care, the user probably won't be able to commit anyway
704 704 return False
705 705 return not (new_file_has_exec or exec_flags_cannot_flip)
706 706
707 707 def checklink(path):
708 708 """check whether the given path is on a symlink-capable filesystem"""
709 709 # mktemp is not racy because symlink creation will fail if the
710 710 # file already exists
711 711 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
712 712 try:
713 713 os.symlink(".", name)
714 714 os.unlink(name)
715 715 return True
716 716 except (OSError, AttributeError):
717 717 return False
718 718
719 719 def endswithsep(path):
720 720 '''Check path ends with os.sep or os.altsep.'''
721 721 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
722 722
723 723 def splitpath(path):
724 724 '''Split path by os.sep.
725 725 Note that this function does not use os.altsep because this is
726 726 an alternative of simple "xxx.split(os.sep)".
727 727 It is recommended to use os.path.normpath() before using this
728 728 function if need.'''
729 729 return path.split(os.sep)
730 730
731 731 def gui():
732 732 '''Are we running in a GUI?'''
733 733 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
734 734
735 735 def mktempcopy(name, emptyok=False, createmode=None):
736 736 """Create a temporary file with the same contents from name
737 737
738 738 The permission bits are copied from the original file.
739 739
740 740 If the temporary file is going to be truncated immediately, you
741 741 can use emptyok=True as an optimization.
742 742
743 743 Returns the name of the temporary file.
744 744 """
745 745 d, fn = os.path.split(name)
746 746 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
747 747 os.close(fd)
748 748 # Temporary files are created with mode 0600, which is usually not
749 749 # what we want. If the original file already exists, just copy
750 750 # its mode. Otherwise, manually obey umask.
751 751 try:
752 752 st_mode = os.lstat(name).st_mode & 0777
753 753 except OSError, inst:
754 754 if inst.errno != errno.ENOENT:
755 755 raise
756 756 st_mode = createmode
757 757 if st_mode is None:
758 758 st_mode = ~umask
759 759 st_mode &= 0666
760 760 os.chmod(temp, st_mode)
761 761 if emptyok:
762 762 return temp
763 763 try:
764 764 try:
765 765 ifp = posixfile(name, "rb")
766 766 except IOError, inst:
767 767 if inst.errno == errno.ENOENT:
768 768 return temp
769 769 if not getattr(inst, 'filename', None):
770 770 inst.filename = name
771 771 raise
772 772 ofp = posixfile(temp, "wb")
773 773 for chunk in filechunkiter(ifp):
774 774 ofp.write(chunk)
775 775 ifp.close()
776 776 ofp.close()
777 777 except:
778 778 try: os.unlink(temp)
779 779 except: pass
780 780 raise
781 781 return temp
782 782
783 783 class atomictempfile(object):
784 784 """file-like object that atomically updates a file
785 785
786 786 All writes will be redirected to a temporary copy of the original
787 787 file. When rename is called, the copy is renamed to the original
788 788 name, making the changes visible.
789 789 """
790 790 def __init__(self, name, mode='w+b', createmode=None):
791 791 self.__name = name
792 792 self._fp = None
793 793 self.temp = mktempcopy(name, emptyok=('w' in mode),
794 794 createmode=createmode)
795 795 self._fp = posixfile(self.temp, mode)
796 796
797 797 def __getattr__(self, name):
798 798 return getattr(self._fp, name)
799 799
800 800 def rename(self):
801 801 if not self._fp.closed:
802 802 self._fp.close()
803 803 rename(self.temp, localpath(self.__name))
804 804
805 805 def __del__(self):
806 806 if not self._fp:
807 807 return
808 808 if not self._fp.closed:
809 809 try:
810 810 os.unlink(self.temp)
811 811 except: pass
812 812 self._fp.close()
813 813
814 814 def makedirs(name, mode=None):
815 815 """recursive directory creation with parent mode inheritance"""
816 816 try:
817 817 os.mkdir(name)
818 818 if mode is not None:
819 819 os.chmod(name, mode)
820 820 return
821 821 except OSError, err:
822 822 if err.errno == errno.EEXIST:
823 823 return
824 824 if err.errno != errno.ENOENT:
825 825 raise
826 826 parent = os.path.abspath(os.path.dirname(name))
827 827 makedirs(parent, mode)
828 828 makedirs(name, mode)
829 829
830 830 class opener(object):
831 831 """Open files relative to a base directory
832 832
833 833 This class is used to hide the details of COW semantics and
834 834 remote file access from higher level code.
835 835 """
836 836 def __init__(self, base, audit=True):
837 837 self.base = base
838 838 if audit:
839 839 self.auditor = path_auditor(base)
840 840 else:
841 841 self.auditor = always
842 842 self.createmode = None
843 843
844 844 @propertycache
845 845 def _can_symlink(self):
846 846 return checklink(self.base)
847 847
848 848 def _fixfilemode(self, name):
849 849 if self.createmode is None:
850 850 return
851 851 os.chmod(name, self.createmode & 0666)
852 852
853 853 def __call__(self, path, mode="r", text=False, atomictemp=False):
854 854 self.auditor(path)
855 855 f = os.path.join(self.base, path)
856 856
857 857 if not text and "b" not in mode:
858 858 mode += "b" # for that other OS
859 859
860 860 nlink = -1
861 861 if mode not in ("r", "rb"):
862 862 try:
863 863 nlink = nlinks(f)
864 864 except OSError:
865 865 nlink = 0
866 866 d = os.path.dirname(f)
867 867 if not os.path.isdir(d):
868 868 makedirs(d, self.createmode)
869 869 if atomictemp:
870 870 return atomictempfile(f, mode, self.createmode)
871 871 if nlink > 1:
872 872 rename(mktempcopy(f), f)
873 873 fp = posixfile(f, mode)
874 874 if nlink == 0:
875 875 self._fixfilemode(f)
876 876 return fp
877 877
878 878 def symlink(self, src, dst):
879 879 self.auditor(dst)
880 880 linkname = os.path.join(self.base, dst)
881 881 try:
882 882 os.unlink(linkname)
883 883 except OSError:
884 884 pass
885 885
886 886 dirname = os.path.dirname(linkname)
887 887 if not os.path.exists(dirname):
888 888 makedirs(dirname, self.createmode)
889 889
890 890 if self._can_symlink:
891 891 try:
892 892 os.symlink(src, linkname)
893 893 except OSError, err:
894 894 raise OSError(err.errno, _('could not symlink to %r: %s') %
895 895 (src, err.strerror), linkname)
896 896 else:
897 897 f = self(dst, "w")
898 898 f.write(src)
899 899 f.close()
900 900 self._fixfilemode(dst)
901 901
902 902 class chunkbuffer(object):
903 903 """Allow arbitrary sized chunks of data to be efficiently read from an
904 904 iterator over chunks of arbitrary size."""
905 905
906 906 def __init__(self, in_iter):
907 907 """in_iter is the iterator that's iterating over the input chunks.
908 908 targetsize is how big a buffer to try to maintain."""
909 909 def splitbig(chunks):
910 910 for chunk in chunks:
911 911 if len(chunk) > 2**20:
912 912 pos = 0
913 913 while pos < len(chunk):
914 914 end = pos + 2 ** 18
915 915 yield chunk[pos:end]
916 916 pos = end
917 917 else:
918 918 yield chunk
919 919 self.iter = splitbig(in_iter)
920 920 self._queue = []
921 921
922 922 def read(self, l):
923 923 """Read L bytes of data from the iterator of chunks of data.
924 924 Returns less than L bytes if the iterator runs dry."""
925 925 left = l
926 926 buf = ''
927 927 queue = self._queue
928 928 while left > 0:
929 929 # refill the queue
930 930 if not queue:
931 931 target = 2**18
932 932 for chunk in self.iter:
933 933 queue.append(chunk)
934 934 target -= len(chunk)
935 935 if target <= 0:
936 936 break
937 937 if not queue:
938 938 break
939 939
940 940 chunk = queue.pop(0)
941 941 left -= len(chunk)
942 942 if left < 0:
943 943 queue.insert(0, chunk[left:])
944 944 buf += chunk[:left]
945 945 else:
946 946 buf += chunk
947 947
948 948 return buf
949 949
950 950 def filechunkiter(f, size=65536, limit=None):
951 951 """Create a generator that produces the data in the file size
952 952 (default 65536) bytes at a time, up to optional limit (default is
953 953 to read all data). Chunks may be less than size bytes if the
954 954 chunk is the last chunk in the file, or the file is a socket or
955 955 some other type of file that sometimes reads less data than is
956 956 requested."""
957 957 assert size >= 0
958 958 assert limit is None or limit >= 0
959 959 while True:
960 960 if limit is None:
961 961 nbytes = size
962 962 else:
963 963 nbytes = min(limit, size)
964 964 s = nbytes and f.read(nbytes)
965 965 if not s:
966 966 break
967 967 if limit:
968 968 limit -= len(s)
969 969 yield s
970 970
971 971 def makedate():
972 972 lt = time.localtime()
973 973 if lt[8] == 1 and time.daylight:
974 974 tz = time.altzone
975 975 else:
976 976 tz = time.timezone
977 977 return time.mktime(lt), tz
978 978
979 979 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
980 980 """represent a (unixtime, offset) tuple as a localized time.
981 981 unixtime is seconds since the epoch, and offset is the time zone's
982 982 number of seconds away from UTC. if timezone is false, do not
983 983 append time zone to string."""
984 984 t, tz = date or makedate()
985 985 if "%1" in format or "%2" in format:
986 986 sign = (tz > 0) and "-" or "+"
987 987 minutes = abs(tz) // 60
988 988 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
989 989 format = format.replace("%2", "%02d" % (minutes % 60))
990 990 s = time.strftime(format, time.gmtime(float(t) - tz))
991 991 return s
992 992
993 993 def shortdate(date=None):
994 994 """turn (timestamp, tzoff) tuple into iso 8631 date."""
995 995 return datestr(date, format='%Y-%m-%d')
996 996
997 997 def strdate(string, format, defaults=[]):
998 998 """parse a localized time string and return a (unixtime, offset) tuple.
999 999 if the string cannot be parsed, ValueError is raised."""
1000 1000 def timezone(string):
1001 1001 tz = string.split()[-1]
1002 1002 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1003 1003 sign = (tz[0] == "+") and 1 or -1
1004 1004 hours = int(tz[1:3])
1005 1005 minutes = int(tz[3:5])
1006 1006 return -sign * (hours * 60 + minutes) * 60
1007 1007 if tz == "GMT" or tz == "UTC":
1008 1008 return 0
1009 1009 return None
1010 1010
1011 1011 # NOTE: unixtime = localunixtime + offset
1012 1012 offset, date = timezone(string), string
1013 1013 if offset != None:
1014 1014 date = " ".join(string.split()[:-1])
1015 1015
1016 1016 # add missing elements from defaults
1017 1017 for part in defaults:
1018 1018 found = [True for p in part if ("%"+p) in format]
1019 1019 if not found:
1020 1020 date += "@" + defaults[part]
1021 1021 format += "@%" + part[0]
1022 1022
1023 1023 timetuple = time.strptime(date, format)
1024 1024 localunixtime = int(calendar.timegm(timetuple))
1025 1025 if offset is None:
1026 1026 # local timezone
1027 1027 unixtime = int(time.mktime(timetuple))
1028 1028 offset = unixtime - localunixtime
1029 1029 else:
1030 1030 unixtime = localunixtime + offset
1031 1031 return unixtime, offset
1032 1032
1033 1033 def parsedate(date, formats=None, defaults=None):
1034 1034 """parse a localized date/time string and return a (unixtime, offset) tuple.
1035 1035
1036 1036 The date may be a "unixtime offset" string or in one of the specified
1037 1037 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1038 1038 """
1039 1039 if not date:
1040 1040 return 0, 0
1041 1041 if isinstance(date, tuple) and len(date) == 2:
1042 1042 return date
1043 1043 if not formats:
1044 1044 formats = defaultdateformats
1045 1045 date = date.strip()
1046 1046 try:
1047 1047 when, offset = map(int, date.split(' '))
1048 1048 except ValueError:
1049 1049 # fill out defaults
1050 1050 if not defaults:
1051 1051 defaults = {}
1052 1052 now = makedate()
1053 1053 for part in "d mb yY HI M S".split():
1054 1054 if part not in defaults:
1055 1055 if part[0] in "HMS":
1056 1056 defaults[part] = "00"
1057 1057 else:
1058 1058 defaults[part] = datestr(now, "%" + part[0])
1059 1059
1060 1060 for format in formats:
1061 1061 try:
1062 1062 when, offset = strdate(date, format, defaults)
1063 1063 except (ValueError, OverflowError):
1064 1064 pass
1065 1065 else:
1066 1066 break
1067 1067 else:
1068 1068 raise Abort(_('invalid date: %r') % date)
1069 1069 # validate explicit (probably user-specified) date and
1070 1070 # time zone offset. values must fit in signed 32 bits for
1071 1071 # current 32-bit linux runtimes. timezones go from UTC-12
1072 1072 # to UTC+14
1073 1073 if abs(when) > 0x7fffffff:
1074 1074 raise Abort(_('date exceeds 32 bits: %d') % when)
1075 1075 if offset < -50400 or offset > 43200:
1076 1076 raise Abort(_('impossible time zone offset: %d') % offset)
1077 1077 return when, offset
1078 1078
1079 1079 def matchdate(date):
1080 1080 """Return a function that matches a given date match specifier
1081 1081
1082 1082 Formats include:
1083 1083
1084 1084 '{date}' match a given date to the accuracy provided
1085 1085
1086 1086 '<{date}' on or before a given date
1087 1087
1088 1088 '>{date}' on or after a given date
1089 1089
1090 1090 """
1091 1091
1092 1092 def lower(date):
1093 1093 d = dict(mb="1", d="1")
1094 1094 return parsedate(date, extendeddateformats, d)[0]
1095 1095
1096 1096 def upper(date):
1097 1097 d = dict(mb="12", HI="23", M="59", S="59")
1098 1098 for days in "31 30 29".split():
1099 1099 try:
1100 1100 d["d"] = days
1101 1101 return parsedate(date, extendeddateformats, d)[0]
1102 1102 except:
1103 1103 pass
1104 1104 d["d"] = "28"
1105 1105 return parsedate(date, extendeddateformats, d)[0]
1106 1106
1107 1107 date = date.strip()
1108 1108 if date[0] == "<":
1109 1109 when = upper(date[1:])
1110 1110 return lambda x: x <= when
1111 1111 elif date[0] == ">":
1112 1112 when = lower(date[1:])
1113 1113 return lambda x: x >= when
1114 1114 elif date[0] == "-":
1115 1115 try:
1116 1116 days = int(date[1:])
1117 1117 except ValueError:
1118 1118 raise Abort(_("invalid day spec: %s") % date[1:])
1119 1119 when = makedate()[0] - days * 3600 * 24
1120 1120 return lambda x: x >= when
1121 1121 elif " to " in date:
1122 1122 a, b = date.split(" to ")
1123 1123 start, stop = lower(a), upper(b)
1124 1124 return lambda x: x >= start and x <= stop
1125 1125 else:
1126 1126 start, stop = lower(date), upper(date)
1127 1127 return lambda x: x >= start and x <= stop
1128 1128
1129 1129 def shortuser(user):
1130 1130 """Return a short representation of a user name or email address."""
1131 1131 f = user.find('@')
1132 1132 if f >= 0:
1133 1133 user = user[:f]
1134 1134 f = user.find('<')
1135 1135 if f >= 0:
1136 1136 user = user[f + 1:]
1137 1137 f = user.find(' ')
1138 1138 if f >= 0:
1139 1139 user = user[:f]
1140 1140 f = user.find('.')
1141 1141 if f >= 0:
1142 1142 user = user[:f]
1143 1143 return user
1144 1144
1145 1145 def email(author):
1146 1146 '''get email of author.'''
1147 1147 r = author.find('>')
1148 1148 if r == -1:
1149 1149 r = None
1150 1150 return author[author.find('<') + 1:r]
1151 1151
1152 1152 def ellipsis(text, maxlength=400):
1153 1153 """Trim string to at most maxlength (default: 400) characters."""
1154 1154 if len(text) <= maxlength:
1155 1155 return text
1156 1156 else:
1157 1157 return "%s..." % (text[:maxlength - 3])
1158 1158
1159 1159 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1160 1160 '''yield every hg repository under path, recursively.'''
1161 1161 def errhandler(err):
1162 1162 if err.filename == path:
1163 1163 raise err
1164 1164 if followsym and hasattr(os.path, 'samestat'):
1165 1165 def _add_dir_if_not_there(dirlst, dirname):
1166 1166 match = False
1167 1167 samestat = os.path.samestat
1168 1168 dirstat = os.stat(dirname)
1169 1169 for lstdirstat in dirlst:
1170 1170 if samestat(dirstat, lstdirstat):
1171 1171 match = True
1172 1172 break
1173 1173 if not match:
1174 1174 dirlst.append(dirstat)
1175 1175 return not match
1176 1176 else:
1177 1177 followsym = False
1178 1178
1179 1179 if (seen_dirs is None) and followsym:
1180 1180 seen_dirs = []
1181 1181 _add_dir_if_not_there(seen_dirs, path)
1182 1182 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1183 1183 dirs.sort()
1184 1184 if '.hg' in dirs:
1185 1185 yield root # found a repository
1186 1186 qroot = os.path.join(root, '.hg', 'patches')
1187 1187 if os.path.isdir(os.path.join(qroot, '.hg')):
1188 1188 yield qroot # we have a patch queue repo here
1189 1189 if recurse:
1190 1190 # avoid recursing inside the .hg directory
1191 1191 dirs.remove('.hg')
1192 1192 else:
1193 1193 dirs[:] = [] # don't descend further
1194 1194 elif followsym:
1195 1195 newdirs = []
1196 1196 for d in dirs:
1197 1197 fname = os.path.join(root, d)
1198 1198 if _add_dir_if_not_there(seen_dirs, fname):
1199 1199 if os.path.islink(fname):
1200 1200 for hgname in walkrepos(fname, True, seen_dirs):
1201 1201 yield hgname
1202 1202 else:
1203 1203 newdirs.append(d)
1204 1204 dirs[:] = newdirs
1205 1205
1206 1206 _rcpath = None
1207 1207
1208 1208 def os_rcpath():
1209 1209 '''return default os-specific hgrc search path'''
1210 1210 path = system_rcpath()
1211 1211 path.extend(user_rcpath())
1212 1212 path = [os.path.normpath(f) for f in path]
1213 1213 return path
1214 1214
1215 1215 def rcpath():
1216 1216 '''return hgrc search path. if env var HGRCPATH is set, use it.
1217 1217 for each item in path, if directory, use files ending in .rc,
1218 1218 else use item.
1219 1219 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1220 1220 if no HGRCPATH, use default os-specific path.'''
1221 1221 global _rcpath
1222 1222 if _rcpath is None:
1223 1223 if 'HGRCPATH' in os.environ:
1224 1224 _rcpath = []
1225 1225 for p in os.environ['HGRCPATH'].split(os.pathsep):
1226 1226 if not p:
1227 1227 continue
1228 1228 p = expandpath(p)
1229 1229 if os.path.isdir(p):
1230 1230 for f, kind in osutil.listdir(p):
1231 1231 if f.endswith('.rc'):
1232 1232 _rcpath.append(os.path.join(p, f))
1233 1233 else:
1234 1234 _rcpath.append(p)
1235 1235 else:
1236 1236 _rcpath = os_rcpath()
1237 1237 return _rcpath
1238 1238
1239 1239 def bytecount(nbytes):
1240 1240 '''return byte count formatted as readable string, with units'''
1241 1241
1242 1242 units = (
1243 1243 (100, 1 << 30, _('%.0f GB')),
1244 1244 (10, 1 << 30, _('%.1f GB')),
1245 1245 (1, 1 << 30, _('%.2f GB')),
1246 1246 (100, 1 << 20, _('%.0f MB')),
1247 1247 (10, 1 << 20, _('%.1f MB')),
1248 1248 (1, 1 << 20, _('%.2f MB')),
1249 1249 (100, 1 << 10, _('%.0f KB')),
1250 1250 (10, 1 << 10, _('%.1f KB')),
1251 1251 (1, 1 << 10, _('%.2f KB')),
1252 1252 (1, 1, _('%.0f bytes')),
1253 1253 )
1254 1254
1255 1255 for multiplier, divisor, format in units:
1256 1256 if nbytes >= divisor * multiplier:
1257 1257 return format % (nbytes / float(divisor))
1258 1258 return units[-1][2] % nbytes
1259 1259
1260 1260 def drop_scheme(scheme, path):
1261 1261 sc = scheme + ':'
1262 1262 if path.startswith(sc):
1263 1263 path = path[len(sc):]
1264 1264 if path.startswith('//'):
1265 1265 if scheme == 'file':
1266 1266 i = path.find('/', 2)
1267 1267 if i == -1:
1268 1268 return ''
1269 1269 # On Windows, absolute paths are rooted at the current drive
1270 1270 # root. On POSIX they are rooted at the file system root.
1271 1271 if os.name == 'nt':
1272 1272 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1273 1273 path = os.path.join(droot, path[i + 1:])
1274 1274 else:
1275 1275 path = path[i:]
1276 1276 else:
1277 1277 path = path[2:]
1278 1278 return path
1279 1279
1280 1280 def uirepr(s):
1281 1281 # Avoid double backslash in Windows path repr()
1282 1282 return repr(s).replace('\\\\', '\\')
1283 1283
1284 1284 #### naming convention of below implementation follows 'textwrap' module
1285 1285
1286 1286 class MBTextWrapper(textwrap.TextWrapper):
1287 1287 def __init__(self, **kwargs):
1288 1288 textwrap.TextWrapper.__init__(self, **kwargs)
1289 1289
1290 1290 def _cutdown(self, str, space_left):
1291 1291 l = 0
1292 1292 ucstr = unicode(str, encoding.encoding)
1293 1293 w = unicodedata.east_asian_width
1294 1294 for i in xrange(len(ucstr)):
1295 1295 l += w(ucstr[i]) in 'WFA' and 2 or 1
1296 1296 if space_left < l:
1297 1297 return (ucstr[:i].encode(encoding.encoding),
1298 1298 ucstr[i:].encode(encoding.encoding))
1299 1299 return str, ''
1300 1300
1301 1301 # ----------------------------------------
1302 1302 # overriding of base class
1303 1303
1304 1304 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1305 1305 space_left = max(width - cur_len, 1)
1306 1306
1307 1307 if self.break_long_words:
1308 1308 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1309 1309 cur_line.append(cut)
1310 1310 reversed_chunks[-1] = res
1311 1311 elif not cur_line:
1312 1312 cur_line.append(reversed_chunks.pop())
1313 1313
1314 1314 #### naming convention of above implementation follows 'textwrap' module
1315 1315
1316 1316 def wrap(line, width=None, initindent='', hangindent=''):
1317 1317 if width is None:
1318 1318 width = termwidth() - 2
1319 1319 maxindent = max(len(hangindent), len(initindent))
1320 1320 if width <= maxindent:
1321 1321 # adjust for weird terminal size
1322 1322 width = max(78, maxindent + 1)
1323 1323 wrapper = MBTextWrapper(width=width,
1324 1324 initial_indent=initindent,
1325 1325 subsequent_indent=hangindent)
1326 1326 return wrapper.fill(line)
1327 1327
1328 1328 def iterlines(iterator):
1329 1329 for chunk in iterator:
1330 1330 for line in chunk.splitlines():
1331 1331 yield line
1332 1332
1333 1333 def expandpath(path):
1334 1334 return os.path.expanduser(os.path.expandvars(path))
1335 1335
1336 1336 def hgcmd():
1337 1337 """Return the command used to execute current hg
1338 1338
1339 1339 This is different from hgexecutable() because on Windows we want
1340 1340 to avoid things opening new shell windows like batch files, so we
1341 1341 get either the python call or current executable.
1342 1342 """
1343 1343 if main_is_frozen():
1344 1344 return [sys.executable]
1345 1345 return gethgcmd()
1346 1346
1347 1347 def rundetached(args, condfn):
1348 1348 """Execute the argument list in a detached process.
1349 1349
1350 1350 condfn is a callable which is called repeatedly and should return
1351 1351 True once the child process is known to have started successfully.
1352 1352 At this point, the child process PID is returned. If the child
1353 1353 process fails to start or finishes before condfn() evaluates to
1354 1354 True, return -1.
1355 1355 """
1356 1356 # Windows case is easier because the child process is either
1357 1357 # successfully starting and validating the condition or exiting
1358 1358 # on failure. We just poll on its PID. On Unix, if the child
1359 1359 # process fails to start, it will be left in a zombie state until
1360 1360 # the parent wait on it, which we cannot do since we expect a long
1361 1361 # running process on success. Instead we listen for SIGCHLD telling
1362 1362 # us our child process terminated.
1363 1363 terminated = set()
1364 1364 def handler(signum, frame):
1365 1365 terminated.add(os.wait())
1366 1366 prevhandler = None
1367 1367 if hasattr(signal, 'SIGCHLD'):
1368 1368 prevhandler = signal.signal(signal.SIGCHLD, handler)
1369 1369 try:
1370 1370 pid = spawndetached(args)
1371 1371 while not condfn():
1372 1372 if ((pid in terminated or not testpid(pid))
1373 1373 and not condfn()):
1374 1374 return -1
1375 1375 time.sleep(0.1)
1376 1376 return pid
1377 1377 finally:
1378 1378 if prevhandler is not None:
1379 1379 signal.signal(signal.SIGCHLD, prevhandler)
1380 1380
1381 1381 try:
1382 1382 any, all = any, all
1383 1383 except NameError:
1384 1384 def any(iterable):
1385 1385 for i in iterable:
1386 1386 if i:
1387 1387 return True
1388 1388 return False
1389 1389
1390 1390 def all(iterable):
1391 1391 for i in iterable:
1392 1392 if not i:
1393 1393 return False
1394 1394 return True
1395 1395
1396 def termwidth():
1397 if 'COLUMNS' in os.environ:
1398 try:
1399 return int(os.environ['COLUMNS'])
1400 except ValueError:
1401 pass
1402 return termwidth_()
1403
1404 1396 def interpolate(prefix, mapping, s, fn=None):
1405 1397 """Return the result of interpolating items in the mapping into string s.
1406 1398
1407 1399 prefix is a single character string, or a two character string with
1408 1400 a backslash as the first character if the prefix needs to be escaped in
1409 1401 a regular expression.
1410 1402
1411 1403 fn is an optional function that will be applied to the replacement text
1412 1404 just before replacement.
1413 1405 """
1414 1406 fn = fn or (lambda s: s)
1415 1407 r = re.compile(r'%s(%s)' % (prefix, '|'.join(mapping.keys())))
1416 1408 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1417 1409
1418 1410 def getport(port):
1419 1411 """Return the port for a given network service.
1420 1412
1421 1413 If port is an integer, it's returned as is. If it's a string, it's
1422 1414 looked up using socket.getservbyname(). If there's no matching
1423 1415 service, util.Abort is raised.
1424 1416 """
1425 1417 try:
1426 1418 return int(port)
1427 1419 except ValueError:
1428 1420 pass
1429 1421
1430 1422 try:
1431 1423 return socket.getservbyname(port)
1432 1424 except socket.error:
1433 1425 raise Abort(_("no port number associated with service '%s'") % port)
1434 1426
1435 1427 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1436 1428 '0': False, 'no': False, 'false': False, 'off': False,
1437 1429 'never': False}
1438 1430
1439 1431 def parsebool(s):
1440 1432 """Parse s into a boolean.
1441 1433
1442 1434 If s is not a valid boolean, returns None.
1443 1435 """
1444 1436 return _booleans.get(s.lower(), None)
@@ -1,195 +1,195
1 1 # win32.py - utility functions that use win32 API
2 2 #
3 3 # Copyright 2005-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 """Utility functions that use win32 API.
9 9
10 10 Mark Hammond's win32all package allows better functionality on
11 11 Windows. This module overrides definitions in util.py. If not
12 12 available, import of this module will fail, and generic code will be
13 13 used.
14 14 """
15 15
16 16 import win32api
17 17
18 18 import errno, os, sys, pywintypes, win32con, win32file, win32process
19 19 import winerror, win32gui, win32console
20 20 import osutil, encoding
21 21 from win32com.shell import shell, shellcon
22 22
23 23 def os_link(src, dst):
24 24 try:
25 25 win32file.CreateHardLink(dst, src)
26 26 except pywintypes.error:
27 27 raise OSError(errno.EINVAL, 'target implements hardlinks improperly')
28 28 except NotImplementedError: # Another fake error win Win98
29 29 raise OSError(errno.EINVAL, 'Hardlinking not supported')
30 30
31 31 def _getfileinfo(pathname):
32 32 """Return number of hardlinks for the given file."""
33 33 try:
34 34 fh = win32file.CreateFile(pathname,
35 35 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
36 36 None, win32file.OPEN_EXISTING, 0, None)
37 37 except pywintypes.error:
38 38 raise OSError(errno.ENOENT, 'The system cannot find the file specified')
39 39 try:
40 40 return win32file.GetFileInformationByHandle(fh)
41 41 finally:
42 42 fh.Close()
43 43
44 44 def nlinks(pathname):
45 45 """Return number of hardlinks for the given file."""
46 46 links = _getfileinfo(pathname)[7]
47 47 if links < 2:
48 48 # Known to be wrong for most network drives
49 49 dirname = os.path.dirname(pathname)
50 50 if not dirname:
51 51 dirname = '.'
52 52 dt = win32file.GetDriveType(dirname + '\\')
53 53 if dt == 4 or dt == 1:
54 54 # Fake hardlink to force COW for network drives
55 55 links = 2
56 56 return links
57 57
58 58 def samefile(fpath1, fpath2):
59 59 """Returns whether fpath1 and fpath2 refer to the same file. This is only
60 60 guaranteed to work for files, not directories."""
61 61 res1 = _getfileinfo(fpath1)
62 62 res2 = _getfileinfo(fpath2)
63 63 # Index 4 is the volume serial number, and 8 and 9 contain the file ID
64 64 return res1[4] == res2[4] and res1[8] == res2[8] and res1[9] == res2[9]
65 65
66 66 def samedevice(fpath1, fpath2):
67 67 """Returns whether fpath1 and fpath2 are on the same device. This is only
68 68 guaranteed to work for files, not directories."""
69 69 res1 = _getfileinfo(fpath1)
70 70 res2 = _getfileinfo(fpath2)
71 71 return res1[4] == res2[4]
72 72
73 73 def testpid(pid):
74 74 '''return True if pid is still running or unable to
75 75 determine, False otherwise'''
76 76 try:
77 77 handle = win32api.OpenProcess(
78 78 win32con.PROCESS_QUERY_INFORMATION, False, pid)
79 79 if handle:
80 80 status = win32process.GetExitCodeProcess(handle)
81 81 return status == win32con.STILL_ACTIVE
82 82 except pywintypes.error, details:
83 83 return details[0] != winerror.ERROR_INVALID_PARAMETER
84 84 return True
85 85
86 86 def lookup_reg(key, valname=None, scope=None):
87 87 ''' Look up a key/value name in the Windows registry.
88 88
89 89 valname: value name. If unspecified, the default value for the key
90 90 is used.
91 91 scope: optionally specify scope for registry lookup, this can be
92 92 a sequence of scopes to look up in order. Default (CURRENT_USER,
93 93 LOCAL_MACHINE).
94 94 '''
95 95 try:
96 96 from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, \
97 97 QueryValueEx, OpenKey
98 98 except ImportError:
99 99 return None
100 100
101 101 if scope is None:
102 102 scope = (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)
103 103 elif not isinstance(scope, (list, tuple)):
104 104 scope = (scope,)
105 105 for s in scope:
106 106 try:
107 107 val = QueryValueEx(OpenKey(s, key), valname)[0]
108 108 # never let a Unicode string escape into the wild
109 109 return encoding.tolocal(val.encode('UTF-8'))
110 110 except EnvironmentError:
111 111 pass
112 112
113 113 def system_rcpath_win32():
114 114 '''return default os-specific hgrc search path'''
115 115 proc = win32api.GetCurrentProcess()
116 116 try:
117 117 # This will fail on windows < NT
118 118 filename = win32process.GetModuleFileNameEx(proc, 0)
119 119 except:
120 120 filename = win32api.GetModuleFileName(0)
121 121 # Use mercurial.ini found in directory with hg.exe
122 122 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
123 123 if os.path.isfile(progrc):
124 124 return [progrc]
125 125 # Use hgrc.d found in directory with hg.exe
126 126 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
127 127 if os.path.isdir(progrcd):
128 128 rcpath = []
129 129 for f, kind in osutil.listdir(progrcd):
130 130 if f.endswith('.rc'):
131 131 rcpath.append(os.path.join(progrcd, f))
132 132 return rcpath
133 133 # else look for a system rcpath in the registry
134 134 try:
135 135 value = win32api.RegQueryValue(
136 136 win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Mercurial')
137 137 rcpath = []
138 138 for p in value.split(os.pathsep):
139 139 if p.lower().endswith('mercurial.ini'):
140 140 rcpath.append(p)
141 141 elif os.path.isdir(p):
142 142 for f, kind in osutil.listdir(p):
143 143 if f.endswith('.rc'):
144 144 rcpath.append(os.path.join(p, f))
145 145 return rcpath
146 146 except pywintypes.error:
147 147 return []
148 148
149 149 def user_rcpath_win32():
150 150 '''return os-specific hgrc search path to the user dir'''
151 151 userdir = os.path.expanduser('~')
152 152 if sys.getwindowsversion()[3] != 2 and userdir == '~':
153 153 # We are on win < nt: fetch the APPDATA directory location and use
154 154 # the parent directory as the user home dir.
155 155 appdir = shell.SHGetPathFromIDList(
156 156 shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_APPDATA))
157 157 userdir = os.path.dirname(appdir)
158 158 return [os.path.join(userdir, 'mercurial.ini'),
159 159 os.path.join(userdir, '.hgrc')]
160 160
161 161 def getuser():
162 162 '''return name of current user'''
163 163 return win32api.GetUserName()
164 164
165 165 def set_signal_handler_win32():
166 166 """Register a termination handler for console events including
167 167 CTRL+C. python signal handlers do not work well with socket
168 168 operations.
169 169 """
170 170 def handler(event):
171 171 win32process.ExitProcess(1)
172 172 win32api.SetConsoleCtrlHandler(handler)
173 173
174 174 def hidewindow():
175 175 def callback(*args, **kwargs):
176 176 hwnd, pid = args
177 177 wpid = win32process.GetWindowThreadProcessId(hwnd)[1]
178 178 if pid == wpid:
179 179 win32gui.ShowWindow(hwnd, win32con.SW_HIDE)
180 180
181 181 pid = win32process.GetCurrentProcessId()
182 182 win32gui.EnumWindows(callback, pid)
183 183
184 def termwidth_():
184 def termwidth():
185 185 try:
186 186 # Query stderr to avoid problems with redirections
187 187 screenbuf = win32console.GetStdHandle(win32console.STD_ERROR_HANDLE)
188 188 try:
189 189 window = screenbuf.GetConsoleScreenBufferInfo()['Window']
190 190 width = window.Right - window.Left
191 191 return width
192 192 finally:
193 193 screenbuf.Detach()
194 194 except pywintypes.error:
195 195 return 79
@@ -1,376 +1,376
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import osutil, error
10 10 import errno, msvcrt, os, re, sys, random, subprocess
11 11
12 12 nulldev = 'NUL:'
13 13 umask = 002
14 14
15 15 # wrap osutil.posixfile to provide friendlier exceptions
16 16 def posixfile(name, mode='r', buffering=-1):
17 17 try:
18 18 return osutil.posixfile(name, mode, buffering)
19 19 except WindowsError, err:
20 20 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
21 21 posixfile.__doc__ = osutil.posixfile.__doc__
22 22
23 23 class winstdout(object):
24 24 '''stdout on windows misbehaves if sent through a pipe'''
25 25
26 26 def __init__(self, fp):
27 27 self.fp = fp
28 28
29 29 def __getattr__(self, key):
30 30 return getattr(self.fp, key)
31 31
32 32 def close(self):
33 33 try:
34 34 self.fp.close()
35 35 except: pass
36 36
37 37 def write(self, s):
38 38 try:
39 39 # This is workaround for "Not enough space" error on
40 40 # writing large size of data to console.
41 41 limit = 16000
42 42 l = len(s)
43 43 start = 0
44 44 self.softspace = 0
45 45 while start < l:
46 46 end = start + limit
47 47 self.fp.write(s[start:end])
48 48 start = end
49 49 except IOError, inst:
50 50 if inst.errno != 0:
51 51 raise
52 52 self.close()
53 53 raise IOError(errno.EPIPE, 'Broken pipe')
54 54
55 55 def flush(self):
56 56 try:
57 57 return self.fp.flush()
58 58 except IOError, inst:
59 59 if inst.errno != errno.EINVAL:
60 60 raise
61 61 self.close()
62 62 raise IOError(errno.EPIPE, 'Broken pipe')
63 63
64 64 sys.stdout = winstdout(sys.stdout)
65 65
66 66 def _is_win_9x():
67 67 '''return true if run on windows 95, 98 or me.'''
68 68 try:
69 69 return sys.getwindowsversion()[3] == 1
70 70 except AttributeError:
71 71 return 'command' in os.environ.get('comspec', '')
72 72
73 73 def openhardlinks():
74 74 return not _is_win_9x() and "win32api" in globals()
75 75
76 76 def system_rcpath():
77 77 try:
78 78 return system_rcpath_win32()
79 79 except:
80 80 return [r'c:\mercurial\mercurial.ini']
81 81
82 82 def user_rcpath():
83 83 '''return os-specific hgrc search path to the user dir'''
84 84 try:
85 85 path = user_rcpath_win32()
86 86 except:
87 87 home = os.path.expanduser('~')
88 88 path = [os.path.join(home, 'mercurial.ini'),
89 89 os.path.join(home, '.hgrc')]
90 90 userprofile = os.environ.get('USERPROFILE')
91 91 if userprofile:
92 92 path.append(os.path.join(userprofile, 'mercurial.ini'))
93 93 path.append(os.path.join(userprofile, '.hgrc'))
94 94 return path
95 95
96 96 def parse_patch_output(output_line):
97 97 """parses the output produced by patch and returns the filename"""
98 98 pf = output_line[14:]
99 99 if pf[0] == '`':
100 100 pf = pf[1:-1] # Remove the quotes
101 101 return pf
102 102
103 103 def sshargs(sshcmd, host, user, port):
104 104 '''Build argument list for ssh or Plink'''
105 105 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
106 106 args = user and ("%s@%s" % (user, host)) or host
107 107 return port and ("%s %s %s" % (args, pflag, port)) or args
108 108
109 109 def testpid(pid):
110 110 '''return False if pid dead, True if running or not known'''
111 111 return True
112 112
113 113 def set_flags(f, l, x):
114 114 pass
115 115
116 116 def set_binary(fd):
117 117 # When run without console, pipes may expose invalid
118 118 # fileno(), usually set to -1.
119 119 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
120 120 msvcrt.setmode(fd.fileno(), os.O_BINARY)
121 121
122 122 def pconvert(path):
123 123 return '/'.join(path.split(os.sep))
124 124
125 125 def localpath(path):
126 126 return path.replace('/', '\\')
127 127
128 128 def normpath(path):
129 129 return pconvert(os.path.normpath(path))
130 130
131 131 def realpath(path):
132 132 '''
133 133 Returns the true, canonical file system path equivalent to the given
134 134 path.
135 135 '''
136 136 # TODO: There may be a more clever way to do this that also handles other,
137 137 # less common file systems.
138 138 return os.path.normpath(os.path.normcase(os.path.realpath(path)))
139 139
140 140 def samestat(s1, s2):
141 141 return False
142 142
143 143 # A sequence of backslashes is special iff it precedes a double quote:
144 144 # - if there's an even number of backslashes, the double quote is not
145 145 # quoted (i.e. it ends the quoted region)
146 146 # - if there's an odd number of backslashes, the double quote is quoted
147 147 # - in both cases, every pair of backslashes is unquoted into a single
148 148 # backslash
149 149 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
150 150 # So, to quote a string, we must surround it in double quotes, double
151 151 # the number of backslashes that preceed double quotes and add another
152 152 # backslash before every double quote (being careful with the double
153 153 # quote we've appended to the end)
154 154 _quotere = None
155 155 def shellquote(s):
156 156 global _quotere
157 157 if _quotere is None:
158 158 _quotere = re.compile(r'(\\*)("|\\$)')
159 159 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
160 160
161 161 def quotecommand(cmd):
162 162 """Build a command string suitable for os.popen* calls."""
163 163 # The extra quotes are needed because popen* runs the command
164 164 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
165 165 return '"' + cmd + '"'
166 166
167 167 def popen(command, mode='r'):
168 168 # Work around "popen spawned process may not write to stdout
169 169 # under windows"
170 170 # http://bugs.python.org/issue1366
171 171 command += " 2> %s" % nulldev
172 172 return os.popen(quotecommand(command), mode)
173 173
174 174 def explain_exit(code):
175 175 return _("exited with status %d") % code, code
176 176
177 177 # if you change this stub into a real check, please try to implement the
178 178 # username and groupname functions above, too.
179 179 def isowner(st):
180 180 return True
181 181
182 182 def find_exe(command):
183 183 '''Find executable for command searching like cmd.exe does.
184 184 If command is a basename then PATH is searched for command.
185 185 PATH isn't searched if command is an absolute or relative path.
186 186 An extension from PATHEXT is found and added if not present.
187 187 If command isn't found None is returned.'''
188 188 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
189 189 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
190 190 if os.path.splitext(command)[1].lower() in pathexts:
191 191 pathexts = ['']
192 192
193 193 def findexisting(pathcommand):
194 194 'Will append extension (if needed) and return existing file'
195 195 for ext in pathexts:
196 196 executable = pathcommand + ext
197 197 if os.path.exists(executable):
198 198 return executable
199 199 return None
200 200
201 201 if os.sep in command:
202 202 return findexisting(command)
203 203
204 204 for path in os.environ.get('PATH', '').split(os.pathsep):
205 205 executable = findexisting(os.path.join(path, command))
206 206 if executable is not None:
207 207 return executable
208 208 return findexisting(os.path.expanduser(os.path.expandvars(command)))
209 209
210 210 def set_signal_handler():
211 211 try:
212 212 set_signal_handler_win32()
213 213 except NameError:
214 214 pass
215 215
216 216 def statfiles(files):
217 217 '''Stat each file in files and yield stat or None if file does not exist.
218 218 Cluster and cache stat per directory to minimize number of OS stat calls.'''
219 219 ncase = os.path.normcase
220 220 dircache = {} # dirname -> filename -> status | None if file does not exist
221 221 for nf in files:
222 222 nf = ncase(nf)
223 223 dir, base = os.path.split(nf)
224 224 if not dir:
225 225 dir = '.'
226 226 cache = dircache.get(dir, None)
227 227 if cache is None:
228 228 try:
229 229 dmap = dict([(ncase(n), s)
230 230 for n, k, s in osutil.listdir(dir, True)])
231 231 except OSError, err:
232 232 # handle directory not found in Python version prior to 2.5
233 233 # Python <= 2.4 returns native Windows code 3 in errno
234 234 # Python >= 2.5 returns ENOENT and adds winerror field
235 235 # EINVAL is raised if dir is not a directory.
236 236 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
237 237 errno.ENOTDIR):
238 238 raise
239 239 dmap = {}
240 240 cache = dircache.setdefault(dir, dmap)
241 241 yield cache.get(base, None)
242 242
243 243 def getuser():
244 244 '''return name of current user'''
245 245 raise error.Abort(_('user name not available - set USERNAME '
246 246 'environment variable'))
247 247
248 248 def username(uid=None):
249 249 """Return the name of the user with the given uid.
250 250
251 251 If uid is None, return the name of the current user."""
252 252 return None
253 253
254 254 def groupname(gid=None):
255 255 """Return the name of the group with the given gid.
256 256
257 257 If gid is None, return the name of the current group."""
258 258 return None
259 259
260 260 def _removedirs(name):
261 261 """special version of os.removedirs that does not remove symlinked
262 262 directories or junction points if they actually contain files"""
263 263 if osutil.listdir(name):
264 264 return
265 265 os.rmdir(name)
266 266 head, tail = os.path.split(name)
267 267 if not tail:
268 268 head, tail = os.path.split(head)
269 269 while head and tail:
270 270 try:
271 271 if osutil.listdir(head):
272 272 return
273 273 os.rmdir(head)
274 274 except:
275 275 break
276 276 head, tail = os.path.split(head)
277 277
278 278 def unlink(f):
279 279 """unlink and remove the directory if it is empty"""
280 280 os.unlink(f)
281 281 # try removing directories that might now be empty
282 282 try:
283 283 _removedirs(os.path.dirname(f))
284 284 except OSError:
285 285 pass
286 286
287 287 def rename(src, dst):
288 288 '''atomically rename file src to dst, replacing dst if it exists'''
289 289 try:
290 290 os.rename(src, dst)
291 291 except OSError: # FIXME: check err (EEXIST ?)
292 292
293 293 # On windows, rename to existing file is not allowed, so we
294 294 # must delete destination first. But if a file is open, unlink
295 295 # schedules it for delete but does not delete it. Rename
296 296 # happens immediately even for open files, so we rename
297 297 # destination to a temporary name, then delete that. Then
298 298 # rename is safe to do.
299 299 # The temporary name is chosen at random to avoid the situation
300 300 # where a file is left lying around from a previous aborted run.
301 301 # The usual race condition this introduces can't be avoided as
302 302 # we need the name to rename into, and not the file itself. Due
303 303 # to the nature of the operation however, any races will at worst
304 304 # lead to the rename failing and the current operation aborting.
305 305
306 306 def tempname(prefix):
307 307 for tries in xrange(10):
308 308 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
309 309 if not os.path.exists(temp):
310 310 return temp
311 311 raise IOError, (errno.EEXIST, "No usable temporary filename found")
312 312
313 313 temp = tempname(dst)
314 314 os.rename(dst, temp)
315 315 try:
316 316 os.unlink(temp)
317 317 except:
318 318 # Some rude AV-scanners on Windows may cause the unlink to
319 319 # fail. Not aborting here just leaks the temp file, whereas
320 320 # aborting at this point may leave serious inconsistencies.
321 321 # Ideally, we would notify the user here.
322 322 pass
323 323 os.rename(src, dst)
324 324
325 325 def spawndetached(args):
326 326 # No standard library function really spawns a fully detached
327 327 # process under win32 because they allocate pipes or other objects
328 328 # to handle standard streams communications. Passing these objects
329 329 # to the child process requires handle inheritance to be enabled
330 330 # which makes really detached processes impossible.
331 331 class STARTUPINFO:
332 332 dwFlags = subprocess.STARTF_USESHOWWINDOW
333 333 hStdInput = None
334 334 hStdOutput = None
335 335 hStdError = None
336 336 wShowWindow = subprocess.SW_HIDE
337 337
338 338 args = subprocess.list2cmdline(args)
339 339 # Not running the command in shell mode makes python26 hang when
340 340 # writing to hgweb output socket.
341 341 comspec = os.environ.get("COMSPEC", "cmd.exe")
342 342 args = comspec + " /c " + args
343 343 hp, ht, pid, tid = subprocess.CreateProcess(
344 344 None, args,
345 345 # no special security
346 346 None, None,
347 347 # Do not inherit handles
348 348 0,
349 349 # DETACHED_PROCESS
350 350 0x00000008,
351 351 os.environ,
352 352 os.getcwd(),
353 353 STARTUPINFO())
354 354 return pid
355 355
356 356 def gethgcmd():
357 357 return [sys.executable] + sys.argv[:1]
358 358
359 def termwidth_():
359 def termwidth():
360 360 # cmd.exe does not handle CR like a unix console, the CR is
361 361 # counted in the line length. On 80 columns consoles, if 80
362 362 # characters are written, the following CR won't apply on the
363 363 # current line but on the new one. Keep room for it.
364 364 return 79
365 365
366 366 def groupmembers(name):
367 367 # Don't support groups on Windows for now
368 368 raise KeyError()
369 369
370 370 try:
371 371 # override functions with win32 versions if possible
372 372 from win32 import *
373 373 except ImportError:
374 374 pass
375 375
376 376 expandglobs = True
General Comments 0
You need to be logged in to leave comments. Login now