##// END OF EJS Templates
Merge with crew-stable
Alexis S. L. Carvalho -
r5336:24de0275 merge default
parent child Browse files
Show More
@@ -1,119 +1,124 b''
1 1 # git support for the convert extension
2 2
3 3 import os
4 4 from mercurial import util
5 5
6 6 from common import NoRepo, commit, converter_source
7 7
8 8 class convert_git(converter_source):
9 9 # Windows does not support GIT_DIR= construct while other systems
10 10 # cannot remove environment variable. Just assume none have
11 11 # both issues.
12 12 if hasattr(os, 'unsetenv'):
13 13 def gitcmd(self, s):
14 14 prevgitdir = os.environ.get('GIT_DIR')
15 15 os.environ['GIT_DIR'] = self.path
16 16 try:
17 17 return os.popen(s)
18 18 finally:
19 19 if prevgitdir is None:
20 20 del os.environ['GIT_DIR']
21 21 else:
22 22 os.environ['GIT_DIR'] = prevgitdir
23 23 else:
24 24 def gitcmd(self, s):
25 25 return os.popen('GIT_DIR=%s %s' % (self.path, s))
26 26
27 27 def __init__(self, ui, path, rev=None):
28 28 super(convert_git, self).__init__(ui, path, rev=rev)
29 29
30 30 if os.path.isdir(path + "/.git"):
31 31 path += "/.git"
32 32 if not os.path.exists(path + "/objects"):
33 33 raise NoRepo("couldn't open GIT repo %s" % path)
34 34 self.path = path
35 35
36 36 def getheads(self):
37 37 if not self.rev:
38 38 return self.gitcmd('git-rev-parse --branches').read().splitlines()
39 39 else:
40 40 fh = self.gitcmd("git-rev-parse --verify %s" % self.rev)
41 41 return [fh.read()[:-1]]
42 42
43 43 def catfile(self, rev, type):
44 44 if rev == "0" * 40: raise IOError()
45 45 fh = self.gitcmd("git-cat-file %s %s 2>%s" % (type, rev,
46 46 util.nulldev))
47 47 return fh.read()
48 48
49 49 def getfile(self, name, rev):
50 50 return self.catfile(rev, "blob")
51 51
52 52 def getmode(self, name, rev):
53 53 return self.modecache[(name, rev)]
54 54
55 55 def getchanges(self, version):
56 56 self.modecache = {}
57 57 fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
58 58 changes = []
59 seen = {}
59 60 for l in fh:
60 if "\t" not in l: continue
61 if "\t" not in l:
62 continue
61 63 m, f = l[:-1].split("\t")
64 if f in seen:
65 continue
66 seen[f] = 1
62 67 m = m.split()
63 68 h = m[3]
64 69 p = (m[1] == "100755")
65 70 s = (m[1] == "120000")
66 71 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
67 72 changes.append((f, h))
68 73 return (changes, {})
69 74
70 75 def getcommit(self, version):
71 76 c = self.catfile(version, "commit") # read the commit hash
72 77 end = c.find("\n\n")
73 78 message = c[end+2:]
74 79 message = self.recode(message)
75 80 l = c[:end].splitlines()
76 81 manifest = l[0].split()[1]
77 82 parents = []
78 83 for e in l[1:]:
79 84 n, v = e.split(" ", 1)
80 85 if n == "author":
81 86 p = v.split()
82 87 tm, tz = p[-2:]
83 88 author = " ".join(p[:-2])
84 89 if author[0] == "<": author = author[1:-1]
85 90 author = self.recode(author)
86 91 if n == "committer":
87 92 p = v.split()
88 93 tm, tz = p[-2:]
89 94 committer = " ".join(p[:-2])
90 95 if committer[0] == "<": committer = committer[1:-1]
91 96 committer = self.recode(committer)
92 97 message += "\ncommitter: %s\n" % committer
93 98 if n == "parent": parents.append(v)
94 99
95 100 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
96 101 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
97 102 date = tm + " " + str(tz)
98 103 author = author or "unknown"
99 104
100 105 c = commit(parents=parents, date=date, author=author, desc=message,
101 106 rev=version)
102 107 return c
103 108
104 109 def gettags(self):
105 110 tags = {}
106 111 fh = self.gitcmd('git-ls-remote --tags "%s" 2>%s' % (self.path,
107 112 util.nulldev))
108 113 prefix = 'refs/tags/'
109 114 for line in fh:
110 115 line = line.strip()
111 116 if not line.endswith("^{}"):
112 117 continue
113 118 node, tag = line.split(None, 1)
114 119 if not tag.startswith(prefix):
115 120 continue
116 121 tag = tag[len(prefix):-3]
117 122 tags[tag] = node
118 123
119 124 return tags
@@ -1,2253 +1,2256 b''
1 1 # queue.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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 '''patch management and development
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 prepare repository to work with patches qinit
20 20 create new patch qnew
21 21 import existing patch qimport
22 22
23 23 print patch series qseries
24 24 print applied patches qapplied
25 25 print name of top applied patch qtop
26 26
27 27 add known patch to applied stack qpush
28 28 remove patch from applied stack qpop
29 29 refresh contents of top applied patch qrefresh
30 30 '''
31 31
32 32 from mercurial.i18n import _
33 33 from mercurial import commands, cmdutil, hg, patch, revlog, util
34 34 from mercurial import repair
35 35 import os, sys, re, errno
36 36
37 37 commands.norepo += " qclone qversion"
38 38
39 39 # Patch names looks like unix-file names.
40 40 # They must be joinable with queue directory and result in the patch path.
41 41 normname = util.normpath
42 42
43 43 class statusentry:
44 44 def __init__(self, rev, name=None):
45 45 if not name:
46 46 fields = rev.split(':', 1)
47 47 if len(fields) == 2:
48 48 self.rev, self.name = fields
49 49 else:
50 50 self.rev, self.name = None, None
51 51 else:
52 52 self.rev, self.name = rev, name
53 53
54 54 def __str__(self):
55 55 return self.rev + ':' + self.name
56 56
57 57 class queue:
58 58 def __init__(self, ui, path, patchdir=None):
59 59 self.basepath = path
60 60 self.path = patchdir or os.path.join(path, "patches")
61 61 self.opener = util.opener(self.path)
62 62 self.ui = ui
63 63 self.applied = []
64 64 self.full_series = []
65 65 self.applied_dirty = 0
66 66 self.series_dirty = 0
67 67 self.series_path = "series"
68 68 self.status_path = "status"
69 69 self.guards_path = "guards"
70 70 self.active_guards = None
71 71 self.guards_dirty = False
72 72 self._diffopts = None
73 73
74 74 if os.path.exists(self.join(self.series_path)):
75 75 self.full_series = self.opener(self.series_path).read().splitlines()
76 76 self.parse_series()
77 77
78 78 if os.path.exists(self.join(self.status_path)):
79 79 lines = self.opener(self.status_path).read().splitlines()
80 80 self.applied = [statusentry(l) for l in lines]
81 81
82 82 def diffopts(self):
83 83 if self._diffopts is None:
84 84 self._diffopts = patch.diffopts(self.ui)
85 85 return self._diffopts
86 86
87 87 def join(self, *p):
88 88 return os.path.join(self.path, *p)
89 89
90 90 def find_series(self, patch):
91 91 pre = re.compile("(\s*)([^#]+)")
92 92 index = 0
93 93 for l in self.full_series:
94 94 m = pre.match(l)
95 95 if m:
96 96 s = m.group(2)
97 97 s = s.rstrip()
98 98 if s == patch:
99 99 return index
100 100 index += 1
101 101 return None
102 102
103 103 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
104 104
105 105 def parse_series(self):
106 106 self.series = []
107 107 self.series_guards = []
108 108 for l in self.full_series:
109 109 h = l.find('#')
110 110 if h == -1:
111 111 patch = l
112 112 comment = ''
113 113 elif h == 0:
114 114 continue
115 115 else:
116 116 patch = l[:h]
117 117 comment = l[h:]
118 118 patch = patch.strip()
119 119 if patch:
120 120 if patch in self.series:
121 121 raise util.Abort(_('%s appears more than once in %s') %
122 122 (patch, self.join(self.series_path)))
123 123 self.series.append(patch)
124 124 self.series_guards.append(self.guard_re.findall(comment))
125 125
126 126 def check_guard(self, guard):
127 127 bad_chars = '# \t\r\n\f'
128 128 first = guard[0]
129 129 for c in '-+':
130 130 if first == c:
131 131 return (_('guard %r starts with invalid character: %r') %
132 132 (guard, c))
133 133 for c in bad_chars:
134 134 if c in guard:
135 135 return _('invalid character in guard %r: %r') % (guard, c)
136 136
137 137 def set_active(self, guards):
138 138 for guard in guards:
139 139 bad = self.check_guard(guard)
140 140 if bad:
141 141 raise util.Abort(bad)
142 142 guards = dict.fromkeys(guards).keys()
143 143 guards.sort()
144 144 self.ui.debug('active guards: %s\n' % ' '.join(guards))
145 145 self.active_guards = guards
146 146 self.guards_dirty = True
147 147
148 148 def active(self):
149 149 if self.active_guards is None:
150 150 self.active_guards = []
151 151 try:
152 152 guards = self.opener(self.guards_path).read().split()
153 153 except IOError, err:
154 154 if err.errno != errno.ENOENT: raise
155 155 guards = []
156 156 for i, guard in enumerate(guards):
157 157 bad = self.check_guard(guard)
158 158 if bad:
159 159 self.ui.warn('%s:%d: %s\n' %
160 160 (self.join(self.guards_path), i + 1, bad))
161 161 else:
162 162 self.active_guards.append(guard)
163 163 return self.active_guards
164 164
165 165 def set_guards(self, idx, guards):
166 166 for g in guards:
167 167 if len(g) < 2:
168 168 raise util.Abort(_('guard %r too short') % g)
169 169 if g[0] not in '-+':
170 170 raise util.Abort(_('guard %r starts with invalid char') % g)
171 171 bad = self.check_guard(g[1:])
172 172 if bad:
173 173 raise util.Abort(bad)
174 174 drop = self.guard_re.sub('', self.full_series[idx])
175 175 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
176 176 self.parse_series()
177 177 self.series_dirty = True
178 178
179 179 def pushable(self, idx):
180 180 if isinstance(idx, str):
181 181 idx = self.series.index(idx)
182 182 patchguards = self.series_guards[idx]
183 183 if not patchguards:
184 184 return True, None
185 185 default = False
186 186 guards = self.active()
187 187 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
188 188 if exactneg:
189 189 return False, exactneg[0]
190 190 pos = [g for g in patchguards if g[0] == '+']
191 191 exactpos = [g for g in pos if g[1:] in guards]
192 192 if pos:
193 193 if exactpos:
194 194 return True, exactpos[0]
195 195 return False, pos
196 196 return True, ''
197 197
198 198 def explain_pushable(self, idx, all_patches=False):
199 199 write = all_patches and self.ui.write or self.ui.warn
200 200 if all_patches or self.ui.verbose:
201 201 if isinstance(idx, str):
202 202 idx = self.series.index(idx)
203 203 pushable, why = self.pushable(idx)
204 204 if all_patches and pushable:
205 205 if why is None:
206 206 write(_('allowing %s - no guards in effect\n') %
207 207 self.series[idx])
208 208 else:
209 209 if not why:
210 210 write(_('allowing %s - no matching negative guards\n') %
211 211 self.series[idx])
212 212 else:
213 213 write(_('allowing %s - guarded by %r\n') %
214 214 (self.series[idx], why))
215 215 if not pushable:
216 216 if why:
217 217 write(_('skipping %s - guarded by %r\n') %
218 218 (self.series[idx], why))
219 219 else:
220 220 write(_('skipping %s - no matching guards\n') %
221 221 self.series[idx])
222 222
223 223 def save_dirty(self):
224 224 def write_list(items, path):
225 225 fp = self.opener(path, 'w')
226 226 for i in items:
227 227 print >> fp, i
228 228 fp.close()
229 229 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
230 230 if self.series_dirty: write_list(self.full_series, self.series_path)
231 231 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
232 232
233 233 def readheaders(self, patch):
234 234 def eatdiff(lines):
235 235 while lines:
236 236 l = lines[-1]
237 237 if (l.startswith("diff -") or
238 238 l.startswith("Index:") or
239 239 l.startswith("===========")):
240 240 del lines[-1]
241 241 else:
242 242 break
243 243 def eatempty(lines):
244 244 while lines:
245 245 l = lines[-1]
246 246 if re.match('\s*$', l):
247 247 del lines[-1]
248 248 else:
249 249 break
250 250
251 251 pf = self.join(patch)
252 252 message = []
253 253 comments = []
254 254 user = None
255 255 date = None
256 256 format = None
257 257 subject = None
258 258 diffstart = 0
259 259
260 260 for line in file(pf):
261 261 line = line.rstrip()
262 262 if line.startswith('diff --git'):
263 263 diffstart = 2
264 264 break
265 265 if diffstart:
266 266 if line.startswith('+++ '):
267 267 diffstart = 2
268 268 break
269 269 if line.startswith("--- "):
270 270 diffstart = 1
271 271 continue
272 272 elif format == "hgpatch":
273 273 # parse values when importing the result of an hg export
274 274 if line.startswith("# User "):
275 275 user = line[7:]
276 276 elif line.startswith("# Date "):
277 277 date = line[7:]
278 278 elif not line.startswith("# ") and line:
279 279 message.append(line)
280 280 format = None
281 281 elif line == '# HG changeset patch':
282 282 format = "hgpatch"
283 283 elif (format != "tagdone" and (line.startswith("Subject: ") or
284 284 line.startswith("subject: "))):
285 285 subject = line[9:]
286 286 format = "tag"
287 287 elif (format != "tagdone" and (line.startswith("From: ") or
288 288 line.startswith("from: "))):
289 289 user = line[6:]
290 290 format = "tag"
291 291 elif format == "tag" and line == "":
292 292 # when looking for tags (subject: from: etc) they
293 293 # end once you find a blank line in the source
294 294 format = "tagdone"
295 295 elif message or line:
296 296 message.append(line)
297 297 comments.append(line)
298 298
299 299 eatdiff(message)
300 300 eatdiff(comments)
301 301 eatempty(message)
302 302 eatempty(comments)
303 303
304 304 # make sure message isn't empty
305 305 if format and format.startswith("tag") and subject:
306 306 message.insert(0, "")
307 307 message.insert(0, subject)
308 308 return (message, comments, user, date, diffstart > 1)
309 309
310 310 def removeundo(self, repo):
311 311 undo = repo.sjoin('undo')
312 312 if not os.path.exists(undo):
313 313 return
314 314 try:
315 315 os.unlink(undo)
316 316 except OSError, inst:
317 317 self.ui.warn('error removing undo: %s\n' % str(inst))
318 318
319 319 def printdiff(self, repo, node1, node2=None, files=None,
320 320 fp=None, changes=None, opts={}):
321 321 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
322 322
323 323 patch.diff(repo, node1, node2, fns, match=matchfn,
324 324 fp=fp, changes=changes, opts=self.diffopts())
325 325
326 326 def mergeone(self, repo, mergeq, head, patch, rev):
327 327 # first try just applying the patch
328 328 (err, n) = self.apply(repo, [ patch ], update_status=False,
329 329 strict=True, merge=rev)
330 330
331 331 if err == 0:
332 332 return (err, n)
333 333
334 334 if n is None:
335 335 raise util.Abort(_("apply failed for patch %s") % patch)
336 336
337 337 self.ui.warn("patch didn't work out, merging %s\n" % patch)
338 338
339 339 # apply failed, strip away that rev and merge.
340 340 hg.clean(repo, head)
341 341 self.strip(repo, n, update=False, backup='strip')
342 342
343 343 ctx = repo.changectx(rev)
344 344 ret = hg.merge(repo, rev)
345 345 if ret:
346 346 raise util.Abort(_("update returned %d") % ret)
347 347 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
348 348 if n == None:
349 349 raise util.Abort(_("repo commit failed"))
350 350 try:
351 351 message, comments, user, date, patchfound = mergeq.readheaders(patch)
352 352 except:
353 353 raise util.Abort(_("unable to read %s") % patch)
354 354
355 355 patchf = self.opener(patch, "w")
356 356 if comments:
357 357 comments = "\n".join(comments) + '\n\n'
358 358 patchf.write(comments)
359 359 self.printdiff(repo, head, n, fp=patchf)
360 360 patchf.close()
361 361 self.removeundo(repo)
362 362 return (0, n)
363 363
364 364 def qparents(self, repo, rev=None):
365 365 if rev is None:
366 366 (p1, p2) = repo.dirstate.parents()
367 367 if p2 == revlog.nullid:
368 368 return p1
369 369 if len(self.applied) == 0:
370 370 return None
371 371 return revlog.bin(self.applied[-1].rev)
372 372 pp = repo.changelog.parents(rev)
373 373 if pp[1] != revlog.nullid:
374 374 arevs = [ x.rev for x in self.applied ]
375 375 p0 = revlog.hex(pp[0])
376 376 p1 = revlog.hex(pp[1])
377 377 if p0 in arevs:
378 378 return pp[0]
379 379 if p1 in arevs:
380 380 return pp[1]
381 381 return pp[0]
382 382
383 383 def mergepatch(self, repo, mergeq, series):
384 384 if len(self.applied) == 0:
385 385 # each of the patches merged in will have two parents. This
386 386 # can confuse the qrefresh, qdiff, and strip code because it
387 387 # needs to know which parent is actually in the patch queue.
388 388 # so, we insert a merge marker with only one parent. This way
389 389 # the first patch in the queue is never a merge patch
390 390 #
391 391 pname = ".hg.patches.merge.marker"
392 392 n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
393 393 self.removeundo(repo)
394 394 self.applied.append(statusentry(revlog.hex(n), pname))
395 395 self.applied_dirty = 1
396 396
397 397 head = self.qparents(repo)
398 398
399 399 for patch in series:
400 400 patch = mergeq.lookup(patch, strict=True)
401 401 if not patch:
402 402 self.ui.warn("patch %s does not exist\n" % patch)
403 403 return (1, None)
404 404 pushable, reason = self.pushable(patch)
405 405 if not pushable:
406 406 self.explain_pushable(patch, all_patches=True)
407 407 continue
408 408 info = mergeq.isapplied(patch)
409 409 if not info:
410 410 self.ui.warn("patch %s is not applied\n" % patch)
411 411 return (1, None)
412 412 rev = revlog.bin(info[1])
413 413 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
414 414 if head:
415 415 self.applied.append(statusentry(revlog.hex(head), patch))
416 416 self.applied_dirty = 1
417 417 if err:
418 418 return (err, head)
419 419 self.save_dirty()
420 420 return (0, head)
421 421
422 422 def patch(self, repo, patchfile):
423 423 '''Apply patchfile to the working directory.
424 424 patchfile: file name of patch'''
425 425 files = {}
426 426 try:
427 427 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
428 428 files=files)
429 429 except Exception, inst:
430 430 self.ui.note(str(inst) + '\n')
431 431 if not self.ui.verbose:
432 432 self.ui.warn("patch failed, unable to continue (try -v)\n")
433 433 return (False, files, False)
434 434
435 435 return (True, files, fuzz)
436 436
437 437 def apply(self, repo, series, list=False, update_status=True,
438 438 strict=False, patchdir=None, merge=None, all_files={}):
439 439 wlock = lock = tr = None
440 440 try:
441 441 wlock = repo.wlock()
442 442 lock = repo.lock()
443 443 tr = repo.transaction()
444 444 try:
445 445 ret = self._apply(repo, series, list, update_status,
446 446 strict, patchdir, merge, all_files=all_files)
447 447 tr.close()
448 448 self.save_dirty()
449 449 return ret
450 450 except:
451 451 try:
452 452 tr.abort()
453 453 finally:
454 454 repo.invalidate()
455 455 repo.dirstate.invalidate()
456 456 raise
457 457 finally:
458 458 del tr, lock, wlock
459 459
460 460 def _apply(self, repo, series, list=False, update_status=True,
461 461 strict=False, patchdir=None, merge=None, all_files={}):
462 462 # TODO unify with commands.py
463 463 if not patchdir:
464 464 patchdir = self.path
465 465 err = 0
466 466 n = None
467 467 for patchname in series:
468 468 pushable, reason = self.pushable(patchname)
469 469 if not pushable:
470 470 self.explain_pushable(patchname, all_patches=True)
471 471 continue
472 472 self.ui.warn("applying %s\n" % patchname)
473 473 pf = os.path.join(patchdir, patchname)
474 474
475 475 try:
476 476 message, comments, user, date, patchfound = self.readheaders(patchname)
477 477 except:
478 478 self.ui.warn("Unable to read %s\n" % patchname)
479 479 err = 1
480 480 break
481 481
482 482 if not message:
483 483 message = "imported patch %s\n" % patchname
484 484 else:
485 485 if list:
486 486 message.append("\nimported patch %s" % patchname)
487 487 message = '\n'.join(message)
488 488
489 489 (patcherr, files, fuzz) = self.patch(repo, pf)
490 490 all_files.update(files)
491 491 patcherr = not patcherr
492 492
493 493 if merge and files:
494 494 # Mark as removed/merged and update dirstate parent info
495 495 removed = []
496 496 merged = []
497 497 for f in files:
498 498 if os.path.exists(repo.wjoin(f)):
499 499 merged.append(f)
500 500 else:
501 501 removed.append(f)
502 502 for f in removed:
503 503 repo.dirstate.remove(f)
504 504 for f in merged:
505 505 repo.dirstate.merge(f)
506 506 p1, p2 = repo.dirstate.parents()
507 507 repo.dirstate.setparents(p1, merge)
508 508 files = patch.updatedir(self.ui, repo, files)
509 509 n = repo.commit(files, message, user, date, force=1)
510 510
511 511 if n == None:
512 512 raise util.Abort(_("repo commit failed"))
513 513
514 514 if update_status:
515 515 self.applied.append(statusentry(revlog.hex(n), patchname))
516 516
517 517 if patcherr:
518 518 if not patchfound:
519 519 self.ui.warn("patch %s is empty\n" % patchname)
520 520 err = 0
521 521 else:
522 522 self.ui.warn("patch failed, rejects left in working dir\n")
523 523 err = 1
524 524 break
525 525
526 526 if fuzz and strict:
527 527 self.ui.warn("fuzz found when applying patch, stopping\n")
528 528 err = 1
529 529 break
530 530 self.removeundo(repo)
531 531 return (err, n)
532 532
533 533 def delete(self, repo, patches, opts):
534 534 if not patches and not opts.get('rev'):
535 535 raise util.Abort(_('qdelete requires at least one revision or '
536 536 'patch name'))
537 537
538 538 realpatches = []
539 539 for patch in patches:
540 540 patch = self.lookup(patch, strict=True)
541 541 info = self.isapplied(patch)
542 542 if info:
543 543 raise util.Abort(_("cannot delete applied patch %s") % patch)
544 544 if patch not in self.series:
545 545 raise util.Abort(_("patch %s not in series file") % patch)
546 546 realpatches.append(patch)
547 547
548 548 appliedbase = 0
549 549 if opts.get('rev'):
550 550 if not self.applied:
551 551 raise util.Abort(_('no patches applied'))
552 552 revs = cmdutil.revrange(repo, opts['rev'])
553 553 if len(revs) > 1 and revs[0] > revs[1]:
554 554 revs.reverse()
555 555 for rev in revs:
556 556 if appliedbase >= len(self.applied):
557 557 raise util.Abort(_("revision %d is not managed") % rev)
558 558
559 559 base = revlog.bin(self.applied[appliedbase].rev)
560 560 node = repo.changelog.node(rev)
561 561 if node != base:
562 562 raise util.Abort(_("cannot delete revision %d above "
563 563 "applied patches") % rev)
564 564 realpatches.append(self.applied[appliedbase].name)
565 565 appliedbase += 1
566 566
567 567 if not opts.get('keep'):
568 568 r = self.qrepo()
569 569 if r:
570 570 r.remove(realpatches, True)
571 571 else:
572 572 for p in realpatches:
573 573 os.unlink(self.join(p))
574 574
575 575 if appliedbase:
576 576 del self.applied[:appliedbase]
577 577 self.applied_dirty = 1
578 578 indices = [self.find_series(p) for p in realpatches]
579 579 indices.sort()
580 580 for i in indices[-1::-1]:
581 581 del self.full_series[i]
582 582 self.parse_series()
583 583 self.series_dirty = 1
584 584
585 585 def check_toppatch(self, repo):
586 586 if len(self.applied) > 0:
587 587 top = revlog.bin(self.applied[-1].rev)
588 588 pp = repo.dirstate.parents()
589 589 if top not in pp:
590 590 raise util.Abort(_("queue top not at same revision as working directory"))
591 591 return top
592 592 return None
593 593 def check_localchanges(self, repo, force=False, refresh=True):
594 594 m, a, r, d = repo.status()[:4]
595 595 if m or a or r or d:
596 596 if not force:
597 597 if refresh:
598 598 raise util.Abort(_("local changes found, refresh first"))
599 599 else:
600 600 raise util.Abort(_("local changes found"))
601 601 return m, a, r, d
602 602
603 603 def new(self, repo, patch, *pats, **opts):
604 604 msg = opts.get('msg')
605 605 force = opts.get('force')
606 606 if os.path.exists(self.join(patch)):
607 607 raise util.Abort(_('patch "%s" already exists') % patch)
608 608 if opts.get('include') or opts.get('exclude') or pats:
609 609 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
610 610 m, a, r, d = repo.status(files=fns, match=match)[:4]
611 611 else:
612 612 m, a, r, d = self.check_localchanges(repo, force)
613 613 commitfiles = m + a + r
614 614 self.check_toppatch(repo)
615 615 wlock = repo.wlock()
616 616 try:
617 617 insert = self.full_series_end()
618 618 if msg:
619 619 n = repo.commit(commitfiles, msg, force=True)
620 620 else:
621 621 n = repo.commit(commitfiles, "[mq]: %s" % patch, force=True)
622 622 if n == None:
623 623 raise util.Abort(_("repo commit failed"))
624 624 self.full_series[insert:insert] = [patch]
625 625 self.applied.append(statusentry(revlog.hex(n), patch))
626 626 self.parse_series()
627 627 self.series_dirty = 1
628 628 self.applied_dirty = 1
629 629 p = self.opener(patch, "w")
630 630 if msg:
631 631 msg = msg + "\n"
632 632 p.write(msg)
633 633 p.close()
634 634 wlock = None
635 635 r = self.qrepo()
636 636 if r: r.add([patch])
637 637 if commitfiles:
638 638 self.refresh(repo, short=True, git=opts.get('git'))
639 639 self.removeundo(repo)
640 640 finally:
641 641 del wlock
642 642
643 643 def strip(self, repo, rev, update=True, backup="all"):
644 644 wlock = lock = None
645 645 try:
646 646 wlock = repo.wlock()
647 647 lock = repo.lock()
648 648
649 649 if update:
650 650 self.check_localchanges(repo, refresh=False)
651 651 urev = self.qparents(repo, rev)
652 652 hg.clean(repo, urev)
653 653 repo.dirstate.write()
654 654
655 655 self.removeundo(repo)
656 656 repair.strip(self.ui, repo, rev, backup)
657 657 finally:
658 658 del lock, wlock
659 659
660 660 def isapplied(self, patch):
661 661 """returns (index, rev, patch)"""
662 662 for i in xrange(len(self.applied)):
663 663 a = self.applied[i]
664 664 if a.name == patch:
665 665 return (i, a.rev, a.name)
666 666 return None
667 667
668 668 # if the exact patch name does not exist, we try a few
669 669 # variations. If strict is passed, we try only #1
670 670 #
671 671 # 1) a number to indicate an offset in the series file
672 672 # 2) a unique substring of the patch name was given
673 673 # 3) patchname[-+]num to indicate an offset in the series file
674 674 def lookup(self, patch, strict=False):
675 675 patch = patch and str(patch)
676 676
677 677 def partial_name(s):
678 678 if s in self.series:
679 679 return s
680 680 matches = [x for x in self.series if s in x]
681 681 if len(matches) > 1:
682 682 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
683 683 for m in matches:
684 684 self.ui.warn(' %s\n' % m)
685 685 return None
686 686 if matches:
687 687 return matches[0]
688 688 if len(self.series) > 0 and len(self.applied) > 0:
689 689 if s == 'qtip':
690 690 return self.series[self.series_end(True)-1]
691 691 if s == 'qbase':
692 692 return self.series[0]
693 693 return None
694 694 if patch == None:
695 695 return None
696 696
697 697 # we don't want to return a partial match until we make
698 698 # sure the file name passed in does not exist (checked below)
699 699 res = partial_name(patch)
700 700 if res and res == patch:
701 701 return res
702 702
703 703 if not os.path.isfile(self.join(patch)):
704 704 try:
705 705 sno = int(patch)
706 706 except(ValueError, OverflowError):
707 707 pass
708 708 else:
709 709 if sno < len(self.series):
710 710 return self.series[sno]
711 711 if not strict:
712 712 # return any partial match made above
713 713 if res:
714 714 return res
715 715 minus = patch.rfind('-')
716 716 if minus >= 0:
717 717 res = partial_name(patch[:minus])
718 718 if res:
719 719 i = self.series.index(res)
720 720 try:
721 721 off = int(patch[minus+1:] or 1)
722 722 except(ValueError, OverflowError):
723 723 pass
724 724 else:
725 725 if i - off >= 0:
726 726 return self.series[i - off]
727 727 plus = patch.rfind('+')
728 728 if plus >= 0:
729 729 res = partial_name(patch[:plus])
730 730 if res:
731 731 i = self.series.index(res)
732 732 try:
733 733 off = int(patch[plus+1:] or 1)
734 734 except(ValueError, OverflowError):
735 735 pass
736 736 else:
737 737 if i + off < len(self.series):
738 738 return self.series[i + off]
739 739 raise util.Abort(_("patch %s not in series") % patch)
740 740
741 741 def push(self, repo, patch=None, force=False, list=False,
742 742 mergeq=None):
743 743 wlock = repo.wlock()
744 744 try:
745 745 patch = self.lookup(patch)
746 746 # Suppose our series file is: A B C and the current 'top'
747 747 # patch is B. qpush C should be performed (moving forward)
748 748 # qpush B is a NOP (no change) qpush A is an error (can't
749 749 # go backwards with qpush)
750 750 if patch:
751 751 info = self.isapplied(patch)
752 752 if info:
753 753 if info[0] < len(self.applied) - 1:
754 754 raise util.Abort(
755 755 _("cannot push to a previous patch: %s") % patch)
756 756 if info[0] < len(self.series) - 1:
757 757 self.ui.warn(
758 758 _('qpush: %s is already at the top\n') % patch)
759 759 else:
760 760 self.ui.warn(_('all patches are currently applied\n'))
761 761 return
762 762
763 763 # Following the above example, starting at 'top' of B:
764 764 # qpush should be performed (pushes C), but a subsequent
765 765 # qpush without an argument is an error (nothing to
766 766 # apply). This allows a loop of "...while hg qpush..." to
767 767 # work as it detects an error when done
768 768 if self.series_end() == len(self.series):
769 769 self.ui.warn(_('patch series already fully applied\n'))
770 770 return 1
771 771 if not force:
772 772 self.check_localchanges(repo)
773 773
774 774 self.applied_dirty = 1;
775 775 start = self.series_end()
776 776 if start > 0:
777 777 self.check_toppatch(repo)
778 778 if not patch:
779 779 patch = self.series[start]
780 780 end = start + 1
781 781 else:
782 782 end = self.series.index(patch, start) + 1
783 783 s = self.series[start:end]
784 784 all_files = {}
785 785 try:
786 786 if mergeq:
787 787 ret = self.mergepatch(repo, mergeq, s)
788 788 else:
789 789 ret = self.apply(repo, s, list, all_files=all_files)
790 790 except:
791 791 self.ui.warn(_('cleaning up working directory...'))
792 792 node = repo.dirstate.parents()[0]
793 793 hg.revert(repo, node, None)
794 794 unknown = repo.status()[4]
795 795 # only remove unknown files that we know we touched or
796 796 # created while patching
797 797 for f in unknown:
798 798 if f in all_files:
799 799 util.unlink(repo.wjoin(f))
800 800 self.ui.warn(_('done\n'))
801 801 raise
802 802 top = self.applied[-1].name
803 803 if ret[0]:
804 804 self.ui.write(
805 805 "Errors during apply, please fix and refresh %s\n" % top)
806 806 else:
807 807 self.ui.write("Now at: %s\n" % top)
808 808 return ret[0]
809 809 finally:
810 810 del wlock
811 811
812 812 def pop(self, repo, patch=None, force=False, update=True, all=False):
813 813 def getfile(f, rev, flags):
814 814 t = repo.file(f).read(rev)
815 815 repo.wwrite(f, t, flags)
816 816
817 817 wlock = repo.wlock()
818 818 try:
819 819 if patch:
820 820 # index, rev, patch
821 821 info = self.isapplied(patch)
822 822 if not info:
823 823 patch = self.lookup(patch)
824 824 info = self.isapplied(patch)
825 825 if not info:
826 826 raise util.Abort(_("patch %s is not applied") % patch)
827 827
828 828 if len(self.applied) == 0:
829 829 # Allow qpop -a to work repeatedly,
830 830 # but not qpop without an argument
831 831 self.ui.warn(_("no patches applied\n"))
832 832 return not all
833 833
834 834 if not update:
835 835 parents = repo.dirstate.parents()
836 836 rr = [ revlog.bin(x.rev) for x in self.applied ]
837 837 for p in parents:
838 838 if p in rr:
839 839 self.ui.warn("qpop: forcing dirstate update\n")
840 840 update = True
841 841
842 842 if not force and update:
843 843 self.check_localchanges(repo)
844 844
845 845 self.applied_dirty = 1;
846 846 end = len(self.applied)
847 847 if not patch:
848 848 if all:
849 849 popi = 0
850 850 else:
851 851 popi = len(self.applied) - 1
852 852 else:
853 853 popi = info[0] + 1
854 854 if popi >= end:
855 855 self.ui.warn("qpop: %s is already at the top\n" % patch)
856 856 return
857 857 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
858 858
859 859 start = info[0]
860 860 rev = revlog.bin(info[1])
861 861
862 862 # we know there are no local changes, so we can make a simplified
863 863 # form of hg.update.
864 864 if update:
865 865 top = self.check_toppatch(repo)
866 866 qp = self.qparents(repo, rev)
867 867 changes = repo.changelog.read(qp)
868 868 mmap = repo.manifest.read(changes[0])
869 869 m, a, r, d, u = repo.status(qp, top)[:5]
870 870 if d:
871 871 raise util.Abort("deletions found between repo revs")
872 872 for f in m:
873 873 getfile(f, mmap[f], mmap.flags(f))
874 874 for f in r:
875 875 getfile(f, mmap[f], mmap.flags(f))
876 876 for f in m + r:
877 877 repo.dirstate.normal(f)
878 878 for f in a:
879 879 try:
880 880 os.unlink(repo.wjoin(f))
881 881 except OSError, e:
882 882 if e.errno != errno.ENOENT:
883 883 raise
884 884 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
885 885 except: pass
886 886 repo.dirstate.forget(f)
887 887 repo.dirstate.setparents(qp, revlog.nullid)
888 888 self.strip(repo, rev, update=False, backup='strip')
889 889 del self.applied[start:end]
890 890 if len(self.applied):
891 891 self.ui.write("Now at: %s\n" % self.applied[-1].name)
892 892 else:
893 893 self.ui.write("Patch queue now empty\n")
894 894 finally:
895 895 del wlock
896 896
897 897 def diff(self, repo, pats, opts):
898 898 top = self.check_toppatch(repo)
899 899 if not top:
900 900 self.ui.write("No patches applied\n")
901 901 return
902 902 qp = self.qparents(repo, top)
903 903 if opts.get('git'):
904 904 self.diffopts().git = True
905 905 self.printdiff(repo, qp, files=pats, opts=opts)
906 906
907 907 def refresh(self, repo, pats=None, **opts):
908 908 if len(self.applied) == 0:
909 909 self.ui.write("No patches applied\n")
910 910 return 1
911 911 wlock = repo.wlock()
912 912 try:
913 913 self.check_toppatch(repo)
914 914 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
915 915 top = revlog.bin(top)
916 916 cparents = repo.changelog.parents(top)
917 917 patchparent = self.qparents(repo, top)
918 918 message, comments, user, date, patchfound = self.readheaders(patchfn)
919 919
920 920 patchf = self.opener(patchfn, 'r+')
921 921
922 922 # if the patch was a git patch, refresh it as a git patch
923 923 for line in patchf:
924 924 if line.startswith('diff --git'):
925 925 self.diffopts().git = True
926 926 break
927 927
928 928 msg = opts.get('msg', '').rstrip()
929 929 if msg and comments:
930 930 # Remove existing message, keeping the rest of the comments
931 931 # fields.
932 932 # If comments contains 'subject: ', message will prepend
933 933 # the field and a blank line.
934 934 if message:
935 935 subj = 'subject: ' + message[0].lower()
936 936 for i in xrange(len(comments)):
937 937 if subj == comments[i].lower():
938 938 del comments[i]
939 939 message = message[2:]
940 940 break
941 941 ci = 0
942 942 for mi in xrange(len(message)):
943 943 while message[mi] != comments[ci]:
944 944 ci += 1
945 945 del comments[ci]
946 946 if msg:
947 947 comments.append(msg)
948 948
949 949 patchf.seek(0)
950 950 patchf.truncate()
951 951
952 952 if comments:
953 953 comments = "\n".join(comments) + '\n\n'
954 954 patchf.write(comments)
955 955
956 956 if opts.get('git'):
957 957 self.diffopts().git = True
958 958 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
959 959 tip = repo.changelog.tip()
960 960 if top == tip:
961 961 # if the top of our patch queue is also the tip, there is an
962 962 # optimization here. We update the dirstate in place and strip
963 963 # off the tip commit. Then just commit the current directory
964 964 # tree. We can also send repo.commit the list of files
965 965 # changed to speed up the diff
966 966 #
967 967 # in short mode, we only diff the files included in the
968 968 # patch already
969 969 #
970 970 # this should really read:
971 971 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
972 972 # but we do it backwards to take advantage of manifest/chlog
973 973 # caching against the next repo.status call
974 974 #
975 975 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
976 976 changes = repo.changelog.read(tip)
977 977 man = repo.manifest.read(changes[0])
978 978 aaa = aa[:]
979 979 if opts.get('short'):
980 980 filelist = mm + aa + dd
981 981 match = dict.fromkeys(filelist).__contains__
982 982 else:
983 983 filelist = None
984 984 match = util.always
985 985 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
986 986
987 987 # we might end up with files that were added between
988 988 # tip and the dirstate parent, but then changed in the
989 989 # local dirstate. in this case, we want them to only
990 990 # show up in the added section
991 991 for x in m:
992 992 if x not in aa:
993 993 mm.append(x)
994 994 # we might end up with files added by the local dirstate that
995 995 # were deleted by the patch. In this case, they should only
996 996 # show up in the changed section.
997 997 for x in a:
998 998 if x in dd:
999 999 del dd[dd.index(x)]
1000 1000 mm.append(x)
1001 1001 else:
1002 1002 aa.append(x)
1003 1003 # make sure any files deleted in the local dirstate
1004 1004 # are not in the add or change column of the patch
1005 1005 forget = []
1006 1006 for x in d + r:
1007 1007 if x in aa:
1008 1008 del aa[aa.index(x)]
1009 1009 forget.append(x)
1010 1010 continue
1011 1011 elif x in mm:
1012 1012 del mm[mm.index(x)]
1013 1013 dd.append(x)
1014 1014
1015 1015 m = util.unique(mm)
1016 1016 r = util.unique(dd)
1017 1017 a = util.unique(aa)
1018 1018 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1019 1019 filelist = util.unique(c[0] + c[1] + c[2])
1020 1020 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1021 1021 fp=patchf, changes=c, opts=self.diffopts())
1022 1022 patchf.close()
1023 1023
1024 1024 repo.dirstate.setparents(*cparents)
1025 1025 copies = {}
1026 1026 for dst in a:
1027 1027 src = repo.dirstate.copied(dst)
1028 1028 if src is not None:
1029 1029 copies.setdefault(src, []).append(dst)
1030 1030 repo.dirstate.add(dst)
1031 1031 # remember the copies between patchparent and tip
1032 1032 # this may be slow, so don't do it if we're not tracking copies
1033 1033 if self.diffopts().git:
1034 1034 for dst in aaa:
1035 1035 f = repo.file(dst)
1036 1036 src = f.renamed(man[dst])
1037 1037 if src:
1038 1038 copies[src[0]] = copies.get(dst, [])
1039 1039 if dst in a:
1040 1040 copies[src[0]].append(dst)
1041 1041 # we can't copy a file created by the patch itself
1042 1042 if dst in copies:
1043 1043 del copies[dst]
1044 1044 for src, dsts in copies.iteritems():
1045 1045 for dst in dsts:
1046 1046 repo.dirstate.copy(src, dst)
1047 1047 for f in r:
1048 1048 repo.dirstate.remove(f)
1049 1049 # if the patch excludes a modified file, mark that
1050 1050 # file with mtime=0 so status can see it.
1051 1051 mm = []
1052 1052 for i in xrange(len(m)-1, -1, -1):
1053 1053 if not matchfn(m[i]):
1054 1054 mm.append(m[i])
1055 1055 del m[i]
1056 1056 for f in m:
1057 1057 repo.dirstate.normal(f)
1058 1058 for f in mm:
1059 1059 repo.dirstate.normallookup(f)
1060 1060 for f in forget:
1061 1061 repo.dirstate.forget(f)
1062 1062
1063 1063 if not msg:
1064 1064 if not message:
1065 1065 message = "[mq]: %s\n" % patchfn
1066 1066 else:
1067 1067 message = "\n".join(message)
1068 1068 else:
1069 1069 message = msg
1070 1070
1071 1071 self.strip(repo, top, update=False,
1072 1072 backup='strip')
1073 1073 n = repo.commit(filelist, message, changes[1], match=matchfn,
1074 1074 force=1)
1075 1075 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1076 1076 self.applied_dirty = 1
1077 1077 self.removeundo(repo)
1078 1078 else:
1079 1079 self.printdiff(repo, patchparent, fp=patchf)
1080 1080 patchf.close()
1081 1081 added = repo.status()[1]
1082 1082 for a in added:
1083 1083 f = repo.wjoin(a)
1084 1084 try:
1085 1085 os.unlink(f)
1086 1086 except OSError, e:
1087 1087 if e.errno != errno.ENOENT:
1088 1088 raise
1089 1089 try: os.removedirs(os.path.dirname(f))
1090 1090 except: pass
1091 1091 # forget the file copies in the dirstate
1092 1092 # push should readd the files later on
1093 1093 repo.dirstate.forget(a)
1094 1094 self.pop(repo, force=True)
1095 1095 self.push(repo, force=True)
1096 1096 finally:
1097 1097 del wlock
1098 1098
1099 1099 def init(self, repo, create=False):
1100 1100 if not create and os.path.isdir(self.path):
1101 1101 raise util.Abort(_("patch queue directory already exists"))
1102 1102 try:
1103 1103 os.mkdir(self.path)
1104 1104 except OSError, inst:
1105 1105 if inst.errno != errno.EEXIST or not create:
1106 1106 raise
1107 1107 if create:
1108 1108 return self.qrepo(create=True)
1109 1109
1110 1110 def unapplied(self, repo, patch=None):
1111 1111 if patch and patch not in self.series:
1112 1112 raise util.Abort(_("patch %s is not in series file") % patch)
1113 1113 if not patch:
1114 1114 start = self.series_end()
1115 1115 else:
1116 1116 start = self.series.index(patch) + 1
1117 1117 unapplied = []
1118 1118 for i in xrange(start, len(self.series)):
1119 1119 pushable, reason = self.pushable(i)
1120 1120 if pushable:
1121 1121 unapplied.append((i, self.series[i]))
1122 1122 self.explain_pushable(i)
1123 1123 return unapplied
1124 1124
1125 1125 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1126 1126 summary=False):
1127 1127 def displayname(patchname):
1128 1128 if summary:
1129 1129 msg = self.readheaders(patchname)[0]
1130 1130 msg = msg and ': ' + msg[0] or ': '
1131 1131 else:
1132 1132 msg = ''
1133 1133 return '%s%s' % (patchname, msg)
1134 1134
1135 1135 applied = dict.fromkeys([p.name for p in self.applied])
1136 1136 if length is None:
1137 1137 length = len(self.series) - start
1138 1138 if not missing:
1139 1139 for i in xrange(start, start+length):
1140 1140 patch = self.series[i]
1141 1141 if patch in applied:
1142 1142 stat = 'A'
1143 1143 elif self.pushable(i)[0]:
1144 1144 stat = 'U'
1145 1145 else:
1146 1146 stat = 'G'
1147 1147 pfx = ''
1148 1148 if self.ui.verbose:
1149 1149 pfx = '%d %s ' % (i, stat)
1150 1150 elif status and status != stat:
1151 1151 continue
1152 1152 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1153 1153 else:
1154 1154 msng_list = []
1155 1155 for root, dirs, files in os.walk(self.path):
1156 1156 d = root[len(self.path) + 1:]
1157 1157 for f in files:
1158 1158 fl = os.path.join(d, f)
1159 1159 if (fl not in self.series and
1160 1160 fl not in (self.status_path, self.series_path,
1161 1161 self.guards_path)
1162 1162 and not fl.startswith('.')):
1163 1163 msng_list.append(fl)
1164 1164 msng_list.sort()
1165 1165 for x in msng_list:
1166 1166 pfx = self.ui.verbose and ('D ') or ''
1167 1167 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1168 1168
1169 1169 def issaveline(self, l):
1170 1170 if l.name == '.hg.patches.save.line':
1171 1171 return True
1172 1172
1173 1173 def qrepo(self, create=False):
1174 1174 if create or os.path.isdir(self.join(".hg")):
1175 1175 return hg.repository(self.ui, path=self.path, create=create)
1176 1176
1177 1177 def restore(self, repo, rev, delete=None, qupdate=None):
1178 1178 c = repo.changelog.read(rev)
1179 1179 desc = c[4].strip()
1180 1180 lines = desc.splitlines()
1181 1181 i = 0
1182 1182 datastart = None
1183 1183 series = []
1184 1184 applied = []
1185 1185 qpp = None
1186 1186 for i in xrange(0, len(lines)):
1187 1187 if lines[i] == 'Patch Data:':
1188 1188 datastart = i + 1
1189 1189 elif lines[i].startswith('Dirstate:'):
1190 1190 l = lines[i].rstrip()
1191 1191 l = l[10:].split(' ')
1192 1192 qpp = [ hg.bin(x) for x in l ]
1193 1193 elif datastart != None:
1194 1194 l = lines[i].rstrip()
1195 1195 se = statusentry(l)
1196 1196 file_ = se.name
1197 1197 if se.rev:
1198 1198 applied.append(se)
1199 1199 else:
1200 1200 series.append(file_)
1201 1201 if datastart == None:
1202 1202 self.ui.warn("No saved patch data found\n")
1203 1203 return 1
1204 1204 self.ui.warn("restoring status: %s\n" % lines[0])
1205 1205 self.full_series = series
1206 1206 self.applied = applied
1207 1207 self.parse_series()
1208 1208 self.series_dirty = 1
1209 1209 self.applied_dirty = 1
1210 1210 heads = repo.changelog.heads()
1211 1211 if delete:
1212 1212 if rev not in heads:
1213 1213 self.ui.warn("save entry has children, leaving it alone\n")
1214 1214 else:
1215 1215 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1216 1216 pp = repo.dirstate.parents()
1217 1217 if rev in pp:
1218 1218 update = True
1219 1219 else:
1220 1220 update = False
1221 1221 self.strip(repo, rev, update=update, backup='strip')
1222 1222 if qpp:
1223 1223 self.ui.warn("saved queue repository parents: %s %s\n" %
1224 1224 (hg.short(qpp[0]), hg.short(qpp[1])))
1225 1225 if qupdate:
1226 1226 print "queue directory updating"
1227 1227 r = self.qrepo()
1228 1228 if not r:
1229 1229 self.ui.warn("Unable to load queue repository\n")
1230 1230 return 1
1231 1231 hg.clean(r, qpp[0])
1232 1232
1233 1233 def save(self, repo, msg=None):
1234 1234 if len(self.applied) == 0:
1235 1235 self.ui.warn("save: no patches applied, exiting\n")
1236 1236 return 1
1237 1237 if self.issaveline(self.applied[-1]):
1238 1238 self.ui.warn("status is already saved\n")
1239 1239 return 1
1240 1240
1241 1241 ar = [ ':' + x for x in self.full_series ]
1242 1242 if not msg:
1243 1243 msg = "hg patches saved state"
1244 1244 else:
1245 1245 msg = "hg patches: " + msg.rstrip('\r\n')
1246 1246 r = self.qrepo()
1247 1247 if r:
1248 1248 pp = r.dirstate.parents()
1249 1249 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1250 1250 msg += "\n\nPatch Data:\n"
1251 1251 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1252 1252 "\n".join(ar) + '\n' or "")
1253 1253 n = repo.commit(None, text, user=None, force=1)
1254 1254 if not n:
1255 1255 self.ui.warn("repo commit failed\n")
1256 1256 return 1
1257 1257 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1258 1258 self.applied_dirty = 1
1259 1259 self.removeundo(repo)
1260 1260
1261 1261 def full_series_end(self):
1262 1262 if len(self.applied) > 0:
1263 1263 p = self.applied[-1].name
1264 1264 end = self.find_series(p)
1265 1265 if end == None:
1266 1266 return len(self.full_series)
1267 1267 return end + 1
1268 1268 return 0
1269 1269
1270 1270 def series_end(self, all_patches=False):
1271 1271 """If all_patches is False, return the index of the next pushable patch
1272 1272 in the series, or the series length. If all_patches is True, return the
1273 1273 index of the first patch past the last applied one.
1274 1274 """
1275 1275 end = 0
1276 1276 def next(start):
1277 1277 if all_patches:
1278 1278 return start
1279 1279 i = start
1280 1280 while i < len(self.series):
1281 1281 p, reason = self.pushable(i)
1282 1282 if p:
1283 1283 break
1284 1284 self.explain_pushable(i)
1285 1285 i += 1
1286 1286 return i
1287 1287 if len(self.applied) > 0:
1288 1288 p = self.applied[-1].name
1289 1289 try:
1290 1290 end = self.series.index(p)
1291 1291 except ValueError:
1292 1292 return 0
1293 1293 return next(end + 1)
1294 1294 return next(end)
1295 1295
1296 1296 def appliedname(self, index):
1297 1297 pname = self.applied[index].name
1298 1298 if not self.ui.verbose:
1299 1299 p = pname
1300 1300 else:
1301 1301 p = str(self.series.index(pname)) + " " + pname
1302 1302 return p
1303 1303
1304 1304 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1305 1305 force=None, git=False):
1306 1306 def checkseries(patchname):
1307 1307 if patchname in self.series:
1308 1308 raise util.Abort(_('patch %s is already in the series file')
1309 1309 % patchname)
1310 1310 def checkfile(patchname):
1311 1311 if not force and os.path.exists(self.join(patchname)):
1312 1312 raise util.Abort(_('patch "%s" already exists')
1313 1313 % patchname)
1314 1314
1315 1315 if rev:
1316 1316 if files:
1317 1317 raise util.Abort(_('option "-r" not valid when importing '
1318 1318 'files'))
1319 1319 rev = cmdutil.revrange(repo, rev)
1320 1320 rev.sort(lambda x, y: cmp(y, x))
1321 1321 if (len(files) > 1 or len(rev) > 1) and patchname:
1322 1322 raise util.Abort(_('option "-n" not valid when importing multiple '
1323 1323 'patches'))
1324 1324 i = 0
1325 1325 added = []
1326 1326 if rev:
1327 1327 # If mq patches are applied, we can only import revisions
1328 1328 # that form a linear path to qbase.
1329 1329 # Otherwise, they should form a linear path to a head.
1330 1330 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1331 1331 if len(heads) > 1:
1332 1332 raise util.Abort(_('revision %d is the root of more than one '
1333 1333 'branch') % rev[-1])
1334 1334 if self.applied:
1335 1335 base = revlog.hex(repo.changelog.node(rev[0]))
1336 1336 if base in [n.rev for n in self.applied]:
1337 1337 raise util.Abort(_('revision %d is already managed')
1338 1338 % rev[0])
1339 1339 if heads != [revlog.bin(self.applied[-1].rev)]:
1340 1340 raise util.Abort(_('revision %d is not the parent of '
1341 1341 'the queue') % rev[0])
1342 1342 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1343 1343 lastparent = repo.changelog.parentrevs(base)[0]
1344 1344 else:
1345 1345 if heads != [repo.changelog.node(rev[0])]:
1346 1346 raise util.Abort(_('revision %d has unmanaged children')
1347 1347 % rev[0])
1348 1348 lastparent = None
1349 1349
1350 1350 if git:
1351 1351 self.diffopts().git = True
1352 1352
1353 1353 for r in rev:
1354 1354 p1, p2 = repo.changelog.parentrevs(r)
1355 1355 n = repo.changelog.node(r)
1356 1356 if p2 != revlog.nullrev:
1357 1357 raise util.Abort(_('cannot import merge revision %d') % r)
1358 1358 if lastparent and lastparent != r:
1359 1359 raise util.Abort(_('revision %d is not the parent of %d')
1360 1360 % (r, lastparent))
1361 1361 lastparent = p1
1362 1362
1363 1363 if not patchname:
1364 1364 patchname = normname('%d.diff' % r)
1365 1365 checkseries(patchname)
1366 1366 checkfile(patchname)
1367 1367 self.full_series.insert(0, patchname)
1368 1368
1369 1369 patchf = self.opener(patchname, "w")
1370 1370 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1371 1371 patchf.close()
1372 1372
1373 1373 se = statusentry(revlog.hex(n), patchname)
1374 1374 self.applied.insert(0, se)
1375 1375
1376 1376 added.append(patchname)
1377 1377 patchname = None
1378 1378 self.parse_series()
1379 1379 self.applied_dirty = 1
1380 1380
1381 1381 for filename in files:
1382 1382 if existing:
1383 1383 if filename == '-':
1384 1384 raise util.Abort(_('-e is incompatible with import from -'))
1385 1385 if not patchname:
1386 1386 patchname = normname(filename)
1387 1387 if not os.path.isfile(self.join(patchname)):
1388 1388 raise util.Abort(_("patch %s does not exist") % patchname)
1389 1389 else:
1390 1390 try:
1391 1391 if filename == '-':
1392 1392 if not patchname:
1393 1393 raise util.Abort(_('need --name to import a patch from -'))
1394 1394 text = sys.stdin.read()
1395 1395 else:
1396 1396 text = file(filename).read()
1397 1397 except IOError:
1398 1398 raise util.Abort(_("unable to read %s") % patchname)
1399 1399 if not patchname:
1400 1400 patchname = normname(os.path.basename(filename))
1401 1401 checkfile(patchname)
1402 1402 patchf = self.opener(patchname, "w")
1403 1403 patchf.write(text)
1404 1404 checkseries(patchname)
1405 1405 index = self.full_series_end() + i
1406 1406 self.full_series[index:index] = [patchname]
1407 1407 self.parse_series()
1408 1408 self.ui.warn("adding %s to series file\n" % patchname)
1409 1409 i += 1
1410 1410 added.append(patchname)
1411 1411 patchname = None
1412 1412 self.series_dirty = 1
1413 1413 qrepo = self.qrepo()
1414 1414 if qrepo:
1415 1415 qrepo.add(added)
1416 1416
1417 1417 def delete(ui, repo, *patches, **opts):
1418 1418 """remove patches from queue
1419 1419
1420 1420 The patches must not be applied, unless they are arguments to
1421 1421 the --rev parameter. At least one patch or revision is required.
1422 1422
1423 1423 With --rev, mq will stop managing the named revisions (converting
1424 1424 them to regular mercurial changesets). The patches must be applied
1425 1425 and at the base of the stack. This option is useful when the patches
1426 1426 have been applied upstream.
1427 1427
1428 1428 With --keep, the patch files are preserved in the patch directory."""
1429 1429 q = repo.mq
1430 1430 q.delete(repo, patches, opts)
1431 1431 q.save_dirty()
1432 1432 return 0
1433 1433
1434 1434 def applied(ui, repo, patch=None, **opts):
1435 1435 """print the patches already applied"""
1436 1436 q = repo.mq
1437 1437 if patch:
1438 1438 if patch not in q.series:
1439 1439 raise util.Abort(_("patch %s is not in series file") % patch)
1440 1440 end = q.series.index(patch) + 1
1441 1441 else:
1442 1442 end = q.series_end(True)
1443 1443 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1444 1444
1445 1445 def unapplied(ui, repo, patch=None, **opts):
1446 1446 """print the patches not yet applied"""
1447 1447 q = repo.mq
1448 1448 if patch:
1449 1449 if patch not in q.series:
1450 1450 raise util.Abort(_("patch %s is not in series file") % patch)
1451 1451 start = q.series.index(patch) + 1
1452 1452 else:
1453 1453 start = q.series_end(True)
1454 1454 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1455 1455
1456 1456 def qimport(ui, repo, *filename, **opts):
1457 1457 """import a patch
1458 1458
1459 1459 The patch will have the same name as its source file unless you
1460 1460 give it a new one with --name.
1461 1461
1462 1462 You can register an existing patch inside the patch directory
1463 1463 with the --existing flag.
1464 1464
1465 1465 With --force, an existing patch of the same name will be overwritten.
1466 1466
1467 1467 An existing changeset may be placed under mq control with --rev
1468 1468 (e.g. qimport --rev tip -n patch will place tip under mq control).
1469 1469 With --git, patches imported with --rev will use the git diff
1470 1470 format.
1471 1471 """
1472 1472 q = repo.mq
1473 1473 q.qimport(repo, filename, patchname=opts['name'],
1474 1474 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1475 1475 git=opts['git'])
1476 1476 q.save_dirty()
1477 1477 return 0
1478 1478
1479 1479 def init(ui, repo, **opts):
1480 1480 """init a new queue repository
1481 1481
1482 1482 The queue repository is unversioned by default. If -c is
1483 1483 specified, qinit will create a separate nested repository
1484 1484 for patches (qinit -c may also be run later to convert
1485 1485 an unversioned patch repository into a versioned one).
1486 1486 You can use qcommit to commit changes to this queue repository."""
1487 1487 q = repo.mq
1488 1488 r = q.init(repo, create=opts['create_repo'])
1489 1489 q.save_dirty()
1490 1490 if r:
1491 1491 if not os.path.exists(r.wjoin('.hgignore')):
1492 1492 fp = r.wopener('.hgignore', 'w')
1493 1493 fp.write('syntax: glob\n')
1494 1494 fp.write('status\n')
1495 1495 fp.write('guards\n')
1496 1496 fp.close()
1497 1497 if not os.path.exists(r.wjoin('series')):
1498 1498 r.wopener('series', 'w').close()
1499 1499 r.add(['.hgignore', 'series'])
1500 1500 commands.add(ui, r)
1501 1501 return 0
1502 1502
1503 1503 def clone(ui, source, dest=None, **opts):
1504 1504 '''clone main and patch repository at same time
1505 1505
1506 1506 If source is local, destination will have no patches applied. If
1507 1507 source is remote, this command can not check if patches are
1508 1508 applied in source, so cannot guarantee that patches are not
1509 1509 applied in destination. If you clone remote repository, be sure
1510 1510 before that it has no patches applied.
1511 1511
1512 1512 Source patch repository is looked for in <src>/.hg/patches by
1513 1513 default. Use -p <url> to change.
1514 1514
1515 1515 The patch directory must be a nested mercurial repository, as
1516 1516 would be created by qinit -c.
1517 1517 '''
1518 1518 def patchdir(repo):
1519 1519 url = repo.url()
1520 1520 if url.endswith('/'):
1521 1521 url = url[:-1]
1522 1522 return url + '/.hg/patches'
1523 1523 cmdutil.setremoteconfig(ui, opts)
1524 1524 if dest is None:
1525 1525 dest = hg.defaultdest(source)
1526 1526 sr = hg.repository(ui, ui.expandpath(source))
1527 1527 patchespath = opts['patches'] or patchdir(sr)
1528 1528 try:
1529 1529 pr = hg.repository(ui, patchespath)
1530 1530 except hg.RepoError:
1531 1531 raise util.Abort(_('versioned patch repository not found'
1532 1532 ' (see qinit -c)'))
1533 1533 qbase, destrev = None, None
1534 1534 if sr.local():
1535 1535 if sr.mq.applied:
1536 1536 qbase = revlog.bin(sr.mq.applied[0].rev)
1537 1537 if not hg.islocal(dest):
1538 1538 heads = dict.fromkeys(sr.heads())
1539 1539 for h in sr.heads(qbase):
1540 1540 del heads[h]
1541 1541 destrev = heads.keys()
1542 1542 destrev.append(sr.changelog.parents(qbase)[0])
1543 1543 ui.note(_('cloning main repo\n'))
1544 1544 sr, dr = hg.clone(ui, sr.url(), dest,
1545 1545 pull=opts['pull'],
1546 1546 rev=destrev,
1547 1547 update=False,
1548 1548 stream=opts['uncompressed'])
1549 1549 ui.note(_('cloning patch repo\n'))
1550 1550 spr, dpr = hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1551 1551 pull=opts['pull'], update=not opts['noupdate'],
1552 1552 stream=opts['uncompressed'])
1553 1553 if dr.local():
1554 1554 if qbase:
1555 1555 ui.note(_('stripping applied patches from destination repo\n'))
1556 1556 dr.mq.strip(dr, qbase, update=False, backup=None)
1557 1557 if not opts['noupdate']:
1558 1558 ui.note(_('updating destination repo\n'))
1559 1559 hg.update(dr, dr.changelog.tip())
1560 1560
1561 1561 def commit(ui, repo, *pats, **opts):
1562 1562 """commit changes in the queue repository"""
1563 1563 q = repo.mq
1564 1564 r = q.qrepo()
1565 1565 if not r: raise util.Abort('no queue repository')
1566 1566 commands.commit(r.ui, r, *pats, **opts)
1567 1567
1568 1568 def series(ui, repo, **opts):
1569 1569 """print the entire series file"""
1570 1570 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1571 1571 return 0
1572 1572
1573 1573 def top(ui, repo, **opts):
1574 1574 """print the name of the current patch"""
1575 1575 q = repo.mq
1576 1576 t = q.applied and q.series_end(True) or 0
1577 1577 if t:
1578 1578 return q.qseries(repo, start=t-1, length=1, status='A',
1579 1579 summary=opts.get('summary'))
1580 1580 else:
1581 1581 ui.write("No patches applied\n")
1582 1582 return 1
1583 1583
1584 1584 def next(ui, repo, **opts):
1585 1585 """print the name of the next patch"""
1586 1586 q = repo.mq
1587 1587 end = q.series_end()
1588 1588 if end == len(q.series):
1589 1589 ui.write("All patches applied\n")
1590 1590 return 1
1591 1591 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1592 1592
1593 1593 def prev(ui, repo, **opts):
1594 1594 """print the name of the previous patch"""
1595 1595 q = repo.mq
1596 1596 l = len(q.applied)
1597 1597 if l == 1:
1598 1598 ui.write("Only one patch applied\n")
1599 1599 return 1
1600 1600 if not l:
1601 1601 ui.write("No patches applied\n")
1602 1602 return 1
1603 1603 return q.qseries(repo, start=l-2, length=1, status='A',
1604 1604 summary=opts.get('summary'))
1605 1605
1606 1606 def new(ui, repo, patch, *args, **opts):
1607 1607 """create a new patch
1608 1608
1609 1609 qnew creates a new patch on top of the currently-applied patch
1610 1610 (if any). It will refuse to run if there are any outstanding
1611 1611 changes unless -f is specified, in which case the patch will
1612 1612 be initialised with them. You may also use -I, -X, and/or a list of
1613 1613 files after the patch name to add only changes to matching files
1614 1614 to the new patch, leaving the rest as uncommitted modifications.
1615 1615
1616 1616 -e, -m or -l set the patch header as well as the commit message.
1617 1617 If none is specified, the patch header is empty and the
1618 1618 commit message is '[mq]: PATCH'"""
1619 1619 q = repo.mq
1620 1620 message = cmdutil.logmessage(opts)
1621 1621 if opts['edit']:
1622 1622 message = ui.edit(message, ui.username())
1623 1623 opts['msg'] = message
1624 1624 q.new(repo, patch, *args, **opts)
1625 1625 q.save_dirty()
1626 1626 return 0
1627 1627
1628 1628 def refresh(ui, repo, *pats, **opts):
1629 1629 """update the current patch
1630 1630
1631 1631 If any file patterns are provided, the refreshed patch will contain only
1632 1632 the modifications that match those patterns; the remaining modifications
1633 1633 will remain in the working directory.
1634 1634
1635 1635 hg add/remove/copy/rename work as usual, though you might want to use
1636 1636 git-style patches (--git or [diff] git=1) to track copies and renames.
1637 1637 """
1638 1638 q = repo.mq
1639 1639 message = cmdutil.logmessage(opts)
1640 1640 if opts['edit']:
1641 if not q.applied:
1642 ui.write(_("No patches applied\n"))
1643 return 1
1641 1644 if message:
1642 1645 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1643 1646 patch = q.applied[-1].name
1644 1647 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1645 1648 message = ui.edit('\n'.join(message), user or ui.username())
1646 1649 ret = q.refresh(repo, pats, msg=message, **opts)
1647 1650 q.save_dirty()
1648 1651 return ret
1649 1652
1650 1653 def diff(ui, repo, *pats, **opts):
1651 1654 """diff of the current patch"""
1652 1655 repo.mq.diff(repo, pats, opts)
1653 1656 return 0
1654 1657
1655 1658 def fold(ui, repo, *files, **opts):
1656 1659 """fold the named patches into the current patch
1657 1660
1658 1661 Patches must not yet be applied. Each patch will be successively
1659 1662 applied to the current patch in the order given. If all the
1660 1663 patches apply successfully, the current patch will be refreshed
1661 1664 with the new cumulative patch, and the folded patches will
1662 1665 be deleted. With -k/--keep, the folded patch files will not
1663 1666 be removed afterwards.
1664 1667
1665 1668 The header for each folded patch will be concatenated with
1666 1669 the current patch header, separated by a line of '* * *'."""
1667 1670
1668 1671 q = repo.mq
1669 1672
1670 1673 if not files:
1671 1674 raise util.Abort(_('qfold requires at least one patch name'))
1672 1675 if not q.check_toppatch(repo):
1673 1676 raise util.Abort(_('No patches applied'))
1674 1677
1675 1678 message = cmdutil.logmessage(opts)
1676 1679 if opts['edit']:
1677 1680 if message:
1678 1681 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1679 1682
1680 1683 parent = q.lookup('qtip')
1681 1684 patches = []
1682 1685 messages = []
1683 1686 for f in files:
1684 1687 p = q.lookup(f)
1685 1688 if p in patches or p == parent:
1686 1689 ui.warn(_('Skipping already folded patch %s') % p)
1687 1690 if q.isapplied(p):
1688 1691 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1689 1692 patches.append(p)
1690 1693
1691 1694 for p in patches:
1692 1695 if not message:
1693 1696 messages.append(q.readheaders(p)[0])
1694 1697 pf = q.join(p)
1695 1698 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1696 1699 if not patchsuccess:
1697 1700 raise util.Abort(_('Error folding patch %s') % p)
1698 1701 patch.updatedir(ui, repo, files)
1699 1702
1700 1703 if not message:
1701 1704 message, comments, user = q.readheaders(parent)[0:3]
1702 1705 for msg in messages:
1703 1706 message.append('* * *')
1704 1707 message.extend(msg)
1705 1708 message = '\n'.join(message)
1706 1709
1707 1710 if opts['edit']:
1708 1711 message = ui.edit(message, user or ui.username())
1709 1712
1710 1713 q.refresh(repo, msg=message)
1711 1714 q.delete(repo, patches, opts)
1712 1715 q.save_dirty()
1713 1716
1714 1717 def goto(ui, repo, patch, **opts):
1715 1718 '''push or pop patches until named patch is at top of stack'''
1716 1719 q = repo.mq
1717 1720 patch = q.lookup(patch)
1718 1721 if q.isapplied(patch):
1719 1722 ret = q.pop(repo, patch, force=opts['force'])
1720 1723 else:
1721 1724 ret = q.push(repo, patch, force=opts['force'])
1722 1725 q.save_dirty()
1723 1726 return ret
1724 1727
1725 1728 def guard(ui, repo, *args, **opts):
1726 1729 '''set or print guards for a patch
1727 1730
1728 1731 Guards control whether a patch can be pushed. A patch with no
1729 1732 guards is always pushed. A patch with a positive guard ("+foo") is
1730 1733 pushed only if the qselect command has activated it. A patch with
1731 1734 a negative guard ("-foo") is never pushed if the qselect command
1732 1735 has activated it.
1733 1736
1734 1737 With no arguments, print the currently active guards.
1735 1738 With arguments, set guards for the named patch.
1736 1739
1737 1740 To set a negative guard "-foo" on topmost patch ("--" is needed so
1738 1741 hg will not interpret "-foo" as an option):
1739 1742 hg qguard -- -foo
1740 1743
1741 1744 To set guards on another patch:
1742 1745 hg qguard other.patch +2.6.17 -stable
1743 1746 '''
1744 1747 def status(idx):
1745 1748 guards = q.series_guards[idx] or ['unguarded']
1746 1749 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1747 1750 q = repo.mq
1748 1751 patch = None
1749 1752 args = list(args)
1750 1753 if opts['list']:
1751 1754 if args or opts['none']:
1752 1755 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1753 1756 for i in xrange(len(q.series)):
1754 1757 status(i)
1755 1758 return
1756 1759 if not args or args[0][0:1] in '-+':
1757 1760 if not q.applied:
1758 1761 raise util.Abort(_('no patches applied'))
1759 1762 patch = q.applied[-1].name
1760 1763 if patch is None and args[0][0:1] not in '-+':
1761 1764 patch = args.pop(0)
1762 1765 if patch is None:
1763 1766 raise util.Abort(_('no patch to work with'))
1764 1767 if args or opts['none']:
1765 1768 idx = q.find_series(patch)
1766 1769 if idx is None:
1767 1770 raise util.Abort(_('no patch named %s') % patch)
1768 1771 q.set_guards(idx, args)
1769 1772 q.save_dirty()
1770 1773 else:
1771 1774 status(q.series.index(q.lookup(patch)))
1772 1775
1773 1776 def header(ui, repo, patch=None):
1774 1777 """Print the header of the topmost or specified patch"""
1775 1778 q = repo.mq
1776 1779
1777 1780 if patch:
1778 1781 patch = q.lookup(patch)
1779 1782 else:
1780 1783 if not q.applied:
1781 1784 ui.write('No patches applied\n')
1782 1785 return 1
1783 1786 patch = q.lookup('qtip')
1784 1787 message = repo.mq.readheaders(patch)[0]
1785 1788
1786 1789 ui.write('\n'.join(message) + '\n')
1787 1790
1788 1791 def lastsavename(path):
1789 1792 (directory, base) = os.path.split(path)
1790 1793 names = os.listdir(directory)
1791 1794 namere = re.compile("%s.([0-9]+)" % base)
1792 1795 maxindex = None
1793 1796 maxname = None
1794 1797 for f in names:
1795 1798 m = namere.match(f)
1796 1799 if m:
1797 1800 index = int(m.group(1))
1798 1801 if maxindex == None or index > maxindex:
1799 1802 maxindex = index
1800 1803 maxname = f
1801 1804 if maxname:
1802 1805 return (os.path.join(directory, maxname), maxindex)
1803 1806 return (None, None)
1804 1807
1805 1808 def savename(path):
1806 1809 (last, index) = lastsavename(path)
1807 1810 if last is None:
1808 1811 index = 0
1809 1812 newpath = path + ".%d" % (index + 1)
1810 1813 return newpath
1811 1814
1812 1815 def push(ui, repo, patch=None, **opts):
1813 1816 """push the next patch onto the stack"""
1814 1817 q = repo.mq
1815 1818 mergeq = None
1816 1819
1817 1820 if opts['all']:
1818 1821 if not q.series:
1819 1822 ui.warn(_('no patches in series\n'))
1820 1823 return 0
1821 1824 patch = q.series[-1]
1822 1825 if opts['merge']:
1823 1826 if opts['name']:
1824 1827 newpath = opts['name']
1825 1828 else:
1826 1829 newpath, i = lastsavename(q.path)
1827 1830 if not newpath:
1828 1831 ui.warn("no saved queues found, please use -n\n")
1829 1832 return 1
1830 1833 mergeq = queue(ui, repo.join(""), newpath)
1831 1834 ui.warn("merging with queue at: %s\n" % mergeq.path)
1832 1835 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1833 1836 mergeq=mergeq)
1834 1837 return ret
1835 1838
1836 1839 def pop(ui, repo, patch=None, **opts):
1837 1840 """pop the current patch off the stack"""
1838 1841 localupdate = True
1839 1842 if opts['name']:
1840 1843 q = queue(ui, repo.join(""), repo.join(opts['name']))
1841 1844 ui.warn('using patch queue: %s\n' % q.path)
1842 1845 localupdate = False
1843 1846 else:
1844 1847 q = repo.mq
1845 1848 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1846 1849 all=opts['all'])
1847 1850 q.save_dirty()
1848 1851 return ret
1849 1852
1850 1853 def rename(ui, repo, patch, name=None, **opts):
1851 1854 """rename a patch
1852 1855
1853 1856 With one argument, renames the current patch to PATCH1.
1854 1857 With two arguments, renames PATCH1 to PATCH2."""
1855 1858
1856 1859 q = repo.mq
1857 1860
1858 1861 if not name:
1859 1862 name = patch
1860 1863 patch = None
1861 1864
1862 1865 if patch:
1863 1866 patch = q.lookup(patch)
1864 1867 else:
1865 1868 if not q.applied:
1866 1869 ui.write(_('No patches applied\n'))
1867 1870 return
1868 1871 patch = q.lookup('qtip')
1869 1872 absdest = q.join(name)
1870 1873 if os.path.isdir(absdest):
1871 1874 name = normname(os.path.join(name, os.path.basename(patch)))
1872 1875 absdest = q.join(name)
1873 1876 if os.path.exists(absdest):
1874 1877 raise util.Abort(_('%s already exists') % absdest)
1875 1878
1876 1879 if name in q.series:
1877 1880 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1878 1881
1879 1882 if ui.verbose:
1880 1883 ui.write('Renaming %s to %s\n' % (patch, name))
1881 1884 i = q.find_series(patch)
1882 1885 guards = q.guard_re.findall(q.full_series[i])
1883 1886 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1884 1887 q.parse_series()
1885 1888 q.series_dirty = 1
1886 1889
1887 1890 info = q.isapplied(patch)
1888 1891 if info:
1889 1892 q.applied[info[0]] = statusentry(info[1], name)
1890 1893 q.applied_dirty = 1
1891 1894
1892 1895 util.rename(q.join(patch), absdest)
1893 1896 r = q.qrepo()
1894 1897 if r:
1895 1898 wlock = r.wlock()
1896 1899 try:
1897 1900 if r.dirstate[name] == 'r':
1898 1901 r.undelete([name])
1899 1902 r.copy(patch, name)
1900 1903 r.remove([patch], False)
1901 1904 finally:
1902 1905 del wlock
1903 1906
1904 1907 q.save_dirty()
1905 1908
1906 1909 def restore(ui, repo, rev, **opts):
1907 1910 """restore the queue state saved by a rev"""
1908 1911 rev = repo.lookup(rev)
1909 1912 q = repo.mq
1910 1913 q.restore(repo, rev, delete=opts['delete'],
1911 1914 qupdate=opts['update'])
1912 1915 q.save_dirty()
1913 1916 return 0
1914 1917
1915 1918 def save(ui, repo, **opts):
1916 1919 """save current queue state"""
1917 1920 q = repo.mq
1918 1921 message = cmdutil.logmessage(opts)
1919 1922 ret = q.save(repo, msg=message)
1920 1923 if ret:
1921 1924 return ret
1922 1925 q.save_dirty()
1923 1926 if opts['copy']:
1924 1927 path = q.path
1925 1928 if opts['name']:
1926 1929 newpath = os.path.join(q.basepath, opts['name'])
1927 1930 if os.path.exists(newpath):
1928 1931 if not os.path.isdir(newpath):
1929 1932 raise util.Abort(_('destination %s exists and is not '
1930 1933 'a directory') % newpath)
1931 1934 if not opts['force']:
1932 1935 raise util.Abort(_('destination %s exists, '
1933 1936 'use -f to force') % newpath)
1934 1937 else:
1935 1938 newpath = savename(path)
1936 1939 ui.warn("copy %s to %s\n" % (path, newpath))
1937 1940 util.copyfiles(path, newpath)
1938 1941 if opts['empty']:
1939 1942 try:
1940 1943 os.unlink(q.join(q.status_path))
1941 1944 except:
1942 1945 pass
1943 1946 return 0
1944 1947
1945 1948 def strip(ui, repo, rev, **opts):
1946 1949 """strip a revision and all later revs on the same branch"""
1947 1950 rev = repo.lookup(rev)
1948 1951 backup = 'all'
1949 1952 if opts['backup']:
1950 1953 backup = 'strip'
1951 1954 elif opts['nobackup']:
1952 1955 backup = 'none'
1953 1956 update = repo.dirstate.parents()[0] != revlog.nullid
1954 1957 repo.mq.strip(repo, rev, backup=backup, update=update)
1955 1958 return 0
1956 1959
1957 1960 def select(ui, repo, *args, **opts):
1958 1961 '''set or print guarded patches to push
1959 1962
1960 1963 Use the qguard command to set or print guards on patch, then use
1961 1964 qselect to tell mq which guards to use. A patch will be pushed if it
1962 1965 has no guards or any positive guards match the currently selected guard,
1963 1966 but will not be pushed if any negative guards match the current guard.
1964 1967 For example:
1965 1968
1966 1969 qguard foo.patch -stable (negative guard)
1967 1970 qguard bar.patch +stable (positive guard)
1968 1971 qselect stable
1969 1972
1970 1973 This activates the "stable" guard. mq will skip foo.patch (because
1971 1974 it has a negative match) but push bar.patch (because it
1972 1975 has a positive match).
1973 1976
1974 1977 With no arguments, prints the currently active guards.
1975 1978 With one argument, sets the active guard.
1976 1979
1977 1980 Use -n/--none to deactivate guards (no other arguments needed).
1978 1981 When no guards are active, patches with positive guards are skipped
1979 1982 and patches with negative guards are pushed.
1980 1983
1981 1984 qselect can change the guards on applied patches. It does not pop
1982 1985 guarded patches by default. Use --pop to pop back to the last applied
1983 1986 patch that is not guarded. Use --reapply (which implies --pop) to push
1984 1987 back to the current patch afterwards, but skip guarded patches.
1985 1988
1986 1989 Use -s/--series to print a list of all guards in the series file (no
1987 1990 other arguments needed). Use -v for more information.'''
1988 1991
1989 1992 q = repo.mq
1990 1993 guards = q.active()
1991 1994 if args or opts['none']:
1992 1995 old_unapplied = q.unapplied(repo)
1993 1996 old_guarded = [i for i in xrange(len(q.applied)) if
1994 1997 not q.pushable(i)[0]]
1995 1998 q.set_active(args)
1996 1999 q.save_dirty()
1997 2000 if not args:
1998 2001 ui.status(_('guards deactivated\n'))
1999 2002 if not opts['pop'] and not opts['reapply']:
2000 2003 unapplied = q.unapplied(repo)
2001 2004 guarded = [i for i in xrange(len(q.applied))
2002 2005 if not q.pushable(i)[0]]
2003 2006 if len(unapplied) != len(old_unapplied):
2004 2007 ui.status(_('number of unguarded, unapplied patches has '
2005 2008 'changed from %d to %d\n') %
2006 2009 (len(old_unapplied), len(unapplied)))
2007 2010 if len(guarded) != len(old_guarded):
2008 2011 ui.status(_('number of guarded, applied patches has changed '
2009 2012 'from %d to %d\n') %
2010 2013 (len(old_guarded), len(guarded)))
2011 2014 elif opts['series']:
2012 2015 guards = {}
2013 2016 noguards = 0
2014 2017 for gs in q.series_guards:
2015 2018 if not gs:
2016 2019 noguards += 1
2017 2020 for g in gs:
2018 2021 guards.setdefault(g, 0)
2019 2022 guards[g] += 1
2020 2023 if ui.verbose:
2021 2024 guards['NONE'] = noguards
2022 2025 guards = guards.items()
2023 2026 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2024 2027 if guards:
2025 2028 ui.note(_('guards in series file:\n'))
2026 2029 for guard, count in guards:
2027 2030 ui.note('%2d ' % count)
2028 2031 ui.write(guard, '\n')
2029 2032 else:
2030 2033 ui.note(_('no guards in series file\n'))
2031 2034 else:
2032 2035 if guards:
2033 2036 ui.note(_('active guards:\n'))
2034 2037 for g in guards:
2035 2038 ui.write(g, '\n')
2036 2039 else:
2037 2040 ui.write(_('no active guards\n'))
2038 2041 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2039 2042 popped = False
2040 2043 if opts['pop'] or opts['reapply']:
2041 2044 for i in xrange(len(q.applied)):
2042 2045 pushable, reason = q.pushable(i)
2043 2046 if not pushable:
2044 2047 ui.status(_('popping guarded patches\n'))
2045 2048 popped = True
2046 2049 if i == 0:
2047 2050 q.pop(repo, all=True)
2048 2051 else:
2049 2052 q.pop(repo, i-1)
2050 2053 break
2051 2054 if popped:
2052 2055 try:
2053 2056 if reapply:
2054 2057 ui.status(_('reapplying unguarded patches\n'))
2055 2058 q.push(repo, reapply)
2056 2059 finally:
2057 2060 q.save_dirty()
2058 2061
2059 2062 def reposetup(ui, repo):
2060 2063 class mqrepo(repo.__class__):
2061 2064 def abort_if_wdir_patched(self, errmsg, force=False):
2062 2065 if self.mq.applied and not force:
2063 2066 parent = revlog.hex(self.dirstate.parents()[0])
2064 2067 if parent in [s.rev for s in self.mq.applied]:
2065 2068 raise util.Abort(errmsg)
2066 2069
2067 2070 def commit(self, *args, **opts):
2068 2071 if len(args) >= 6:
2069 2072 force = args[5]
2070 2073 else:
2071 2074 force = opts.get('force')
2072 2075 self.abort_if_wdir_patched(
2073 2076 _('cannot commit over an applied mq patch'),
2074 2077 force)
2075 2078
2076 2079 return super(mqrepo, self).commit(*args, **opts)
2077 2080
2078 2081 def push(self, remote, force=False, revs=None):
2079 2082 if self.mq.applied and not force and not revs:
2080 2083 raise util.Abort(_('source has mq patches applied'))
2081 2084 return super(mqrepo, self).push(remote, force, revs)
2082 2085
2083 2086 def tags(self):
2084 2087 if self.tagscache:
2085 2088 return self.tagscache
2086 2089
2087 2090 tagscache = super(mqrepo, self).tags()
2088 2091
2089 2092 q = self.mq
2090 2093 if not q.applied:
2091 2094 return tagscache
2092 2095
2093 2096 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2094 2097 mqtags.append((mqtags[-1][0], 'qtip'))
2095 2098 mqtags.append((mqtags[0][0], 'qbase'))
2096 2099 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2097 2100 for patch in mqtags:
2098 2101 if patch[1] in tagscache:
2099 2102 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2100 2103 else:
2101 2104 tagscache[patch[1]] = patch[0]
2102 2105
2103 2106 return tagscache
2104 2107
2105 2108 def _branchtags(self):
2106 2109 q = self.mq
2107 2110 if not q.applied:
2108 2111 return super(mqrepo, self)._branchtags()
2109 2112
2110 2113 self.branchcache = {} # avoid recursion in changectx
2111 2114 cl = self.changelog
2112 2115 partial, last, lrev = self._readbranchcache()
2113 2116
2114 2117 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2115 2118 start = lrev + 1
2116 2119 if start < qbase:
2117 2120 # update the cache (excluding the patches) and save it
2118 2121 self._updatebranchcache(partial, lrev+1, qbase)
2119 2122 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2120 2123 start = qbase
2121 2124 # if start = qbase, the cache is as updated as it should be.
2122 2125 # if start > qbase, the cache includes (part of) the patches.
2123 2126 # we might as well use it, but we won't save it.
2124 2127
2125 2128 # update the cache up to the tip
2126 2129 self._updatebranchcache(partial, start, cl.count())
2127 2130
2128 2131 return partial
2129 2132
2130 2133 if repo.local():
2131 2134 repo.__class__ = mqrepo
2132 2135 repo.mq = queue(ui, repo.join(""))
2133 2136
2134 2137 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2135 2138
2136 2139 cmdtable = {
2137 2140 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2138 2141 "qclone":
2139 2142 (clone,
2140 2143 [('', 'pull', None, _('use pull protocol to copy metadata')),
2141 2144 ('U', 'noupdate', None, _('do not update the new working directories')),
2142 2145 ('', 'uncompressed', None,
2143 2146 _('use uncompressed transfer (fast over LAN)')),
2144 2147 ('p', 'patches', '', _('location of source patch repo')),
2145 2148 ] + commands.remoteopts,
2146 2149 _('hg qclone [OPTION]... SOURCE [DEST]')),
2147 2150 "qcommit|qci":
2148 2151 (commit,
2149 2152 commands.table["^commit|ci"][1],
2150 2153 _('hg qcommit [OPTION]... [FILE]...')),
2151 2154 "^qdiff":
2152 2155 (diff,
2153 2156 [('g', 'git', None, _('use git extended diff format')),
2154 2157 ] + commands.walkopts,
2155 2158 _('hg qdiff [-I] [-X] [-g] [FILE]...')),
2156 2159 "qdelete|qremove|qrm":
2157 2160 (delete,
2158 2161 [('k', 'keep', None, _('keep patch file')),
2159 2162 ('r', 'rev', [], _('stop managing a revision'))],
2160 2163 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2161 2164 'qfold':
2162 2165 (fold,
2163 2166 [('e', 'edit', None, _('edit patch header')),
2164 2167 ('k', 'keep', None, _('keep folded patch files')),
2165 2168 ] + commands.commitopts,
2166 2169 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2167 2170 'qgoto':
2168 2171 (goto,
2169 2172 [('f', 'force', None, _('overwrite any local changes'))],
2170 2173 _('hg qgoto [OPTION]... PATCH')),
2171 2174 'qguard':
2172 2175 (guard,
2173 2176 [('l', 'list', None, _('list all patches and guards')),
2174 2177 ('n', 'none', None, _('drop all guards'))],
2175 2178 _('hg qguard [-l] [-n] [PATCH] [+GUARD]... [-GUARD]...')),
2176 2179 'qheader': (header, [], _('hg qheader [PATCH]')),
2177 2180 "^qimport":
2178 2181 (qimport,
2179 2182 [('e', 'existing', None, 'import file in patch dir'),
2180 2183 ('n', 'name', '', 'patch file name'),
2181 2184 ('f', 'force', None, 'overwrite existing files'),
2182 2185 ('r', 'rev', [], 'place existing revisions under mq control'),
2183 2186 ('g', 'git', None, _('use git extended diff format'))],
2184 2187 _('hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...')),
2185 2188 "^qinit":
2186 2189 (init,
2187 2190 [('c', 'create-repo', None, 'create queue repository')],
2188 2191 _('hg qinit [-c]')),
2189 2192 "qnew":
2190 2193 (new,
2191 2194 [('e', 'edit', None, _('edit commit message')),
2192 2195 ('f', 'force', None, _('import uncommitted changes into patch')),
2193 2196 ('g', 'git', None, _('use git extended diff format')),
2194 2197 ] + commands.walkopts + commands.commitopts,
2195 2198 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2196 2199 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2197 2200 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2198 2201 "^qpop":
2199 2202 (pop,
2200 2203 [('a', 'all', None, _('pop all patches')),
2201 2204 ('n', 'name', '', _('queue name to pop')),
2202 2205 ('f', 'force', None, _('forget any local changes'))],
2203 2206 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2204 2207 "^qpush":
2205 2208 (push,
2206 2209 [('f', 'force', None, _('apply if the patch has rejects')),
2207 2210 ('l', 'list', None, _('list patch name in commit text')),
2208 2211 ('a', 'all', None, _('apply all patches')),
2209 2212 ('m', 'merge', None, _('merge from another queue')),
2210 2213 ('n', 'name', '', _('merge queue name'))],
2211 2214 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2212 2215 "^qrefresh":
2213 2216 (refresh,
2214 2217 [('e', 'edit', None, _('edit commit message')),
2215 2218 ('g', 'git', None, _('use git extended diff format')),
2216 2219 ('s', 'short', None, _('refresh only files already in the patch')),
2217 2220 ] + commands.walkopts + commands.commitopts,
2218 2221 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2219 2222 'qrename|qmv':
2220 2223 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2221 2224 "qrestore":
2222 2225 (restore,
2223 2226 [('d', 'delete', None, _('delete save entry')),
2224 2227 ('u', 'update', None, _('update queue working dir'))],
2225 2228 _('hg qrestore [-d] [-u] REV')),
2226 2229 "qsave":
2227 2230 (save,
2228 2231 [('c', 'copy', None, _('copy patch directory')),
2229 2232 ('n', 'name', '', _('copy directory name')),
2230 2233 ('e', 'empty', None, _('clear queue status file')),
2231 2234 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2232 2235 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2233 2236 "qselect":
2234 2237 (select,
2235 2238 [('n', 'none', None, _('disable all guards')),
2236 2239 ('s', 'series', None, _('list all guards in series file')),
2237 2240 ('', 'pop', None, _('pop to before first guarded applied patch')),
2238 2241 ('', 'reapply', None, _('pop, then reapply patches'))],
2239 2242 _('hg qselect [OPTION]... [GUARD]...')),
2240 2243 "qseries":
2241 2244 (series,
2242 2245 [('m', 'missing', None, _('print patches not in series')),
2243 2246 ] + seriesopts,
2244 2247 _('hg qseries [-ms]')),
2245 2248 "^strip":
2246 2249 (strip,
2247 2250 [('f', 'force', None, _('force multi-head removal')),
2248 2251 ('b', 'backup', None, _('bundle unrelated changesets')),
2249 2252 ('n', 'nobackup', None, _('no backups'))],
2250 2253 _('hg strip [-f] [-b] [-n] REV')),
2251 2254 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2252 2255 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2253 2256 }
@@ -1,3156 +1,3163 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import _
10 10 import os, re, sys, urllib
11 11 import hg, util, revlog, bundlerepo, extensions
12 12 import difflib, patch, time, help, mdiff, tempfile
13 13 import errno, version, socket
14 14 import archival, changegroup, cmdutil, hgweb.server, sshserver
15 15
16 16 # Commands start here, listed alphabetically
17 17
18 18 def add(ui, repo, *pats, **opts):
19 19 """add the specified files on the next commit
20 20
21 21 Schedule files to be version controlled and added to the repository.
22 22
23 23 The files will be added to the repository at the next commit. To
24 24 undo an add before that, see hg revert.
25 25
26 26 If no names are given, add all files in the repository.
27 27 """
28 28
29 29 names = []
30 30 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
31 31 if exact:
32 32 if ui.verbose:
33 33 ui.status(_('adding %s\n') % rel)
34 34 names.append(abs)
35 35 elif abs not in repo.dirstate:
36 36 ui.status(_('adding %s\n') % rel)
37 37 names.append(abs)
38 38 if not opts.get('dry_run'):
39 39 repo.add(names)
40 40
41 41 def addremove(ui, repo, *pats, **opts):
42 42 """add all new files, delete all missing files
43 43
44 44 Add all new files and remove all missing files from the repository.
45 45
46 46 New files are ignored if they match any of the patterns in .hgignore. As
47 47 with add, these changes take effect at the next commit.
48 48
49 49 Use the -s option to detect renamed files. With a parameter > 0,
50 50 this compares every removed file with every added file and records
51 51 those similar enough as renames. This option takes a percentage
52 52 between 0 (disabled) and 100 (files must be identical) as its
53 53 parameter. Detecting renamed files this way can be expensive.
54 54 """
55 55 try:
56 56 sim = float(opts.get('similarity') or 0)
57 57 except ValueError:
58 58 raise util.Abort(_('similarity must be a number'))
59 59 if sim < 0 or sim > 100:
60 60 raise util.Abort(_('similarity must be between 0 and 100'))
61 61 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
62 62
63 63 def annotate(ui, repo, *pats, **opts):
64 64 """show changeset information per file line
65 65
66 66 List changes in files, showing the revision id responsible for each line
67 67
68 68 This command is useful to discover who did a change or when a change took
69 69 place.
70 70
71 71 Without the -a option, annotate will avoid processing files it
72 72 detects as binary. With -a, annotate will generate an annotation
73 73 anyway, probably with undesirable results.
74 74 """
75 75 getdate = util.cachefunc(lambda x: util.datestr(x[0].date()))
76 76
77 77 if not pats:
78 78 raise util.Abort(_('at least one file name or pattern required'))
79 79
80 80 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
81 81 ('number', lambda x: str(x[0].rev())),
82 82 ('changeset', lambda x: short(x[0].node())),
83 83 ('date', getdate),
84 84 ('follow', lambda x: x[0].path()),
85 85 ]
86 86
87 87 if (not opts['user'] and not opts['changeset'] and not opts['date']
88 88 and not opts['follow']):
89 89 opts['number'] = 1
90 90
91 91 linenumber = opts.get('line_number') is not None
92 92 if (linenumber and (not opts['changeset']) and (not opts['number'])):
93 93 raise util.Abort(_('at least one of -n/-c is required for -l'))
94 94
95 95 funcmap = [func for op, func in opmap if opts.get(op)]
96 96 if linenumber:
97 97 lastfunc = funcmap[-1]
98 98 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
99 99
100 100 ctx = repo.changectx(opts['rev'])
101 101
102 102 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
103 103 node=ctx.node()):
104 104 fctx = ctx.filectx(abs)
105 105 if not opts['text'] and util.binary(fctx.data()):
106 106 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
107 107 continue
108 108
109 109 lines = fctx.annotate(follow=opts.get('follow'),
110 110 linenumber=linenumber)
111 111 pieces = []
112 112
113 113 for f in funcmap:
114 114 l = [f(n) for n, dummy in lines]
115 115 if l:
116 116 m = max(map(len, l))
117 117 pieces.append(["%*s" % (m, x) for x in l])
118 118
119 119 if pieces:
120 120 for p, l in zip(zip(*pieces), lines):
121 121 ui.write("%s: %s" % (" ".join(p), l[1]))
122 122
123 123 def archive(ui, repo, dest, **opts):
124 124 '''create unversioned archive of a repository revision
125 125
126 126 By default, the revision used is the parent of the working
127 127 directory; use "-r" to specify a different revision.
128 128
129 129 To specify the type of archive to create, use "-t". Valid
130 130 types are:
131 131
132 132 "files" (default): a directory full of files
133 133 "tar": tar archive, uncompressed
134 134 "tbz2": tar archive, compressed using bzip2
135 135 "tgz": tar archive, compressed using gzip
136 136 "uzip": zip archive, uncompressed
137 137 "zip": zip archive, compressed using deflate
138 138
139 139 The exact name of the destination archive or directory is given
140 140 using a format string; see "hg help export" for details.
141 141
142 142 Each member added to an archive file has a directory prefix
143 143 prepended. Use "-p" to specify a format string for the prefix.
144 144 The default is the basename of the archive, with suffixes removed.
145 145 '''
146 146
147 147 ctx = repo.changectx(opts['rev'])
148 148 if not ctx:
149 149 raise util.Abort(_('repository has no revisions'))
150 150 node = ctx.node()
151 151 dest = cmdutil.make_filename(repo, dest, node)
152 152 if os.path.realpath(dest) == repo.root:
153 153 raise util.Abort(_('repository root cannot be destination'))
154 154 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
155 155 kind = opts.get('type') or 'files'
156 156 prefix = opts['prefix']
157 157 if dest == '-':
158 158 if kind == 'files':
159 159 raise util.Abort(_('cannot archive plain files to stdout'))
160 160 dest = sys.stdout
161 161 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
162 162 prefix = cmdutil.make_filename(repo, prefix, node)
163 163 archival.archive(repo, dest, node, kind, not opts['no_decode'],
164 164 matchfn, prefix)
165 165
166 166 def backout(ui, repo, node=None, rev=None, **opts):
167 167 '''reverse effect of earlier changeset
168 168
169 169 Commit the backed out changes as a new changeset. The new
170 170 changeset is a child of the backed out changeset.
171 171
172 172 If you back out a changeset other than the tip, a new head is
173 173 created. This head is the parent of the working directory. If
174 174 you back out an old changeset, your working directory will appear
175 175 old after the backout. You should merge the backout changeset
176 176 with another head.
177 177
178 178 The --merge option remembers the parent of the working directory
179 179 before starting the backout, then merges the new head with that
180 180 changeset afterwards. This saves you from doing the merge by
181 181 hand. The result of this merge is not committed, as for a normal
182 182 merge.'''
183 183 if rev and node:
184 184 raise util.Abort(_("please specify just one revision"))
185 185
186 186 if not rev:
187 187 rev = node
188 188
189 189 if not rev:
190 190 raise util.Abort(_("please specify a revision to backout"))
191 191
192 192 cmdutil.bail_if_changed(repo)
193 193 op1, op2 = repo.dirstate.parents()
194 194 if op2 != nullid:
195 195 raise util.Abort(_('outstanding uncommitted merge'))
196 196 node = repo.lookup(rev)
197 197 p1, p2 = repo.changelog.parents(node)
198 198 if p1 == nullid:
199 199 raise util.Abort(_('cannot back out a change with no parents'))
200 200 if p2 != nullid:
201 201 if not opts['parent']:
202 202 raise util.Abort(_('cannot back out a merge changeset without '
203 203 '--parent'))
204 204 p = repo.lookup(opts['parent'])
205 205 if p not in (p1, p2):
206 206 raise util.Abort(_('%s is not a parent of %s') %
207 207 (short(p), short(node)))
208 208 parent = p
209 209 else:
210 210 if opts['parent']:
211 211 raise util.Abort(_('cannot use --parent on non-merge changeset'))
212 212 parent = p1
213 213 hg.clean(repo, node, show_stats=False)
214 214 revert_opts = opts.copy()
215 215 revert_opts['date'] = None
216 216 revert_opts['all'] = True
217 217 revert_opts['rev'] = hex(parent)
218 218 revert(ui, repo, **revert_opts)
219 219 commit_opts = opts.copy()
220 220 commit_opts['addremove'] = False
221 221 if not commit_opts['message'] and not commit_opts['logfile']:
222 222 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
223 223 commit_opts['force_editor'] = True
224 224 commit(ui, repo, **commit_opts)
225 225 def nice(node):
226 226 return '%d:%s' % (repo.changelog.rev(node), short(node))
227 227 ui.status(_('changeset %s backs out changeset %s\n') %
228 228 (nice(repo.changelog.tip()), nice(node)))
229 229 if op1 != node:
230 230 if opts['merge']:
231 231 ui.status(_('merging with changeset %s\n') % nice(op1))
232 232 hg.merge(repo, hex(op1))
233 233 else:
234 234 ui.status(_('the backout changeset is a new head - '
235 235 'do not forget to merge\n'))
236 236 ui.status(_('(use "backout --merge" '
237 237 'if you want to auto-merge)\n'))
238 238
239 239 def branch(ui, repo, label=None, **opts):
240 240 """set or show the current branch name
241 241
242 242 With no argument, show the current branch name. With one argument,
243 243 set the working directory branch name (the branch does not exist in
244 244 the repository until the next commit).
245 245
246 246 Unless --force is specified, branch will not let you set a
247 247 branch name that shadows an existing branch.
248 248 """
249 249
250 250 if label:
251 251 if not opts.get('force') and label in repo.branchtags():
252 252 if label not in [p.branch() for p in repo.workingctx().parents()]:
253 253 raise util.Abort(_('a branch of the same name already exists'
254 254 ' (use --force to override)'))
255 255 repo.dirstate.setbranch(util.fromlocal(label))
256 256 ui.status(_('marked working directory as branch %s\n') % label)
257 257 else:
258 258 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
259 259
260 260 def branches(ui, repo, active=False):
261 261 """list repository named branches
262 262
263 263 List the repository's named branches, indicating which ones are
264 264 inactive. If active is specified, only show active branches.
265 265
266 266 A branch is considered active if it contains unmerged heads.
267 267 """
268 268 b = repo.branchtags()
269 269 heads = dict.fromkeys(repo.heads(), 1)
270 270 l = [((n in heads), repo.changelog.rev(n), n, t) for t, n in b.items()]
271 271 l.sort()
272 272 l.reverse()
273 273 for ishead, r, n, t in l:
274 274 if active and not ishead:
275 275 # If we're only displaying active branches, abort the loop on
276 276 # encountering the first inactive head
277 277 break
278 278 else:
279 279 hexfunc = ui.debugflag and hex or short
280 280 if ui.quiet:
281 281 ui.write("%s\n" % t)
282 282 else:
283 283 spaces = " " * (30 - util.locallen(t))
284 284 # The code only gets here if inactive branches are being
285 285 # displayed or the branch is active.
286 286 isinactive = ((not ishead) and " (inactive)") or ''
287 287 ui.write("%s%s %s:%s%s\n" % (t, spaces, r, hexfunc(n), isinactive))
288 288
289 289 def bundle(ui, repo, fname, dest=None, **opts):
290 290 """create a changegroup file
291 291
292 292 Generate a compressed changegroup file collecting changesets not
293 293 found in the other repository.
294 294
295 295 If no destination repository is specified the destination is assumed
296 296 to have all the nodes specified by one or more --base parameters.
297 297
298 298 The bundle file can then be transferred using conventional means and
299 299 applied to another repository with the unbundle or pull command.
300 300 This is useful when direct push and pull are not available or when
301 301 exporting an entire repository is undesirable.
302 302
303 303 Applying bundles preserves all changeset contents including
304 304 permissions, copy/rename information, and revision history.
305 305 """
306 306 revs = opts.get('rev') or None
307 307 if revs:
308 308 revs = [repo.lookup(rev) for rev in revs]
309 309 base = opts.get('base')
310 310 if base:
311 311 if dest:
312 312 raise util.Abort(_("--base is incompatible with specifiying "
313 313 "a destination"))
314 314 base = [repo.lookup(rev) for rev in base]
315 315 # create the right base
316 316 # XXX: nodesbetween / changegroup* should be "fixed" instead
317 317 o = []
318 318 has = {nullid: None}
319 319 for n in base:
320 320 has.update(repo.changelog.reachable(n))
321 321 if revs:
322 322 visit = list(revs)
323 323 else:
324 324 visit = repo.changelog.heads()
325 325 seen = {}
326 326 while visit:
327 327 n = visit.pop(0)
328 328 parents = [p for p in repo.changelog.parents(n) if p not in has]
329 329 if len(parents) == 0:
330 330 o.insert(0, n)
331 331 else:
332 332 for p in parents:
333 333 if p not in seen:
334 334 seen[p] = 1
335 335 visit.append(p)
336 336 else:
337 337 cmdutil.setremoteconfig(ui, opts)
338 338 dest, revs, checkout = hg.parseurl(
339 339 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
340 340 other = hg.repository(ui, dest)
341 341 o = repo.findoutgoing(other, force=opts['force'])
342 342
343 343 if revs:
344 344 cg = repo.changegroupsubset(o, revs, 'bundle')
345 345 else:
346 346 cg = repo.changegroup(o, 'bundle')
347 347 changegroup.writebundle(cg, fname, "HG10BZ")
348 348
349 349 def cat(ui, repo, file1, *pats, **opts):
350 350 """output the current or given revision of files
351 351
352 352 Print the specified files as they were at the given revision.
353 353 If no revision is given, the parent of the working directory is used,
354 354 or tip if no revision is checked out.
355 355
356 356 Output may be to a file, in which case the name of the file is
357 357 given using a format string. The formatting rules are the same as
358 358 for the export command, with the following additions:
359 359
360 360 %s basename of file being printed
361 361 %d dirname of file being printed, or '.' if in repo root
362 362 %p root-relative path name of file being printed
363 363 """
364 364 ctx = repo.changectx(opts['rev'])
365 365 err = 1
366 366 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
367 367 ctx.node()):
368 368 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
369 369 fp.write(ctx.filectx(abs).data())
370 370 err = 0
371 371 return err
372 372
373 373 def clone(ui, source, dest=None, **opts):
374 374 """make a copy of an existing repository
375 375
376 376 Create a copy of an existing repository in a new directory.
377 377
378 378 If no destination directory name is specified, it defaults to the
379 379 basename of the source.
380 380
381 381 The location of the source is added to the new repository's
382 382 .hg/hgrc file, as the default to be used for future pulls.
383 383
384 384 For efficiency, hardlinks are used for cloning whenever the source
385 385 and destination are on the same filesystem (note this applies only
386 386 to the repository data, not to the checked out files). Some
387 387 filesystems, such as AFS, implement hardlinking incorrectly, but
388 388 do not report errors. In these cases, use the --pull option to
389 389 avoid hardlinking.
390 390
391 391 You can safely clone repositories and checked out files using full
392 392 hardlinks with
393 393
394 394 $ cp -al REPO REPOCLONE
395 395
396 396 which is the fastest way to clone. However, the operation is not
397 397 atomic (making sure REPO is not modified during the operation is
398 398 up to you) and you have to make sure your editor breaks hardlinks
399 399 (Emacs and most Linux Kernel tools do so).
400 400
401 401 If you use the -r option to clone up to a specific revision, no
402 402 subsequent revisions will be present in the cloned repository.
403 403 This option implies --pull, even on local repositories.
404 404
405 405 See pull for valid source format details.
406 406
407 407 It is possible to specify an ssh:// URL as the destination, but no
408 408 .hg/hgrc and working directory will be created on the remote side.
409 409 Look at the help text for the pull command for important details
410 410 about ssh:// URLs.
411 411 """
412 412 cmdutil.setremoteconfig(ui, opts)
413 413 hg.clone(ui, source, dest,
414 414 pull=opts['pull'],
415 415 stream=opts['uncompressed'],
416 416 rev=opts['rev'],
417 417 update=not opts['noupdate'])
418 418
419 419 def commit(ui, repo, *pats, **opts):
420 420 """commit the specified files or all outstanding changes
421 421
422 422 Commit changes to the given files into the repository.
423 423
424 424 If a list of files is omitted, all changes reported by "hg status"
425 425 will be committed.
426 426
427 427 If no commit message is specified, the editor configured in your hgrc
428 428 or in the EDITOR environment variable is started to enter a message.
429 429 """
430 430 def commitfunc(ui, repo, files, message, match, opts):
431 431 return repo.commit(files, message, opts['user'], opts['date'], match,
432 432 force_editor=opts.get('force_editor'))
433 433 cmdutil.commit(ui, repo, commitfunc, pats, opts)
434 434
435 435 def docopy(ui, repo, pats, opts):
436 436 # called with the repo lock held
437 437 #
438 438 # hgsep => pathname that uses "/" to separate directories
439 439 # ossep => pathname that uses os.sep to separate directories
440 440 cwd = repo.getcwd()
441 441 errors = 0
442 442 copied = []
443 443 targets = {}
444 444
445 445 # abs: hgsep
446 446 # rel: ossep
447 447 # return: hgsep
448 448 def okaytocopy(abs, rel, exact):
449 449 reasons = {'?': _('is not managed'),
450 450 'r': _('has been marked for remove')}
451 451 state = repo.dirstate[abs]
452 452 reason = reasons.get(state)
453 453 if reason:
454 454 if exact:
455 455 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
456 456 else:
457 457 if state == 'a':
458 458 origsrc = repo.dirstate.copied(abs)
459 459 if origsrc is not None:
460 460 return origsrc
461 461 return abs
462 462
463 463 # origsrc: hgsep
464 464 # abssrc: hgsep
465 465 # relsrc: ossep
466 466 # otarget: ossep
467 467 def copy(origsrc, abssrc, relsrc, otarget, exact):
468 468 abstarget = util.canonpath(repo.root, cwd, otarget)
469 469 reltarget = repo.pathto(abstarget, cwd)
470 470 prevsrc = targets.get(abstarget)
471 471 src = repo.wjoin(abssrc)
472 472 target = repo.wjoin(abstarget)
473 473 if prevsrc is not None:
474 474 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
475 475 (reltarget, repo.pathto(abssrc, cwd),
476 476 repo.pathto(prevsrc, cwd)))
477 477 return
478 478 if (not opts['after'] and os.path.exists(target) or
479 479 opts['after'] and repo.dirstate[abstarget] in 'mn'):
480 480 if not opts['force']:
481 481 ui.warn(_('%s: not overwriting - file exists\n') %
482 482 reltarget)
483 483 return
484 484 if not opts['after'] and not opts.get('dry_run'):
485 485 os.unlink(target)
486 486 if opts['after']:
487 487 if not os.path.exists(target):
488 488 return
489 489 else:
490 490 targetdir = os.path.dirname(target) or '.'
491 491 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
492 492 os.makedirs(targetdir)
493 493 try:
494 494 restore = repo.dirstate[abstarget] == 'r'
495 495 if restore and not opts.get('dry_run'):
496 496 repo.undelete([abstarget])
497 497 try:
498 498 if not opts.get('dry_run'):
499 499 util.copyfile(src, target)
500 500 restore = False
501 501 finally:
502 502 if restore:
503 503 repo.remove([abstarget])
504 504 except IOError, inst:
505 505 if inst.errno == errno.ENOENT:
506 506 ui.warn(_('%s: deleted in working copy\n') % relsrc)
507 507 else:
508 508 ui.warn(_('%s: cannot copy - %s\n') %
509 509 (relsrc, inst.strerror))
510 510 errors += 1
511 511 return
512 512 if ui.verbose or not exact:
513 513 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
514 514 targets[abstarget] = abssrc
515 515 if abstarget != origsrc:
516 516 if repo.dirstate[origsrc] == 'a':
517 517 if not ui.quiet:
518 518 ui.warn(_("%s has not been committed yet, so no copy "
519 519 "data will be stored for %s.\n")
520 520 % (repo.pathto(origsrc, cwd), reltarget))
521 521 if abstarget not in repo.dirstate and not opts.get('dry_run'):
522 522 repo.add([abstarget])
523 523 elif not opts.get('dry_run'):
524 524 repo.copy(origsrc, abstarget)
525 525 copied.append((abssrc, relsrc, exact))
526 526
527 527 # pat: ossep
528 528 # dest ossep
529 529 # srcs: list of (hgsep, hgsep, ossep, bool)
530 530 # return: function that takes hgsep and returns ossep
531 531 def targetpathfn(pat, dest, srcs):
532 532 if os.path.isdir(pat):
533 533 abspfx = util.canonpath(repo.root, cwd, pat)
534 534 abspfx = util.localpath(abspfx)
535 535 if destdirexists:
536 536 striplen = len(os.path.split(abspfx)[0])
537 537 else:
538 538 striplen = len(abspfx)
539 539 if striplen:
540 540 striplen += len(os.sep)
541 541 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
542 542 elif destdirexists:
543 543 res = lambda p: os.path.join(dest,
544 544 os.path.basename(util.localpath(p)))
545 545 else:
546 546 res = lambda p: dest
547 547 return res
548 548
549 549 # pat: ossep
550 550 # dest ossep
551 551 # srcs: list of (hgsep, hgsep, ossep, bool)
552 552 # return: function that takes hgsep and returns ossep
553 553 def targetpathafterfn(pat, dest, srcs):
554 554 if util.patkind(pat, None)[0]:
555 555 # a mercurial pattern
556 556 res = lambda p: os.path.join(dest,
557 557 os.path.basename(util.localpath(p)))
558 558 else:
559 559 abspfx = util.canonpath(repo.root, cwd, pat)
560 560 if len(abspfx) < len(srcs[0][0]):
561 561 # A directory. Either the target path contains the last
562 562 # component of the source path or it does not.
563 563 def evalpath(striplen):
564 564 score = 0
565 565 for s in srcs:
566 566 t = os.path.join(dest, util.localpath(s[0])[striplen:])
567 567 if os.path.exists(t):
568 568 score += 1
569 569 return score
570 570
571 571 abspfx = util.localpath(abspfx)
572 572 striplen = len(abspfx)
573 573 if striplen:
574 574 striplen += len(os.sep)
575 575 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
576 576 score = evalpath(striplen)
577 577 striplen1 = len(os.path.split(abspfx)[0])
578 578 if striplen1:
579 579 striplen1 += len(os.sep)
580 580 if evalpath(striplen1) > score:
581 581 striplen = striplen1
582 582 res = lambda p: os.path.join(dest,
583 583 util.localpath(p)[striplen:])
584 584 else:
585 585 # a file
586 586 if destdirexists:
587 587 res = lambda p: os.path.join(dest,
588 588 os.path.basename(util.localpath(p)))
589 589 else:
590 590 res = lambda p: dest
591 591 return res
592 592
593 593
594 594 pats = util.expand_glob(pats)
595 595 if not pats:
596 596 raise util.Abort(_('no source or destination specified'))
597 597 if len(pats) == 1:
598 598 raise util.Abort(_('no destination specified'))
599 599 dest = pats.pop()
600 600 destdirexists = os.path.isdir(dest)
601 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
602 raise util.Abort(_('with multiple sources, destination must be an '
603 'existing directory'))
601 if not destdirexists:
602 if len(pats) > 1 or util.patkind(pats[0], None)[0]:
603 raise util.Abort(_('with multiple sources, destination must be an '
604 'existing directory'))
605 if dest.endswith(os.sep) or os.altsep and dest.endswith(os.altsep):
606 raise util.Abort(_('destination %s is not a directory') % dest)
604 607 if opts['after']:
605 608 tfn = targetpathafterfn
606 609 else:
607 610 tfn = targetpathfn
608 611 copylist = []
609 612 for pat in pats:
610 613 srcs = []
611 614 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts,
612 615 globbed=True):
613 616 origsrc = okaytocopy(abssrc, relsrc, exact)
614 617 if origsrc:
615 618 srcs.append((origsrc, abssrc, relsrc, exact))
616 619 if not srcs:
617 620 continue
618 621 copylist.append((tfn(pat, dest, srcs), srcs))
619 622 if not copylist:
620 623 raise util.Abort(_('no files to copy'))
621 624
622 625 for targetpath, srcs in copylist:
623 626 for origsrc, abssrc, relsrc, exact in srcs:
624 627 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
625 628
626 629 if errors:
627 630 ui.warn(_('(consider using --after)\n'))
628 631 return errors, copied
629 632
630 633 def copy(ui, repo, *pats, **opts):
631 634 """mark files as copied for the next commit
632 635
633 636 Mark dest as having copies of source files. If dest is a
634 637 directory, copies are put in that directory. If dest is a file,
635 638 there can only be one source.
636 639
637 640 By default, this command copies the contents of files as they
638 641 stand in the working directory. If invoked with --after, the
639 642 operation is recorded, but no copying is performed.
640 643
641 644 This command takes effect in the next commit. To undo a copy
642 645 before that, see hg revert.
643 646 """
644 647 wlock = repo.wlock(False)
645 648 try:
646 649 errs, copied = docopy(ui, repo, pats, opts)
647 650 finally:
648 651 del wlock
649 652 return errs
650 653
651 654 def debugancestor(ui, index, rev1, rev2):
652 655 """find the ancestor revision of two revisions in a given index"""
653 656 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
654 657 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
655 658 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
656 659
657 660 def debugcomplete(ui, cmd='', **opts):
658 661 """returns the completion list associated with the given command"""
659 662
660 663 if opts['options']:
661 664 options = []
662 665 otables = [globalopts]
663 666 if cmd:
664 667 aliases, entry = cmdutil.findcmd(ui, cmd, table)
665 668 otables.append(entry[1])
666 669 for t in otables:
667 670 for o in t:
668 671 if o[0]:
669 672 options.append('-%s' % o[0])
670 673 options.append('--%s' % o[1])
671 674 ui.write("%s\n" % "\n".join(options))
672 675 return
673 676
674 677 clist = cmdutil.findpossible(ui, cmd, table).keys()
675 678 clist.sort()
676 679 ui.write("%s\n" % "\n".join(clist))
677 680
678 681 def debugrebuildstate(ui, repo, rev=""):
679 682 """rebuild the dirstate as it would look like for the given revision"""
680 683 if rev == "":
681 684 rev = repo.changelog.tip()
682 685 ctx = repo.changectx(rev)
683 686 files = ctx.manifest()
684 687 wlock = repo.wlock()
685 688 try:
686 689 repo.dirstate.rebuild(rev, files)
687 690 finally:
688 691 del wlock
689 692
690 693 def debugcheckstate(ui, repo):
691 694 """validate the correctness of the current dirstate"""
692 695 parent1, parent2 = repo.dirstate.parents()
693 696 m1 = repo.changectx(parent1).manifest()
694 697 m2 = repo.changectx(parent2).manifest()
695 698 errors = 0
696 699 for f in repo.dirstate:
697 700 state = repo.dirstate[f]
698 701 if state in "nr" and f not in m1:
699 702 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
700 703 errors += 1
701 704 if state in "a" and f in m1:
702 705 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
703 706 errors += 1
704 707 if state in "m" and f not in m1 and f not in m2:
705 708 ui.warn(_("%s in state %s, but not in either manifest\n") %
706 709 (f, state))
707 710 errors += 1
708 711 for f in m1:
709 712 state = repo.dirstate[f]
710 713 if state not in "nrm":
711 714 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
712 715 errors += 1
713 716 if errors:
714 717 error = _(".hg/dirstate inconsistent with current parent's manifest")
715 718 raise util.Abort(error)
716 719
717 720 def showconfig(ui, repo, *values, **opts):
718 721 """show combined config settings from all hgrc files
719 722
720 723 With no args, print names and values of all config items.
721 724
722 725 With one arg of the form section.name, print just the value of
723 726 that config item.
724 727
725 728 With multiple args, print names and values of all config items
726 729 with matching section names."""
727 730
728 731 untrusted = bool(opts.get('untrusted'))
729 732 if values:
730 733 if len([v for v in values if '.' in v]) > 1:
731 734 raise util.Abort(_('only one config item permitted'))
732 735 for section, name, value in ui.walkconfig(untrusted=untrusted):
733 736 sectname = section + '.' + name
734 737 if values:
735 738 for v in values:
736 739 if v == section:
737 740 ui.write('%s=%s\n' % (sectname, value))
738 741 elif v == sectname:
739 742 ui.write(value, '\n')
740 743 else:
741 744 ui.write('%s=%s\n' % (sectname, value))
742 745
743 746 def debugsetparents(ui, repo, rev1, rev2=None):
744 747 """manually set the parents of the current working directory
745 748
746 749 This is useful for writing repository conversion tools, but should
747 750 be used with care.
748 751 """
749 752
750 753 if not rev2:
751 754 rev2 = hex(nullid)
752 755
753 756 wlock = repo.wlock()
754 757 try:
755 758 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
756 759 finally:
757 760 del wlock
758 761
759 762 def debugstate(ui, repo):
760 763 """show the contents of the current dirstate"""
761 764 k = repo.dirstate._map.items()
762 765 k.sort()
763 766 for file_, ent in k:
764 767 if ent[3] == -1:
765 768 # Pad or slice to locale representation
766 769 locale_len = len(time.strftime("%x %X", time.localtime(0)))
767 770 timestr = 'unset'
768 771 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
769 772 else:
770 773 timestr = time.strftime("%x %X", time.localtime(ent[3]))
771 774 if ent[1] & 020000:
772 775 mode = 'lnk'
773 776 else:
774 777 mode = '%3o' % (ent[1] & 0777)
775 778 ui.write("%c %s %10d %s %s\n" % (ent[0], mode, ent[2], timestr, file_))
776 779 for f in repo.dirstate.copies():
777 780 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
778 781
779 782 def debugdata(ui, file_, rev):
780 783 """dump the contents of a data file revision"""
781 784 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
782 785 try:
783 786 ui.write(r.revision(r.lookup(rev)))
784 787 except KeyError:
785 788 raise util.Abort(_('invalid revision identifier %s') % rev)
786 789
787 790 def debugdate(ui, date, range=None, **opts):
788 791 """parse and display a date"""
789 792 if opts["extended"]:
790 793 d = util.parsedate(date, util.extendeddateformats)
791 794 else:
792 795 d = util.parsedate(date)
793 796 ui.write("internal: %s %s\n" % d)
794 797 ui.write("standard: %s\n" % util.datestr(d))
795 798 if range:
796 799 m = util.matchdate(range)
797 800 ui.write("match: %s\n" % m(d[0]))
798 801
799 802 def debugindex(ui, file_):
800 803 """dump the contents of an index file"""
801 804 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
802 805 ui.write(" rev offset length base linkrev" +
803 806 " nodeid p1 p2\n")
804 807 for i in xrange(r.count()):
805 808 node = r.node(i)
806 809 try:
807 810 pp = r.parents(node)
808 811 except:
809 812 pp = [nullid, nullid]
810 813 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
811 814 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
812 815 short(node), short(pp[0]), short(pp[1])))
813 816
814 817 def debugindexdot(ui, file_):
815 818 """dump an index DAG as a .dot file"""
816 819 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
817 820 ui.write("digraph G {\n")
818 821 for i in xrange(r.count()):
819 822 node = r.node(i)
820 823 pp = r.parents(node)
821 824 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
822 825 if pp[1] != nullid:
823 826 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
824 827 ui.write("}\n")
825 828
826 829 def debuginstall(ui):
827 830 '''test Mercurial installation'''
828 831
829 832 def writetemp(contents):
830 833 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
831 834 f = os.fdopen(fd, "wb")
832 835 f.write(contents)
833 836 f.close()
834 837 return name
835 838
836 839 problems = 0
837 840
838 841 # encoding
839 842 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
840 843 try:
841 844 util.fromlocal("test")
842 845 except util.Abort, inst:
843 846 ui.write(" %s\n" % inst)
844 847 ui.write(_(" (check that your locale is properly set)\n"))
845 848 problems += 1
846 849
847 850 # compiled modules
848 851 ui.status(_("Checking extensions...\n"))
849 852 try:
850 853 import bdiff, mpatch, base85
851 854 except Exception, inst:
852 855 ui.write(" %s\n" % inst)
853 856 ui.write(_(" One or more extensions could not be found"))
854 857 ui.write(_(" (check that you compiled the extensions)\n"))
855 858 problems += 1
856 859
857 860 # templates
858 861 ui.status(_("Checking templates...\n"))
859 862 try:
860 863 import templater
861 864 t = templater.templater(templater.templatepath("map-cmdline.default"))
862 865 except Exception, inst:
863 866 ui.write(" %s\n" % inst)
864 867 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
865 868 problems += 1
866 869
867 870 # patch
868 871 ui.status(_("Checking patch...\n"))
869 872 patchproblems = 0
870 873 a = "1\n2\n3\n4\n"
871 874 b = "1\n2\n3\ninsert\n4\n"
872 875 fa = writetemp(a)
873 876 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa))
874 877 fd = writetemp(d)
875 878
876 879 files = {}
877 880 try:
878 881 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
879 882 except util.Abort, e:
880 883 ui.write(_(" patch call failed:\n"))
881 884 ui.write(" " + str(e) + "\n")
882 885 patchproblems += 1
883 886 else:
884 887 if list(files) != [os.path.basename(fa)]:
885 888 ui.write(_(" unexpected patch output!\n"))
886 889 patchproblems += 1
887 890 a = file(fa).read()
888 891 if a != b:
889 892 ui.write(_(" patch test failed!\n"))
890 893 patchproblems += 1
891 894
892 895 if patchproblems:
893 896 if ui.config('ui', 'patch'):
894 897 ui.write(_(" (Current patch tool may be incompatible with patch,"
895 898 " or misconfigured. Please check your .hgrc file)\n"))
896 899 else:
897 900 ui.write(_(" Internal patcher failure, please report this error"
898 901 " to http://www.selenic.com/mercurial/bts\n"))
899 902 problems += patchproblems
900 903
901 904 os.unlink(fa)
902 905 os.unlink(fd)
903 906
904 907 # merge helper
905 908 ui.status(_("Checking merge helper...\n"))
906 909 cmd = (os.environ.get("HGMERGE") or ui.config("ui", "merge")
907 910 or "hgmerge")
908 911 cmdpath = util.find_exe(cmd) or util.find_exe(cmd.split()[0])
909 912 if not cmdpath:
910 913 if cmd == 'hgmerge':
911 914 ui.write(_(" No merge helper set and can't find default"
912 915 " hgmerge script in PATH\n"))
913 916 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
914 917 else:
915 918 ui.write(_(" Can't find merge helper '%s' in PATH\n") % cmd)
916 919 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
917 920 problems += 1
918 921 else:
919 922 # actually attempt a patch here
920 923 fa = writetemp("1\n2\n3\n4\n")
921 924 fl = writetemp("1\n2\n3\ninsert\n4\n")
922 925 fr = writetemp("begin\n1\n2\n3\n4\n")
923 926 r = util.system('%s "%s" "%s" "%s"' % (cmd, fl, fa, fr))
924 927 if r:
925 928 ui.write(_(" Got unexpected merge error %d!\n") % r)
926 929 problems += 1
927 930 m = file(fl).read()
928 931 if m != "begin\n1\n2\n3\ninsert\n4\n":
929 932 ui.write(_(" Got unexpected merge results!\n"))
930 933 ui.write(_(" (your merge helper may have the"
931 934 " wrong argument order)\n"))
932 935 ui.write(_(" Result: %r\n") % m)
933 936 problems += 1
934 937 os.unlink(fa)
935 938 os.unlink(fl)
936 939 os.unlink(fr)
937 940
938 941 # editor
939 942 ui.status(_("Checking commit editor...\n"))
940 943 editor = (os.environ.get("HGEDITOR") or
941 944 ui.config("ui", "editor") or
942 945 os.environ.get("EDITOR", "vi"))
943 946 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
944 947 if not cmdpath:
945 948 if editor == 'vi':
946 949 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
947 950 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
948 951 else:
949 952 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
950 953 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
951 954 problems += 1
952 955
953 956 # check username
954 957 ui.status(_("Checking username...\n"))
955 958 user = os.environ.get("HGUSER")
956 959 if user is None:
957 960 user = ui.config("ui", "username")
958 961 if user is None:
959 962 user = os.environ.get("EMAIL")
960 963 if not user:
961 964 ui.warn(" ")
962 965 ui.username()
963 966 ui.write(_(" (specify a username in your .hgrc file)\n"))
964 967
965 968 if not problems:
966 969 ui.status(_("No problems detected\n"))
967 970 else:
968 971 ui.write(_("%s problems detected,"
969 972 " please check your install!\n") % problems)
970 973
971 974 return problems
972 975
973 976 def debugrename(ui, repo, file1, *pats, **opts):
974 977 """dump rename information"""
975 978
976 979 ctx = repo.changectx(opts.get('rev', 'tip'))
977 980 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
978 981 ctx.node()):
979 982 m = ctx.filectx(abs).renamed()
980 983 if m:
981 984 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
982 985 else:
983 986 ui.write(_("%s not renamed\n") % rel)
984 987
985 988 def debugwalk(ui, repo, *pats, **opts):
986 989 """show how files match on given patterns"""
987 990 items = list(cmdutil.walk(repo, pats, opts))
988 991 if not items:
989 992 return
990 993 fmt = '%%s %%-%ds %%-%ds %%s' % (
991 994 max([len(abs) for (src, abs, rel, exact) in items]),
992 995 max([len(rel) for (src, abs, rel, exact) in items]))
993 996 for src, abs, rel, exact in items:
994 997 line = fmt % (src, abs, rel, exact and 'exact' or '')
995 998 ui.write("%s\n" % line.rstrip())
996 999
997 1000 def diff(ui, repo, *pats, **opts):
998 1001 """diff repository (or selected files)
999 1002
1000 1003 Show differences between revisions for the specified files.
1001 1004
1002 1005 Differences between files are shown using the unified diff format.
1003 1006
1004 1007 NOTE: diff may generate unexpected results for merges, as it will
1005 1008 default to comparing against the working directory's first parent
1006 1009 changeset if no revisions are specified.
1007 1010
1008 1011 When two revision arguments are given, then changes are shown
1009 1012 between those revisions. If only one revision is specified then
1010 1013 that revision is compared to the working directory, and, when no
1011 1014 revisions are specified, the working directory files are compared
1012 1015 to its parent.
1013 1016
1014 1017 Without the -a option, diff will avoid generating diffs of files
1015 1018 it detects as binary. With -a, diff will generate a diff anyway,
1016 1019 probably with undesirable results.
1017 1020 """
1018 1021 node1, node2 = cmdutil.revpair(repo, opts['rev'])
1019 1022
1020 1023 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1021 1024
1022 1025 patch.diff(repo, node1, node2, fns, match=matchfn,
1023 1026 opts=patch.diffopts(ui, opts))
1024 1027
1025 1028 def export(ui, repo, *changesets, **opts):
1026 1029 """dump the header and diffs for one or more changesets
1027 1030
1028 1031 Print the changeset header and diffs for one or more revisions.
1029 1032
1030 1033 The information shown in the changeset header is: author,
1031 1034 changeset hash, parent(s) and commit comment.
1032 1035
1033 1036 NOTE: export may generate unexpected diff output for merge changesets,
1034 1037 as it will compare the merge changeset against its first parent only.
1035 1038
1036 1039 Output may be to a file, in which case the name of the file is
1037 1040 given using a format string. The formatting rules are as follows:
1038 1041
1039 1042 %% literal "%" character
1040 1043 %H changeset hash (40 bytes of hexadecimal)
1041 1044 %N number of patches being generated
1042 1045 %R changeset revision number
1043 1046 %b basename of the exporting repository
1044 1047 %h short-form changeset hash (12 bytes of hexadecimal)
1045 1048 %n zero-padded sequence number, starting at 1
1046 1049 %r zero-padded changeset revision number
1047 1050
1048 1051 Without the -a option, export will avoid generating diffs of files
1049 1052 it detects as binary. With -a, export will generate a diff anyway,
1050 1053 probably with undesirable results.
1051 1054
1052 1055 With the --switch-parent option, the diff will be against the second
1053 1056 parent. It can be useful to review a merge.
1054 1057 """
1055 1058 if not changesets:
1056 1059 raise util.Abort(_("export requires at least one changeset"))
1057 1060 revs = cmdutil.revrange(repo, changesets)
1058 1061 if len(revs) > 1:
1059 1062 ui.note(_('exporting patches:\n'))
1060 1063 else:
1061 1064 ui.note(_('exporting patch:\n'))
1062 1065 patch.export(repo, revs, template=opts['output'],
1063 1066 switch_parent=opts['switch_parent'],
1064 1067 opts=patch.diffopts(ui, opts))
1065 1068
1066 1069 def grep(ui, repo, pattern, *pats, **opts):
1067 1070 """search for a pattern in specified files and revisions
1068 1071
1069 1072 Search revisions of files for a regular expression.
1070 1073
1071 1074 This command behaves differently than Unix grep. It only accepts
1072 1075 Python/Perl regexps. It searches repository history, not the
1073 1076 working directory. It always prints the revision number in which
1074 1077 a match appears.
1075 1078
1076 1079 By default, grep only prints output for the first revision of a
1077 1080 file in which it finds a match. To get it to print every revision
1078 1081 that contains a change in match status ("-" for a match that
1079 1082 becomes a non-match, or "+" for a non-match that becomes a match),
1080 1083 use the --all flag.
1081 1084 """
1082 1085 reflags = 0
1083 1086 if opts['ignore_case']:
1084 1087 reflags |= re.I
1085 1088 try:
1086 1089 regexp = re.compile(pattern, reflags)
1087 1090 except Exception, inst:
1088 1091 ui.warn(_("grep: invalid match pattern: %s!\n") % inst)
1089 1092 return None
1090 1093 sep, eol = ':', '\n'
1091 1094 if opts['print0']:
1092 1095 sep = eol = '\0'
1093 1096
1094 1097 fcache = {}
1095 1098 def getfile(fn):
1096 1099 if fn not in fcache:
1097 1100 fcache[fn] = repo.file(fn)
1098 1101 return fcache[fn]
1099 1102
1100 1103 def matchlines(body):
1101 1104 begin = 0
1102 1105 linenum = 0
1103 1106 while True:
1104 1107 match = regexp.search(body, begin)
1105 1108 if not match:
1106 1109 break
1107 1110 mstart, mend = match.span()
1108 1111 linenum += body.count('\n', begin, mstart) + 1
1109 1112 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1110 1113 lend = body.find('\n', mend)
1111 1114 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1112 1115 begin = lend + 1
1113 1116
1114 1117 class linestate(object):
1115 1118 def __init__(self, line, linenum, colstart, colend):
1116 1119 self.line = line
1117 1120 self.linenum = linenum
1118 1121 self.colstart = colstart
1119 1122 self.colend = colend
1120 1123
1121 1124 def __eq__(self, other):
1122 1125 return self.line == other.line
1123 1126
1124 1127 matches = {}
1125 1128 copies = {}
1126 1129 def grepbody(fn, rev, body):
1127 1130 matches[rev].setdefault(fn, [])
1128 1131 m = matches[rev][fn]
1129 1132 for lnum, cstart, cend, line in matchlines(body):
1130 1133 s = linestate(line, lnum, cstart, cend)
1131 1134 m.append(s)
1132 1135
1133 1136 def difflinestates(a, b):
1134 1137 sm = difflib.SequenceMatcher(None, a, b)
1135 1138 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1136 1139 if tag == 'insert':
1137 1140 for i in xrange(blo, bhi):
1138 1141 yield ('+', b[i])
1139 1142 elif tag == 'delete':
1140 1143 for i in xrange(alo, ahi):
1141 1144 yield ('-', a[i])
1142 1145 elif tag == 'replace':
1143 1146 for i in xrange(alo, ahi):
1144 1147 yield ('-', a[i])
1145 1148 for i in xrange(blo, bhi):
1146 1149 yield ('+', b[i])
1147 1150
1148 1151 prev = {}
1149 1152 def display(fn, rev, states, prevstates):
1150 1153 found = False
1151 1154 filerevmatches = {}
1152 1155 r = prev.get(fn, -1)
1153 1156 if opts['all']:
1154 1157 iter = difflinestates(states, prevstates)
1155 1158 else:
1156 1159 iter = [('', l) for l in prevstates]
1157 1160 for change, l in iter:
1158 1161 cols = [fn, str(r)]
1159 1162 if opts['line_number']:
1160 1163 cols.append(str(l.linenum))
1161 1164 if opts['all']:
1162 1165 cols.append(change)
1163 1166 if opts['user']:
1164 1167 cols.append(ui.shortuser(get(r)[1]))
1165 1168 if opts['files_with_matches']:
1166 1169 c = (fn, r)
1167 1170 if c in filerevmatches:
1168 1171 continue
1169 1172 filerevmatches[c] = 1
1170 1173 else:
1171 1174 cols.append(l.line)
1172 1175 ui.write(sep.join(cols), eol)
1173 1176 found = True
1174 1177 return found
1175 1178
1176 1179 fstate = {}
1177 1180 skip = {}
1178 1181 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1179 1182 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1180 1183 found = False
1181 1184 follow = opts.get('follow')
1182 1185 for st, rev, fns in changeiter:
1183 1186 if st == 'window':
1184 1187 matches.clear()
1185 1188 elif st == 'add':
1186 1189 mf = repo.changectx(rev).manifest()
1187 1190 matches[rev] = {}
1188 1191 for fn in fns:
1189 1192 if fn in skip:
1190 1193 continue
1191 1194 try:
1192 1195 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1193 1196 fstate.setdefault(fn, [])
1194 1197 if follow:
1195 1198 copied = getfile(fn).renamed(mf[fn])
1196 1199 if copied:
1197 1200 copies.setdefault(rev, {})[fn] = copied[0]
1198 1201 except KeyError:
1199 1202 pass
1200 1203 elif st == 'iter':
1201 1204 states = matches[rev].items()
1202 1205 states.sort()
1203 1206 for fn, m in states:
1204 1207 copy = copies.get(rev, {}).get(fn)
1205 1208 if fn in skip:
1206 1209 if copy:
1207 1210 skip[copy] = True
1208 1211 continue
1209 1212 if fn in prev or fstate[fn]:
1210 1213 r = display(fn, rev, m, fstate[fn])
1211 1214 found = found or r
1212 1215 if r and not opts['all']:
1213 1216 skip[fn] = True
1214 1217 if copy:
1215 1218 skip[copy] = True
1216 1219 fstate[fn] = m
1217 1220 if copy:
1218 1221 fstate[copy] = m
1219 1222 prev[fn] = rev
1220 1223
1221 1224 fstate = fstate.items()
1222 1225 fstate.sort()
1223 1226 for fn, state in fstate:
1224 1227 if fn in skip:
1225 1228 continue
1226 1229 if fn not in copies.get(prev[fn], {}):
1227 1230 found = display(fn, rev, {}, state) or found
1228 1231 return (not found and 1) or 0
1229 1232
1230 1233 def heads(ui, repo, *branchrevs, **opts):
1231 1234 """show current repository heads or show branch heads
1232 1235
1233 1236 With no arguments, show all repository head changesets.
1234 1237
1235 1238 If branch or revisions names are given this will show the heads of
1236 1239 the specified branches or the branches those revisions are tagged
1237 1240 with.
1238 1241
1239 1242 Repository "heads" are changesets that don't have child
1240 1243 changesets. They are where development generally takes place and
1241 1244 are the usual targets for update and merge operations.
1242 1245
1243 1246 Branch heads are changesets that have a given branch tag, but have
1244 1247 no child changesets with that tag. They are usually where
1245 1248 development on the given branch takes place.
1246 1249 """
1247 1250 if opts['rev']:
1248 1251 start = repo.lookup(opts['rev'])
1249 1252 else:
1250 1253 start = None
1251 1254 if not branchrevs:
1252 1255 # Assume we're looking repo-wide heads if no revs were specified.
1253 1256 heads = repo.heads(start)
1254 1257 else:
1255 1258 heads = []
1256 1259 visitedset = util.set()
1257 1260 for branchrev in branchrevs:
1258 1261 branch = repo.changectx(branchrev).branch()
1259 1262 if branch in visitedset:
1260 1263 continue
1261 1264 visitedset.add(branch)
1262 1265 bheads = repo.branchheads(branch, start)
1263 1266 if not bheads:
1264 1267 if branch != branchrev:
1265 1268 ui.warn(_("no changes on branch %s containing %s are "
1266 1269 "reachable from %s\n")
1267 1270 % (branch, branchrev, opts['rev']))
1268 1271 else:
1269 1272 ui.warn(_("no changes on branch %s are reachable from %s\n")
1270 1273 % (branch, opts['rev']))
1271 1274 heads.extend(bheads)
1272 1275 if not heads:
1273 1276 return 1
1274 1277 displayer = cmdutil.show_changeset(ui, repo, opts)
1275 1278 for n in heads:
1276 1279 displayer.show(changenode=n)
1277 1280
1278 1281 def help_(ui, name=None, with_version=False):
1279 1282 """show help for a command, extension, or list of commands
1280 1283
1281 1284 With no arguments, print a list of commands and short help.
1282 1285
1283 1286 Given a command name, print help for that command.
1284 1287
1285 1288 Given an extension name, print help for that extension, and the
1286 1289 commands it provides."""
1287 1290 option_lists = []
1288 1291
1289 1292 def addglobalopts(aliases):
1290 1293 if ui.verbose:
1291 1294 option_lists.append((_("global options:"), globalopts))
1292 1295 if name == 'shortlist':
1293 1296 option_lists.append((_('use "hg help" for the full list '
1294 1297 'of commands'), ()))
1295 1298 else:
1296 1299 if name == 'shortlist':
1297 1300 msg = _('use "hg help" for the full list of commands '
1298 1301 'or "hg -v" for details')
1299 1302 elif aliases:
1300 1303 msg = _('use "hg -v help%s" to show aliases and '
1301 1304 'global options') % (name and " " + name or "")
1302 1305 else:
1303 1306 msg = _('use "hg -v help %s" to show global options') % name
1304 1307 option_lists.append((msg, ()))
1305 1308
1306 1309 def helpcmd(name):
1307 1310 if with_version:
1308 1311 version_(ui)
1309 1312 ui.write('\n')
1310 1313 aliases, i = cmdutil.findcmd(ui, name, table)
1311 1314 # synopsis
1312 1315 ui.write("%s\n\n" % i[2])
1313 1316
1314 1317 # description
1315 1318 doc = i[0].__doc__
1316 1319 if not doc:
1317 1320 doc = _("(No help text available)")
1318 1321 if ui.quiet:
1319 1322 doc = doc.splitlines(0)[0]
1320 1323 ui.write("%s\n" % doc.rstrip())
1321 1324
1322 1325 if not ui.quiet:
1323 1326 # aliases
1324 1327 if len(aliases) > 1:
1325 1328 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1326 1329
1327 1330 # options
1328 1331 if i[1]:
1329 1332 option_lists.append((_("options:\n"), i[1]))
1330 1333
1331 1334 addglobalopts(False)
1332 1335
1333 1336 def helplist(header, select=None):
1334 1337 h = {}
1335 1338 cmds = {}
1336 1339 for c, e in table.items():
1337 1340 f = c.split("|", 1)[0]
1338 1341 if select and not select(f):
1339 1342 continue
1340 1343 if name == "shortlist" and not f.startswith("^"):
1341 1344 continue
1342 1345 f = f.lstrip("^")
1343 1346 if not ui.debugflag and f.startswith("debug"):
1344 1347 continue
1345 1348 doc = e[0].__doc__
1346 1349 if not doc:
1347 1350 doc = _("(No help text available)")
1348 1351 h[f] = doc.splitlines(0)[0].rstrip()
1349 1352 cmds[f] = c.lstrip("^")
1350 1353
1351 1354 if not h:
1352 1355 ui.status(_('no commands defined\n'))
1353 1356 return
1354 1357
1355 1358 ui.status(header)
1356 1359 fns = h.keys()
1357 1360 fns.sort()
1358 1361 m = max(map(len, fns))
1359 1362 for f in fns:
1360 1363 if ui.verbose:
1361 1364 commands = cmds[f].replace("|",", ")
1362 1365 ui.write(" %s:\n %s\n"%(commands, h[f]))
1363 1366 else:
1364 1367 ui.write(' %-*s %s\n' % (m, f, h[f]))
1365 1368
1366 1369 if not ui.quiet:
1367 1370 addglobalopts(True)
1368 1371
1369 1372 def helptopic(name):
1370 1373 v = None
1371 1374 for i in help.helptable:
1372 1375 l = i.split('|')
1373 1376 if name in l:
1374 1377 v = i
1375 1378 header = l[-1]
1376 1379 if not v:
1377 1380 raise cmdutil.UnknownCommand(name)
1378 1381
1379 1382 # description
1380 1383 doc = help.helptable[v]
1381 1384 if not doc:
1382 1385 doc = _("(No help text available)")
1383 1386 if callable(doc):
1384 1387 doc = doc()
1385 1388
1386 1389 ui.write("%s\n" % header)
1387 1390 ui.write("%s\n" % doc.rstrip())
1388 1391
1389 1392 def helpext(name):
1390 1393 try:
1391 1394 mod = extensions.find(name)
1392 1395 except KeyError:
1393 1396 raise cmdutil.UnknownCommand(name)
1394 1397
1395 1398 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1396 1399 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1397 1400 for d in doc[1:]:
1398 1401 ui.write(d, '\n')
1399 1402
1400 1403 ui.status('\n')
1401 1404
1402 1405 try:
1403 1406 ct = mod.cmdtable
1404 1407 except AttributeError:
1405 1408 ct = {}
1406 1409
1407 1410 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1408 1411 helplist(_('list of commands:\n\n'), modcmds.has_key)
1409 1412
1410 1413 if name and name != 'shortlist':
1411 1414 i = None
1412 1415 for f in (helpcmd, helptopic, helpext):
1413 1416 try:
1414 1417 f(name)
1415 1418 i = None
1416 1419 break
1417 1420 except cmdutil.UnknownCommand, inst:
1418 1421 i = inst
1419 1422 if i:
1420 1423 raise i
1421 1424
1422 1425 else:
1423 1426 # program name
1424 1427 if ui.verbose or with_version:
1425 1428 version_(ui)
1426 1429 else:
1427 1430 ui.status(_("Mercurial Distributed SCM\n"))
1428 1431 ui.status('\n')
1429 1432
1430 1433 # list of commands
1431 1434 if name == "shortlist":
1432 1435 header = _('basic commands:\n\n')
1433 1436 else:
1434 1437 header = _('list of commands:\n\n')
1435 1438
1436 1439 helplist(header)
1437 1440
1438 1441 # list all option lists
1439 1442 opt_output = []
1440 1443 for title, options in option_lists:
1441 1444 opt_output.append(("\n%s" % title, None))
1442 1445 for shortopt, longopt, default, desc in options:
1443 1446 if "DEPRECATED" in desc and not ui.verbose: continue
1444 1447 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1445 1448 longopt and " --%s" % longopt),
1446 1449 "%s%s" % (desc,
1447 1450 default
1448 1451 and _(" (default: %s)") % default
1449 1452 or "")))
1450 1453
1451 1454 if opt_output:
1452 1455 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1453 1456 for first, second in opt_output:
1454 1457 if second:
1455 1458 ui.write(" %-*s %s\n" % (opts_len, first, second))
1456 1459 else:
1457 1460 ui.write("%s\n" % first)
1458 1461
1459 1462 def identify(ui, repo, source=None,
1460 1463 rev=None, num=None, id=None, branch=None, tags=None):
1461 1464 """identify the working copy or specified revision
1462 1465
1463 1466 With no revision, print a summary of the current state of the repo.
1464 1467
1465 1468 With a path, do a lookup in another repository.
1466 1469
1467 1470 This summary identifies the repository state using one or two parent
1468 1471 hash identifiers, followed by a "+" if there are uncommitted changes
1469 1472 in the working directory, a list of tags for this revision and a branch
1470 1473 name for non-default branches.
1471 1474 """
1472 1475
1476 if not repo and not source:
1477 raise util.Abort(_("There is no Mercurial repository here "
1478 "(.hg not found)"))
1479
1473 1480 hexfunc = ui.debugflag and hex or short
1474 1481 default = not (num or id or branch or tags)
1475 1482 output = []
1476 1483
1477 1484 if source:
1478 1485 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1479 1486 srepo = hg.repository(ui, source)
1480 1487 if not rev and revs:
1481 1488 rev = revs[0]
1482 1489 if not rev:
1483 1490 rev = "tip"
1484 1491 if num or branch or tags:
1485 1492 raise util.Abort(
1486 1493 "can't query remote revision number, branch, or tags")
1487 1494 output = [hexfunc(srepo.lookup(rev))]
1488 1495 elif not rev:
1489 1496 ctx = repo.workingctx()
1490 1497 parents = ctx.parents()
1491 1498 changed = False
1492 1499 if default or id or num:
1493 1500 changed = ctx.files() + ctx.deleted()
1494 1501 if default or id:
1495 1502 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1496 1503 (changed) and "+" or "")]
1497 1504 if num:
1498 1505 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1499 1506 (changed) and "+" or ""))
1500 1507 else:
1501 1508 ctx = repo.changectx(rev)
1502 1509 if default or id:
1503 1510 output = [hexfunc(ctx.node())]
1504 1511 if num:
1505 1512 output.append(str(ctx.rev()))
1506 1513
1507 1514 if not source and default and not ui.quiet:
1508 1515 b = util.tolocal(ctx.branch())
1509 1516 if b != 'default':
1510 1517 output.append("(%s)" % b)
1511 1518
1512 1519 # multiple tags for a single parent separated by '/'
1513 1520 t = "/".join(ctx.tags())
1514 1521 if t:
1515 1522 output.append(t)
1516 1523
1517 1524 if branch:
1518 1525 output.append(util.tolocal(ctx.branch()))
1519 1526
1520 1527 if tags:
1521 1528 output.extend(ctx.tags())
1522 1529
1523 1530 ui.write("%s\n" % ' '.join(output))
1524 1531
1525 1532 def import_(ui, repo, patch1, *patches, **opts):
1526 1533 """import an ordered set of patches
1527 1534
1528 1535 Import a list of patches and commit them individually.
1529 1536
1530 1537 If there are outstanding changes in the working directory, import
1531 1538 will abort unless given the -f flag.
1532 1539
1533 1540 You can import a patch straight from a mail message. Even patches
1534 1541 as attachments work (body part must be type text/plain or
1535 1542 text/x-patch to be used). From and Subject headers of email
1536 1543 message are used as default committer and commit message. All
1537 1544 text/plain body parts before first diff are added to commit
1538 1545 message.
1539 1546
1540 1547 If the imported patch was generated by hg export, user and description
1541 1548 from patch override values from message headers and body. Values
1542 1549 given on command line with -m and -u override these.
1543 1550
1544 1551 If --exact is specified, import will set the working directory
1545 1552 to the parent of each patch before applying it, and will abort
1546 1553 if the resulting changeset has a different ID than the one
1547 1554 recorded in the patch. This may happen due to character set
1548 1555 problems or other deficiencies in the text patch format.
1549 1556
1550 1557 To read a patch from standard input, use patch name "-".
1551 1558 """
1552 1559 patches = (patch1,) + patches
1553 1560
1554 1561 if opts.get('exact') or not opts['force']:
1555 1562 cmdutil.bail_if_changed(repo)
1556 1563
1557 1564 d = opts["base"]
1558 1565 strip = opts["strip"]
1559 1566 wlock = lock = None
1560 1567 try:
1561 1568 wlock = repo.wlock()
1562 1569 lock = repo.lock()
1563 1570 for p in patches:
1564 1571 pf = os.path.join(d, p)
1565 1572
1566 1573 if pf == '-':
1567 1574 ui.status(_("applying patch from stdin\n"))
1568 1575 data = patch.extract(ui, sys.stdin)
1569 1576 else:
1570 1577 ui.status(_("applying %s\n") % p)
1571 1578 if os.path.exists(pf):
1572 1579 data = patch.extract(ui, file(pf, 'rb'))
1573 1580 else:
1574 1581 data = patch.extract(ui, urllib.urlopen(pf))
1575 1582 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1576 1583
1577 1584 if tmpname is None:
1578 1585 raise util.Abort(_('no diffs found'))
1579 1586
1580 1587 try:
1581 1588 cmdline_message = cmdutil.logmessage(opts)
1582 1589 if cmdline_message:
1583 1590 # pickup the cmdline msg
1584 1591 message = cmdline_message
1585 1592 elif message:
1586 1593 # pickup the patch msg
1587 1594 message = message.strip()
1588 1595 else:
1589 1596 # launch the editor
1590 1597 message = None
1591 1598 ui.debug(_('message:\n%s\n') % message)
1592 1599
1593 1600 wp = repo.workingctx().parents()
1594 1601 if opts.get('exact'):
1595 1602 if not nodeid or not p1:
1596 1603 raise util.Abort(_('not a mercurial patch'))
1597 1604 p1 = repo.lookup(p1)
1598 1605 p2 = repo.lookup(p2 or hex(nullid))
1599 1606
1600 1607 if p1 != wp[0].node():
1601 1608 hg.clean(repo, p1)
1602 1609 repo.dirstate.setparents(p1, p2)
1603 1610 elif p2:
1604 1611 try:
1605 1612 p1 = repo.lookup(p1)
1606 1613 p2 = repo.lookup(p2)
1607 1614 if p1 == wp[0].node():
1608 1615 repo.dirstate.setparents(p1, p2)
1609 1616 except hg.RepoError:
1610 1617 pass
1611 1618 if opts.get('exact') or opts.get('import_branch'):
1612 1619 repo.dirstate.setbranch(branch or 'default')
1613 1620
1614 1621 files = {}
1615 1622 try:
1616 1623 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1617 1624 files=files)
1618 1625 finally:
1619 1626 files = patch.updatedir(ui, repo, files)
1620 1627 n = repo.commit(files, message, user, date)
1621 1628 if opts.get('exact'):
1622 1629 if hex(n) != nodeid:
1623 1630 repo.rollback()
1624 1631 raise util.Abort(_('patch is damaged' +
1625 1632 ' or loses information'))
1626 1633 finally:
1627 1634 os.unlink(tmpname)
1628 1635 finally:
1629 1636 del lock, wlock
1630 1637
1631 1638 def incoming(ui, repo, source="default", **opts):
1632 1639 """show new changesets found in source
1633 1640
1634 1641 Show new changesets found in the specified path/URL or the default
1635 1642 pull location. These are the changesets that would be pulled if a pull
1636 1643 was requested.
1637 1644
1638 1645 For remote repository, using --bundle avoids downloading the changesets
1639 1646 twice if the incoming is followed by a pull.
1640 1647
1641 1648 See pull for valid source format details.
1642 1649 """
1643 1650 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
1644 1651 cmdutil.setremoteconfig(ui, opts)
1645 1652
1646 1653 other = hg.repository(ui, source)
1647 1654 ui.status(_('comparing with %s\n') % source)
1648 1655 if revs:
1649 1656 revs = [other.lookup(rev) for rev in revs]
1650 1657 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1651 1658 if not incoming:
1652 1659 try:
1653 1660 os.unlink(opts["bundle"])
1654 1661 except:
1655 1662 pass
1656 1663 ui.status(_("no changes found\n"))
1657 1664 return 1
1658 1665
1659 1666 cleanup = None
1660 1667 try:
1661 1668 fname = opts["bundle"]
1662 1669 if fname or not other.local():
1663 1670 # create a bundle (uncompressed if other repo is not local)
1664 1671 if revs is None:
1665 1672 cg = other.changegroup(incoming, "incoming")
1666 1673 else:
1667 1674 cg = other.changegroupsubset(incoming, revs, 'incoming')
1668 1675 bundletype = other.local() and "HG10BZ" or "HG10UN"
1669 1676 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1670 1677 # keep written bundle?
1671 1678 if opts["bundle"]:
1672 1679 cleanup = None
1673 1680 if not other.local():
1674 1681 # use the created uncompressed bundlerepo
1675 1682 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1676 1683
1677 1684 o = other.changelog.nodesbetween(incoming, revs)[0]
1678 1685 if opts['newest_first']:
1679 1686 o.reverse()
1680 1687 displayer = cmdutil.show_changeset(ui, other, opts)
1681 1688 for n in o:
1682 1689 parents = [p for p in other.changelog.parents(n) if p != nullid]
1683 1690 if opts['no_merges'] and len(parents) == 2:
1684 1691 continue
1685 1692 displayer.show(changenode=n)
1686 1693 finally:
1687 1694 if hasattr(other, 'close'):
1688 1695 other.close()
1689 1696 if cleanup:
1690 1697 os.unlink(cleanup)
1691 1698
1692 1699 def init(ui, dest=".", **opts):
1693 1700 """create a new repository in the given directory
1694 1701
1695 1702 Initialize a new repository in the given directory. If the given
1696 1703 directory does not exist, it is created.
1697 1704
1698 1705 If no directory is given, the current directory is used.
1699 1706
1700 1707 It is possible to specify an ssh:// URL as the destination.
1701 1708 Look at the help text for the pull command for important details
1702 1709 about ssh:// URLs.
1703 1710 """
1704 1711 cmdutil.setremoteconfig(ui, opts)
1705 1712 hg.repository(ui, dest, create=1)
1706 1713
1707 1714 def locate(ui, repo, *pats, **opts):
1708 1715 """locate files matching specific patterns
1709 1716
1710 1717 Print all files under Mercurial control whose names match the
1711 1718 given patterns.
1712 1719
1713 1720 This command searches the entire repository by default. To search
1714 1721 just the current directory and its subdirectories, use
1715 1722 "--include .".
1716 1723
1717 1724 If no patterns are given to match, this command prints all file
1718 1725 names.
1719 1726
1720 1727 If you want to feed the output of this command into the "xargs"
1721 1728 command, use the "-0" option to both this command and "xargs".
1722 1729 This will avoid the problem of "xargs" treating single filenames
1723 1730 that contain white space as multiple filenames.
1724 1731 """
1725 1732 end = opts['print0'] and '\0' or '\n'
1726 1733 rev = opts['rev']
1727 1734 if rev:
1728 1735 node = repo.lookup(rev)
1729 1736 else:
1730 1737 node = None
1731 1738
1732 1739 ret = 1
1733 1740 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1734 1741 badmatch=util.always,
1735 1742 default='relglob'):
1736 1743 if src == 'b':
1737 1744 continue
1738 1745 if not node and abs not in repo.dirstate:
1739 1746 continue
1740 1747 if opts['fullpath']:
1741 1748 ui.write(os.path.join(repo.root, abs), end)
1742 1749 else:
1743 1750 ui.write(((pats and rel) or abs), end)
1744 1751 ret = 0
1745 1752
1746 1753 return ret
1747 1754
1748 1755 def log(ui, repo, *pats, **opts):
1749 1756 """show revision history of entire repository or files
1750 1757
1751 1758 Print the revision history of the specified files or the entire
1752 1759 project.
1753 1760
1754 1761 File history is shown without following rename or copy history of
1755 1762 files. Use -f/--follow with a file name to follow history across
1756 1763 renames and copies. --follow without a file name will only show
1757 1764 ancestors or descendants of the starting revision. --follow-first
1758 1765 only follows the first parent of merge revisions.
1759 1766
1760 1767 If no revision range is specified, the default is tip:0 unless
1761 1768 --follow is set, in which case the working directory parent is
1762 1769 used as the starting revision.
1763 1770
1764 1771 By default this command outputs: changeset id and hash, tags,
1765 1772 non-trivial parents, user, date and time, and a summary for each
1766 1773 commit. When the -v/--verbose switch is used, the list of changed
1767 1774 files and full commit message is shown.
1768 1775
1769 1776 NOTE: log -p may generate unexpected diff output for merge
1770 1777 changesets, as it will compare the merge changeset against its
1771 1778 first parent only. Also, the files: list will only reflect files
1772 1779 that are different from BOTH parents.
1773 1780
1774 1781 """
1775 1782
1776 1783 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1777 1784 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1778 1785
1779 1786 if opts['limit']:
1780 1787 try:
1781 1788 limit = int(opts['limit'])
1782 1789 except ValueError:
1783 1790 raise util.Abort(_('limit must be a positive integer'))
1784 1791 if limit <= 0: raise util.Abort(_('limit must be positive'))
1785 1792 else:
1786 1793 limit = sys.maxint
1787 1794 count = 0
1788 1795
1789 1796 if opts['copies'] and opts['rev']:
1790 1797 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1791 1798 else:
1792 1799 endrev = repo.changelog.count()
1793 1800 rcache = {}
1794 1801 ncache = {}
1795 1802 dcache = []
1796 1803 def getrenamed(fn, rev, man):
1797 1804 '''looks up all renames for a file (up to endrev) the first
1798 1805 time the file is given. It indexes on the changerev and only
1799 1806 parses the manifest if linkrev != changerev.
1800 1807 Returns rename info for fn at changerev rev.'''
1801 1808 if fn not in rcache:
1802 1809 rcache[fn] = {}
1803 1810 ncache[fn] = {}
1804 1811 fl = repo.file(fn)
1805 1812 for i in xrange(fl.count()):
1806 1813 node = fl.node(i)
1807 1814 lr = fl.linkrev(node)
1808 1815 renamed = fl.renamed(node)
1809 1816 rcache[fn][lr] = renamed
1810 1817 if renamed:
1811 1818 ncache[fn][node] = renamed
1812 1819 if lr >= endrev:
1813 1820 break
1814 1821 if rev in rcache[fn]:
1815 1822 return rcache[fn][rev]
1816 1823 mr = repo.manifest.rev(man)
1817 1824 if repo.manifest.parentrevs(mr) != (mr - 1, nullrev):
1818 1825 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1819 1826 if not dcache or dcache[0] != man:
1820 1827 dcache[:] = [man, repo.manifest.readdelta(man)]
1821 1828 if fn in dcache[1]:
1822 1829 return ncache[fn].get(dcache[1][fn])
1823 1830 return None
1824 1831
1825 1832 df = False
1826 1833 if opts["date"]:
1827 1834 df = util.matchdate(opts["date"])
1828 1835
1829 1836 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1830 1837 for st, rev, fns in changeiter:
1831 1838 if st == 'add':
1832 1839 changenode = repo.changelog.node(rev)
1833 1840 parents = [p for p in repo.changelog.parentrevs(rev)
1834 1841 if p != nullrev]
1835 1842 if opts['no_merges'] and len(parents) == 2:
1836 1843 continue
1837 1844 if opts['only_merges'] and len(parents) != 2:
1838 1845 continue
1839 1846
1840 1847 if df:
1841 1848 changes = get(rev)
1842 1849 if not df(changes[2][0]):
1843 1850 continue
1844 1851
1845 1852 if opts['keyword']:
1846 1853 changes = get(rev)
1847 1854 miss = 0
1848 1855 for k in [kw.lower() for kw in opts['keyword']]:
1849 1856 if not (k in changes[1].lower() or
1850 1857 k in changes[4].lower() or
1851 1858 k in " ".join(changes[3]).lower()):
1852 1859 miss = 1
1853 1860 break
1854 1861 if miss:
1855 1862 continue
1856 1863
1857 1864 copies = []
1858 1865 if opts.get('copies') and rev:
1859 1866 mf = get(rev)[0]
1860 1867 for fn in get(rev)[3]:
1861 1868 rename = getrenamed(fn, rev, mf)
1862 1869 if rename:
1863 1870 copies.append((fn, rename[0]))
1864 1871 displayer.show(rev, changenode, copies=copies)
1865 1872 elif st == 'iter':
1866 1873 if count == limit: break
1867 1874 if displayer.flush(rev):
1868 1875 count += 1
1869 1876
1870 1877 def manifest(ui, repo, node=None, rev=None):
1871 1878 """output the current or given revision of the project manifest
1872 1879
1873 1880 Print a list of version controlled files for the given revision.
1874 1881 If no revision is given, the parent of the working directory is used,
1875 1882 or tip if no revision is checked out.
1876 1883
1877 1884 The manifest is the list of files being version controlled. If no revision
1878 1885 is given then the first parent of the working directory is used.
1879 1886
1880 1887 With -v flag, print file permissions. With --debug flag, print
1881 1888 file revision hashes.
1882 1889 """
1883 1890
1884 1891 if rev and node:
1885 1892 raise util.Abort(_("please specify just one revision"))
1886 1893
1887 1894 if not node:
1888 1895 node = rev
1889 1896
1890 1897 m = repo.changectx(node).manifest()
1891 1898 files = m.keys()
1892 1899 files.sort()
1893 1900
1894 1901 for f in files:
1895 1902 if ui.debugflag:
1896 1903 ui.write("%40s " % hex(m[f]))
1897 1904 if ui.verbose:
1898 1905 ui.write("%3s " % (m.execf(f) and "755" or "644"))
1899 1906 ui.write("%s\n" % f)
1900 1907
1901 1908 def merge(ui, repo, node=None, force=None, rev=None):
1902 1909 """merge working directory with another revision
1903 1910
1904 1911 Merge the contents of the current working directory and the
1905 1912 requested revision. Files that changed between either parent are
1906 1913 marked as changed for the next commit and a commit must be
1907 1914 performed before any further updates are allowed.
1908 1915
1909 1916 If no revision is specified, the working directory's parent is a
1910 1917 head revision, and the repository contains exactly one other head,
1911 1918 the other head is merged with by default. Otherwise, an explicit
1912 1919 revision to merge with must be provided.
1913 1920 """
1914 1921
1915 1922 if rev and node:
1916 1923 raise util.Abort(_("please specify just one revision"))
1917 1924 if not node:
1918 1925 node = rev
1919 1926
1920 1927 if not node:
1921 1928 heads = repo.heads()
1922 1929 if len(heads) > 2:
1923 1930 raise util.Abort(_('repo has %d heads - '
1924 1931 'please merge with an explicit rev') %
1925 1932 len(heads))
1926 1933 parent = repo.dirstate.parents()[0]
1927 1934 if len(heads) == 1:
1928 1935 msg = _('there is nothing to merge')
1929 1936 if parent != repo.lookup(repo.workingctx().branch()):
1930 1937 msg = _('%s - use "hg update" instead' % msg)
1931 1938 raise util.Abort(msg)
1932 1939
1933 1940 if parent not in heads:
1934 1941 raise util.Abort(_('working dir not at a head rev - '
1935 1942 'use "hg update" or merge with an explicit rev'))
1936 1943 node = parent == heads[0] and heads[-1] or heads[0]
1937 1944 return hg.merge(repo, node, force=force)
1938 1945
1939 1946 def outgoing(ui, repo, dest=None, **opts):
1940 1947 """show changesets not found in destination
1941 1948
1942 1949 Show changesets not found in the specified destination repository or
1943 1950 the default push location. These are the changesets that would be pushed
1944 1951 if a push was requested.
1945 1952
1946 1953 See pull for valid destination format details.
1947 1954 """
1948 1955 dest, revs, checkout = hg.parseurl(
1949 1956 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1950 1957 cmdutil.setremoteconfig(ui, opts)
1951 1958 if revs:
1952 1959 revs = [repo.lookup(rev) for rev in revs]
1953 1960
1954 1961 other = hg.repository(ui, dest)
1955 1962 ui.status(_('comparing with %s\n') % dest)
1956 1963 o = repo.findoutgoing(other, force=opts['force'])
1957 1964 if not o:
1958 1965 ui.status(_("no changes found\n"))
1959 1966 return 1
1960 1967 o = repo.changelog.nodesbetween(o, revs)[0]
1961 1968 if opts['newest_first']:
1962 1969 o.reverse()
1963 1970 displayer = cmdutil.show_changeset(ui, repo, opts)
1964 1971 for n in o:
1965 1972 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1966 1973 if opts['no_merges'] and len(parents) == 2:
1967 1974 continue
1968 1975 displayer.show(changenode=n)
1969 1976
1970 1977 def parents(ui, repo, file_=None, **opts):
1971 1978 """show the parents of the working dir or revision
1972 1979
1973 1980 Print the working directory's parent revisions. If a
1974 1981 revision is given via --rev, the parent of that revision
1975 1982 will be printed. If a file argument is given, revision in
1976 1983 which the file was last changed (before the working directory
1977 1984 revision or the argument to --rev if given) is printed.
1978 1985 """
1979 1986 rev = opts.get('rev')
1980 1987 if rev:
1981 1988 ctx = repo.changectx(rev)
1982 1989 else:
1983 1990 ctx = repo.workingctx()
1984 1991
1985 1992 if file_:
1986 1993 files, match, anypats = cmdutil.matchpats(repo, (file_,), opts)
1987 1994 if anypats or len(files) != 1:
1988 1995 raise util.Abort(_('can only specify an explicit file name'))
1989 1996 file_ = files[0]
1990 1997 filenodes = []
1991 1998 for cp in ctx.parents():
1992 1999 if not cp:
1993 2000 continue
1994 2001 try:
1995 2002 filenodes.append(cp.filenode(file_))
1996 2003 except revlog.LookupError:
1997 2004 pass
1998 2005 if not filenodes:
1999 2006 raise util.Abort(_("'%s' not found in manifest!") % file_)
2000 2007 fl = repo.file(file_)
2001 2008 p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes]
2002 2009 else:
2003 2010 p = [cp.node() for cp in ctx.parents()]
2004 2011
2005 2012 displayer = cmdutil.show_changeset(ui, repo, opts)
2006 2013 for n in p:
2007 2014 if n != nullid:
2008 2015 displayer.show(changenode=n)
2009 2016
2010 2017 def paths(ui, repo, search=None):
2011 2018 """show definition of symbolic path names
2012 2019
2013 2020 Show definition of symbolic path name NAME. If no name is given, show
2014 2021 definition of available names.
2015 2022
2016 2023 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2017 2024 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2018 2025 """
2019 2026 if search:
2020 2027 for name, path in ui.configitems("paths"):
2021 2028 if name == search:
2022 2029 ui.write("%s\n" % path)
2023 2030 return
2024 2031 ui.warn(_("not found!\n"))
2025 2032 return 1
2026 2033 else:
2027 2034 for name, path in ui.configitems("paths"):
2028 2035 ui.write("%s = %s\n" % (name, path))
2029 2036
2030 2037 def postincoming(ui, repo, modheads, optupdate, checkout):
2031 2038 if modheads == 0:
2032 2039 return
2033 2040 if optupdate:
2034 2041 if modheads <= 1 or checkout:
2035 2042 return hg.update(repo, checkout)
2036 2043 else:
2037 2044 ui.status(_("not updating, since new heads added\n"))
2038 2045 if modheads > 1:
2039 2046 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2040 2047 else:
2041 2048 ui.status(_("(run 'hg update' to get a working copy)\n"))
2042 2049
2043 2050 def pull(ui, repo, source="default", **opts):
2044 2051 """pull changes from the specified source
2045 2052
2046 2053 Pull changes from a remote repository to a local one.
2047 2054
2048 2055 This finds all changes from the repository at the specified path
2049 2056 or URL and adds them to the local repository. By default, this
2050 2057 does not update the copy of the project in the working directory.
2051 2058
2052 2059 Valid URLs are of the form:
2053 2060
2054 2061 local/filesystem/path (or file://local/filesystem/path)
2055 2062 http://[user@]host[:port]/[path]
2056 2063 https://[user@]host[:port]/[path]
2057 2064 ssh://[user@]host[:port]/[path]
2058 2065 static-http://host[:port]/[path]
2059 2066
2060 2067 Paths in the local filesystem can either point to Mercurial
2061 2068 repositories or to bundle files (as created by 'hg bundle' or
2062 2069 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
2063 2070 allows access to a Mercurial repository where you simply use a web
2064 2071 server to publish the .hg directory as static content.
2065 2072
2066 2073 An optional identifier after # indicates a particular branch, tag,
2067 2074 or changeset to pull.
2068 2075
2069 2076 Some notes about using SSH with Mercurial:
2070 2077 - SSH requires an accessible shell account on the destination machine
2071 2078 and a copy of hg in the remote path or specified with as remotecmd.
2072 2079 - path is relative to the remote user's home directory by default.
2073 2080 Use an extra slash at the start of a path to specify an absolute path:
2074 2081 ssh://example.com//tmp/repository
2075 2082 - Mercurial doesn't use its own compression via SSH; the right thing
2076 2083 to do is to configure it in your ~/.ssh/config, e.g.:
2077 2084 Host *.mylocalnetwork.example.com
2078 2085 Compression no
2079 2086 Host *
2080 2087 Compression yes
2081 2088 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2082 2089 with the --ssh command line option.
2083 2090 """
2084 2091 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
2085 2092 cmdutil.setremoteconfig(ui, opts)
2086 2093
2087 2094 other = hg.repository(ui, source)
2088 2095 ui.status(_('pulling from %s\n') % (source))
2089 2096 if revs:
2090 2097 try:
2091 2098 revs = [other.lookup(rev) for rev in revs]
2092 2099 except repo.NoCapability:
2093 2100 error = _("Other repository doesn't support revision lookup, "
2094 2101 "so a rev cannot be specified.")
2095 2102 raise util.Abort(error)
2096 2103
2097 2104 modheads = repo.pull(other, heads=revs, force=opts['force'])
2098 2105 return postincoming(ui, repo, modheads, opts['update'], checkout)
2099 2106
2100 2107 def push(ui, repo, dest=None, **opts):
2101 2108 """push changes to the specified destination
2102 2109
2103 2110 Push changes from the local repository to the given destination.
2104 2111
2105 2112 This is the symmetrical operation for pull. It helps to move
2106 2113 changes from the current repository to a different one. If the
2107 2114 destination is local this is identical to a pull in that directory
2108 2115 from the current one.
2109 2116
2110 2117 By default, push will refuse to run if it detects the result would
2111 2118 increase the number of remote heads. This generally indicates the
2112 2119 the client has forgotten to sync and merge before pushing.
2113 2120
2114 2121 Valid URLs are of the form:
2115 2122
2116 2123 local/filesystem/path (or file://local/filesystem/path)
2117 2124 ssh://[user@]host[:port]/[path]
2118 2125 http://[user@]host[:port]/[path]
2119 2126 https://[user@]host[:port]/[path]
2120 2127
2121 2128 An optional identifier after # indicates a particular branch, tag,
2122 2129 or changeset to push.
2123 2130
2124 2131 Look at the help text for the pull command for important details
2125 2132 about ssh:// URLs.
2126 2133
2127 2134 Pushing to http:// and https:// URLs is only possible, if this
2128 2135 feature is explicitly enabled on the remote Mercurial server.
2129 2136 """
2130 2137 dest, revs, checkout = hg.parseurl(
2131 2138 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2132 2139 cmdutil.setremoteconfig(ui, opts)
2133 2140
2134 2141 other = hg.repository(ui, dest)
2135 2142 ui.status('pushing to %s\n' % (dest))
2136 2143 if revs:
2137 2144 revs = [repo.lookup(rev) for rev in revs]
2138 2145 r = repo.push(other, opts['force'], revs=revs)
2139 2146 return r == 0
2140 2147
2141 2148 def rawcommit(ui, repo, *pats, **opts):
2142 2149 """raw commit interface (DEPRECATED)
2143 2150
2144 2151 (DEPRECATED)
2145 2152 Lowlevel commit, for use in helper scripts.
2146 2153
2147 2154 This command is not intended to be used by normal users, as it is
2148 2155 primarily useful for importing from other SCMs.
2149 2156
2150 2157 This command is now deprecated and will be removed in a future
2151 2158 release, please use debugsetparents and commit instead.
2152 2159 """
2153 2160
2154 2161 ui.warn(_("(the rawcommit command is deprecated)\n"))
2155 2162
2156 2163 message = cmdutil.logmessage(opts)
2157 2164
2158 2165 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2159 2166 if opts['files']:
2160 2167 files += open(opts['files']).read().splitlines()
2161 2168
2162 2169 parents = [repo.lookup(p) for p in opts['parent']]
2163 2170
2164 2171 try:
2165 2172 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2166 2173 except ValueError, inst:
2167 2174 raise util.Abort(str(inst))
2168 2175
2169 2176 def recover(ui, repo):
2170 2177 """roll back an interrupted transaction
2171 2178
2172 2179 Recover from an interrupted commit or pull.
2173 2180
2174 2181 This command tries to fix the repository status after an interrupted
2175 2182 operation. It should only be necessary when Mercurial suggests it.
2176 2183 """
2177 2184 if repo.recover():
2178 2185 return hg.verify(repo)
2179 2186 return 1
2180 2187
2181 2188 def remove(ui, repo, *pats, **opts):
2182 2189 """remove the specified files on the next commit
2183 2190
2184 2191 Schedule the indicated files for removal from the repository.
2185 2192
2186 2193 This only removes files from the current branch, not from the
2187 2194 entire project history. If the files still exist in the working
2188 2195 directory, they will be deleted from it. If invoked with --after,
2189 2196 files are marked as removed, but not actually unlinked unless --force
2190 2197 is also given. Without exact file names, --after will only mark
2191 2198 files as removed if they are no longer in the working directory.
2192 2199
2193 2200 This command schedules the files to be removed at the next commit.
2194 2201 To undo a remove before that, see hg revert.
2195 2202
2196 2203 Modified files and added files are not removed by default. To
2197 2204 remove them, use the -f/--force option.
2198 2205 """
2199 2206 if not opts['after'] and not pats:
2200 2207 raise util.Abort(_('no files specified'))
2201 2208 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2202 2209 exact = dict.fromkeys(files)
2203 2210 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2204 2211 modified, added, removed, deleted, unknown = mardu
2205 2212 remove, forget = [], []
2206 2213 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2207 2214 reason = None
2208 2215 if abs in modified and not opts['force']:
2209 2216 reason = _('is modified (use -f to force removal)')
2210 2217 elif abs in added:
2211 2218 if opts['force']:
2212 2219 forget.append(abs)
2213 2220 continue
2214 2221 reason = _('has been marked for add (use -f to force removal)')
2215 2222 elif abs not in repo.dirstate:
2216 2223 reason = _('is not managed')
2217 2224 elif opts['after'] and not exact and abs not in deleted:
2218 2225 continue
2219 2226 elif abs in removed:
2220 2227 continue
2221 2228 if reason:
2222 2229 if exact:
2223 2230 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2224 2231 else:
2225 2232 if ui.verbose or not exact:
2226 2233 ui.status(_('removing %s\n') % rel)
2227 2234 remove.append(abs)
2228 2235 repo.forget(forget)
2229 2236 repo.remove(remove, unlink=opts['force'] or not opts['after'])
2230 2237
2231 2238 def rename(ui, repo, *pats, **opts):
2232 2239 """rename files; equivalent of copy + remove
2233 2240
2234 2241 Mark dest as copies of sources; mark sources for deletion. If
2235 2242 dest is a directory, copies are put in that directory. If dest is
2236 2243 a file, there can only be one source.
2237 2244
2238 2245 By default, this command copies the contents of files as they
2239 2246 stand in the working directory. If invoked with --after, the
2240 2247 operation is recorded, but no copying is performed.
2241 2248
2242 2249 This command takes effect in the next commit. To undo a rename
2243 2250 before that, see hg revert.
2244 2251 """
2245 2252 wlock = repo.wlock(False)
2246 2253 try:
2247 2254 errs, copied = docopy(ui, repo, pats, opts)
2248 2255 names = []
2249 2256 for abs, rel, exact in copied:
2250 2257 if ui.verbose or not exact:
2251 2258 ui.status(_('removing %s\n') % rel)
2252 2259 names.append(abs)
2253 2260 if not opts.get('dry_run'):
2254 2261 repo.remove(names, True)
2255 2262 return errs
2256 2263 finally:
2257 2264 del wlock
2258 2265
2259 2266 def revert(ui, repo, *pats, **opts):
2260 2267 """revert files or dirs to their states as of some revision
2261 2268
2262 2269 With no revision specified, revert the named files or directories
2263 2270 to the contents they had in the parent of the working directory.
2264 2271 This restores the contents of the affected files to an unmodified
2265 2272 state and unschedules adds, removes, copies, and renames. If the
2266 2273 working directory has two parents, you must explicitly specify the
2267 2274 revision to revert to.
2268 2275
2269 2276 Modified files are saved with a .orig suffix before reverting.
2270 2277 To disable these backups, use --no-backup.
2271 2278
2272 2279 Using the -r option, revert the given files or directories to their
2273 2280 contents as of a specific revision. This can be helpful to "roll
2274 2281 back" some or all of a change that should not have been committed.
2275 2282
2276 2283 Revert modifies the working directory. It does not commit any
2277 2284 changes, or change the parent of the working directory. If you
2278 2285 revert to a revision other than the parent of the working
2279 2286 directory, the reverted files will thus appear modified
2280 2287 afterwards.
2281 2288
2282 2289 If a file has been deleted, it is restored. If the executable
2283 2290 mode of a file was changed, it is reset.
2284 2291
2285 2292 If names are given, all files matching the names are reverted.
2286 2293
2287 2294 If no arguments are given, no files are reverted.
2288 2295 """
2289 2296
2290 2297 if opts["date"]:
2291 2298 if opts["rev"]:
2292 2299 raise util.Abort(_("you can't specify a revision and a date"))
2293 2300 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2294 2301
2295 2302 if not pats and not opts['all']:
2296 2303 raise util.Abort(_('no files or directories specified; '
2297 2304 'use --all to revert the whole repo'))
2298 2305
2299 2306 parent, p2 = repo.dirstate.parents()
2300 2307 if not opts['rev'] and p2 != nullid:
2301 2308 raise util.Abort(_('uncommitted merge - please provide a '
2302 2309 'specific revision'))
2303 2310 ctx = repo.changectx(opts['rev'])
2304 2311 node = ctx.node()
2305 2312 mf = ctx.manifest()
2306 2313 if node == parent:
2307 2314 pmf = mf
2308 2315 else:
2309 2316 pmf = None
2310 2317
2311 2318 # need all matching names in dirstate and manifest of target rev,
2312 2319 # so have to walk both. do not print errors if files exist in one
2313 2320 # but not other.
2314 2321
2315 2322 names = {}
2316 2323 target_only = {}
2317 2324
2318 2325 wlock = repo.wlock()
2319 2326 try:
2320 2327 # walk dirstate.
2321 2328 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2322 2329 badmatch=mf.has_key):
2323 2330 names[abs] = (rel, exact)
2324 2331 if src == 'b':
2325 2332 target_only[abs] = True
2326 2333
2327 2334 # walk target manifest.
2328 2335
2329 2336 def badmatch(path):
2330 2337 if path in names:
2331 2338 return True
2332 2339 path_ = path + '/'
2333 2340 for f in names:
2334 2341 if f.startswith(path_):
2335 2342 return True
2336 2343 return False
2337 2344
2338 2345 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2339 2346 badmatch=badmatch):
2340 2347 if abs in names or src == 'b':
2341 2348 continue
2342 2349 names[abs] = (rel, exact)
2343 2350 target_only[abs] = True
2344 2351
2345 2352 changes = repo.status(match=names.has_key)[:5]
2346 2353 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2347 2354
2348 2355 # if f is a rename, also revert the source
2349 2356 cwd = repo.getcwd()
2350 2357 for f in added:
2351 2358 src = repo.dirstate.copied(f)
2352 2359 if src and src not in names and repo.dirstate[src] == 'r':
2353 2360 removed[src] = None
2354 2361 names[src] = (repo.pathto(src, cwd), True)
2355 2362
2356 2363 revert = ([], _('reverting %s\n'))
2357 2364 add = ([], _('adding %s\n'))
2358 2365 remove = ([], _('removing %s\n'))
2359 2366 forget = ([], _('forgetting %s\n'))
2360 2367 undelete = ([], _('undeleting %s\n'))
2361 2368 update = {}
2362 2369
2363 2370 disptable = (
2364 2371 # dispatch table:
2365 2372 # file state
2366 2373 # action if in target manifest
2367 2374 # action if not in target manifest
2368 2375 # make backup if in target manifest
2369 2376 # make backup if not in target manifest
2370 2377 (modified, revert, remove, True, True),
2371 2378 (added, revert, forget, True, False),
2372 2379 (removed, undelete, None, False, False),
2373 2380 (deleted, revert, remove, False, False),
2374 2381 (unknown, add, None, True, False),
2375 2382 (target_only, add, None, False, False),
2376 2383 )
2377 2384
2378 2385 entries = names.items()
2379 2386 entries.sort()
2380 2387
2381 2388 for abs, (rel, exact) in entries:
2382 2389 mfentry = mf.get(abs)
2383 2390 target = repo.wjoin(abs)
2384 2391 def handle(xlist, dobackup):
2385 2392 xlist[0].append(abs)
2386 2393 update[abs] = 1
2387 2394 if dobackup and not opts['no_backup'] and util.lexists(target):
2388 2395 bakname = "%s.orig" % rel
2389 2396 ui.note(_('saving current version of %s as %s\n') %
2390 2397 (rel, bakname))
2391 2398 if not opts.get('dry_run'):
2392 2399 util.copyfile(target, bakname)
2393 2400 if ui.verbose or not exact:
2394 2401 ui.status(xlist[1] % rel)
2395 2402 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2396 2403 if abs not in table: continue
2397 2404 # file has changed in dirstate
2398 2405 if mfentry:
2399 2406 handle(hitlist, backuphit)
2400 2407 elif misslist is not None:
2401 2408 handle(misslist, backupmiss)
2402 2409 else:
2403 2410 if exact: ui.warn(_('file not managed: %s\n') % rel)
2404 2411 break
2405 2412 else:
2406 2413 # file has not changed in dirstate
2407 2414 if node == parent:
2408 2415 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2409 2416 continue
2410 2417 if pmf is None:
2411 2418 # only need parent manifest in this unlikely case,
2412 2419 # so do not read by default
2413 2420 pmf = repo.changectx(parent).manifest()
2414 2421 if abs in pmf:
2415 2422 if mfentry:
2416 2423 # if version of file is same in parent and target
2417 2424 # manifests, do nothing
2418 2425 if pmf[abs] != mfentry:
2419 2426 handle(revert, False)
2420 2427 else:
2421 2428 handle(remove, False)
2422 2429
2423 2430 if not opts.get('dry_run'):
2424 2431 for f in forget[0]:
2425 2432 repo.dirstate.forget(f)
2426 2433 r = hg.revert(repo, node, update.has_key)
2427 2434 for f in add[0]:
2428 2435 repo.dirstate.add(f)
2429 2436 for f in undelete[0]:
2430 2437 repo.dirstate.normal(f)
2431 2438 for f in remove[0]:
2432 2439 repo.dirstate.remove(f)
2433 2440 return r
2434 2441 finally:
2435 2442 del wlock
2436 2443
2437 2444 def rollback(ui, repo):
2438 2445 """roll back the last transaction in this repository
2439 2446
2440 2447 Roll back the last transaction in this repository, restoring the
2441 2448 project to its state prior to the transaction.
2442 2449
2443 2450 Transactions are used to encapsulate the effects of all commands
2444 2451 that create new changesets or propagate existing changesets into a
2445 2452 repository. For example, the following commands are transactional,
2446 2453 and their effects can be rolled back:
2447 2454
2448 2455 commit
2449 2456 import
2450 2457 pull
2451 2458 push (with this repository as destination)
2452 2459 unbundle
2453 2460
2454 2461 This command should be used with care. There is only one level of
2455 2462 rollback, and there is no way to undo a rollback. It will also
2456 2463 restore the dirstate at the time of the last transaction, which
2457 2464 may lose subsequent dirstate changes.
2458 2465
2459 2466 This command is not intended for use on public repositories. Once
2460 2467 changes are visible for pull by other users, rolling a transaction
2461 2468 back locally is ineffective (someone else may already have pulled
2462 2469 the changes). Furthermore, a race is possible with readers of the
2463 2470 repository; for example an in-progress pull from the repository
2464 2471 may fail if a rollback is performed.
2465 2472 """
2466 2473 repo.rollback()
2467 2474
2468 2475 def root(ui, repo):
2469 2476 """print the root (top) of the current working dir
2470 2477
2471 2478 Print the root directory of the current repository.
2472 2479 """
2473 2480 ui.write(repo.root + "\n")
2474 2481
2475 2482 def serve(ui, repo, **opts):
2476 2483 """export the repository via HTTP
2477 2484
2478 2485 Start a local HTTP repository browser and pull server.
2479 2486
2480 2487 By default, the server logs accesses to stdout and errors to
2481 2488 stderr. Use the "-A" and "-E" options to log to files.
2482 2489 """
2483 2490
2484 2491 if opts["stdio"]:
2485 2492 if repo is None:
2486 2493 raise hg.RepoError(_("There is no Mercurial repository here"
2487 2494 " (.hg not found)"))
2488 2495 s = sshserver.sshserver(ui, repo)
2489 2496 s.serve_forever()
2490 2497
2491 2498 parentui = ui.parentui or ui
2492 2499 optlist = ("name templates style address port ipv6"
2493 2500 " accesslog errorlog webdir_conf certificate")
2494 2501 for o in optlist.split():
2495 2502 if opts[o]:
2496 2503 parentui.setconfig("web", o, str(opts[o]))
2497 2504 if (repo is not None) and (repo.ui != parentui):
2498 2505 repo.ui.setconfig("web", o, str(opts[o]))
2499 2506
2500 2507 if repo is None and not ui.config("web", "webdir_conf"):
2501 2508 raise hg.RepoError(_("There is no Mercurial repository here"
2502 2509 " (.hg not found)"))
2503 2510
2504 2511 class service:
2505 2512 def init(self):
2506 2513 util.set_signal_handler()
2507 2514 try:
2508 2515 self.httpd = hgweb.server.create_server(parentui, repo)
2509 2516 except socket.error, inst:
2510 2517 raise util.Abort(_('cannot start server: ') + inst.args[1])
2511 2518
2512 2519 if not ui.verbose: return
2513 2520
2514 2521 if self.httpd.port != 80:
2515 2522 ui.status(_('listening at http://%s:%d/\n') %
2516 2523 (self.httpd.addr, self.httpd.port))
2517 2524 else:
2518 2525 ui.status(_('listening at http://%s/\n') % self.httpd.addr)
2519 2526
2520 2527 def run(self):
2521 2528 self.httpd.serve_forever()
2522 2529
2523 2530 service = service()
2524 2531
2525 2532 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2526 2533
2527 2534 def status(ui, repo, *pats, **opts):
2528 2535 """show changed files in the working directory
2529 2536
2530 2537 Show status of files in the repository. If names are given, only
2531 2538 files that match are shown. Files that are clean or ignored, are
2532 2539 not listed unless -c (clean), -i (ignored) or -A is given.
2533 2540
2534 2541 NOTE: status may appear to disagree with diff if permissions have
2535 2542 changed or a merge has occurred. The standard diff format does not
2536 2543 report permission changes and diff only reports changes relative
2537 2544 to one merge parent.
2538 2545
2539 2546 If one revision is given, it is used as the base revision.
2540 2547 If two revisions are given, the difference between them is shown.
2541 2548
2542 2549 The codes used to show the status of files are:
2543 2550 M = modified
2544 2551 A = added
2545 2552 R = removed
2546 2553 C = clean
2547 2554 ! = deleted, but still tracked
2548 2555 ? = not tracked
2549 2556 I = ignored (not shown by default)
2550 2557 = the previous added file was copied from here
2551 2558 """
2552 2559
2553 2560 all = opts['all']
2554 2561 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2555 2562
2556 2563 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2557 2564 cwd = (pats and repo.getcwd()) or ''
2558 2565 modified, added, removed, deleted, unknown, ignored, clean = [
2559 2566 n for n in repo.status(node1=node1, node2=node2, files=files,
2560 2567 match=matchfn,
2561 2568 list_ignored=all or opts['ignored'],
2562 2569 list_clean=all or opts['clean'])]
2563 2570
2564 2571 changetypes = (('modified', 'M', modified),
2565 2572 ('added', 'A', added),
2566 2573 ('removed', 'R', removed),
2567 2574 ('deleted', '!', deleted),
2568 2575 ('unknown', '?', unknown),
2569 2576 ('ignored', 'I', ignored))
2570 2577
2571 2578 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2572 2579
2573 2580 end = opts['print0'] and '\0' or '\n'
2574 2581
2575 2582 for opt, char, changes in ([ct for ct in explicit_changetypes
2576 2583 if all or opts[ct[0]]]
2577 2584 or changetypes):
2578 2585 if opts['no_status']:
2579 2586 format = "%%s%s" % end
2580 2587 else:
2581 2588 format = "%s %%s%s" % (char, end)
2582 2589
2583 2590 for f in changes:
2584 2591 ui.write(format % repo.pathto(f, cwd))
2585 2592 if ((all or opts.get('copies')) and not opts.get('no_status')):
2586 2593 copied = repo.dirstate.copied(f)
2587 2594 if copied:
2588 2595 ui.write(' %s%s' % (repo.pathto(copied, cwd), end))
2589 2596
2590 2597 def tag(ui, repo, name, rev_=None, **opts):
2591 2598 """add a tag for the current or given revision
2592 2599
2593 2600 Name a particular revision using <name>.
2594 2601
2595 2602 Tags are used to name particular revisions of the repository and are
2596 2603 very useful to compare different revision, to go back to significant
2597 2604 earlier versions or to mark branch points as releases, etc.
2598 2605
2599 2606 If no revision is given, the parent of the working directory is used,
2600 2607 or tip if no revision is checked out.
2601 2608
2602 2609 To facilitate version control, distribution, and merging of tags,
2603 2610 they are stored as a file named ".hgtags" which is managed
2604 2611 similarly to other project files and can be hand-edited if
2605 2612 necessary. The file '.hg/localtags' is used for local tags (not
2606 2613 shared among repositories).
2607 2614 """
2608 2615 if name in ['tip', '.', 'null']:
2609 2616 raise util.Abort(_("the name '%s' is reserved") % name)
2610 2617 if rev_ is not None:
2611 2618 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2612 2619 "please use 'hg tag [-r REV] NAME' instead\n"))
2613 2620 if opts['rev']:
2614 2621 raise util.Abort(_("use only one form to specify the revision"))
2615 2622 if opts['rev'] and opts['remove']:
2616 2623 raise util.Abort(_("--rev and --remove are incompatible"))
2617 2624 if opts['rev']:
2618 2625 rev_ = opts['rev']
2619 2626 message = opts['message']
2620 2627 if opts['remove']:
2621 2628 if not name in repo.tags():
2622 2629 raise util.Abort(_('tag %s does not exist') % name)
2623 2630 rev_ = nullid
2624 2631 if not message:
2625 2632 message = _('Removed tag %s') % name
2626 2633 elif name in repo.tags() and not opts['force']:
2627 2634 raise util.Abort(_('a tag named %s already exists (use -f to force)')
2628 2635 % name)
2629 2636 if not rev_ and repo.dirstate.parents()[1] != nullid:
2630 2637 raise util.Abort(_('uncommitted merge - please provide a '
2631 2638 'specific revision'))
2632 2639 r = repo.changectx(rev_).node()
2633 2640
2634 2641 if not message:
2635 2642 message = _('Added tag %s for changeset %s') % (name, short(r))
2636 2643
2637 2644 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2638 2645
2639 2646 def tags(ui, repo):
2640 2647 """list repository tags
2641 2648
2642 2649 List the repository tags.
2643 2650
2644 2651 This lists both regular and local tags.
2645 2652 """
2646 2653
2647 2654 l = repo.tagslist()
2648 2655 l.reverse()
2649 2656 hexfunc = ui.debugflag and hex or short
2650 2657 for t, n in l:
2651 2658 try:
2652 2659 hn = hexfunc(n)
2653 2660 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2654 2661 except revlog.LookupError:
2655 2662 r = " ?:%s" % hn
2656 2663 if ui.quiet:
2657 2664 ui.write("%s\n" % t)
2658 2665 else:
2659 2666 spaces = " " * (30 - util.locallen(t))
2660 2667 ui.write("%s%s %s\n" % (t, spaces, r))
2661 2668
2662 2669 def tip(ui, repo, **opts):
2663 2670 """show the tip revision
2664 2671
2665 2672 Show the tip revision.
2666 2673 """
2667 2674 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2668 2675
2669 2676 def unbundle(ui, repo, fname1, *fnames, **opts):
2670 2677 """apply one or more changegroup files
2671 2678
2672 2679 Apply one or more compressed changegroup files generated by the
2673 2680 bundle command.
2674 2681 """
2675 2682 fnames = (fname1,) + fnames
2676 2683 for fname in fnames:
2677 2684 if os.path.exists(fname):
2678 2685 f = open(fname, "rb")
2679 2686 else:
2680 2687 f = urllib.urlopen(fname)
2681 2688 gen = changegroup.readbundle(f, fname)
2682 2689 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2683 2690
2684 2691 return postincoming(ui, repo, modheads, opts['update'], None)
2685 2692
2686 2693 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2687 2694 """update working directory
2688 2695
2689 2696 Update the working directory to the specified revision, or the
2690 2697 tip of the current branch if none is specified.
2691 2698
2692 2699 If there are no outstanding changes in the working directory and
2693 2700 there is a linear relationship between the current version and the
2694 2701 requested version, the result is the requested version.
2695 2702
2696 2703 To merge the working directory with another revision, use the
2697 2704 merge command.
2698 2705
2699 2706 By default, update will refuse to run if doing so would require
2700 2707 discarding local changes.
2701 2708 """
2702 2709 if rev and node:
2703 2710 raise util.Abort(_("please specify just one revision"))
2704 2711
2705 2712 if not rev:
2706 2713 rev = node
2707 2714
2708 2715 if date:
2709 2716 if rev:
2710 2717 raise util.Abort(_("you can't specify a revision and a date"))
2711 2718 rev = cmdutil.finddate(ui, repo, date)
2712 2719
2713 2720 if clean:
2714 2721 return hg.clean(repo, rev)
2715 2722 else:
2716 2723 return hg.update(repo, rev)
2717 2724
2718 2725 def verify(ui, repo):
2719 2726 """verify the integrity of the repository
2720 2727
2721 2728 Verify the integrity of the current repository.
2722 2729
2723 2730 This will perform an extensive check of the repository's
2724 2731 integrity, validating the hashes and checksums of each entry in
2725 2732 the changelog, manifest, and tracked files, as well as the
2726 2733 integrity of their crosslinks and indices.
2727 2734 """
2728 2735 return hg.verify(repo)
2729 2736
2730 2737 def version_(ui):
2731 2738 """output version and copyright information"""
2732 2739 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2733 2740 % version.get_version())
2734 2741 ui.status(_(
2735 2742 "\nCopyright (C) 2005-2007 Matt Mackall <mpm@selenic.com> and others\n"
2736 2743 "This is free software; see the source for copying conditions. "
2737 2744 "There is NO\nwarranty; "
2738 2745 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2739 2746 ))
2740 2747
2741 2748 # Command options and aliases are listed here, alphabetically
2742 2749
2743 2750 globalopts = [
2744 2751 ('R', 'repository', '',
2745 2752 _('repository root directory or symbolic path name')),
2746 2753 ('', 'cwd', '', _('change working directory')),
2747 2754 ('y', 'noninteractive', None,
2748 2755 _('do not prompt, assume \'yes\' for any required answers')),
2749 2756 ('q', 'quiet', None, _('suppress output')),
2750 2757 ('v', 'verbose', None, _('enable additional output')),
2751 2758 ('', 'config', [], _('set/override config option')),
2752 2759 ('', 'debug', None, _('enable debugging output')),
2753 2760 ('', 'debugger', None, _('start debugger')),
2754 2761 ('', 'encoding', util._encoding, _('set the charset encoding')),
2755 2762 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2756 2763 ('', 'lsprof', None, _('print improved command execution profile')),
2757 2764 ('', 'traceback', None, _('print traceback on exception')),
2758 2765 ('', 'time', None, _('time how long the command takes')),
2759 2766 ('', 'profile', None, _('print command execution profile')),
2760 2767 ('', 'version', None, _('output version information and exit')),
2761 2768 ('h', 'help', None, _('display help and exit')),
2762 2769 ]
2763 2770
2764 2771 dryrunopts = [('n', 'dry-run', None,
2765 2772 _('do not perform actions, just print output'))]
2766 2773
2767 2774 remoteopts = [
2768 2775 ('e', 'ssh', '', _('specify ssh command to use')),
2769 2776 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2770 2777 ]
2771 2778
2772 2779 walkopts = [
2773 2780 ('I', 'include', [], _('include names matching the given patterns')),
2774 2781 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2775 2782 ]
2776 2783
2777 2784 commitopts = [
2778 2785 ('m', 'message', '', _('use <text> as commit message')),
2779 2786 ('l', 'logfile', '', _('read commit message from <file>')),
2780 2787 ]
2781 2788
2782 2789 commitopts2 = [
2783 2790 ('d', 'date', '', _('record datecode as commit date')),
2784 2791 ('u', 'user', '', _('record user as committer')),
2785 2792 ]
2786 2793
2787 2794 table = {
2788 2795 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2789 2796 "addremove":
2790 2797 (addremove,
2791 2798 [('s', 'similarity', '',
2792 2799 _('guess renamed files by similarity (0<=s<=100)')),
2793 2800 ] + walkopts + dryrunopts,
2794 2801 _('hg addremove [OPTION]... [FILE]...')),
2795 2802 "^annotate":
2796 2803 (annotate,
2797 2804 [('r', 'rev', '', _('annotate the specified revision')),
2798 2805 ('f', 'follow', None, _('follow file copies and renames')),
2799 2806 ('a', 'text', None, _('treat all files as text')),
2800 2807 ('u', 'user', None, _('list the author')),
2801 2808 ('d', 'date', None, _('list the date')),
2802 2809 ('n', 'number', None, _('list the revision number (default)')),
2803 2810 ('c', 'changeset', None, _('list the changeset')),
2804 2811 ('l', 'line-number', None,
2805 2812 _('show line number at the first appearance'))
2806 2813 ] + walkopts,
2807 2814 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
2808 2815 "archive":
2809 2816 (archive,
2810 2817 [('', 'no-decode', None, _('do not pass files through decoders')),
2811 2818 ('p', 'prefix', '', _('directory prefix for files in archive')),
2812 2819 ('r', 'rev', '', _('revision to distribute')),
2813 2820 ('t', 'type', '', _('type of distribution to create')),
2814 2821 ] + walkopts,
2815 2822 _('hg archive [OPTION]... DEST')),
2816 2823 "backout":
2817 2824 (backout,
2818 2825 [('', 'merge', None,
2819 2826 _('merge with old dirstate parent after backout')),
2820 2827 ('', 'parent', '', _('parent to choose when backing out merge')),
2821 2828 ('r', 'rev', '', _('revision to backout')),
2822 2829 ] + walkopts + commitopts + commitopts2,
2823 2830 _('hg backout [OPTION]... [-r] REV')),
2824 2831 "branch":
2825 2832 (branch,
2826 2833 [('f', 'force', None,
2827 2834 _('set branch name even if it shadows an existing branch'))],
2828 2835 _('hg branch [NAME]')),
2829 2836 "branches":
2830 2837 (branches,
2831 2838 [('a', 'active', False,
2832 2839 _('show only branches that have unmerged heads'))],
2833 2840 _('hg branches [-a]')),
2834 2841 "bundle":
2835 2842 (bundle,
2836 2843 [('f', 'force', None,
2837 2844 _('run even when remote repository is unrelated')),
2838 2845 ('r', 'rev', [],
2839 2846 _('a changeset you would like to bundle')),
2840 2847 ('', 'base', [],
2841 2848 _('a base changeset to specify instead of a destination')),
2842 2849 ] + remoteopts,
2843 2850 _('hg bundle [-f] [-r REV]... [--base REV]... FILE [DEST]')),
2844 2851 "cat":
2845 2852 (cat,
2846 2853 [('o', 'output', '', _('print output to file with formatted name')),
2847 2854 ('r', 'rev', '', _('print the given revision')),
2848 2855 ] + walkopts,
2849 2856 _('hg cat [OPTION]... FILE...')),
2850 2857 "^clone":
2851 2858 (clone,
2852 2859 [('U', 'noupdate', None, _('do not update the new working directory')),
2853 2860 ('r', 'rev', [],
2854 2861 _('a changeset you would like to have after cloning')),
2855 2862 ('', 'pull', None, _('use pull protocol to copy metadata')),
2856 2863 ('', 'uncompressed', None,
2857 2864 _('use uncompressed transfer (fast over LAN)')),
2858 2865 ] + remoteopts,
2859 2866 _('hg clone [OPTION]... SOURCE [DEST]')),
2860 2867 "^commit|ci":
2861 2868 (commit,
2862 2869 [('A', 'addremove', None,
2863 2870 _('mark new/missing files as added/removed before committing')),
2864 2871 ] + walkopts + commitopts + commitopts2,
2865 2872 _('hg commit [OPTION]... [FILE]...')),
2866 2873 "copy|cp":
2867 2874 (copy,
2868 2875 [('A', 'after', None, _('record a copy that has already occurred')),
2869 2876 ('f', 'force', None,
2870 2877 _('forcibly copy over an existing managed file')),
2871 2878 ] + walkopts + dryrunopts,
2872 2879 _('hg copy [OPTION]... [SOURCE]... DEST')),
2873 2880 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2874 2881 "debugcomplete":
2875 2882 (debugcomplete,
2876 2883 [('o', 'options', None, _('show the command options'))],
2877 2884 _('debugcomplete [-o] CMD')),
2878 2885 "debuginstall": (debuginstall, [], _('debuginstall')),
2879 2886 "debugrebuildstate":
2880 2887 (debugrebuildstate,
2881 2888 [('r', 'rev', '', _('revision to rebuild to'))],
2882 2889 _('debugrebuildstate [-r REV] [REV]')),
2883 2890 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2884 2891 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2885 2892 "debugstate": (debugstate, [], _('debugstate')),
2886 2893 "debugdate":
2887 2894 (debugdate,
2888 2895 [('e', 'extended', None, _('try extended date formats'))],
2889 2896 _('debugdate [-e] DATE [RANGE]')),
2890 2897 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2891 2898 "debugindex": (debugindex, [], _('debugindex FILE')),
2892 2899 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2893 2900 "debugrename":
2894 2901 (debugrename,
2895 2902 [('r', 'rev', '', _('revision to debug'))],
2896 2903 _('debugrename [-r REV] FILE')),
2897 2904 "debugwalk": (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2898 2905 "^diff":
2899 2906 (diff,
2900 2907 [('r', 'rev', [], _('revision')),
2901 2908 ('a', 'text', None, _('treat all files as text')),
2902 2909 ('p', 'show-function', None,
2903 2910 _('show which function each change is in')),
2904 2911 ('g', 'git', None, _('use git extended diff format')),
2905 2912 ('', 'nodates', None, _("don't include dates in diff headers")),
2906 2913 ('w', 'ignore-all-space', None,
2907 2914 _('ignore white space when comparing lines')),
2908 2915 ('b', 'ignore-space-change', None,
2909 2916 _('ignore changes in the amount of white space')),
2910 2917 ('B', 'ignore-blank-lines', None,
2911 2918 _('ignore changes whose lines are all blank')),
2912 2919 ] + walkopts,
2913 2920 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
2914 2921 "^export":
2915 2922 (export,
2916 2923 [('o', 'output', '', _('print output to file with formatted name')),
2917 2924 ('a', 'text', None, _('treat all files as text')),
2918 2925 ('g', 'git', None, _('use git extended diff format')),
2919 2926 ('', 'nodates', None, _("don't include dates in diff headers")),
2920 2927 ('', 'switch-parent', None, _('diff against the second parent'))],
2921 2928 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
2922 2929 "grep":
2923 2930 (grep,
2924 2931 [('0', 'print0', None, _('end fields with NUL')),
2925 2932 ('', 'all', None, _('print all revisions that match')),
2926 2933 ('f', 'follow', None,
2927 2934 _('follow changeset history, or file history across copies and renames')),
2928 2935 ('i', 'ignore-case', None, _('ignore case when matching')),
2929 2936 ('l', 'files-with-matches', None,
2930 2937 _('print only filenames and revs that match')),
2931 2938 ('n', 'line-number', None, _('print matching line numbers')),
2932 2939 ('r', 'rev', [], _('search in given revision range')),
2933 2940 ('u', 'user', None, _('print user who committed change')),
2934 2941 ] + walkopts,
2935 2942 _('hg grep [OPTION]... PATTERN [FILE]...')),
2936 2943 "heads":
2937 2944 (heads,
2938 2945 [('', 'style', '', _('display using template map file')),
2939 2946 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2940 2947 ('', 'template', '', _('display with template'))],
2941 2948 _('hg heads [-r REV] [REV]...')),
2942 2949 "help": (help_, [], _('hg help [COMMAND]')),
2943 2950 "identify|id":
2944 2951 (identify,
2945 2952 [('r', 'rev', '', _('identify the specified rev')),
2946 2953 ('n', 'num', None, _('show local revision number')),
2947 2954 ('i', 'id', None, _('show global revision id')),
2948 2955 ('b', 'branch', None, _('show branch')),
2949 2956 ('t', 'tags', None, _('show tags'))],
2950 2957 _('hg identify [-nibt] [-r REV] [SOURCE]')),
2951 2958 "import|patch":
2952 2959 (import_,
2953 2960 [('p', 'strip', 1,
2954 2961 _('directory strip option for patch. This has the same\n'
2955 2962 'meaning as the corresponding patch option')),
2956 2963 ('b', 'base', '', _('base path')),
2957 2964 ('f', 'force', None,
2958 2965 _('skip check for outstanding uncommitted changes')),
2959 2966 ('', 'exact', None,
2960 2967 _('apply patch to the nodes from which it was generated')),
2961 2968 ('', 'import-branch', None,
2962 2969 _('Use any branch information in patch (implied by --exact)'))] + commitopts,
2963 2970 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2964 2971 "incoming|in": (incoming,
2965 2972 [('M', 'no-merges', None, _('do not show merges')),
2966 2973 ('f', 'force', None,
2967 2974 _('run even when remote repository is unrelated')),
2968 2975 ('', 'style', '', _('display using template map file')),
2969 2976 ('n', 'newest-first', None, _('show newest record first')),
2970 2977 ('', 'bundle', '', _('file to store the bundles into')),
2971 2978 ('p', 'patch', None, _('show patch')),
2972 2979 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2973 2980 ('', 'template', '', _('display with template')),
2974 2981 ] + remoteopts,
2975 2982 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
2976 2983 ' [--bundle FILENAME] [SOURCE]')),
2977 2984 "^init":
2978 2985 (init,
2979 2986 remoteopts,
2980 2987 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
2981 2988 "locate":
2982 2989 (locate,
2983 2990 [('r', 'rev', '', _('search the repository as it stood at rev')),
2984 2991 ('0', 'print0', None,
2985 2992 _('end filenames with NUL, for use with xargs')),
2986 2993 ('f', 'fullpath', None,
2987 2994 _('print complete paths from the filesystem root')),
2988 2995 ] + walkopts,
2989 2996 _('hg locate [OPTION]... [PATTERN]...')),
2990 2997 "^log|history":
2991 2998 (log,
2992 2999 [('f', 'follow', None,
2993 3000 _('follow changeset history, or file history across copies and renames')),
2994 3001 ('', 'follow-first', None,
2995 3002 _('only follow the first parent of merge changesets')),
2996 3003 ('d', 'date', '', _('show revs matching date spec')),
2997 3004 ('C', 'copies', None, _('show copied files')),
2998 3005 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
2999 3006 ('l', 'limit', '', _('limit number of changes displayed')),
3000 3007 ('r', 'rev', [], _('show the specified revision or range')),
3001 3008 ('', 'removed', None, _('include revs where files were removed')),
3002 3009 ('M', 'no-merges', None, _('do not show merges')),
3003 3010 ('', 'style', '', _('display using template map file')),
3004 3011 ('m', 'only-merges', None, _('show only merges')),
3005 3012 ('p', 'patch', None, _('show patch')),
3006 3013 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3007 3014 ('', 'template', '', _('display with template')),
3008 3015 ] + walkopts,
3009 3016 _('hg log [OPTION]... [FILE]')),
3010 3017 "manifest": (manifest, [('r', 'rev', '', _('revision to display'))],
3011 3018 _('hg manifest [-r REV]')),
3012 3019 "^merge":
3013 3020 (merge,
3014 3021 [('f', 'force', None, _('force a merge with outstanding changes')),
3015 3022 ('r', 'rev', '', _('revision to merge')),
3016 3023 ],
3017 3024 _('hg merge [-f] [[-r] REV]')),
3018 3025 "outgoing|out": (outgoing,
3019 3026 [('M', 'no-merges', None, _('do not show merges')),
3020 3027 ('f', 'force', None,
3021 3028 _('run even when remote repository is unrelated')),
3022 3029 ('p', 'patch', None, _('show patch')),
3023 3030 ('', 'style', '', _('display using template map file')),
3024 3031 ('r', 'rev', [], _('a specific revision you would like to push')),
3025 3032 ('n', 'newest-first', None, _('show newest record first')),
3026 3033 ('', 'template', '', _('display with template')),
3027 3034 ] + remoteopts,
3028 3035 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3029 3036 "^parents":
3030 3037 (parents,
3031 3038 [('r', 'rev', '', _('show parents from the specified rev')),
3032 3039 ('', 'style', '', _('display using template map file')),
3033 3040 ('', 'template', '', _('display with template'))],
3034 3041 _('hg parents [-r REV] [FILE]')),
3035 3042 "paths": (paths, [], _('hg paths [NAME]')),
3036 3043 "^pull":
3037 3044 (pull,
3038 3045 [('u', 'update', None,
3039 3046 _('update to new tip if changesets were pulled')),
3040 3047 ('f', 'force', None,
3041 3048 _('run even when remote repository is unrelated')),
3042 3049 ('r', 'rev', [],
3043 3050 _('a specific revision up to which you would like to pull')),
3044 3051 ] + remoteopts,
3045 3052 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3046 3053 "^push":
3047 3054 (push,
3048 3055 [('f', 'force', None, _('force push')),
3049 3056 ('r', 'rev', [], _('a specific revision you would like to push')),
3050 3057 ] + remoteopts,
3051 3058 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3052 3059 "debugrawcommit|rawcommit":
3053 3060 (rawcommit,
3054 3061 [('p', 'parent', [], _('parent')),
3055 3062 ('F', 'files', '', _('file list'))
3056 3063 ] + commitopts + commitopts2,
3057 3064 _('hg debugrawcommit [OPTION]... [FILE]...')),
3058 3065 "recover": (recover, [], _('hg recover')),
3059 3066 "^remove|rm":
3060 3067 (remove,
3061 3068 [('A', 'after', None, _('record remove that has already occurred')),
3062 3069 ('f', 'force', None, _('remove file even if modified')),
3063 3070 ] + walkopts,
3064 3071 _('hg remove [OPTION]... FILE...')),
3065 3072 "rename|mv":
3066 3073 (rename,
3067 3074 [('A', 'after', None, _('record a rename that has already occurred')),
3068 3075 ('f', 'force', None,
3069 3076 _('forcibly copy over an existing managed file')),
3070 3077 ] + walkopts + dryrunopts,
3071 3078 _('hg rename [OPTION]... SOURCE... DEST')),
3072 3079 "^revert":
3073 3080 (revert,
3074 3081 [('a', 'all', None, _('revert all changes when no arguments given')),
3075 3082 ('d', 'date', '', _('tipmost revision matching date')),
3076 3083 ('r', 'rev', '', _('revision to revert to')),
3077 3084 ('', 'no-backup', None, _('do not save backup copies of files')),
3078 3085 ] + walkopts + dryrunopts,
3079 3086 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3080 3087 "rollback": (rollback, [], _('hg rollback')),
3081 3088 "root": (root, [], _('hg root')),
3082 3089 "showconfig|debugconfig":
3083 3090 (showconfig,
3084 3091 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3085 3092 _('showconfig [-u] [NAME]...')),
3086 3093 "^serve":
3087 3094 (serve,
3088 3095 [('A', 'accesslog', '', _('name of access log file to write to')),
3089 3096 ('d', 'daemon', None, _('run server in background')),
3090 3097 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3091 3098 ('E', 'errorlog', '', _('name of error log file to write to')),
3092 3099 ('p', 'port', 0, _('port to use (default: 8000)')),
3093 3100 ('a', 'address', '', _('address to use')),
3094 3101 ('n', 'name', '',
3095 3102 _('name to show in web pages (default: working dir)')),
3096 3103 ('', 'webdir-conf', '', _('name of the webdir config file'
3097 3104 ' (serve more than one repo)')),
3098 3105 ('', 'pid-file', '', _('name of file to write process ID to')),
3099 3106 ('', 'stdio', None, _('for remote clients')),
3100 3107 ('t', 'templates', '', _('web templates to use')),
3101 3108 ('', 'style', '', _('template style to use')),
3102 3109 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3103 3110 ('', 'certificate', '', _('SSL certificate file'))],
3104 3111 _('hg serve [OPTION]...')),
3105 3112 "^status|st":
3106 3113 (status,
3107 3114 [('A', 'all', None, _('show status of all files')),
3108 3115 ('m', 'modified', None, _('show only modified files')),
3109 3116 ('a', 'added', None, _('show only added files')),
3110 3117 ('r', 'removed', None, _('show only removed files')),
3111 3118 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3112 3119 ('c', 'clean', None, _('show only files without changes')),
3113 3120 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3114 3121 ('i', 'ignored', None, _('show only ignored files')),
3115 3122 ('n', 'no-status', None, _('hide status prefix')),
3116 3123 ('C', 'copies', None, _('show source of copied files')),
3117 3124 ('0', 'print0', None,
3118 3125 _('end filenames with NUL, for use with xargs')),
3119 3126 ('', 'rev', [], _('show difference from revision')),
3120 3127 ] + walkopts,
3121 3128 _('hg status [OPTION]... [FILE]...')),
3122 3129 "tag":
3123 3130 (tag,
3124 3131 [('f', 'force', None, _('replace existing tag')),
3125 3132 ('l', 'local', None, _('make the tag local')),
3126 3133 ('r', 'rev', '', _('revision to tag')),
3127 3134 ('', 'remove', None, _('remove a tag')),
3128 3135 # -l/--local is already there, commitopts cannot be used
3129 3136 ('m', 'message', '', _('use <text> as commit message')),
3130 3137 ] + commitopts2,
3131 3138 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3132 3139 "tags": (tags, [], _('hg tags')),
3133 3140 "tip":
3134 3141 (tip,
3135 3142 [('', 'style', '', _('display using template map file')),
3136 3143 ('p', 'patch', None, _('show patch')),
3137 3144 ('', 'template', '', _('display with template'))],
3138 3145 _('hg tip [-p]')),
3139 3146 "unbundle":
3140 3147 (unbundle,
3141 3148 [('u', 'update', None,
3142 3149 _('update to new tip if changesets were unbundled'))],
3143 3150 _('hg unbundle [-u] FILE...')),
3144 3151 "^update|up|checkout|co":
3145 3152 (update,
3146 3153 [('C', 'clean', None, _('overwrite locally modified files')),
3147 3154 ('d', 'date', '', _('tipmost revision matching date')),
3148 3155 ('r', 'rev', '', _('revision'))],
3149 3156 _('hg update [-C] [-d DATE] [[-r] REV]')),
3150 3157 "verify": (verify, [], _('hg verify')),
3151 3158 "version": (version_, [], _('hg version')),
3152 3159 }
3153 3160
3154 3161 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3155 3162 " debugindex debugindexdot debugdate debuginstall")
3156 optionalrepo = ("paths serve showconfig")
3163 optionalrepo = ("identify paths serve showconfig")
@@ -1,1205 +1,1208 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os, mimetypes, re, zlib, mimetools, cStringIO, sys
10 10 import tempfile, urllib, bz2
11 11 from mercurial.node import *
12 12 from mercurial.i18n import gettext as _
13 13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
14 14 from mercurial import revlog, templater
15 15 from common import get_mtime, staticfile, style_map, paritygen
16 16
17 17 def _up(p):
18 18 if p[0] != "/":
19 19 p = "/" + p
20 20 if p[-1] == "/":
21 21 p = p[:-1]
22 22 up = os.path.dirname(p)
23 23 if up == "/":
24 24 return "/"
25 25 return up + "/"
26 26
27 27 def revnavgen(pos, pagelen, limit, nodefunc):
28 28 def seq(factor, limit=None):
29 29 if limit:
30 30 yield limit
31 31 if limit >= 20 and limit <= 40:
32 32 yield 50
33 33 else:
34 34 yield 1 * factor
35 35 yield 3 * factor
36 36 for f in seq(factor * 10):
37 37 yield f
38 38
39 39 def nav(**map):
40 40 l = []
41 41 last = 0
42 42 for f in seq(1, pagelen):
43 43 if f < pagelen or f <= last:
44 44 continue
45 45 if f > limit:
46 46 break
47 47 last = f
48 48 if pos + f < limit:
49 49 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
50 50 if pos - f >= 0:
51 51 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
52 52
53 53 try:
54 54 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
55 55
56 56 for label, node in l:
57 57 yield {"label": label, "node": node}
58 58
59 59 yield {"label": "tip", "node": "tip"}
60 60 except hg.RepoError:
61 61 pass
62 62
63 63 return nav
64 64
65 65 class hgweb(object):
66 66 def __init__(self, repo, name=None):
67 67 if isinstance(repo, str):
68 68 parentui = ui.ui(report_untrusted=False, interactive=False)
69 69 self.repo = hg.repository(parentui, repo)
70 70 else:
71 71 self.repo = repo
72 72
73 73 self.mtime = -1
74 74 self.reponame = name
75 75 self.archives = 'zip', 'gz', 'bz2'
76 76 self.stripecount = 1
77 77 # a repo owner may set web.templates in .hg/hgrc to get any file
78 78 # readable by the user running the CGI script
79 79 self.templatepath = self.config("web", "templates",
80 80 templater.templatepath(),
81 81 untrusted=False)
82 82
83 83 # The CGI scripts are often run by a user different from the repo owner.
84 84 # Trust the settings from the .hg/hgrc files by default.
85 85 def config(self, section, name, default=None, untrusted=True):
86 86 return self.repo.ui.config(section, name, default,
87 87 untrusted=untrusted)
88 88
89 89 def configbool(self, section, name, default=False, untrusted=True):
90 90 return self.repo.ui.configbool(section, name, default,
91 91 untrusted=untrusted)
92 92
93 93 def configlist(self, section, name, default=None, untrusted=True):
94 94 return self.repo.ui.configlist(section, name, default,
95 95 untrusted=untrusted)
96 96
97 97 def refresh(self):
98 98 mtime = get_mtime(self.repo.root)
99 99 if mtime != self.mtime:
100 100 self.mtime = mtime
101 101 self.repo = hg.repository(self.repo.ui, self.repo.root)
102 102 self.maxchanges = int(self.config("web", "maxchanges", 10))
103 103 self.stripecount = int(self.config("web", "stripes", 1))
104 104 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
105 105 self.maxfiles = int(self.config("web", "maxfiles", 10))
106 106 self.allowpull = self.configbool("web", "allowpull", True)
107 107 self.encoding = self.config("web", "encoding", util._encoding)
108 108
109 109 def archivelist(self, nodeid):
110 110 allowed = self.configlist("web", "allow_archive")
111 111 for i, spec in self.archive_specs.iteritems():
112 112 if i in allowed or self.configbool("web", "allow" + i):
113 113 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
114 114
115 115 def listfilediffs(self, files, changeset):
116 116 for f in files[:self.maxfiles]:
117 117 yield self.t("filedifflink", node=hex(changeset), file=f)
118 118 if len(files) > self.maxfiles:
119 119 yield self.t("fileellipses")
120 120
121 121 def siblings(self, siblings=[], hiderev=None, **args):
122 122 siblings = [s for s in siblings if s.node() != nullid]
123 123 if len(siblings) == 1 and siblings[0].rev() == hiderev:
124 124 return
125 125 for s in siblings:
126 126 d = {'node': hex(s.node()), 'rev': s.rev()}
127 127 if hasattr(s, 'path'):
128 128 d['file'] = s.path()
129 129 d.update(args)
130 130 yield d
131 131
132 132 def renamelink(self, fl, node):
133 133 r = fl.renamed(node)
134 134 if r:
135 135 return [dict(file=r[0], node=hex(r[1]))]
136 136 return []
137 137
138 138 def nodetagsdict(self, node):
139 139 return [{"name": i} for i in self.repo.nodetags(node)]
140 140
141 141 def nodebranchdict(self, ctx):
142 142 branches = []
143 143 branch = ctx.branch()
144 if self.repo.branchtags()[branch] == ctx.node():
144 # If this is an empty repo, ctx.node() == nullid,
145 # ctx.branch() == 'default', but branchtags() is
146 # an empty dict. Using dict.get avoids a traceback.
147 if self.repo.branchtags().get(branch) == ctx.node():
145 148 branches.append({"name": branch})
146 149 return branches
147 150
148 151 def showtag(self, t1, node=nullid, **args):
149 152 for t in self.repo.nodetags(node):
150 153 yield self.t(t1, tag=t, **args)
151 154
152 155 def diff(self, node1, node2, files):
153 156 def filterfiles(filters, files):
154 157 l = [x for x in files if x in filters]
155 158
156 159 for t in filters:
157 160 if t and t[-1] != os.sep:
158 161 t += os.sep
159 162 l += [x for x in files if x.startswith(t)]
160 163 return l
161 164
162 165 parity = paritygen(self.stripecount)
163 166 def diffblock(diff, f, fn):
164 167 yield self.t("diffblock",
165 168 lines=prettyprintlines(diff),
166 169 parity=parity.next(),
167 170 file=f,
168 171 filenode=hex(fn or nullid))
169 172
170 173 def prettyprintlines(diff):
171 174 for l in diff.splitlines(1):
172 175 if l.startswith('+'):
173 176 yield self.t("difflineplus", line=l)
174 177 elif l.startswith('-'):
175 178 yield self.t("difflineminus", line=l)
176 179 elif l.startswith('@'):
177 180 yield self.t("difflineat", line=l)
178 181 else:
179 182 yield self.t("diffline", line=l)
180 183
181 184 r = self.repo
182 185 c1 = r.changectx(node1)
183 186 c2 = r.changectx(node2)
184 187 date1 = util.datestr(c1.date())
185 188 date2 = util.datestr(c2.date())
186 189
187 190 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
188 191 if files:
189 192 modified, added, removed = map(lambda x: filterfiles(files, x),
190 193 (modified, added, removed))
191 194
192 195 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
193 196 for f in modified:
194 197 to = c1.filectx(f).data()
195 198 tn = c2.filectx(f).data()
196 199 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
197 200 opts=diffopts), f, tn)
198 201 for f in added:
199 202 to = None
200 203 tn = c2.filectx(f).data()
201 204 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
202 205 opts=diffopts), f, tn)
203 206 for f in removed:
204 207 to = c1.filectx(f).data()
205 208 tn = None
206 209 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
207 210 opts=diffopts), f, tn)
208 211
209 212 def changelog(self, ctx, shortlog=False):
210 213 def changelist(limit=0,**map):
211 214 cl = self.repo.changelog
212 215 l = [] # build a list in forward order for efficiency
213 216 for i in xrange(start, end):
214 217 ctx = self.repo.changectx(i)
215 218 n = ctx.node()
216 219
217 220 l.insert(0, {"parity": parity.next(),
218 221 "author": ctx.user(),
219 222 "parent": self.siblings(ctx.parents(), i - 1),
220 223 "child": self.siblings(ctx.children(), i + 1),
221 224 "changelogtag": self.showtag("changelogtag",n),
222 225 "desc": ctx.description(),
223 226 "date": ctx.date(),
224 227 "files": self.listfilediffs(ctx.files(), n),
225 228 "rev": i,
226 229 "node": hex(n),
227 230 "tags": self.nodetagsdict(n),
228 231 "branches": self.nodebranchdict(ctx)})
229 232
230 233 if limit > 0:
231 234 l = l[:limit]
232 235
233 236 for e in l:
234 237 yield e
235 238
236 239 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
237 240 cl = self.repo.changelog
238 241 count = cl.count()
239 242 pos = ctx.rev()
240 243 start = max(0, pos - maxchanges + 1)
241 244 end = min(count, start + maxchanges)
242 245 pos = end - 1
243 246 parity = paritygen(self.stripecount, offset=start-end)
244 247
245 248 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
246 249
247 250 yield self.t(shortlog and 'shortlog' or 'changelog',
248 251 changenav=changenav,
249 252 node=hex(cl.tip()),
250 253 rev=pos, changesets=count,
251 254 entries=lambda **x: changelist(limit=0,**x),
252 255 latestentry=lambda **x: changelist(limit=1,**x),
253 256 archives=self.archivelist("tip"))
254 257
255 258 def search(self, query):
256 259
257 260 def changelist(**map):
258 261 cl = self.repo.changelog
259 262 count = 0
260 263 qw = query.lower().split()
261 264
262 265 def revgen():
263 266 for i in xrange(cl.count() - 1, 0, -100):
264 267 l = []
265 268 for j in xrange(max(0, i - 100), i):
266 269 ctx = self.repo.changectx(j)
267 270 l.append(ctx)
268 271 l.reverse()
269 272 for e in l:
270 273 yield e
271 274
272 275 for ctx in revgen():
273 276 miss = 0
274 277 for q in qw:
275 278 if not (q in ctx.user().lower() or
276 279 q in ctx.description().lower() or
277 280 q in " ".join(ctx.files()).lower()):
278 281 miss = 1
279 282 break
280 283 if miss:
281 284 continue
282 285
283 286 count += 1
284 287 n = ctx.node()
285 288
286 289 yield self.t('searchentry',
287 290 parity=parity.next(),
288 291 author=ctx.user(),
289 292 parent=self.siblings(ctx.parents()),
290 293 child=self.siblings(ctx.children()),
291 294 changelogtag=self.showtag("changelogtag",n),
292 295 desc=ctx.description(),
293 296 date=ctx.date(),
294 297 files=self.listfilediffs(ctx.files(), n),
295 298 rev=ctx.rev(),
296 299 node=hex(n),
297 300 tags=self.nodetagsdict(n),
298 301 branches=self.nodebranchdict(ctx))
299 302
300 303 if count >= self.maxchanges:
301 304 break
302 305
303 306 cl = self.repo.changelog
304 307 parity = paritygen(self.stripecount)
305 308
306 309 yield self.t('search',
307 310 query=query,
308 311 node=hex(cl.tip()),
309 312 entries=changelist,
310 313 archives=self.archivelist("tip"))
311 314
312 315 def changeset(self, ctx):
313 316 n = ctx.node()
314 317 parents = ctx.parents()
315 318 p1 = parents[0].node()
316 319
317 320 files = []
318 321 parity = paritygen(self.stripecount)
319 322 for f in ctx.files():
320 323 files.append(self.t("filenodelink",
321 324 node=hex(n), file=f,
322 325 parity=parity.next()))
323 326
324 327 def diff(**map):
325 328 yield self.diff(p1, n, None)
326 329
327 330 yield self.t('changeset',
328 331 diff=diff,
329 332 rev=ctx.rev(),
330 333 node=hex(n),
331 334 parent=self.siblings(parents),
332 335 child=self.siblings(ctx.children()),
333 336 changesettag=self.showtag("changesettag",n),
334 337 author=ctx.user(),
335 338 desc=ctx.description(),
336 339 date=ctx.date(),
337 340 files=files,
338 341 archives=self.archivelist(hex(n)),
339 342 tags=self.nodetagsdict(n),
340 343 branches=self.nodebranchdict(ctx))
341 344
342 345 def filelog(self, fctx):
343 346 f = fctx.path()
344 347 fl = fctx.filelog()
345 348 count = fl.count()
346 349 pagelen = self.maxshortchanges
347 350 pos = fctx.filerev()
348 351 start = max(0, pos - pagelen + 1)
349 352 end = min(count, start + pagelen)
350 353 pos = end - 1
351 354 parity = paritygen(self.stripecount, offset=start-end)
352 355
353 356 def entries(limit=0, **map):
354 357 l = []
355 358
356 359 for i in xrange(start, end):
357 360 ctx = fctx.filectx(i)
358 361 n = fl.node(i)
359 362
360 363 l.insert(0, {"parity": parity.next(),
361 364 "filerev": i,
362 365 "file": f,
363 366 "node": hex(ctx.node()),
364 367 "author": ctx.user(),
365 368 "date": ctx.date(),
366 369 "rename": self.renamelink(fl, n),
367 370 "parent": self.siblings(fctx.parents()),
368 371 "child": self.siblings(fctx.children()),
369 372 "desc": ctx.description()})
370 373
371 374 if limit > 0:
372 375 l = l[:limit]
373 376
374 377 for e in l:
375 378 yield e
376 379
377 380 nodefunc = lambda x: fctx.filectx(fileid=x)
378 381 nav = revnavgen(pos, pagelen, count, nodefunc)
379 382 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
380 383 entries=lambda **x: entries(limit=0, **x),
381 384 latestentry=lambda **x: entries(limit=1, **x))
382 385
383 386 def filerevision(self, fctx):
384 387 f = fctx.path()
385 388 text = fctx.data()
386 389 fl = fctx.filelog()
387 390 n = fctx.filenode()
388 391 parity = paritygen(self.stripecount)
389 392
390 393 mt = mimetypes.guess_type(f)[0]
391 394 rawtext = text
392 395 if util.binary(text):
393 396 mt = mt or 'application/octet-stream'
394 397 text = "(binary:%s)" % mt
395 398 mt = mt or 'text/plain'
396 399
397 400 def lines():
398 401 for l, t in enumerate(text.splitlines(1)):
399 402 yield {"line": t,
400 403 "linenumber": "% 6d" % (l + 1),
401 404 "parity": parity.next()}
402 405
403 406 yield self.t("filerevision",
404 407 file=f,
405 408 path=_up(f),
406 409 text=lines(),
407 410 raw=rawtext,
408 411 mimetype=mt,
409 412 rev=fctx.rev(),
410 413 node=hex(fctx.node()),
411 414 author=fctx.user(),
412 415 date=fctx.date(),
413 416 desc=fctx.description(),
414 417 parent=self.siblings(fctx.parents()),
415 418 child=self.siblings(fctx.children()),
416 419 rename=self.renamelink(fl, n),
417 420 permissions=fctx.manifest().flags(f))
418 421
419 422 def fileannotate(self, fctx):
420 423 f = fctx.path()
421 424 n = fctx.filenode()
422 425 fl = fctx.filelog()
423 426 parity = paritygen(self.stripecount)
424 427
425 428 def annotate(**map):
426 429 last = None
427 430 for f, l in fctx.annotate(follow=True):
428 431 fnode = f.filenode()
429 432 name = self.repo.ui.shortuser(f.user())
430 433
431 434 if last != fnode:
432 435 last = fnode
433 436
434 437 yield {"parity": parity.next(),
435 438 "node": hex(f.node()),
436 439 "rev": f.rev(),
437 440 "author": name,
438 441 "file": f.path(),
439 442 "line": l}
440 443
441 444 yield self.t("fileannotate",
442 445 file=f,
443 446 annotate=annotate,
444 447 path=_up(f),
445 448 rev=fctx.rev(),
446 449 node=hex(fctx.node()),
447 450 author=fctx.user(),
448 451 date=fctx.date(),
449 452 desc=fctx.description(),
450 453 rename=self.renamelink(fl, n),
451 454 parent=self.siblings(fctx.parents()),
452 455 child=self.siblings(fctx.children()),
453 456 permissions=fctx.manifest().flags(f))
454 457
455 458 def manifest(self, ctx, path):
456 459 mf = ctx.manifest()
457 460 node = ctx.node()
458 461
459 462 files = {}
460 463 parity = paritygen(self.stripecount)
461 464
462 465 if path and path[-1] != "/":
463 466 path += "/"
464 467 l = len(path)
465 468 abspath = "/" + path
466 469
467 470 for f, n in mf.items():
468 471 if f[:l] != path:
469 472 continue
470 473 remain = f[l:]
471 474 if "/" in remain:
472 475 short = remain[:remain.index("/") + 1] # bleah
473 476 files[short] = (f, None)
474 477 else:
475 478 short = os.path.basename(remain)
476 479 files[short] = (f, n)
477 480
478 481 def filelist(**map):
479 482 fl = files.keys()
480 483 fl.sort()
481 484 for f in fl:
482 485 full, fnode = files[f]
483 486 if not fnode:
484 487 continue
485 488
486 489 fctx = ctx.filectx(full)
487 490 yield {"file": full,
488 491 "parity": parity.next(),
489 492 "basename": f,
490 493 "date": fctx.changectx().date(),
491 494 "size": fctx.size(),
492 495 "permissions": mf.flags(full)}
493 496
494 497 def dirlist(**map):
495 498 fl = files.keys()
496 499 fl.sort()
497 500 for f in fl:
498 501 full, fnode = files[f]
499 502 if fnode:
500 503 continue
501 504
502 505 yield {"parity": parity.next(),
503 506 "path": "%s%s" % (abspath, f),
504 507 "basename": f[:-1]}
505 508
506 509 yield self.t("manifest",
507 510 rev=ctx.rev(),
508 511 node=hex(node),
509 512 path=abspath,
510 513 up=_up(abspath),
511 514 upparity=parity.next(),
512 515 fentries=filelist,
513 516 dentries=dirlist,
514 517 archives=self.archivelist(hex(node)),
515 518 tags=self.nodetagsdict(node),
516 519 branches=self.nodebranchdict(ctx))
517 520
518 521 def tags(self):
519 522 i = self.repo.tagslist()
520 523 i.reverse()
521 524 parity = paritygen(self.stripecount)
522 525
523 526 def entries(notip=False,limit=0, **map):
524 527 count = 0
525 528 for k, n in i:
526 529 if notip and k == "tip":
527 530 continue
528 531 if limit > 0 and count >= limit:
529 532 continue
530 533 count = count + 1
531 534 yield {"parity": parity.next(),
532 535 "tag": k,
533 536 "date": self.repo.changectx(n).date(),
534 537 "node": hex(n)}
535 538
536 539 yield self.t("tags",
537 540 node=hex(self.repo.changelog.tip()),
538 541 entries=lambda **x: entries(False,0, **x),
539 542 entriesnotip=lambda **x: entries(True,0, **x),
540 543 latestentry=lambda **x: entries(True,1, **x))
541 544
542 545 def summary(self):
543 546 i = self.repo.tagslist()
544 547 i.reverse()
545 548
546 549 def tagentries(**map):
547 550 parity = paritygen(self.stripecount)
548 551 count = 0
549 552 for k, n in i:
550 553 if k == "tip": # skip tip
551 554 continue;
552 555
553 556 count += 1
554 557 if count > 10: # limit to 10 tags
555 558 break;
556 559
557 560 yield self.t("tagentry",
558 561 parity=parity.next(),
559 562 tag=k,
560 563 node=hex(n),
561 564 date=self.repo.changectx(n).date())
562 565
563 566
564 567 def branches(**map):
565 568 parity = paritygen(self.stripecount)
566 569
567 570 b = self.repo.branchtags()
568 571 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
569 572 l.sort()
570 573
571 574 for r,n,t in l:
572 575 ctx = self.repo.changectx(n)
573 576
574 577 yield {'parity': parity.next(),
575 578 'branch': t,
576 579 'node': hex(n),
577 580 'date': ctx.date()}
578 581
579 582 def changelist(**map):
580 583 parity = paritygen(self.stripecount, offset=start-end)
581 584 l = [] # build a list in forward order for efficiency
582 585 for i in xrange(start, end):
583 586 ctx = self.repo.changectx(i)
584 587 n = ctx.node()
585 588 hn = hex(n)
586 589
587 590 l.insert(0, self.t(
588 591 'shortlogentry',
589 592 parity=parity.next(),
590 593 author=ctx.user(),
591 594 desc=ctx.description(),
592 595 date=ctx.date(),
593 596 rev=i,
594 597 node=hn,
595 598 tags=self.nodetagsdict(n),
596 599 branches=self.nodebranchdict(ctx)))
597 600
598 601 yield l
599 602
600 603 cl = self.repo.changelog
601 604 count = cl.count()
602 605 start = max(0, count - self.maxchanges)
603 606 end = min(count, start + self.maxchanges)
604 607
605 608 yield self.t("summary",
606 609 desc=self.config("web", "description", "unknown"),
607 610 owner=(self.config("ui", "username") or # preferred
608 611 self.config("web", "contact") or # deprecated
609 612 self.config("web", "author", "unknown")), # also
610 613 lastchange=cl.read(cl.tip())[2],
611 614 tags=tagentries,
612 615 branches=branches,
613 616 shortlog=changelist,
614 617 node=hex(cl.tip()),
615 618 archives=self.archivelist("tip"))
616 619
617 620 def filediff(self, fctx):
618 621 n = fctx.node()
619 622 path = fctx.path()
620 623 parents = fctx.parents()
621 624 p1 = parents and parents[0].node() or nullid
622 625
623 626 def diff(**map):
624 627 yield self.diff(p1, n, [path])
625 628
626 629 yield self.t("filediff",
627 630 file=path,
628 631 node=hex(n),
629 632 rev=fctx.rev(),
630 633 parent=self.siblings(parents),
631 634 child=self.siblings(fctx.children()),
632 635 diff=diff)
633 636
634 637 archive_specs = {
635 638 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
636 639 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
637 640 'zip': ('application/zip', 'zip', '.zip', None),
638 641 }
639 642
640 643 def archive(self, req, key, type_):
641 644 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
642 645 cnode = self.repo.lookup(key)
643 646 arch_version = key
644 647 if cnode == key or key == 'tip':
645 648 arch_version = short(cnode)
646 649 name = "%s-%s" % (reponame, arch_version)
647 650 mimetype, artype, extension, encoding = self.archive_specs[type_]
648 651 headers = [('Content-type', mimetype),
649 652 ('Content-disposition', 'attachment; filename=%s%s' %
650 653 (name, extension))]
651 654 if encoding:
652 655 headers.append(('Content-encoding', encoding))
653 656 req.header(headers)
654 657 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
655 658
656 659 # add tags to things
657 660 # tags -> list of changesets corresponding to tags
658 661 # find tag, changeset, file
659 662
660 663 def cleanpath(self, path):
661 664 path = path.lstrip('/')
662 665 return util.canonpath(self.repo.root, '', path)
663 666
664 667 def run(self):
665 668 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
666 669 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
667 670 import mercurial.hgweb.wsgicgi as wsgicgi
668 671 from request import wsgiapplication
669 672 def make_web_app():
670 673 return self
671 674 wsgicgi.launch(wsgiapplication(make_web_app))
672 675
673 676 def run_wsgi(self, req):
674 677 def header(**map):
675 678 header_file = cStringIO.StringIO(
676 679 ''.join(self.t("header", encoding=self.encoding, **map)))
677 680 msg = mimetools.Message(header_file, 0)
678 681 req.header(msg.items())
679 682 yield header_file.read()
680 683
681 684 def rawfileheader(**map):
682 685 req.header([('Content-type', map['mimetype']),
683 686 ('Content-disposition', 'filename=%s' % map['file']),
684 687 ('Content-length', str(len(map['raw'])))])
685 688 yield ''
686 689
687 690 def footer(**map):
688 691 yield self.t("footer", **map)
689 692
690 693 def motd(**map):
691 694 yield self.config("web", "motd", "")
692 695
693 696 def expand_form(form):
694 697 shortcuts = {
695 698 'cl': [('cmd', ['changelog']), ('rev', None)],
696 699 'sl': [('cmd', ['shortlog']), ('rev', None)],
697 700 'cs': [('cmd', ['changeset']), ('node', None)],
698 701 'f': [('cmd', ['file']), ('filenode', None)],
699 702 'fl': [('cmd', ['filelog']), ('filenode', None)],
700 703 'fd': [('cmd', ['filediff']), ('node', None)],
701 704 'fa': [('cmd', ['annotate']), ('filenode', None)],
702 705 'mf': [('cmd', ['manifest']), ('manifest', None)],
703 706 'ca': [('cmd', ['archive']), ('node', None)],
704 707 'tags': [('cmd', ['tags'])],
705 708 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
706 709 'static': [('cmd', ['static']), ('file', None)]
707 710 }
708 711
709 712 for k in shortcuts.iterkeys():
710 713 if form.has_key(k):
711 714 for name, value in shortcuts[k]:
712 715 if value is None:
713 716 value = form[k]
714 717 form[name] = value
715 718 del form[k]
716 719
717 720 def rewrite_request(req):
718 721 '''translate new web interface to traditional format'''
719 722
720 723 def spliturl(req):
721 724 def firstitem(query):
722 725 return query.split('&', 1)[0].split(';', 1)[0]
723 726
724 727 def normurl(url):
725 728 inner = '/'.join([x for x in url.split('/') if x])
726 729 tl = len(url) > 1 and url.endswith('/') and '/' or ''
727 730
728 731 return '%s%s%s' % (url.startswith('/') and '/' or '',
729 732 inner, tl)
730 733
731 734 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
732 735 pi = normurl(req.env.get('PATH_INFO', ''))
733 736 if pi:
734 737 # strip leading /
735 738 pi = pi[1:]
736 739 if pi:
737 740 root = root[:root.rfind(pi)]
738 741 if req.env.has_key('REPO_NAME'):
739 742 rn = req.env['REPO_NAME'] + '/'
740 743 root += rn
741 744 query = pi[len(rn):]
742 745 else:
743 746 query = pi
744 747 else:
745 748 root += '?'
746 749 query = firstitem(req.env['QUERY_STRING'])
747 750
748 751 return (root, query)
749 752
750 753 req.url, query = spliturl(req)
751 754
752 755 if req.form.has_key('cmd'):
753 756 # old style
754 757 return
755 758
756 759 args = query.split('/', 2)
757 760 if not args or not args[0]:
758 761 return
759 762
760 763 cmd = args.pop(0)
761 764 style = cmd.rfind('-')
762 765 if style != -1:
763 766 req.form['style'] = [cmd[:style]]
764 767 cmd = cmd[style+1:]
765 768 # avoid accepting e.g. style parameter as command
766 769 if hasattr(self, 'do_' + cmd):
767 770 req.form['cmd'] = [cmd]
768 771
769 772 if args and args[0]:
770 773 node = args.pop(0)
771 774 req.form['node'] = [node]
772 775 if args:
773 776 req.form['file'] = args
774 777
775 778 if cmd == 'static':
776 779 req.form['file'] = req.form['node']
777 780 elif cmd == 'archive':
778 781 fn = req.form['node'][0]
779 782 for type_, spec in self.archive_specs.iteritems():
780 783 ext = spec[2]
781 784 if fn.endswith(ext):
782 785 req.form['node'] = [fn[:-len(ext)]]
783 786 req.form['type'] = [type_]
784 787
785 788 def sessionvars(**map):
786 789 fields = []
787 790 if req.form.has_key('style'):
788 791 style = req.form['style'][0]
789 792 if style != self.config('web', 'style', ''):
790 793 fields.append(('style', style))
791 794
792 795 separator = req.url[-1] == '?' and ';' or '?'
793 796 for name, value in fields:
794 797 yield dict(name=name, value=value, separator=separator)
795 798 separator = ';'
796 799
797 800 self.refresh()
798 801
799 802 expand_form(req.form)
800 803 rewrite_request(req)
801 804
802 805 style = self.config("web", "style", "")
803 806 if req.form.has_key('style'):
804 807 style = req.form['style'][0]
805 808 mapfile = style_map(self.templatepath, style)
806 809
807 810 proto = req.env.get('wsgi.url_scheme')
808 811 if proto == 'https':
809 812 proto = 'https'
810 813 default_port = "443"
811 814 else:
812 815 proto = 'http'
813 816 default_port = "80"
814 817
815 818 port = req.env["SERVER_PORT"]
816 819 port = port != default_port and (":" + port) or ""
817 820 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
818 821 staticurl = self.config("web", "staticurl") or req.url + 'static/'
819 822 if not staticurl.endswith('/'):
820 823 staticurl += '/'
821 824
822 825 if not self.reponame:
823 826 self.reponame = (self.config("web", "name")
824 827 or req.env.get('REPO_NAME')
825 828 or req.url.strip('/') or self.repo.root)
826 829
827 830 self.t = templater.templater(mapfile, templater.common_filters,
828 831 defaults={"url": req.url,
829 832 "staticurl": staticurl,
830 833 "urlbase": urlbase,
831 834 "repo": self.reponame,
832 835 "header": header,
833 836 "footer": footer,
834 837 "motd": motd,
835 838 "rawfileheader": rawfileheader,
836 839 "sessionvars": sessionvars
837 840 })
838 841
839 842 try:
840 843 if not req.form.has_key('cmd'):
841 844 req.form['cmd'] = [self.t.cache['default']]
842 845
843 846 cmd = req.form['cmd'][0]
844 847
845 848 method = getattr(self, 'do_' + cmd, None)
846 849 if method:
847 850 try:
848 851 method(req)
849 852 except (hg.RepoError, revlog.RevlogError), inst:
850 853 req.write(self.t("error", error=str(inst)))
851 854 else:
852 855 req.write(self.t("error", error='No such method: ' + cmd))
853 856 finally:
854 857 self.t = None
855 858
856 859 def changectx(self, req):
857 860 if req.form.has_key('node'):
858 861 changeid = req.form['node'][0]
859 862 elif req.form.has_key('manifest'):
860 863 changeid = req.form['manifest'][0]
861 864 else:
862 865 changeid = self.repo.changelog.count() - 1
863 866
864 867 try:
865 868 ctx = self.repo.changectx(changeid)
866 869 except hg.RepoError:
867 870 man = self.repo.manifest
868 871 mn = man.lookup(changeid)
869 872 ctx = self.repo.changectx(man.linkrev(mn))
870 873
871 874 return ctx
872 875
873 876 def filectx(self, req):
874 877 path = self.cleanpath(req.form['file'][0])
875 878 if req.form.has_key('node'):
876 879 changeid = req.form['node'][0]
877 880 else:
878 881 changeid = req.form['filenode'][0]
879 882 try:
880 883 ctx = self.repo.changectx(changeid)
881 884 fctx = ctx.filectx(path)
882 885 except hg.RepoError:
883 886 fctx = self.repo.filectx(path, fileid=changeid)
884 887
885 888 return fctx
886 889
887 890 def do_log(self, req):
888 891 if req.form.has_key('file') and req.form['file'][0]:
889 892 self.do_filelog(req)
890 893 else:
891 894 self.do_changelog(req)
892 895
893 896 def do_rev(self, req):
894 897 self.do_changeset(req)
895 898
896 899 def do_file(self, req):
897 900 path = self.cleanpath(req.form.get('file', [''])[0])
898 901 if path:
899 902 try:
900 903 req.write(self.filerevision(self.filectx(req)))
901 904 return
902 905 except revlog.LookupError:
903 906 pass
904 907
905 908 req.write(self.manifest(self.changectx(req), path))
906 909
907 910 def do_diff(self, req):
908 911 self.do_filediff(req)
909 912
910 913 def do_changelog(self, req, shortlog = False):
911 914 if req.form.has_key('node'):
912 915 ctx = self.changectx(req)
913 916 else:
914 917 if req.form.has_key('rev'):
915 918 hi = req.form['rev'][0]
916 919 else:
917 920 hi = self.repo.changelog.count() - 1
918 921 try:
919 922 ctx = self.repo.changectx(hi)
920 923 except hg.RepoError:
921 924 req.write(self.search(hi)) # XXX redirect to 404 page?
922 925 return
923 926
924 927 req.write(self.changelog(ctx, shortlog = shortlog))
925 928
926 929 def do_shortlog(self, req):
927 930 self.do_changelog(req, shortlog = True)
928 931
929 932 def do_changeset(self, req):
930 933 req.write(self.changeset(self.changectx(req)))
931 934
932 935 def do_manifest(self, req):
933 936 req.write(self.manifest(self.changectx(req),
934 937 self.cleanpath(req.form['path'][0])))
935 938
936 939 def do_tags(self, req):
937 940 req.write(self.tags())
938 941
939 942 def do_summary(self, req):
940 943 req.write(self.summary())
941 944
942 945 def do_filediff(self, req):
943 946 req.write(self.filediff(self.filectx(req)))
944 947
945 948 def do_annotate(self, req):
946 949 req.write(self.fileannotate(self.filectx(req)))
947 950
948 951 def do_filelog(self, req):
949 952 req.write(self.filelog(self.filectx(req)))
950 953
951 954 def do_lookup(self, req):
952 955 try:
953 956 r = hex(self.repo.lookup(req.form['key'][0]))
954 957 success = 1
955 958 except Exception,inst:
956 959 r = str(inst)
957 960 success = 0
958 961 resp = "%s %s\n" % (success, r)
959 962 req.httphdr("application/mercurial-0.1", length=len(resp))
960 963 req.write(resp)
961 964
962 965 def do_heads(self, req):
963 966 resp = " ".join(map(hex, self.repo.heads())) + "\n"
964 967 req.httphdr("application/mercurial-0.1", length=len(resp))
965 968 req.write(resp)
966 969
967 970 def do_branches(self, req):
968 971 nodes = []
969 972 if req.form.has_key('nodes'):
970 973 nodes = map(bin, req.form['nodes'][0].split(" "))
971 974 resp = cStringIO.StringIO()
972 975 for b in self.repo.branches(nodes):
973 976 resp.write(" ".join(map(hex, b)) + "\n")
974 977 resp = resp.getvalue()
975 978 req.httphdr("application/mercurial-0.1", length=len(resp))
976 979 req.write(resp)
977 980
978 981 def do_between(self, req):
979 982 if req.form.has_key('pairs'):
980 983 pairs = [map(bin, p.split("-"))
981 984 for p in req.form['pairs'][0].split(" ")]
982 985 resp = cStringIO.StringIO()
983 986 for b in self.repo.between(pairs):
984 987 resp.write(" ".join(map(hex, b)) + "\n")
985 988 resp = resp.getvalue()
986 989 req.httphdr("application/mercurial-0.1", length=len(resp))
987 990 req.write(resp)
988 991
989 992 def do_changegroup(self, req):
990 993 req.httphdr("application/mercurial-0.1")
991 994 nodes = []
992 995 if not self.allowpull:
993 996 return
994 997
995 998 if req.form.has_key('roots'):
996 999 nodes = map(bin, req.form['roots'][0].split(" "))
997 1000
998 1001 z = zlib.compressobj()
999 1002 f = self.repo.changegroup(nodes, 'serve')
1000 1003 while 1:
1001 1004 chunk = f.read(4096)
1002 1005 if not chunk:
1003 1006 break
1004 1007 req.write(z.compress(chunk))
1005 1008
1006 1009 req.write(z.flush())
1007 1010
1008 1011 def do_changegroupsubset(self, req):
1009 1012 req.httphdr("application/mercurial-0.1")
1010 1013 bases = []
1011 1014 heads = []
1012 1015 if not self.allowpull:
1013 1016 return
1014 1017
1015 1018 if req.form.has_key('bases'):
1016 1019 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
1017 1020 if req.form.has_key('heads'):
1018 1021 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
1019 1022
1020 1023 z = zlib.compressobj()
1021 1024 f = self.repo.changegroupsubset(bases, heads, 'serve')
1022 1025 while 1:
1023 1026 chunk = f.read(4096)
1024 1027 if not chunk:
1025 1028 break
1026 1029 req.write(z.compress(chunk))
1027 1030
1028 1031 req.write(z.flush())
1029 1032
1030 1033 def do_archive(self, req):
1031 1034 type_ = req.form['type'][0]
1032 1035 allowed = self.configlist("web", "allow_archive")
1033 1036 if (type_ in self.archives and (type_ in allowed or
1034 1037 self.configbool("web", "allow" + type_, False))):
1035 1038 self.archive(req, req.form['node'][0], type_)
1036 1039 return
1037 1040
1038 1041 req.write(self.t("error"))
1039 1042
1040 1043 def do_static(self, req):
1041 1044 fname = req.form['file'][0]
1042 1045 # a repo owner may set web.static in .hg/hgrc to get any file
1043 1046 # readable by the user running the CGI script
1044 1047 static = self.config("web", "static",
1045 1048 os.path.join(self.templatepath, "static"),
1046 1049 untrusted=False)
1047 1050 req.write(staticfile(static, fname, req)
1048 1051 or self.t("error", error="%r not found" % fname))
1049 1052
1050 1053 def do_capabilities(self, req):
1051 1054 caps = ['lookup', 'changegroupsubset']
1052 1055 if self.configbool('server', 'uncompressed'):
1053 1056 caps.append('stream=%d' % self.repo.changelog.version)
1054 1057 # XXX: make configurable and/or share code with do_unbundle:
1055 1058 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1056 1059 if unbundleversions:
1057 1060 caps.append('unbundle=%s' % ','.join(unbundleversions))
1058 1061 resp = ' '.join(caps)
1059 1062 req.httphdr("application/mercurial-0.1", length=len(resp))
1060 1063 req.write(resp)
1061 1064
1062 1065 def check_perm(self, req, op, default):
1063 1066 '''check permission for operation based on user auth.
1064 1067 return true if op allowed, else false.
1065 1068 default is policy to use if no config given.'''
1066 1069
1067 1070 user = req.env.get('REMOTE_USER')
1068 1071
1069 1072 deny = self.configlist('web', 'deny_' + op)
1070 1073 if deny and (not user or deny == ['*'] or user in deny):
1071 1074 return False
1072 1075
1073 1076 allow = self.configlist('web', 'allow_' + op)
1074 1077 return (allow and (allow == ['*'] or user in allow)) or default
1075 1078
1076 1079 def do_unbundle(self, req):
1077 1080 def bail(response, headers={}):
1078 1081 length = int(req.env['CONTENT_LENGTH'])
1079 1082 for s in util.filechunkiter(req, limit=length):
1080 1083 # drain incoming bundle, else client will not see
1081 1084 # response when run outside cgi script
1082 1085 pass
1083 1086 req.httphdr("application/mercurial-0.1", headers=headers)
1084 1087 req.write('0\n')
1085 1088 req.write(response)
1086 1089
1087 1090 # require ssl by default, auth info cannot be sniffed and
1088 1091 # replayed
1089 1092 ssl_req = self.configbool('web', 'push_ssl', True)
1090 1093 if ssl_req:
1091 1094 if req.env.get('wsgi.url_scheme') != 'https':
1092 1095 bail(_('ssl required\n'))
1093 1096 return
1094 1097 proto = 'https'
1095 1098 else:
1096 1099 proto = 'http'
1097 1100
1098 1101 # do not allow push unless explicitly allowed
1099 1102 if not self.check_perm(req, 'push', False):
1100 1103 bail(_('push not authorized\n'),
1101 1104 headers={'status': '401 Unauthorized'})
1102 1105 return
1103 1106
1104 1107 their_heads = req.form['heads'][0].split(' ')
1105 1108
1106 1109 def check_heads():
1107 1110 heads = map(hex, self.repo.heads())
1108 1111 return their_heads == [hex('force')] or their_heads == heads
1109 1112
1110 1113 # fail early if possible
1111 1114 if not check_heads():
1112 1115 bail(_('unsynced changes\n'))
1113 1116 return
1114 1117
1115 1118 req.httphdr("application/mercurial-0.1")
1116 1119
1117 1120 # do not lock repo until all changegroup data is
1118 1121 # streamed. save to temporary file.
1119 1122
1120 1123 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1121 1124 fp = os.fdopen(fd, 'wb+')
1122 1125 try:
1123 1126 length = int(req.env['CONTENT_LENGTH'])
1124 1127 for s in util.filechunkiter(req, limit=length):
1125 1128 fp.write(s)
1126 1129
1127 1130 try:
1128 1131 lock = self.repo.lock()
1129 1132 try:
1130 1133 if not check_heads():
1131 1134 req.write('0\n')
1132 1135 req.write(_('unsynced changes\n'))
1133 1136 return
1134 1137
1135 1138 fp.seek(0)
1136 1139 header = fp.read(6)
1137 1140 if not header.startswith("HG"):
1138 1141 # old client with uncompressed bundle
1139 1142 def generator(f):
1140 1143 yield header
1141 1144 for chunk in f:
1142 1145 yield chunk
1143 1146 elif not header.startswith("HG10"):
1144 1147 req.write("0\n")
1145 1148 req.write(_("unknown bundle version\n"))
1146 1149 return
1147 1150 elif header == "HG10GZ":
1148 1151 def generator(f):
1149 1152 zd = zlib.decompressobj()
1150 1153 for chunk in f:
1151 1154 yield zd.decompress(chunk)
1152 1155 elif header == "HG10BZ":
1153 1156 def generator(f):
1154 1157 zd = bz2.BZ2Decompressor()
1155 1158 zd.decompress("BZ")
1156 1159 for chunk in f:
1157 1160 yield zd.decompress(chunk)
1158 1161 elif header == "HG10UN":
1159 1162 def generator(f):
1160 1163 for chunk in f:
1161 1164 yield chunk
1162 1165 else:
1163 1166 req.write("0\n")
1164 1167 req.write(_("unknown bundle compression type\n"))
1165 1168 return
1166 1169 gen = generator(util.filechunkiter(fp, 4096))
1167 1170
1168 1171 # send addchangegroup output to client
1169 1172
1170 1173 old_stdout = sys.stdout
1171 1174 sys.stdout = cStringIO.StringIO()
1172 1175
1173 1176 try:
1174 1177 url = 'remote:%s:%s' % (proto,
1175 1178 req.env.get('REMOTE_HOST', ''))
1176 1179 try:
1177 1180 ret = self.repo.addchangegroup(
1178 1181 util.chunkbuffer(gen), 'serve', url)
1179 1182 except util.Abort, inst:
1180 1183 sys.stdout.write("abort: %s\n" % inst)
1181 1184 ret = 0
1182 1185 finally:
1183 1186 val = sys.stdout.getvalue()
1184 1187 sys.stdout = old_stdout
1185 1188 req.write('%d\n' % ret)
1186 1189 req.write(val)
1187 1190 finally:
1188 1191 del lock
1189 1192 except (OSError, IOError), inst:
1190 1193 req.write('0\n')
1191 1194 filename = getattr(inst, 'filename', '')
1192 1195 # Don't send our filesystem layout to the client
1193 1196 if filename.startswith(self.repo.root):
1194 1197 filename = filename[len(self.repo.root)+1:]
1195 1198 else:
1196 1199 filename = ''
1197 1200 error = getattr(inst, 'strerror', 'Unknown error')
1198 1201 req.write('%s: %s\n' % (error, filename))
1199 1202 finally:
1200 1203 fp.close()
1201 1204 os.unlink(tempname)
1202 1205
1203 1206 def do_stream_out(self, req):
1204 1207 req.httphdr("application/mercurial-0.1")
1205 1208 streamclone.stream_out(self.repo, req, untrusted=True)
@@ -1,259 +1,260 b''
1 1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os, mimetools, cStringIO
10 10 from mercurial.i18n import gettext as _
11 11 from mercurial import ui, hg, util, templater
12 12 from common import get_mtime, staticfile, style_map, paritygen
13 13 from hgweb_mod import hgweb
14 14
15 15 # This is a stopgap
16 16 class hgwebdir(object):
17 17 def __init__(self, config, parentui=None):
18 18 def cleannames(items):
19 19 return [(util.pconvert(name.strip(os.sep)), path)
20 20 for name, path in items]
21 21
22 22 self.parentui = parentui
23 23 self.motd = None
24 24 self.style = None
25 25 self.stripecount = None
26 26 self.repos_sorted = ('name', False)
27 27 if isinstance(config, (list, tuple)):
28 28 self.repos = cleannames(config)
29 29 self.repos_sorted = ('', False)
30 30 elif isinstance(config, dict):
31 31 self.repos = cleannames(config.items())
32 32 self.repos.sort()
33 33 else:
34 34 if isinstance(config, util.configparser):
35 35 cp = config
36 36 else:
37 37 cp = util.configparser()
38 38 cp.read(config)
39 39 self.repos = []
40 40 if cp.has_section('web'):
41 41 if cp.has_option('web', 'motd'):
42 42 self.motd = cp.get('web', 'motd')
43 43 if cp.has_option('web', 'style'):
44 44 self.style = cp.get('web', 'style')
45 45 if cp.has_option('web', 'stripes'):
46 46 self.stripecount = int(cp.get('web', 'stripes'))
47 47 if cp.has_section('paths'):
48 48 self.repos.extend(cleannames(cp.items('paths')))
49 49 if cp.has_section('collections'):
50 50 for prefix, root in cp.items('collections'):
51 51 for path in util.walkrepos(root):
52 52 repo = os.path.normpath(path)
53 53 name = repo
54 54 if name.startswith(prefix):
55 55 name = name[len(prefix):]
56 56 self.repos.append((name.lstrip(os.sep), repo))
57 57 self.repos.sort()
58 58
59 59 def run(self):
60 60 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
61 61 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
62 62 import mercurial.hgweb.wsgicgi as wsgicgi
63 63 from request import wsgiapplication
64 64 def make_web_app():
65 65 return self
66 66 wsgicgi.launch(wsgiapplication(make_web_app))
67 67
68 68 def run_wsgi(self, req):
69 69 def header(**map):
70 70 header_file = cStringIO.StringIO(
71 71 ''.join(tmpl("header", encoding=util._encoding, **map)))
72 72 msg = mimetools.Message(header_file, 0)
73 73 req.header(msg.items())
74 74 yield header_file.read()
75 75
76 76 def footer(**map):
77 77 yield tmpl("footer", **map)
78 78
79 79 def motd(**map):
80 80 if self.motd is not None:
81 81 yield self.motd
82 82 else:
83 83 yield config('web', 'motd', '')
84 84
85 85 parentui = self.parentui or ui.ui(report_untrusted=False,
86 86 interactive=False)
87 87
88 88 def config(section, name, default=None, untrusted=True):
89 89 return parentui.config(section, name, default, untrusted)
90 90
91 91 url = req.env['REQUEST_URI'].split('?')[0]
92 92 if not url.endswith('/'):
93 93 url += '/'
94 94 pathinfo = req.env.get('PATH_INFO', '').strip('/') + '/'
95 95 base = url[:len(url) - len(pathinfo)]
96 96 if not base.endswith('/'):
97 97 base += '/'
98 98
99 99 staticurl = config('web', 'staticurl') or base + 'static/'
100 100 if not staticurl.endswith('/'):
101 101 staticurl += '/'
102 102
103 103 style = self.style
104 104 if style is None:
105 105 style = config('web', 'style', '')
106 106 if req.form.has_key('style'):
107 107 style = req.form['style'][0]
108 108 if self.stripecount is None:
109 109 self.stripecount = int(config('web', 'stripes', 1))
110 110 mapfile = style_map(templater.templatepath(), style)
111 111 tmpl = templater.templater(mapfile, templater.common_filters,
112 112 defaults={"header": header,
113 113 "footer": footer,
114 114 "motd": motd,
115 115 "url": url,
116 116 "staticurl": staticurl})
117 117
118 118 def archivelist(ui, nodeid, url):
119 119 allowed = ui.configlist("web", "allow_archive", untrusted=True)
120 120 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
121 121 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
122 122 untrusted=True):
123 123 yield {"type" : i[0], "extension": i[1],
124 124 "node": nodeid, "url": url}
125 125
126 126 def entries(sortcolumn="", descending=False, subdir="", **map):
127 127 def sessionvars(**map):
128 128 fields = []
129 129 if req.form.has_key('style'):
130 130 style = req.form['style'][0]
131 131 if style != get('web', 'style', ''):
132 132 fields.append(('style', style))
133 133
134 134 separator = url[-1] == '?' and ';' or '?'
135 135 for name, value in fields:
136 136 yield dict(name=name, value=value, separator=separator)
137 137 separator = ';'
138 138
139 139 rows = []
140 140 parity = paritygen(self.stripecount)
141 141 for name, path in self.repos:
142 142 if not name.startswith(subdir):
143 143 continue
144 144 name = name[len(subdir):]
145 145
146 146 u = ui.ui(parentui=parentui)
147 147 try:
148 148 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
149 except IOError:
150 pass
149 except Exception, e:
150 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
151 continue
151 152 def get(section, name, default=None):
152 153 return u.config(section, name, default, untrusted=True)
153 154
154 155 if u.configbool("web", "hidden", untrusted=True):
155 156 continue
156 157
157 158 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
158 159 .replace("//", "/")) + '/'
159 160
160 161 # update time with local timezone
161 162 try:
162 163 d = (get_mtime(path), util.makedate()[1])
163 164 except OSError:
164 165 continue
165 166
166 167 contact = (get("ui", "username") or # preferred
167 168 get("web", "contact") or # deprecated
168 169 get("web", "author", "")) # also
169 170 description = get("web", "description", "")
170 171 name = get("web", "name", name)
171 172 row = dict(contact=contact or "unknown",
172 173 contact_sort=contact.upper() or "unknown",
173 174 name=name,
174 175 name_sort=name,
175 176 url=url,
176 177 description=description or "unknown",
177 178 description_sort=description.upper() or "unknown",
178 179 lastchange=d,
179 180 lastchange_sort=d[1]-d[0],
180 181 sessionvars=sessionvars,
181 182 archives=archivelist(u, "tip", url))
182 183 if (not sortcolumn
183 184 or (sortcolumn, descending) == self.repos_sorted):
184 185 # fast path for unsorted output
185 186 row['parity'] = parity.next()
186 187 yield row
187 188 else:
188 189 rows.append((row["%s_sort" % sortcolumn], row))
189 190 if rows:
190 191 rows.sort()
191 192 if descending:
192 193 rows.reverse()
193 194 for key, row in rows:
194 195 row['parity'] = parity.next()
195 196 yield row
196 197
197 198 def makeindex(req, subdir=""):
198 199 sortable = ["name", "description", "contact", "lastchange"]
199 200 sortcolumn, descending = self.repos_sorted
200 201 if req.form.has_key('sort'):
201 202 sortcolumn = req.form['sort'][0]
202 203 descending = sortcolumn.startswith('-')
203 204 if descending:
204 205 sortcolumn = sortcolumn[1:]
205 206 if sortcolumn not in sortable:
206 207 sortcolumn = ""
207 208
208 209 sort = [("sort_%s" % column,
209 210 "%s%s" % ((not descending and column == sortcolumn)
210 211 and "-" or "", column))
211 212 for column in sortable]
212 213 req.write(tmpl("index", entries=entries, subdir=subdir,
213 214 sortcolumn=sortcolumn, descending=descending,
214 215 **dict(sort)))
215 216
216 217 try:
217 218 virtual = req.env.get("PATH_INFO", "").strip('/')
218 219 if virtual.startswith('static/'):
219 220 static = os.path.join(templater.templatepath(), 'static')
220 221 fname = virtual[7:]
221 222 req.write(staticfile(static, fname, req) or
222 223 tmpl('error', error='%r not found' % fname))
223 224 elif virtual:
224 225 repos = dict(self.repos)
225 226 while virtual:
226 227 real = repos.get(virtual)
227 228 if real:
228 229 req.env['REPO_NAME'] = virtual
229 230 try:
230 231 repo = hg.repository(parentui, real)
231 232 hgweb(repo).run_wsgi(req)
232 233 except IOError, inst:
233 234 req.write(tmpl("error", error=inst.strerror))
234 235 except hg.RepoError, inst:
235 236 req.write(tmpl("error", error=str(inst)))
236 237 return
237 238
238 239 # browse subdirectories
239 240 subdir = virtual + '/'
240 241 if [r for r in repos if r.startswith(subdir)]:
241 242 makeindex(req, subdir)
242 243 return
243 244
244 245 up = virtual.rfind('/')
245 246 if up < 0:
246 247 break
247 248 virtual = virtual[:up]
248 249
249 250 req.write(tmpl("notfound", repo=virtual))
250 251 else:
251 252 if req.form.has_key('static'):
252 253 static = os.path.join(templater.templatepath(), "static")
253 254 fname = req.form['static'][0]
254 255 req.write(staticfile(static, fname, req)
255 256 or tmpl("error", error="%r not found" % fname))
256 257 else:
257 258 makeindex(req)
258 259 finally:
259 260 tmpl = None
@@ -1,456 +1,455 b''
1 1 # httprepo.py - HTTP repository proxy classes for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 from node import *
10 10 from remoterepo import *
11 11 from i18n import _
12 12 import repo, os, urllib, urllib2, urlparse, zlib, util, httplib
13 13 import errno, keepalive, tempfile, socket, changegroup
14 14
15 15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
16 16 def __init__(self, ui):
17 17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
18 18 self.ui = ui
19 19
20 20 def find_user_password(self, realm, authuri):
21 21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
22 22 self, realm, authuri)
23 23 user, passwd = authinfo
24 24 if user and passwd:
25 25 return (user, passwd)
26 26
27 27 if not self.ui.interactive:
28 28 raise util.Abort(_('http authorization required'))
29 29
30 30 self.ui.write(_("http authorization required\n"))
31 31 self.ui.status(_("realm: %s\n") % realm)
32 32 if user:
33 33 self.ui.status(_("user: %s\n") % user)
34 34 else:
35 35 user = self.ui.prompt(_("user:"), default=None)
36 36
37 37 if not passwd:
38 38 passwd = self.ui.getpass()
39 39
40 40 self.add_password(realm, authuri, user, passwd)
41 41 return (user, passwd)
42 42
43 43 def netlocsplit(netloc):
44 44 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
45 45
46 46 a = netloc.find('@')
47 47 if a == -1:
48 48 user, passwd = None, None
49 49 else:
50 50 userpass, netloc = netloc[:a], netloc[a+1:]
51 51 c = userpass.find(':')
52 52 if c == -1:
53 53 user, passwd = urllib.unquote(userpass), None
54 54 else:
55 55 user = urllib.unquote(userpass[:c])
56 56 passwd = urllib.unquote(userpass[c+1:])
57 57 c = netloc.find(':')
58 58 if c == -1:
59 59 host, port = netloc, None
60 60 else:
61 61 host, port = netloc[:c], netloc[c+1:]
62 62 return host, port, user, passwd
63 63
64 64 def netlocunsplit(host, port, user=None, passwd=None):
65 65 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
66 66 if port:
67 67 hostport = host + ':' + port
68 68 else:
69 69 hostport = host
70 70 if user:
71 71 if passwd:
72 72 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
73 73 else:
74 74 userpass = urllib.quote(user)
75 75 return userpass + '@' + hostport
76 76 return hostport
77 77
78 78 # work around a bug in Python < 2.4.2
79 79 # (it leaves a "\n" at the end of Proxy-authorization headers)
80 80 class request(urllib2.Request):
81 81 def add_header(self, key, val):
82 82 if key.lower() == 'proxy-authorization':
83 83 val = val.strip()
84 84 return urllib2.Request.add_header(self, key, val)
85 85
86 86 class httpsendfile(file):
87 87 def __len__(self):
88 88 return os.fstat(self.fileno()).st_size
89 89
90 90 def _gen_sendfile(connection):
91 91 def _sendfile(self, data):
92 92 # send a file
93 93 if isinstance(data, httpsendfile):
94 94 # if auth required, some data sent twice, so rewind here
95 95 data.seek(0)
96 96 for chunk in util.filechunkiter(data):
97 97 connection.send(self, chunk)
98 98 else:
99 99 connection.send(self, data)
100 100 return _sendfile
101 101
102 102 class httpconnection(keepalive.HTTPConnection):
103 103 # must be able to send big bundle as stream.
104 104 send = _gen_sendfile(keepalive.HTTPConnection)
105 105
106 106 class basehttphandler(keepalive.HTTPHandler):
107 107 def http_open(self, req):
108 108 return self.do_open(httpconnection, req)
109 109
110 110 has_https = hasattr(urllib2, 'HTTPSHandler')
111 111 if has_https:
112 112 class httpsconnection(httplib.HTTPSConnection):
113 113 response_class = keepalive.HTTPResponse
114 114 # must be able to send big bundle as stream.
115 115 send = _gen_sendfile(httplib.HTTPSConnection)
116 116
117 117 class httphandler(basehttphandler, urllib2.HTTPSHandler):
118 118 def https_open(self, req):
119 119 return self.do_open(httpsconnection, req)
120 120 else:
121 121 class httphandler(basehttphandler):
122 122 pass
123 123
124 124 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
125 125 # it doesn't know about the auth type requested. This can happen if
126 126 # somebody is using BasicAuth and types a bad password.
127 127 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
128 128 def http_error_auth_reqed(self, auth_header, host, req, headers):
129 129 try:
130 130 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
131 131 self, auth_header, host, req, headers)
132 132 except ValueError, inst:
133 133 arg = inst.args[0]
134 134 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
135 135 return
136 136 raise
137 137
138 138 def zgenerator(f):
139 139 zd = zlib.decompressobj()
140 140 try:
141 141 for chunk in util.filechunkiter(f):
142 142 yield zd.decompress(chunk)
143 143 except httplib.HTTPException, inst:
144 144 raise IOError(None, _('connection ended unexpectedly'))
145 145 yield zd.flush()
146 146
147 147 _safe = ('abcdefghijklmnopqrstuvwxyz'
148 148 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
149 149 '0123456789' '_.-/')
150 150 _safeset = None
151 151 _hex = None
152 152 def quotepath(path):
153 153 '''quote the path part of a URL
154 154
155 155 This is similar to urllib.quote, but it also tries to avoid
156 156 quoting things twice (inspired by wget):
157 157
158 158 >>> quotepath('abc def')
159 159 'abc%20def'
160 160 >>> quotepath('abc%20def')
161 161 'abc%20def'
162 162 >>> quotepath('abc%20 def')
163 163 'abc%20%20def'
164 164 >>> quotepath('abc def%20')
165 165 'abc%20def%20'
166 166 >>> quotepath('abc def%2')
167 167 'abc%20def%252'
168 168 >>> quotepath('abc def%')
169 169 'abc%20def%25'
170 170 '''
171 171 global _safeset, _hex
172 172 if _safeset is None:
173 173 _safeset = util.set(_safe)
174 174 _hex = util.set('abcdefABCDEF0123456789')
175 175 l = list(path)
176 176 for i in xrange(len(l)):
177 177 c = l[i]
178 178 if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex):
179 179 pass
180 180 elif c not in _safeset:
181 181 l[i] = '%%%02X' % ord(c)
182 182 return ''.join(l)
183 183
184 184 class httprepository(remoterepository):
185 185 def __init__(self, ui, path):
186 186 self.path = path
187 187 self.caps = None
188 188 self.handler = None
189 189 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
190 190 if query or frag:
191 191 raise util.Abort(_('unsupported URL component: "%s"') %
192 192 (query or frag))
193 193 if not urlpath:
194 194 urlpath = '/'
195 195 urlpath = quotepath(urlpath)
196 196 host, port, user, passwd = netlocsplit(netloc)
197 197
198 198 # urllib cannot handle URLs with embedded user or passwd
199 199 self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
200 200 urlpath, '', ''))
201 201 self.ui = ui
202 202 self.ui.debug(_('using %s\n') % self._url)
203 203
204 204 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
205 205 # XXX proxyauthinfo = None
206 206 self.handler = httphandler()
207 207 handlers = [self.handler]
208 208
209 209 if proxyurl:
210 210 # proxy can be proper url or host[:port]
211 211 if not (proxyurl.startswith('http:') or
212 212 proxyurl.startswith('https:')):
213 213 proxyurl = 'http://' + proxyurl + '/'
214 214 snpqf = urlparse.urlsplit(proxyurl)
215 215 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
216 216 hpup = netlocsplit(proxynetloc)
217 217
218 218 proxyhost, proxyport, proxyuser, proxypasswd = hpup
219 219 if not proxyuser:
220 220 proxyuser = ui.config("http_proxy", "user")
221 221 proxypasswd = ui.config("http_proxy", "passwd")
222 222
223 223 # see if we should use a proxy for this url
224 224 no_list = [ "localhost", "127.0.0.1" ]
225 225 no_list.extend([p.lower() for
226 226 p in ui.configlist("http_proxy", "no")])
227 227 no_list.extend([p.strip().lower() for
228 228 p in os.getenv("no_proxy", '').split(',')
229 229 if p.strip()])
230 230 # "http_proxy.always" config is for running tests on localhost
231 231 if (not ui.configbool("http_proxy", "always") and
232 232 host.lower() in no_list):
233 233 ui.debug(_('disabling proxy for %s\n') % host)
234 234 else:
235 235 proxyurl = urlparse.urlunsplit((
236 236 proxyscheme, netlocunsplit(proxyhost, proxyport,
237 237 proxyuser, proxypasswd or ''),
238 238 proxypath, proxyquery, proxyfrag))
239 239 handlers.append(urllib2.ProxyHandler({scheme: proxyurl}))
240 240 ui.debug(_('proxying through http://%s:%s\n') %
241 241 (proxyhost, proxyport))
242 242
243 243 # urllib2 takes proxy values from the environment and those
244 244 # will take precedence if found, so drop them
245 245 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
246 246 try:
247 247 if os.environ.has_key(env):
248 248 del os.environ[env]
249 249 except OSError:
250 250 pass
251 251
252 252 passmgr = passwordmgr(ui)
253 253 if user:
254 254 ui.debug(_('http auth: user %s, password %s\n') %
255 255 (user, passwd and '*' * len(passwd) or 'not set'))
256 256 passmgr.add_password(None, self._url, user, passwd or '')
257 257
258 258 handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
259 259 httpdigestauthhandler(passmgr)))
260 260 opener = urllib2.build_opener(*handlers)
261 261
262 262 # 1.0 here is the _protocol_ version
263 263 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
264 264 urllib2.install_opener(opener)
265 265
266 266 def __del__(self):
267 267 if self.handler:
268 268 self.handler.close_all()
269 269 self.handler = None
270 270
271 271 def url(self):
272 272 return self.path
273 273
274 274 # look up capabilities only when needed
275 275
276 276 def get_caps(self):
277 277 if self.caps is None:
278 278 try:
279 279 self.caps = util.set(self.do_read('capabilities').split())
280 280 except repo.RepoError:
281 281 self.caps = util.set()
282 282 self.ui.debug(_('capabilities: %s\n') %
283 283 (' '.join(self.caps or ['none'])))
284 284 return self.caps
285 285
286 286 capabilities = property(get_caps)
287 287
288 288 def lock(self):
289 289 raise util.Abort(_('operation not supported over http'))
290 290
291 291 def do_cmd(self, cmd, **args):
292 292 data = args.pop('data', None)
293 293 headers = args.pop('headers', {})
294 294 self.ui.debug(_("sending %s command\n") % cmd)
295 295 q = {"cmd": cmd}
296 296 q.update(args)
297 297 qs = '?%s' % urllib.urlencode(q)
298 298 cu = "%s%s" % (self._url, qs)
299 299 try:
300 300 if data:
301 self.ui.debug(_("sending %s bytes\n") %
302 headers.get('content-length', 'X'))
301 self.ui.debug(_("sending %s bytes\n") % len(data))
303 302 resp = urllib2.urlopen(request(cu, data, headers))
304 303 except urllib2.HTTPError, inst:
305 304 if inst.code == 401:
306 305 raise util.Abort(_('authorization failed'))
307 306 raise
308 307 except httplib.HTTPException, inst:
309 308 self.ui.debug(_('http error while sending %s command\n') % cmd)
310 309 self.ui.print_exc()
311 310 raise IOError(None, inst)
312 311 except IndexError:
313 312 # this only happens with Python 2.3, later versions raise URLError
314 313 raise util.Abort(_('http error, possibly caused by proxy setting'))
315 314 # record the url we got redirected to
316 315 resp_url = resp.geturl()
317 316 if resp_url.endswith(qs):
318 317 resp_url = resp_url[:-len(qs)]
319 318 if self._url != resp_url:
320 319 self.ui.status(_('real URL is %s\n') % resp_url)
321 320 self._url = resp_url
322 321 try:
323 322 proto = resp.getheader('content-type')
324 323 except AttributeError:
325 324 proto = resp.headers['content-type']
326 325
327 326 # accept old "text/plain" and "application/hg-changegroup" for now
328 327 if not (proto.startswith('application/mercurial-') or
329 328 proto.startswith('text/plain') or
330 329 proto.startswith('application/hg-changegroup')):
331 330 self.ui.debug(_("Requested URL: '%s'\n") % cu)
332 331 raise repo.RepoError(_("'%s' does not appear to be an hg repository")
333 332 % self._url)
334 333
335 334 if proto.startswith('application/mercurial-'):
336 335 try:
337 336 version = proto.split('-', 1)[1]
338 337 version_info = tuple([int(n) for n in version.split('.')])
339 338 except ValueError:
340 339 raise repo.RepoError(_("'%s' sent a broken Content-type "
341 340 "header (%s)") % (self._url, proto))
342 341 if version_info > (0, 1):
343 342 raise repo.RepoError(_("'%s' uses newer protocol %s") %
344 343 (self._url, version))
345 344
346 345 return resp
347 346
348 347 def do_read(self, cmd, **args):
349 348 fp = self.do_cmd(cmd, **args)
350 349 try:
351 350 return fp.read()
352 351 finally:
353 352 # if using keepalive, allow connection to be reused
354 353 fp.close()
355 354
356 355 def lookup(self, key):
357 356 self.requirecap('lookup', _('look up remote revision'))
358 357 d = self.do_cmd("lookup", key = key).read()
359 358 success, data = d[:-1].split(' ', 1)
360 359 if int(success):
361 360 return bin(data)
362 361 raise repo.RepoError(data)
363 362
364 363 def heads(self):
365 364 d = self.do_read("heads")
366 365 try:
367 366 return map(bin, d[:-1].split(" "))
368 367 except:
369 368 raise util.UnexpectedOutput(_("unexpected response:"), d)
370 369
371 370 def branches(self, nodes):
372 371 n = " ".join(map(hex, nodes))
373 372 d = self.do_read("branches", nodes=n)
374 373 try:
375 374 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
376 375 return br
377 376 except:
378 377 raise util.UnexpectedOutput(_("unexpected response:"), d)
379 378
380 379 def between(self, pairs):
381 380 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
382 381 d = self.do_read("between", pairs=n)
383 382 try:
384 383 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
385 384 return p
386 385 except:
387 386 raise util.UnexpectedOutput(_("unexpected response:"), d)
388 387
389 388 def changegroup(self, nodes, kind):
390 389 n = " ".join(map(hex, nodes))
391 390 f = self.do_cmd("changegroup", roots=n)
392 391 return util.chunkbuffer(zgenerator(f))
393 392
394 393 def changegroupsubset(self, bases, heads, source):
395 394 self.requirecap('changegroupsubset', _('look up remote changes'))
396 395 baselst = " ".join([hex(n) for n in bases])
397 396 headlst = " ".join([hex(n) for n in heads])
398 397 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
399 398 return util.chunkbuffer(zgenerator(f))
400 399
401 400 def unbundle(self, cg, heads, source):
402 401 # have to stream bundle to a temp file because we do not have
403 402 # http 1.1 chunked transfer.
404 403
405 404 type = ""
406 405 types = self.capable('unbundle')
407 406 # servers older than d1b16a746db6 will send 'unbundle' as a
408 407 # boolean capability
409 408 try:
410 409 types = types.split(',')
411 410 except AttributeError:
412 411 types = [""]
413 412 if types:
414 413 for x in types:
415 414 if x in changegroup.bundletypes:
416 415 type = x
417 416 break
418 417
419 418 tempname = changegroup.writebundle(cg, None, type)
420 419 fp = httpsendfile(tempname, "rb")
421 420 try:
422 421 try:
423 422 rfp = self.do_cmd(
424 423 'unbundle', data=fp,
425 424 headers={'content-type': 'application/octet-stream'},
426 425 heads=' '.join(map(hex, heads)))
427 426 try:
428 427 ret = int(rfp.readline())
429 428 self.ui.write(rfp.read())
430 429 return ret
431 430 finally:
432 431 rfp.close()
433 432 except socket.error, err:
434 433 if err[0] in (errno.ECONNRESET, errno.EPIPE):
435 434 raise util.Abort(_('push failed: %s') % err[1])
436 435 raise util.Abort(err[1])
437 436 finally:
438 437 fp.close()
439 438 os.unlink(tempname)
440 439
441 440 def stream_out(self):
442 441 return self.do_cmd('stream_out')
443 442
444 443 class httpsrepository(httprepository):
445 444 def __init__(self, ui, path):
446 445 if not has_https:
447 446 raise util.Abort(_('Python support for SSL and HTTPS '
448 447 'is not installed'))
449 448 httprepository.__init__(self, ui, path)
450 449
451 450 def instance(ui, path, create):
452 451 if create:
453 452 raise util.Abort(_('cannot create new http repository'))
454 453 if path.startswith('https:'):
455 454 return httpsrepository(ui, path)
456 455 return httprepository(ui, path)
@@ -1,19 +1,46 b''
1 1 #!/bin/sh
2 2
3 3 "$TESTDIR/hghave" git || exit 80
4 4
5 5 echo "[extensions]" >> $HGRCPATH
6 6 echo "convert=" >> $HGRCPATH
7 7
8 GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
9 GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
10 GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
11 GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
12 GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
13 GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
14
15 count=10
16 commit()
17 {
18 GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000"
19 GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
20 git commit "$@" >/dev/null 2>/dev/null || echo "git commit error"
21 count=`expr $count + 1`
22 }
23
8 24 mkdir git-repo
9 25 cd git-repo
10 26 git init-db >/dev/null 2>/dev/null
11 27 echo a > a
12 28 git add a
13 git commit -m t1 >/dev/null 2>/dev/null || echo "git commit error"
29 commit -m t1
30
14 31 echo b >> a
15 git commit -a -m t2 >/dev/null || echo "git commit error"
32 commit -a -m t2.1
33
34 git checkout -b other HEAD^ >/dev/null 2>/dev/null
35 echo c > a
36 echo a >> a
37 commit -a -m t2.2
38
39 git checkout master >/dev/null 2>/dev/null
40 git pull --no-commit . other > /dev/null 2>/dev/null
41 commit -m 'Merge branch other'
16 42 cd ..
17 43
18 hg convert git-repo
44 hg convert --datesort git-repo
19 45
46 hg -R git-repo-hg tip -v
@@ -1,7 +1,22 b''
1 1 assuming destination git-repo-hg
2 2 initializing destination git-repo-hg repository
3 3 scanning source...
4 4 sorting...
5 5 converting...
6 1 t1
7 0 t2
6 3 t1
7 2 t2.1
8 1 t2.2
9 0 Merge branch other
10 changeset: 3:69b3a302b4a1
11 tag: tip
12 parent: 1:0de2a40e261b
13 parent: 2:8815d3b33506
14 user: test <test@example.org>
15 date: Mon Jan 01 00:00:13 2007 +0000
16 files: a
17 description:
18 Merge branch other
19
20 committer: test <test@example.org>
21
22
@@ -1,50 +1,55 b''
1 1 #!/bin/sh
2 2
3 3 # Environement setup for MQ
4 4 echo "[extensions]" >> $HGRCPATH
5 5 echo "mq=" >> $HGRCPATH
6 6
7 7 #Repo init
8 8 hg init
9 9 hg qinit
10 10
11 echo =======================
12 echo "Should fail if no patches applied"
13 hg qrefresh
14 hg qrefresh -e
15
11 16 hg qnew -m "First commit message" first-patch
12 17 echo aaaa > file
13 18 hg add file
14 19 hg qrefresh
15 20 echo =======================
16 21 echo "Should display 'First commit message'"
17 22 hg log -l1 -v | sed -n '/description/,$p'
18 23 echo
19 24
20 25 # Testing changing message with -m
21 26 echo bbbb > file
22 27 hg qrefresh -m "Second commit message"
23 28 echo =======================
24 29 echo "Should display 'Second commit message'"
25 30 hg log -l1 -v | sed -n '/description/,$p'
26 31 echo
27 32
28 33
29 34 # Testing changing message with -l
30 35 echo "Third commit message" > logfile
31 36 echo " This is the 3rd log message" >> logfile
32 37 echo bbbb > file
33 38 hg qrefresh -l logfile
34 39 echo =======================
35 40 printf "Should display 'Third commit message\\\n This is the 3rd log message'\n"
36 41 hg log -l1 -v | sed -n '/description/,$p'
37 42 echo
38 43
39 44 # Testing changing message with -l-
40 45 hg qnew -m "First commit message" second-patch
41 46 echo aaaa > file2
42 47 hg add file2
43 48 echo bbbb > file2
44 49 (echo "Fifth commit message"
45 50 echo " This is the 5th log message" >> logfile) |\
46 51 hg qrefresh -l-
47 52 echo =======================
48 53 printf "Should display 'Fifth commit message\\\n This is the 5th log message'\n"
49 54 hg log -l1 -v | sed -n '/description/,$p'
50 55 echo
@@ -1,29 +1,33 b''
1 =======================
2 Should fail if no patches applied
3 No patches applied
4 No patches applied
1 5 =======================
2 6 Should display 'First commit message'
3 7 description:
4 8 First commit message
5 9
6 10
7 11
8 12 =======================
9 13 Should display 'Second commit message'
10 14 description:
11 15 Second commit message
12 16
13 17
14 18
15 19 =======================
16 20 Should display 'Third commit message\n This is the 3rd log message'
17 21 description:
18 22 Third commit message
19 23 This is the 3rd log message
20 24
21 25
22 26
23 27 =======================
24 28 Should display 'Fifth commit message\n This is the 5th log message'
25 29 description:
26 30 Fifth commit message
27 31
28 32
29 33
@@ -1,210 +1,215 b''
1 1 #!/bin/sh
2 2
3 3 hg init
4 4 mkdir d1 d1/d11 d2
5 5 echo d1/a > d1/a
6 6 echo d1/ba > d1/ba
7 7 echo d1/a1 > d1/d11/a1
8 8 echo d1/b > d1/b
9 9 echo d2/b > d2/b
10 10 hg add d1/a d1/b d1/ba d1/d11/a1 d2/b
11 11 hg commit -m "1" -d "1000000 0"
12 12
13 13 echo "# rename a single file"
14 14 hg rename d1/d11/a1 d2/c
15 15 hg status -C
16 16 hg update -C
17 17
18 18 echo "# rename --after a single file"
19 19 mv d1/d11/a1 d2/c
20 20 hg rename --after d1/d11/a1 d2/c
21 21 hg status -C
22 22 hg update -C
23 23
24 24 echo "# move a single file to an existing directory"
25 25 hg rename d1/d11/a1 d2
26 26 hg status -C
27 27 hg update -C
28 28
29 29 echo "# move --after a single file to an existing directory"
30 30 mv d1/d11/a1 d2
31 31 hg rename --after d1/d11/a1 d2
32 32 hg status -C
33 33 hg update -C
34 34
35 35 echo "# rename a file using a relative path"
36 36 (cd d1/d11; hg rename ../../d2/b e)
37 37 hg status -C
38 38 hg update -C
39 39
40 40 echo "# rename --after a file using a relative path"
41 41 (cd d1/d11; mv ../../d2/b e; hg rename --after ../../d2/b e)
42 42 hg status -C
43 43 hg update -C
44 44
45 45 echo "# rename directory d1 as d3"
46 46 hg rename d1/ d3
47 47 hg status -C
48 48 hg update -C
49 49
50 50 echo "# rename --after directory d1 as d3"
51 51 mv d1 d3
52 52 hg rename --after d1 d3
53 53 hg status -C
54 54 hg update -C
55 55
56 56 echo "# move a directory using a relative path"
57 57 (cd d2; mkdir d3; hg rename ../d1/d11 d3)
58 58 hg status -C
59 59 hg update -C
60 60
61 61 echo "# move --after a directory using a relative path"
62 62 (cd d2; mkdir d3; mv ../d1/d11 d3; hg rename --after ../d1/d11 d3)
63 63 hg status -C
64 64 hg update -C
65 65
66 66 echo "# move directory d1/d11 to an existing directory d2 (removes empty d1)"
67 67 hg rename d1/d11/ d2
68 68 hg status -C
69 69 hg update -C
70 70
71 71 echo "# move directories d1 and d2 to a new directory d3"
72 72 mkdir d3
73 73 hg rename d1 d2 d3
74 74 hg status -C
75 75 hg update -C
76 76
77 77 echo "# move --after directories d1 and d2 to a new directory d3"
78 78 mkdir d3
79 79 mv d1 d2 d3
80 80 hg rename --after d1 d2 d3
81 81 hg status -C
82 82 hg update -C
83 83
84 84 echo "# move everything under directory d1 to existing directory d2, do not"
85 85 echo "# overwrite existing files (d2/b)"
86 86 hg rename d1/* d2
87 87 hg status -C
88 88 diff d1/b d2/b
89 89 hg update -C
90 90
91 echo "# attempt to move one file into a non-existent directory"
92 hg rename d1/a dx/
93 hg status -C
94 hg update -C
95
91 96 echo "# attempt to move potentially more than one file into a non-existent"
92 97 echo "# directory"
93 98 hg rename 'glob:d1/**' dx
94 99
95 100 echo "# move every file under d1 to d2/d21 (glob)"
96 101 mkdir d2/d21
97 102 hg rename 'glob:d1/**' d2/d21
98 103 hg status -C
99 104 hg update -C
100 105
101 106 echo "# move --after some files under d1 to d2/d21 (glob)"
102 107 mkdir d2/d21
103 108 mv d1/a d1/d11/a1 d2/d21
104 109 hg rename --after 'glob:d1/**' d2/d21
105 110 hg status -C
106 111 hg update -C
107 112
108 113 echo "# move every file under d1 starting with an 'a' to d2/d21 (regexp)"
109 114 mkdir d2/d21
110 115 hg rename 're:d1/([^a][^/]*/)*a.*' d2/d21
111 116 hg status -C
112 117 hg update -C
113 118
114 119 echo "# attempt to overwrite an existing file"
115 120 echo "ca" > d1/ca
116 121 hg rename d1/ba d1/ca
117 122 hg status -C
118 123 hg update -C
119 124
120 125 echo "# forced overwrite of an existing file"
121 126 echo "ca" > d1/ca
122 127 hg rename --force d1/ba d1/ca
123 128 hg status -C
124 129 hg update -C
125 130
126 131 echo "# replace a symlink with a file"
127 132 ln -s ba d1/ca
128 133 hg rename --force d1/ba d1/ca
129 134 hg status -C
130 135 hg update -C
131 136
132 137 echo "# do not copy more than one source file to the same destination file"
133 138 mkdir d3
134 139 hg rename d1/* d2/* d3
135 140 hg status -C
136 141 hg update -C
137 142
138 143 echo "# move a whole subtree with \"hg rename .\""
139 144 mkdir d3
140 145 (cd d1; hg rename . ../d3)
141 146 hg status -C
142 147 hg update -C
143 148
144 149 echo "# move a whole subtree with \"hg rename --after .\""
145 150 mkdir d3
146 151 mv d1/* d3
147 152 (cd d1; hg rename --after . ../d3)
148 153 hg status -C
149 154 hg update -C
150 155
151 156 echo "# move the parent tree with \"hg rename ..\""
152 157 (cd d1/d11; hg rename .. ../../d3)
153 158 hg status -C
154 159 hg update -C
155 160
156 161 echo "# skip removed files"
157 162 hg remove d1/b
158 163 hg rename d1 d3
159 164 hg status -C
160 165 hg update -C
161 166
162 167 echo "# transitive rename"
163 168 hg rename d1/b d1/bb
164 169 hg rename d1/bb d1/bc
165 170 hg status -C
166 171 hg update -C
167 172
168 173 echo "# transitive rename --after"
169 174 hg rename d1/b d1/bb
170 175 mv d1/bb d1/bc
171 176 hg rename --after d1/bb d1/bc
172 177 hg status -C
173 178 hg update -C
174 179
175 180 echo "# idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)"
176 181 hg rename d1/b d1/bb
177 182 echo "some stuff added to d1/bb" >> d1/bb
178 183 hg rename d1/bb d1/b
179 184 hg status -C
180 185 hg update -C
181 186
182 187 echo "# check illegal path components"
183 188
184 189 hg rename d1/d11/a1 .hg/foo
185 190 hg status -C
186 191 hg rename d1/d11/a1 ../foo
187 192 hg status -C
188 193
189 194 mv d1/d11/a1 .hg/foo
190 195 hg rename --after d1/d11/a1 .hg/foo
191 196 hg status -C
192 197 hg update -C
193 198 rm .hg/foo
194 199
195 200 hg rename d1/d11/a1 .hg
196 201 hg status -C
197 202 hg rename d1/d11/a1 ..
198 203 hg status -C
199 204
200 205 mv d1/d11/a1 .hg
201 206 hg rename --after d1/d11/a1 .hg
202 207 hg status -C
203 208 hg update -C
204 209 rm .hg/a1
205 210
206 211 (cd d1/d11; hg rename ../../d2/b ../../.hg/foo)
207 212 hg status -C
208 213 (cd d1/d11; hg rename ../../d2/b ../../../foo)
209 214 hg status -C
210 215
@@ -1,358 +1,361 b''
1 1 # rename a single file
2 2 A d2/c
3 3 d1/d11/a1
4 4 R d1/d11/a1
5 5 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
6 6 # rename --after a single file
7 7 A d2/c
8 8 d1/d11/a1
9 9 R d1/d11/a1
10 10 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
11 11 # move a single file to an existing directory
12 12 A d2/a1
13 13 d1/d11/a1
14 14 R d1/d11/a1
15 15 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
16 16 # move --after a single file to an existing directory
17 17 A d2/a1
18 18 d1/d11/a1
19 19 R d1/d11/a1
20 20 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
21 21 # rename a file using a relative path
22 22 A d1/d11/e
23 23 d2/b
24 24 R d2/b
25 25 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
26 26 # rename --after a file using a relative path
27 27 A d1/d11/e
28 28 d2/b
29 29 R d2/b
30 30 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
31 31 # rename directory d1 as d3
32 32 copying d1/a to d3/a
33 33 copying d1/b to d3/b
34 34 copying d1/ba to d3/ba
35 35 copying d1/d11/a1 to d3/d11/a1
36 36 removing d1/a
37 37 removing d1/b
38 38 removing d1/ba
39 39 removing d1/d11/a1
40 40 A d3/a
41 41 d1/a
42 42 A d3/b
43 43 d1/b
44 44 A d3/ba
45 45 d1/ba
46 46 A d3/d11/a1
47 47 d1/d11/a1
48 48 R d1/a
49 49 R d1/b
50 50 R d1/ba
51 51 R d1/d11/a1
52 52 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
53 53 # rename --after directory d1 as d3
54 54 copying d1/a to d3/a
55 55 copying d1/b to d3/b
56 56 copying d1/ba to d3/ba
57 57 copying d1/d11/a1 to d3/d11/a1
58 58 removing d1/a
59 59 removing d1/b
60 60 removing d1/ba
61 61 removing d1/d11/a1
62 62 A d3/a
63 63 d1/a
64 64 A d3/b
65 65 d1/b
66 66 A d3/ba
67 67 d1/ba
68 68 A d3/d11/a1
69 69 d1/d11/a1
70 70 R d1/a
71 71 R d1/b
72 72 R d1/ba
73 73 R d1/d11/a1
74 74 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
75 75 # move a directory using a relative path
76 76 copying ../d1/d11/a1 to d3/d11/a1
77 77 removing ../d1/d11/a1
78 78 A d2/d3/d11/a1
79 79 d1/d11/a1
80 80 R d1/d11/a1
81 81 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
82 82 # move --after a directory using a relative path
83 83 copying ../d1/d11/a1 to d3/d11/a1
84 84 removing ../d1/d11/a1
85 85 A d2/d3/d11/a1
86 86 d1/d11/a1
87 87 R d1/d11/a1
88 88 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
89 89 # move directory d1/d11 to an existing directory d2 (removes empty d1)
90 90 copying d1/d11/a1 to d2/d11/a1
91 91 removing d1/d11/a1
92 92 A d2/d11/a1
93 93 d1/d11/a1
94 94 R d1/d11/a1
95 95 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
96 96 # move directories d1 and d2 to a new directory d3
97 97 copying d1/a to d3/d1/a
98 98 copying d1/b to d3/d1/b
99 99 copying d1/ba to d3/d1/ba
100 100 copying d1/d11/a1 to d3/d1/d11/a1
101 101 copying d2/b to d3/d2/b
102 102 removing d1/a
103 103 removing d1/b
104 104 removing d1/ba
105 105 removing d1/d11/a1
106 106 removing d2/b
107 107 A d3/d1/a
108 108 d1/a
109 109 A d3/d1/b
110 110 d1/b
111 111 A d3/d1/ba
112 112 d1/ba
113 113 A d3/d1/d11/a1
114 114 d1/d11/a1
115 115 A d3/d2/b
116 116 d2/b
117 117 R d1/a
118 118 R d1/b
119 119 R d1/ba
120 120 R d1/d11/a1
121 121 R d2/b
122 122 5 files updated, 0 files merged, 5 files removed, 0 files unresolved
123 123 # move --after directories d1 and d2 to a new directory d3
124 124 copying d1/a to d3/d1/a
125 125 copying d1/b to d3/d1/b
126 126 copying d1/ba to d3/d1/ba
127 127 copying d1/d11/a1 to d3/d1/d11/a1
128 128 copying d2/b to d3/d2/b
129 129 removing d1/a
130 130 removing d1/b
131 131 removing d1/ba
132 132 removing d1/d11/a1
133 133 removing d2/b
134 134 A d3/d1/a
135 135 d1/a
136 136 A d3/d1/b
137 137 d1/b
138 138 A d3/d1/ba
139 139 d1/ba
140 140 A d3/d1/d11/a1
141 141 d1/d11/a1
142 142 A d3/d2/b
143 143 d2/b
144 144 R d1/a
145 145 R d1/b
146 146 R d1/ba
147 147 R d1/d11/a1
148 148 R d2/b
149 149 5 files updated, 0 files merged, 5 files removed, 0 files unresolved
150 150 # move everything under directory d1 to existing directory d2, do not
151 151 # overwrite existing files (d2/b)
152 152 d2/b: not overwriting - file exists
153 153 copying d1/d11/a1 to d2/d11/a1
154 154 removing d1/d11/a1
155 155 A d2/a
156 156 d1/a
157 157 A d2/ba
158 158 d1/ba
159 159 A d2/d11/a1
160 160 d1/d11/a1
161 161 R d1/a
162 162 R d1/ba
163 163 R d1/d11/a1
164 164 1c1
165 165 < d1/b
166 166 ---
167 167 > d2/b
168 168 3 files updated, 0 files merged, 3 files removed, 0 files unresolved
169 # attempt to move one file into a non-existent directory
170 abort: destination dx/ is not a directory
171 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
169 172 # attempt to move potentially more than one file into a non-existent
170 173 # directory
171 174 abort: with multiple sources, destination must be an existing directory
172 175 # move every file under d1 to d2/d21 (glob)
173 176 copying d1/a to d2/d21/a
174 177 copying d1/b to d2/d21/b
175 178 copying d1/ba to d2/d21/ba
176 179 copying d1/d11/a1 to d2/d21/a1
177 180 removing d1/a
178 181 removing d1/b
179 182 removing d1/ba
180 183 removing d1/d11/a1
181 184 A d2/d21/a
182 185 d1/a
183 186 A d2/d21/a1
184 187 d1/d11/a1
185 188 A d2/d21/b
186 189 d1/b
187 190 A d2/d21/ba
188 191 d1/ba
189 192 R d1/a
190 193 R d1/b
191 194 R d1/ba
192 195 R d1/d11/a1
193 196 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
194 197 # move --after some files under d1 to d2/d21 (glob)
195 198 copying d1/a to d2/d21/a
196 199 copying d1/d11/a1 to d2/d21/a1
197 200 removing d1/a
198 201 removing d1/d11/a1
199 202 A d2/d21/a
200 203 d1/a
201 204 A d2/d21/a1
202 205 d1/d11/a1
203 206 R d1/a
204 207 R d1/d11/a1
205 208 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
206 209 # move every file under d1 starting with an 'a' to d2/d21 (regexp)
207 210 copying d1/a to d2/d21/a
208 211 copying d1/d11/a1 to d2/d21/a1
209 212 removing d1/a
210 213 removing d1/d11/a1
211 214 A d2/d21/a
212 215 d1/a
213 216 A d2/d21/a1
214 217 d1/d11/a1
215 218 R d1/a
216 219 R d1/d11/a1
217 220 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
218 221 # attempt to overwrite an existing file
219 222 d1/ca: not overwriting - file exists
220 223 ? d1/ca
221 224 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 225 # forced overwrite of an existing file
223 226 A d1/ca
224 227 d1/ba
225 228 R d1/ba
226 229 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
227 230 # replace a symlink with a file
228 231 A d1/ca
229 232 d1/ba
230 233 R d1/ba
231 234 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
232 235 # do not copy more than one source file to the same destination file
233 236 copying d1/d11/a1 to d3/d11/a1
234 237 d3/b: not overwriting - d2/b collides with d1/b
235 238 removing d1/d11/a1
236 239 A d3/a
237 240 d1/a
238 241 A d3/b
239 242 d1/b
240 243 A d3/ba
241 244 d1/ba
242 245 A d3/d11/a1
243 246 d1/d11/a1
244 247 R d1/a
245 248 R d1/b
246 249 R d1/ba
247 250 R d1/d11/a1
248 251 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
249 252 # move a whole subtree with "hg rename ."
250 253 copying a to ../d3/d1/a
251 254 copying b to ../d3/d1/b
252 255 copying ba to ../d3/d1/ba
253 256 copying d11/a1 to ../d3/d1/d11/a1
254 257 removing a
255 258 removing b
256 259 removing ba
257 260 removing d11/a1
258 261 A d3/d1/a
259 262 d1/a
260 263 A d3/d1/b
261 264 d1/b
262 265 A d3/d1/ba
263 266 d1/ba
264 267 A d3/d1/d11/a1
265 268 d1/d11/a1
266 269 R d1/a
267 270 R d1/b
268 271 R d1/ba
269 272 R d1/d11/a1
270 273 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
271 274 # move a whole subtree with "hg rename --after ."
272 275 copying a to ../d3/a
273 276 copying b to ../d3/b
274 277 copying ba to ../d3/ba
275 278 copying d11/a1 to ../d3/d11/a1
276 279 removing a
277 280 removing b
278 281 removing ba
279 282 removing d11/a1
280 283 A d3/a
281 284 d1/a
282 285 A d3/b
283 286 d1/b
284 287 A d3/ba
285 288 d1/ba
286 289 A d3/d11/a1
287 290 d1/d11/a1
288 291 R d1/a
289 292 R d1/b
290 293 R d1/ba
291 294 R d1/d11/a1
292 295 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
293 296 # move the parent tree with "hg rename .."
294 297 copying ../a to ../../d3/a
295 298 copying ../b to ../../d3/b
296 299 copying ../ba to ../../d3/ba
297 300 copying a1 to ../../d3/d11/a1
298 301 removing ../a
299 302 removing ../b
300 303 removing ../ba
301 304 removing a1
302 305 A d3/a
303 306 d1/a
304 307 A d3/b
305 308 d1/b
306 309 A d3/ba
307 310 d1/ba
308 311 A d3/d11/a1
309 312 d1/d11/a1
310 313 R d1/a
311 314 R d1/b
312 315 R d1/ba
313 316 R d1/d11/a1
314 317 4 files updated, 0 files merged, 4 files removed, 0 files unresolved
315 318 # skip removed files
316 319 copying d1/a to d3/a
317 320 copying d1/ba to d3/ba
318 321 copying d1/d11/a1 to d3/d11/a1
319 322 removing d1/a
320 323 removing d1/ba
321 324 removing d1/d11/a1
322 325 A d3/a
323 326 d1/a
324 327 A d3/ba
325 328 d1/ba
326 329 A d3/d11/a1
327 330 d1/d11/a1
328 331 R d1/a
329 332 R d1/b
330 333 R d1/ba
331 334 R d1/d11/a1
332 335 4 files updated, 0 files merged, 3 files removed, 0 files unresolved
333 336 # transitive rename
334 337 A d1/bc
335 338 d1/b
336 339 R d1/b
337 340 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
338 341 # transitive rename --after
339 342 A d1/bc
340 343 d1/b
341 344 R d1/b
342 345 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
343 346 # idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)
344 347 M d1/b
345 348 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
346 349 # check illegal path components
347 350 abort: path contains illegal component: .hg/foo
348 351 abort: ../foo not under root
349 352 abort: path contains illegal component: .hg/foo
350 353 ! d1/d11/a1
351 354 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
352 355 abort: path contains illegal component: .hg/a1
353 356 abort: ../a1 not under root
354 357 abort: path contains illegal component: .hg/a1
355 358 ! d1/d11/a1
356 359 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
357 360 abort: path contains illegal component: .hg/foo
358 361 abort: ../../../foo not under root
General Comments 0
You need to be logged in to leave comments. Login now