##// END OF EJS Templates
Merge with stable
Matt Mackall -
r11681:c5e555e0 merge default
parent child Browse files
Show More
@@ -1,576 +1,575 b''
1 1 # keyword.py - $Keyword$ expansion for Mercurial
2 2 #
3 3 # Copyright 2007-2010 Christian Ebert <blacktrash@gmx.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 #
8 8 # $Id$
9 9 #
10 10 # Keyword expansion hack against the grain of a DSCM
11 11 #
12 12 # There are many good reasons why this is not needed in a distributed
13 13 # SCM, still it may be useful in very small projects based on single
14 14 # files (like LaTeX packages), that are mostly addressed to an
15 15 # audience not running a version control system.
16 16 #
17 17 # For in-depth discussion refer to
18 18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
19 19 #
20 20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 21 #
22 22 # Binary files are not touched.
23 23 #
24 24 # Files to act upon/ignore are specified in the [keyword] section.
25 25 # Customized keyword template mappings in the [keywordmaps] section.
26 26 #
27 27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28 28
29 29 '''expand keywords in tracked files
30 30
31 31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 32 tracked text files selected by your configuration.
33 33
34 34 Keywords are only expanded in local repositories and not stored in the
35 35 change history. The mechanism can be regarded as a convenience for the
36 36 current user or for archive distribution.
37 37
38 38 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
39 39 sections of hgrc files.
40 40
41 41 Example::
42 42
43 43 [keyword]
44 44 # expand keywords in every python file except those matching "x*"
45 45 **.py =
46 46 x* = ignore
47 47
48 48 [keywordset]
49 49 # prefer svn- over cvs-like default keywordmaps
50 50 svn = True
51 51
52 52 NOTE: the more specific you are in your filename patterns the less you
53 53 lose speed in huge repositories.
54 54
55 55 For [keywordmaps] template mapping and expansion demonstration and
56 56 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
57 57 available templates and filters.
58 58
59 59 Three additional date template filters are provided::
60 60
61 61 utcdate "2006/09/18 15:13:13"
62 62 svnutcdate "2006-09-18 15:13:13Z"
63 63 svnisodate "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
64 64
65 65 The default template mappings (view with :hg:`kwdemo -d`) can be
66 66 replaced with customized keywords and templates. Again, run
67 67 :hg:`kwdemo` to control the results of your config changes.
68 68
69 69 Before changing/disabling active keywords, run :hg:`kwshrink` to avoid
70 70 the risk of inadvertently storing expanded keywords in the change
71 71 history.
72 72
73 73 To force expansion after enabling it, or a configuration change, run
74 74 :hg:`kwexpand`.
75 75
76 76 Expansions spanning more than one line and incremental expansions,
77 77 like CVS' $Log$, are not supported. A keyword template map "Log =
78 78 {desc}" expands to the first line of the changeset description.
79 79 '''
80 80
81 81 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
82 82 from mercurial import patch, localrepo, templater, templatefilters, util, match
83 83 from mercurial.hgweb import webcommands
84 84 from mercurial.i18n import _
85 85 import re, shutil, tempfile
86 86
87 87 commands.optionalrepo += ' kwdemo'
88 88
89 89 # hg commands that do not act on keywords
90 90 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
91 91 ' log outgoing push rename rollback tip verify'
92 92 ' convert email glog')
93 93
94 94 # hg commands that trigger expansion only when writing to working dir,
95 95 # not when reading filelog, and unexpand when reading from working dir
96 96 restricted = 'merge record qrecord resolve transplant'
97 97
98 98 # commands using dorecord
99 99 recordcommands = 'record qrecord'
100 100 # names of extensions using dorecord
101 101 recordextensions = 'record'
102 102
103 103 # date like in cvs' $Date
104 104 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
105 105 # date like in svn's $Date
106 106 svnisodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
107 107 # date like in svn's $Id
108 108 svnutcdate = lambda x: util.datestr((x[0], 0), '%Y-%m-%d %H:%M:%SZ')
109 109
110 110 # make keyword tools accessible
111 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
111 kwtools = {'templater': None, 'hgcmd': ''}
112 112
113 113
114 114 def _defaultkwmaps(ui):
115 115 '''Returns default keywordmaps according to keywordset configuration.'''
116 116 templates = {
117 117 'Revision': '{node|short}',
118 118 'Author': '{author|user}',
119 119 }
120 120 kwsets = ({
121 121 'Date': '{date|utcdate}',
122 122 'RCSfile': '{file|basename},v',
123 123 'RCSFile': '{file|basename},v', # kept for backwards compatibility
124 124 # with hg-keyword
125 125 'Source': '{root}/{file},v',
126 126 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
127 127 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
128 128 }, {
129 129 'Date': '{date|svnisodate}',
130 130 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
131 131 'LastChangedRevision': '{node|short}',
132 132 'LastChangedBy': '{author|user}',
133 133 'LastChangedDate': '{date|svnisodate}',
134 134 })
135 135 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
136 136 return templates
137 137
138 138 class kwtemplater(object):
139 139 '''
140 140 Sets up keyword templates, corresponding keyword regex, and
141 141 provides keyword substitution functions.
142 142 '''
143 143
144 def __init__(self, ui, repo):
144 def __init__(self, ui, repo, inc, exc):
145 145 self.ui = ui
146 146 self.repo = repo
147 self.match = match.match(repo.root, '', [],
148 kwtools['inc'], kwtools['exc'])
147 self.match = match.match(repo.root, '', [], inc, exc)
149 148 self.restrict = kwtools['hgcmd'] in restricted.split()
150 149 self.record = kwtools['hgcmd'] in recordcommands.split()
151 150
152 151 kwmaps = self.ui.configitems('keywordmaps')
153 152 if kwmaps: # override default templates
154 153 self.templates = dict((k, templater.parsestring(v, False))
155 154 for k, v in kwmaps)
156 155 else:
157 156 self.templates = _defaultkwmaps(self.ui)
158 157 escaped = map(re.escape, self.templates.keys())
159 158 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
160 159 self.re_kw = re.compile(kwpat)
161 160
162 161 templatefilters.filters.update({'utcdate': utcdate,
163 162 'svnisodate': svnisodate,
164 163 'svnutcdate': svnutcdate})
165 164
166 165 def substitute(self, data, path, ctx, subfunc):
167 166 '''Replaces keywords in data with expanded template.'''
168 167 def kwsub(mobj):
169 168 kw = mobj.group(1)
170 169 ct = cmdutil.changeset_templater(self.ui, self.repo,
171 170 False, None, '', False)
172 171 ct.use_template(self.templates[kw])
173 172 self.ui.pushbuffer()
174 173 ct.show(ctx, root=self.repo.root, file=path)
175 174 ekw = templatefilters.firstline(self.ui.popbuffer())
176 175 return '$%s: %s $' % (kw, ekw)
177 176 return subfunc(kwsub, data)
178 177
179 178 def expand(self, path, node, data):
180 179 '''Returns data with keywords expanded.'''
181 180 if not self.restrict and self.match(path) and not util.binary(data):
182 181 ctx = self.repo.filectx(path, fileid=node).changectx()
183 182 return self.substitute(data, path, ctx, self.re_kw.sub)
184 183 return data
185 184
186 185 def iskwfile(self, path, flagfunc):
187 186 '''Returns true if path matches [keyword] pattern
188 187 and is not a symbolic link.
189 188 Caveat: localrepository._link fails on Windows.'''
190 189 return self.match(path) and not 'l' in flagfunc(path)
191 190
192 191 def overwrite(self, ctx, candidates, iswctx, expand):
193 192 '''Overwrites selected files expanding/shrinking keywords.'''
194 193 if self.record:
195 194 candidates = [f for f in ctx.files() if f in ctx]
196 195 candidates = [f for f in candidates if self.iskwfile(f, ctx.flags)]
197 196 if candidates:
198 197 self.restrict = True # do not expand when reading
199 198 mf = ctx.manifest()
200 199 msg = (expand and _('overwriting %s expanding keywords\n')
201 200 or _('overwriting %s shrinking keywords\n'))
202 201 for f in candidates:
203 202 if not self.record:
204 203 data = self.repo.file(f).read(mf[f])
205 204 else:
206 205 data = self.repo.wread(f)
207 206 if util.binary(data):
208 207 continue
209 208 if expand:
210 209 if iswctx:
211 210 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
212 211 data, found = self.substitute(data, f, ctx,
213 212 self.re_kw.subn)
214 213 else:
215 214 found = self.re_kw.search(data)
216 215 if found:
217 216 self.ui.note(msg % f)
218 217 self.repo.wwrite(f, data, mf.flags(f))
219 218 if iswctx:
220 219 self.repo.dirstate.normal(f)
221 220 elif self.record:
222 221 self.repo.dirstate.normallookup(f)
223 222 self.restrict = False
224 223
225 224 def shrinktext(self, text):
226 225 '''Unconditionally removes all keyword substitutions from text.'''
227 226 return self.re_kw.sub(r'$\1$', text)
228 227
229 228 def shrink(self, fname, text):
230 229 '''Returns text with all keyword substitutions removed.'''
231 230 if self.match(fname) and not util.binary(text):
232 231 return self.shrinktext(text)
233 232 return text
234 233
235 234 def shrinklines(self, fname, lines):
236 235 '''Returns lines with keyword substitutions removed.'''
237 236 if self.match(fname):
238 237 text = ''.join(lines)
239 238 if not util.binary(text):
240 239 return self.shrinktext(text).splitlines(True)
241 240 return lines
242 241
243 242 def wread(self, fname, data):
244 243 '''If in restricted mode returns data read from wdir with
245 244 keyword substitutions removed.'''
246 245 return self.restrict and self.shrink(fname, data) or data
247 246
248 247 class kwfilelog(filelog.filelog):
249 248 '''
250 249 Subclass of filelog to hook into its read, add, cmp methods.
251 250 Keywords are "stored" unexpanded, and processed on reading.
252 251 '''
253 252 def __init__(self, opener, kwt, path):
254 253 super(kwfilelog, self).__init__(opener, path)
255 254 self.kwt = kwt
256 255 self.path = path
257 256
258 257 def read(self, node):
259 258 '''Expands keywords when reading filelog.'''
260 259 data = super(kwfilelog, self).read(node)
261 260 return self.kwt.expand(self.path, node, data)
262 261
263 262 def add(self, text, meta, tr, link, p1=None, p2=None):
264 263 '''Removes keyword substitutions when adding to filelog.'''
265 264 text = self.kwt.shrink(self.path, text)
266 265 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
267 266
268 267 def cmp(self, node, text):
269 268 '''Removes keyword substitutions for comparison.'''
270 269 text = self.kwt.shrink(self.path, text)
271 270 if self.renamed(node):
272 271 t2 = super(kwfilelog, self).read(node)
273 272 return t2 != text
274 273 return revlog.revlog.cmp(self, node, text)
275 274
276 275 def _status(ui, repo, kwt, *pats, **opts):
277 276 '''Bails out if [keyword] configuration is not active.
278 277 Returns status of working directory.'''
279 278 if kwt:
280 279 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
281 280 unknown=opts.get('unknown') or opts.get('all'))
282 281 if ui.configitems('keyword'):
283 282 raise util.Abort(_('[keyword] patterns cannot match'))
284 283 raise util.Abort(_('no [keyword] patterns configured'))
285 284
286 285 def _kwfwrite(ui, repo, expand, *pats, **opts):
287 286 '''Selects files and passes them to kwtemplater.overwrite.'''
288 287 wctx = repo[None]
289 288 if len(wctx.parents()) > 1:
290 289 raise util.Abort(_('outstanding uncommitted merge'))
291 290 kwt = kwtools['templater']
292 291 wlock = repo.wlock()
293 292 try:
294 293 status = _status(ui, repo, kwt, *pats, **opts)
295 294 modified, added, removed, deleted, unknown, ignored, clean = status
296 295 if modified or added or removed or deleted:
297 296 raise util.Abort(_('outstanding uncommitted changes'))
298 297 kwt.overwrite(wctx, clean, True, expand)
299 298 finally:
300 299 wlock.release()
301 300
302 301 def demo(ui, repo, *args, **opts):
303 302 '''print [keywordmaps] configuration and an expansion example
304 303
305 304 Show current, custom, or default keyword template maps and their
306 305 expansions.
307 306
308 307 Extend the current configuration by specifying maps as arguments
309 308 and using -f/--rcfile to source an external hgrc file.
310 309
311 310 Use -d/--default to disable current configuration.
312 311
313 312 See :hg:`help templates` for information on templates and filters.
314 313 '''
315 314 def demoitems(section, items):
316 315 ui.write('[%s]\n' % section)
317 316 for k, v in sorted(items):
318 317 ui.write('%s = %s\n' % (k, v))
319 318
320 319 fn = 'demo.txt'
321 320 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
322 321 ui.note(_('creating temporary repository at %s\n') % tmpdir)
323 322 repo = localrepo.localrepository(ui, tmpdir, True)
324 323 ui.setconfig('keyword', fn, '')
325 324
326 325 uikwmaps = ui.configitems('keywordmaps')
327 326 if args or opts.get('rcfile'):
328 327 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
329 328 if uikwmaps:
330 329 ui.status(_('\textending current template maps\n'))
331 330 if opts.get('default') or not uikwmaps:
332 331 ui.status(_('\toverriding default template maps\n'))
333 332 if opts.get('rcfile'):
334 333 ui.readconfig(opts.get('rcfile'))
335 334 if args:
336 335 # simulate hgrc parsing
337 336 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
338 337 fp = repo.opener('hgrc', 'w')
339 338 fp.writelines(rcmaps)
340 339 fp.close()
341 340 ui.readconfig(repo.join('hgrc'))
342 341 kwmaps = dict(ui.configitems('keywordmaps'))
343 342 elif opts.get('default'):
344 343 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
345 344 kwmaps = _defaultkwmaps(ui)
346 345 if uikwmaps:
347 346 ui.status(_('\tdisabling current template maps\n'))
348 347 for k, v in kwmaps.iteritems():
349 348 ui.setconfig('keywordmaps', k, v)
350 349 else:
351 350 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
352 351 kwmaps = dict(uikwmaps) or _defaultkwmaps(ui)
353 352
354 353 uisetup(ui)
355 354 reposetup(ui, repo)
356 355 ui.write('[extensions]\nkeyword =\n')
357 356 demoitems('keyword', ui.configitems('keyword'))
358 357 demoitems('keywordmaps', kwmaps.iteritems())
359 358 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
360 359 repo.wopener(fn, 'w').write(keywords)
361 360 repo[None].add([fn])
362 361 ui.note(_('\nkeywords written to %s:\n') % fn)
363 362 ui.note(keywords)
364 363 repo.dirstate.setbranch('demobranch')
365 364 for name, cmd in ui.configitems('hooks'):
366 365 if name.split('.', 1)[0].find('commit') > -1:
367 366 repo.ui.setconfig('hooks', name, '')
368 367 msg = _('hg keyword configuration and expansion example')
369 368 ui.note("hg ci -m '%s'\n" % msg)
370 369 repo.commit(text=msg)
371 370 ui.status(_('\n\tkeywords expanded\n'))
372 371 ui.write(repo.wread(fn))
373 372 shutil.rmtree(tmpdir, ignore_errors=True)
374 373
375 374 def expand(ui, repo, *pats, **opts):
376 375 '''expand keywords in the working directory
377 376
378 377 Run after (re)enabling keyword expansion.
379 378
380 379 kwexpand refuses to run if given files contain local changes.
381 380 '''
382 381 # 3rd argument sets expansion to True
383 382 _kwfwrite(ui, repo, True, *pats, **opts)
384 383
385 384 def files(ui, repo, *pats, **opts):
386 385 '''show files configured for keyword expansion
387 386
388 387 List which files in the working directory are matched by the
389 388 [keyword] configuration patterns.
390 389
391 390 Useful to prevent inadvertent keyword expansion and to speed up
392 391 execution by including only files that are actual candidates for
393 392 expansion.
394 393
395 394 See :hg:`help keyword` on how to construct patterns both for
396 395 inclusion and exclusion of files.
397 396
398 397 With -A/--all and -v/--verbose the codes used to show the status
399 398 of files are::
400 399
401 400 K = keyword expansion candidate
402 401 k = keyword expansion candidate (not tracked)
403 402 I = ignored
404 403 i = ignored (not tracked)
405 404 '''
406 405 kwt = kwtools['templater']
407 406 status = _status(ui, repo, kwt, *pats, **opts)
408 407 cwd = pats and repo.getcwd() or ''
409 408 modified, added, removed, deleted, unknown, ignored, clean = status
410 409 files = []
411 410 if not opts.get('unknown') or opts.get('all'):
412 411 files = sorted(modified + added + clean)
413 412 wctx = repo[None]
414 413 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
415 414 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
416 415 if not opts.get('ignore') or opts.get('all'):
417 416 showfiles = kwfiles, kwunknown
418 417 else:
419 418 showfiles = [], []
420 419 if opts.get('all') or opts.get('ignore'):
421 420 showfiles += ([f for f in files if f not in kwfiles],
422 421 [f for f in unknown if f not in kwunknown])
423 422 for char, filenames in zip('KkIi', showfiles):
424 423 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
425 424 for f in filenames:
426 425 ui.write(fmt % repo.pathto(f, cwd))
427 426
428 427 def shrink(ui, repo, *pats, **opts):
429 428 '''revert expanded keywords in the working directory
430 429
431 430 Run before changing/disabling active keywords or if you experience
432 431 problems with :hg:`import` or :hg:`merge`.
433 432
434 433 kwshrink refuses to run if given files contain local changes.
435 434 '''
436 435 # 3rd argument sets expansion to False
437 436 _kwfwrite(ui, repo, False, *pats, **opts)
438 437
439 438
440 439 def uisetup(ui):
441 '''Collects [keyword] config in kwtools.
442 Monkeypatches dispatch._parse if needed.'''
443
444 for pat, opt in ui.configitems('keyword'):
445 if opt != 'ignore':
446 kwtools['inc'].append(pat)
447 else:
448 kwtools['exc'].append(pat)
440 ''' Monkeypatches dispatch._parse to retrieve user command.'''
449 441
450 if kwtools['inc']:
451 def kwdispatch_parse(orig, ui, args):
452 '''Monkeypatch dispatch._parse to obtain running hg command.'''
453 cmd, func, args, options, cmdoptions = orig(ui, args)
454 kwtools['hgcmd'] = cmd
455 return cmd, func, args, options, cmdoptions
442 def kwdispatch_parse(orig, ui, args):
443 '''Monkeypatch dispatch._parse to obtain running hg command.'''
444 cmd, func, args, options, cmdoptions = orig(ui, args)
445 kwtools['hgcmd'] = cmd
446 return cmd, func, args, options, cmdoptions
456 447
457 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
448 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
458 449
459 450 def reposetup(ui, repo):
460 451 '''Sets up repo as kwrepo for keyword substitution.
461 452 Overrides file method to return kwfilelog instead of filelog
462 453 if file matches user configuration.
463 454 Wraps commit to overwrite configured files with updated
464 455 keyword substitutions.
465 456 Monkeypatches patch and webcommands.'''
466 457
467 458 try:
468 if (not repo.local() or not kwtools['inc']
469 or kwtools['hgcmd'] in nokwcommands.split()
459 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
470 460 or '.hg' in util.splitpath(repo.root)
471 461 or repo._url.startswith('bundle:')):
472 462 return
473 463 except AttributeError:
474 464 pass
475 465
476 kwtools['templater'] = kwt = kwtemplater(ui, repo)
466 inc, exc = [], ['.hg*']
467 for pat, opt in ui.configitems('keyword'):
468 if opt != 'ignore':
469 inc.append(pat)
470 else:
471 exc.append(pat)
472 if not inc:
473 return
474
475 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
477 476
478 477 class kwrepo(repo.__class__):
479 478 def file(self, f):
480 479 if f[0] == '/':
481 480 f = f[1:]
482 481 return kwfilelog(self.sopener, kwt, f)
483 482
484 483 def wread(self, filename):
485 484 data = super(kwrepo, self).wread(filename)
486 485 return kwt.wread(filename, data)
487 486
488 487 def commit(self, *args, **opts):
489 488 # use custom commitctx for user commands
490 489 # other extensions can still wrap repo.commitctx directly
491 490 self.commitctx = self.kwcommitctx
492 491 try:
493 492 return super(kwrepo, self).commit(*args, **opts)
494 493 finally:
495 494 del self.commitctx
496 495
497 496 def kwcommitctx(self, ctx, error=False):
498 497 n = super(kwrepo, self).commitctx(ctx, error)
499 498 # no lock needed, only called from repo.commit() which already locks
500 499 if not kwt.record:
501 500 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
502 501 False, True)
503 502 return n
504 503
505 504 # monkeypatches
506 505 def kwpatchfile_init(orig, self, ui, fname, opener,
507 506 missing=False, eolmode=None):
508 507 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
509 508 rejects or conflicts due to expanded keywords in working dir.'''
510 509 orig(self, ui, fname, opener, missing, eolmode)
511 510 # shrink keywords read from working dir
512 511 self.lines = kwt.shrinklines(self.fname, self.lines)
513 512
514 513 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
515 514 opts=None):
516 515 '''Monkeypatch patch.diff to avoid expansion except when
517 516 comparing against working dir.'''
518 517 if node2 is not None:
519 518 kwt.match = util.never
520 519 elif node1 is not None and node1 != repo['.'].node():
521 520 kwt.restrict = True
522 521 return orig(repo, node1, node2, match, changes, opts)
523 522
524 523 def kwweb_skip(orig, web, req, tmpl):
525 524 '''Wraps webcommands.x turning off keyword expansion.'''
526 525 kwt.match = util.never
527 526 return orig(web, req, tmpl)
528 527
529 528 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
530 529 '''Wraps record.dorecord expanding keywords after recording.'''
531 530 wlock = repo.wlock()
532 531 try:
533 532 # record returns 0 even when nothing has changed
534 533 # therefore compare nodes before and after
535 534 ctx = repo['.']
536 535 ret = orig(ui, repo, commitfunc, *pats, **opts)
537 536 recordctx = repo['.']
538 537 if ctx != recordctx:
539 538 kwt.overwrite(recordctx, None, False, True)
540 539 return ret
541 540 finally:
542 541 wlock.release()
543 542
544 543 repo.__class__ = kwrepo
545 544
546 545 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
547 546 if not kwt.restrict:
548 547 extensions.wrapfunction(patch, 'diff', kw_diff)
549 548 for c in 'annotate changeset rev filediff diff'.split():
550 549 extensions.wrapfunction(webcommands, c, kwweb_skip)
551 550 for name in recordextensions.split():
552 551 try:
553 552 record = extensions.find(name)
554 553 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
555 554 except KeyError:
556 555 pass
557 556
558 557 cmdtable = {
559 558 'kwdemo':
560 559 (demo,
561 560 [('d', 'default', None, _('show default keyword template maps')),
562 561 ('f', 'rcfile', '',
563 562 _('read maps from rcfile'), _('FILE'))],
564 563 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
565 564 'kwexpand': (expand, commands.walkopts,
566 565 _('hg kwexpand [OPTION]... [FILE]...')),
567 566 'kwfiles':
568 567 (files,
569 568 [('A', 'all', None, _('show keyword status flags of all files')),
570 569 ('i', 'ignore', None, _('show files excluded from expansion')),
571 570 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
572 571 ] + commands.walkopts,
573 572 _('hg kwfiles [OPTION]... [FILE]...')),
574 573 'kwshrink': (shrink, commands.walkopts,
575 574 _('hg kwshrink [OPTION]... [FILE]...')),
576 575 }
@@ -1,550 +1,555 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback
10 10 import util, commands, hg, fancyopts, extensions, hook, error
11 11 import cmdutil, encoding
12 12 import ui as uimod
13 13
14 14 def run():
15 15 "run the command in sys.argv"
16 16 sys.exit(dispatch(sys.argv[1:]))
17 17
18 18 def dispatch(args):
19 19 "run the command specified in args"
20 20 try:
21 21 u = uimod.ui()
22 22 if '--traceback' in args:
23 23 u.setconfig('ui', 'traceback', 'on')
24 24 except util.Abort, inst:
25 25 sys.stderr.write(_("abort: %s\n") % inst)
26 26 if inst.hint:
27 27 sys.stdout.write(_("(%s)\n") % inst.hint)
28 28 return -1
29 29 except error.ParseError, inst:
30 30 if len(inst.args) > 1:
31 31 sys.stderr.write(_("hg: parse error at %s: %s\n") %
32 32 (inst.args[1], inst.args[0]))
33 33 else:
34 34 sys.stderr.write(_("hg: parse error: %s\n") % inst.args[0])
35 35 return -1
36 36 return _runcatch(u, args)
37 37
38 38 def _runcatch(ui, args):
39 39 def catchterm(*args):
40 40 raise error.SignalInterrupt
41 41
42 42 try:
43 43 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
44 44 num = getattr(signal, name, None)
45 45 if num:
46 46 signal.signal(num, catchterm)
47 47 except ValueError:
48 48 pass # happens if called in a thread
49 49
50 50 try:
51 51 try:
52 52 # enter the debugger before command execution
53 53 if '--debugger' in args:
54 54 ui.warn(_("entering debugger - "
55 55 "type c to continue starting hg or h for help\n"))
56 56 pdb.set_trace()
57 57 try:
58 58 return _dispatch(ui, args)
59 59 finally:
60 60 ui.flush()
61 61 except:
62 62 # enter the debugger when we hit an exception
63 63 if '--debugger' in args:
64 64 traceback.print_exc()
65 65 pdb.post_mortem(sys.exc_info()[2])
66 66 ui.traceback()
67 67 raise
68 68
69 69 # Global exception handling, alphabetically
70 70 # Mercurial-specific first, followed by built-in and library exceptions
71 71 except error.AmbiguousCommand, inst:
72 72 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
73 73 (inst.args[0], " ".join(inst.args[1])))
74 74 except error.ParseError, inst:
75 75 if len(inst.args) > 1:
76 76 ui.warn(_("hg: parse error at %s: %s\n") %
77 77 (inst.args[1], inst.args[0]))
78 78 else:
79 79 ui.warn(_("hg: parse error: %s\n") % inst.args[0])
80 80 return -1
81 81 except error.LockHeld, inst:
82 82 if inst.errno == errno.ETIMEDOUT:
83 83 reason = _('timed out waiting for lock held by %s') % inst.locker
84 84 else:
85 85 reason = _('lock held by %s') % inst.locker
86 86 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
87 87 except error.LockUnavailable, inst:
88 88 ui.warn(_("abort: could not lock %s: %s\n") %
89 89 (inst.desc or inst.filename, inst.strerror))
90 90 except error.CommandError, inst:
91 91 if inst.args[0]:
92 92 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
93 93 commands.help_(ui, inst.args[0])
94 94 else:
95 95 ui.warn(_("hg: %s\n") % inst.args[1])
96 96 commands.help_(ui, 'shortlist')
97 97 except error.RepoError, inst:
98 98 ui.warn(_("abort: %s!\n") % inst)
99 99 except error.ResponseError, inst:
100 100 ui.warn(_("abort: %s") % inst.args[0])
101 101 if not isinstance(inst.args[1], basestring):
102 102 ui.warn(" %r\n" % (inst.args[1],))
103 103 elif not inst.args[1]:
104 104 ui.warn(_(" empty string\n"))
105 105 else:
106 106 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
107 107 except error.RevlogError, inst:
108 108 ui.warn(_("abort: %s!\n") % inst)
109 109 except error.SignalInterrupt:
110 110 ui.warn(_("killed!\n"))
111 111 except error.UnknownCommand, inst:
112 112 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
113 113 try:
114 114 # check if the command is in a disabled extension
115 115 # (but don't check for extensions themselves)
116 116 commands.help_(ui, inst.args[0], unknowncmd=True)
117 117 except error.UnknownCommand:
118 118 commands.help_(ui, 'shortlist')
119 119 except util.Abort, inst:
120 120 ui.warn(_("abort: %s\n") % inst)
121 121 if inst.hint:
122 122 ui.status(_("(%s)\n") % inst.hint)
123 123 except ImportError, inst:
124 124 ui.warn(_("abort: %s!\n") % inst)
125 125 m = str(inst).split()[-1]
126 126 if m in "mpatch bdiff".split():
127 127 ui.warn(_("(did you forget to compile extensions?)\n"))
128 128 elif m in "zlib".split():
129 129 ui.warn(_("(is your Python install correct?)\n"))
130 130 except IOError, inst:
131 131 if hasattr(inst, "code"):
132 132 ui.warn(_("abort: %s\n") % inst)
133 133 elif hasattr(inst, "reason"):
134 134 try: # usually it is in the form (errno, strerror)
135 135 reason = inst.reason.args[1]
136 136 except: # it might be anything, for example a string
137 137 reason = inst.reason
138 138 ui.warn(_("abort: error: %s\n") % reason)
139 139 elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
140 140 if ui.debugflag:
141 141 ui.warn(_("broken pipe\n"))
142 142 elif getattr(inst, "strerror", None):
143 143 if getattr(inst, "filename", None):
144 144 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
145 145 else:
146 146 ui.warn(_("abort: %s\n") % inst.strerror)
147 147 else:
148 148 raise
149 149 except OSError, inst:
150 150 if getattr(inst, "filename", None):
151 151 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
152 152 else:
153 153 ui.warn(_("abort: %s\n") % inst.strerror)
154 154 except KeyboardInterrupt:
155 155 try:
156 156 ui.warn(_("interrupted!\n"))
157 157 except IOError, inst:
158 158 if inst.errno == errno.EPIPE:
159 159 if ui.debugflag:
160 160 ui.warn(_("\nbroken pipe\n"))
161 161 else:
162 162 raise
163 163 except MemoryError:
164 164 ui.warn(_("abort: out of memory\n"))
165 165 except SystemExit, inst:
166 166 # Commands shouldn't sys.exit directly, but give a return code.
167 167 # Just in case catch this and and pass exit code to caller.
168 168 return inst.code
169 169 except socket.error, inst:
170 170 ui.warn(_("abort: %s\n") % inst.args[-1])
171 171 except:
172 172 ui.warn(_("** unknown exception encountered, details follow\n"))
173 173 ui.warn(_("** report bug details to "
174 174 "http://mercurial.selenic.com/bts/\n"))
175 175 ui.warn(_("** or mercurial@selenic.com\n"))
176 176 ui.warn(_("** Python %s\n") % sys.version.replace('\n', ''))
177 177 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
178 178 % util.version())
179 179 ui.warn(_("** Extensions loaded: %s\n")
180 180 % ", ".join([x[0] for x in extensions.extensions()]))
181 181 raise
182 182
183 183 return -1
184 184
185 185 def aliasargs(fn):
186 186 if hasattr(fn, 'args'):
187 187 return fn.args
188 188 return []
189 189
190 190 class cmdalias(object):
191 191 def __init__(self, name, definition, cmdtable):
192 192 self.name = name
193 193 self.definition = definition
194 194 self.args = []
195 195 self.opts = []
196 196 self.help = ''
197 197 self.norepo = True
198 198 self.badalias = False
199 199
200 200 try:
201 201 cmdutil.findcmd(self.name, cmdtable, True)
202 202 self.shadows = True
203 203 except error.UnknownCommand:
204 204 self.shadows = False
205 205
206 206 if not self.definition:
207 207 def fn(ui, *args):
208 208 ui.warn(_("no definition for alias '%s'\n") % self.name)
209 209 return 1
210 210 self.fn = fn
211 211 self.badalias = True
212 212
213 213 return
214 214
215 215 if self.definition.startswith('!'):
216 216 def fn(ui, *args):
217 217 cmd = '%s %s' % (self.definition[1:], ' '.join(args))
218 218 return util.system(cmd)
219 219 self.fn = fn
220 220 return
221 221
222 222 args = shlex.split(self.definition)
223 223 cmd = args.pop(0)
224 224 args = map(util.expandpath, args)
225 225
226 226 try:
227 227 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
228 228 if len(tableentry) > 2:
229 229 self.fn, self.opts, self.help = tableentry
230 230 else:
231 231 self.fn, self.opts = tableentry
232 232
233 233 self.args = aliasargs(self.fn) + args
234 234 if cmd not in commands.norepo.split(' '):
235 235 self.norepo = False
236 236 if self.help.startswith("hg " + cmd):
237 237 # drop prefix in old-style help lines so hg shows the alias
238 238 self.help = self.help[4 + len(cmd):]
239 239 self.__doc__ = self.fn.__doc__
240 240
241 241 except error.UnknownCommand:
242 242 def fn(ui, *args):
243 243 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
244 244 % (self.name, cmd))
245 245 try:
246 246 # check if the command is in a disabled extension
247 247 commands.help_(ui, cmd, unknowncmd=True)
248 248 except error.UnknownCommand:
249 249 pass
250 250 return 1
251 251 self.fn = fn
252 252 self.badalias = True
253 253 except error.AmbiguousCommand:
254 254 def fn(ui, *args):
255 255 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
256 256 % (self.name, cmd))
257 257 return 1
258 258 self.fn = fn
259 259 self.badalias = True
260 260
261 261 def __call__(self, ui, *args, **opts):
262 262 if self.shadows:
263 263 ui.debug("alias '%s' shadows command\n" % self.name)
264 264
265 return self.fn(ui, *args, **opts)
265 return util.checksignature(self.fn)(ui, *args, **opts)
266 266
267 267 def addaliases(ui, cmdtable):
268 268 # aliases are processed after extensions have been loaded, so they
269 269 # may use extension commands. Aliases can also use other alias definitions,
270 270 # but only if they have been defined prior to the current definition.
271 271 for alias, definition in ui.configitems('alias'):
272 272 aliasdef = cmdalias(alias, definition, cmdtable)
273 273 cmdtable[alias] = (aliasdef, aliasdef.opts, aliasdef.help)
274 274 if aliasdef.norepo:
275 275 commands.norepo += ' %s' % alias
276 276
277 277 def _parse(ui, args):
278 278 options = {}
279 279 cmdoptions = {}
280 280
281 281 try:
282 282 args = fancyopts.fancyopts(args, commands.globalopts, options)
283 283 except fancyopts.getopt.GetoptError, inst:
284 284 raise error.CommandError(None, inst)
285 285
286 286 if args:
287 287 cmd, args = args[0], args[1:]
288 288 aliases, entry = cmdutil.findcmd(cmd, commands.table,
289 289 ui.config("ui", "strict"))
290 290 cmd = aliases[0]
291 291 args = aliasargs(entry[0]) + args
292 292 defaults = ui.config("defaults", cmd)
293 293 if defaults:
294 294 args = map(util.expandpath, shlex.split(defaults)) + args
295 295 c = list(entry[1])
296 296 else:
297 297 cmd = None
298 298 c = []
299 299
300 300 # combine global options into local
301 301 for o in commands.globalopts:
302 302 c.append((o[0], o[1], options[o[1]], o[3]))
303 303
304 304 try:
305 305 args = fancyopts.fancyopts(args, c, cmdoptions, True)
306 306 except fancyopts.getopt.GetoptError, inst:
307 307 raise error.CommandError(cmd, inst)
308 308
309 309 # separate global options back out
310 310 for o in commands.globalopts:
311 311 n = o[1]
312 312 options[n] = cmdoptions[n]
313 313 del cmdoptions[n]
314 314
315 315 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
316 316
317 317 def _parseconfig(ui, config):
318 318 """parse the --config options from the command line"""
319 319 for cfg in config:
320 320 try:
321 321 name, value = cfg.split('=', 1)
322 322 section, name = name.split('.', 1)
323 323 if not section or not name:
324 324 raise IndexError
325 325 ui.setconfig(section, name, value)
326 326 except (IndexError, ValueError):
327 327 raise util.Abort(_('malformed --config option: %r '
328 328 '(use --config section.name=value)') % cfg)
329 329
330 330 def _earlygetopt(aliases, args):
331 331 """Return list of values for an option (or aliases).
332 332
333 333 The values are listed in the order they appear in args.
334 334 The options and values are removed from args.
335 335 """
336 336 try:
337 337 argcount = args.index("--")
338 338 except ValueError:
339 339 argcount = len(args)
340 340 shortopts = [opt for opt in aliases if len(opt) == 2]
341 341 values = []
342 342 pos = 0
343 343 while pos < argcount:
344 344 if args[pos] in aliases:
345 345 if pos + 1 >= argcount:
346 346 # ignore and let getopt report an error if there is no value
347 347 break
348 348 del args[pos]
349 349 values.append(args.pop(pos))
350 350 argcount -= 2
351 351 elif args[pos][:2] in shortopts:
352 352 # short option can have no following space, e.g. hg log -Rfoo
353 353 values.append(args.pop(pos)[2:])
354 354 argcount -= 1
355 355 else:
356 356 pos += 1
357 357 return values
358 358
359 359 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
360 360 # run pre-hook, and abort if it fails
361 361 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs),
362 362 pats=cmdpats, opts=cmdoptions)
363 363 if ret:
364 364 return ret
365 365 ret = _runcommand(ui, options, cmd, d)
366 366 # run post-hook, passing command result
367 367 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
368 368 result=ret, pats=cmdpats, opts=cmdoptions)
369 369 return ret
370 370
371 371 _loaded = set()
372 372 def _dispatch(ui, args):
373 373 # read --config before doing anything else
374 374 # (e.g. to change trust settings for reading .hg/hgrc)
375 375 _parseconfig(ui, _earlygetopt(['--config'], args))
376 376
377 377 # check for cwd
378 378 cwd = _earlygetopt(['--cwd'], args)
379 379 if cwd:
380 380 os.chdir(cwd[-1])
381 381
382 382 # read the local repository .hgrc into a local ui object
383 path = cmdutil.findrepo(os.getcwd()) or ""
383 try:
384 wd = os.getcwd()
385 except OSError, e:
386 raise util.Abort(_("error getting current working directory: %s") %
387 e.strerror)
388 path = cmdutil.findrepo(wd) or ""
384 389 if not path:
385 390 lui = ui
386 391 else:
387 392 try:
388 393 lui = ui.copy()
389 394 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
390 395 except IOError:
391 396 pass
392 397
393 398 # now we can expand paths, even ones in .hg/hgrc
394 399 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
395 400 if rpath:
396 401 path = lui.expandpath(rpath[-1])
397 402 lui = ui.copy()
398 403 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
399 404
400 405 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
401 406 # reposetup. Programs like TortoiseHg will call _dispatch several
402 407 # times so we keep track of configured extensions in _loaded.
403 408 extensions.loadall(lui)
404 409 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
405 410 # Propagate any changes to lui.__class__ by extensions
406 411 ui.__class__ = lui.__class__
407 412
408 413 # (uisetup and extsetup are handled in extensions.loadall)
409 414
410 415 for name, module in exts:
411 416 cmdtable = getattr(module, 'cmdtable', {})
412 417 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
413 418 if overrides:
414 419 ui.warn(_("extension '%s' overrides commands: %s\n")
415 420 % (name, " ".join(overrides)))
416 421 commands.table.update(cmdtable)
417 422 _loaded.add(name)
418 423
419 424 # (reposetup is handled in hg.repository)
420 425
421 426 addaliases(lui, commands.table)
422 427
423 428 # check for fallback encoding
424 429 fallback = lui.config('ui', 'fallbackencoding')
425 430 if fallback:
426 431 encoding.fallbackencoding = fallback
427 432
428 433 fullargs = args
429 434 cmd, func, args, options, cmdoptions = _parse(lui, args)
430 435
431 436 if options["config"]:
432 437 raise util.Abort(_("Option --config may not be abbreviated!"))
433 438 if options["cwd"]:
434 439 raise util.Abort(_("Option --cwd may not be abbreviated!"))
435 440 if options["repository"]:
436 441 raise util.Abort(_(
437 442 "Option -R has to be separated from other options (e.g. not -qR) "
438 443 "and --repository may only be abbreviated as --repo!"))
439 444
440 445 if options["encoding"]:
441 446 encoding.encoding = options["encoding"]
442 447 if options["encodingmode"]:
443 448 encoding.encodingmode = options["encodingmode"]
444 449 if options["time"]:
445 450 def get_times():
446 451 t = os.times()
447 452 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
448 453 t = (t[0], t[1], t[2], t[3], time.clock())
449 454 return t
450 455 s = get_times()
451 456 def print_time():
452 457 t = get_times()
453 458 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
454 459 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
455 460 atexit.register(print_time)
456 461
457 462 if options['verbose'] or options['debug'] or options['quiet']:
458 463 ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
459 464 ui.setconfig('ui', 'debug', str(bool(options['debug'])))
460 465 ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
461 466 if options['traceback']:
462 467 ui.setconfig('ui', 'traceback', 'on')
463 468 if options['noninteractive']:
464 469 ui.setconfig('ui', 'interactive', 'off')
465 470
466 471 if options['help']:
467 472 return commands.help_(ui, cmd, options['version'])
468 473 elif options['version']:
469 474 return commands.version_(ui)
470 475 elif not cmd:
471 476 return commands.help_(ui, 'shortlist')
472 477
473 478 repo = None
474 479 cmdpats = args[:]
475 480 if cmd not in commands.norepo.split():
476 481 try:
477 482 repo = hg.repository(ui, path=path)
478 483 ui = repo.ui
479 484 if not repo.local():
480 485 raise util.Abort(_("repository '%s' is not local") % path)
481 486 ui.setconfig("bundle", "mainreporoot", repo.root)
482 487 except error.RepoError:
483 488 if cmd not in commands.optionalrepo.split():
484 489 if args and not path: # try to infer -R from command args
485 490 repos = map(cmdutil.findrepo, args)
486 491 guess = repos[0]
487 492 if guess and repos.count(guess) == len(repos):
488 493 return _dispatch(ui, ['--repository', guess] + fullargs)
489 494 if not path:
490 495 raise error.RepoError(_("There is no Mercurial repository"
491 496 " here (.hg not found)"))
492 497 raise
493 498 args.insert(0, repo)
494 499 elif rpath:
495 500 ui.warn(_("warning: --repository ignored\n"))
496 501
497 502 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
498 503 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
499 504 cmdpats, cmdoptions)
500 505
501 506 def _runcommand(ui, options, cmd, cmdfunc):
502 507 def checkargs():
503 508 try:
504 509 return cmdfunc()
505 510 except error.SignatureError:
506 511 raise error.CommandError(cmd, _("invalid arguments"))
507 512
508 513 if options['profile']:
509 514 format = ui.config('profiling', 'format', default='text')
510 515
511 516 if not format in ['text', 'kcachegrind']:
512 517 ui.warn(_("unrecognized profiling format '%s'"
513 518 " - Ignored\n") % format)
514 519 format = 'text'
515 520
516 521 output = ui.config('profiling', 'output')
517 522
518 523 if output:
519 524 path = ui.expandpath(output)
520 525 ostream = open(path, 'wb')
521 526 else:
522 527 ostream = sys.stderr
523 528
524 529 try:
525 530 from mercurial import lsprof
526 531 except ImportError:
527 532 raise util.Abort(_(
528 533 'lsprof not available - install from '
529 534 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
530 535 p = lsprof.Profiler()
531 536 p.enable(subcalls=True)
532 537 try:
533 538 return checkargs()
534 539 finally:
535 540 p.disable()
536 541
537 542 if format == 'kcachegrind':
538 543 import lsprofcalltree
539 544 calltree = lsprofcalltree.KCacheGrind(p)
540 545 calltree.output(ostream)
541 546 else:
542 547 # format == 'text'
543 548 stats = lsprof.Stats(p.getstats())
544 549 stats.sort()
545 550 stats.pprint(top=10, file=ostream, climit=5)
546 551
547 552 if output:
548 553 ostream.close()
549 554 else:
550 555 return checkargs()
@@ -1,352 +1,352 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 of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import os, re, time, urlparse
10 10 from mercurial.i18n import _
11 11 from mercurial import ui, hg, util, templater
12 12 from mercurial import error, encoding
13 13 from common import ErrorResponse, get_mtime, staticfile, paritygen, \
14 14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 15 from hgweb_mod import hgweb
16 16 from request import wsgirequest
17 17 import webutil
18 18
19 19 def cleannames(items):
20 20 return [(util.pconvert(name).strip('/'), path) for name, path in items]
21 21
22 22 def findrepos(paths):
23 23 repos = []
24 24 for prefix, root in cleannames(paths):
25 25 roothead, roottail = os.path.split(root)
26 26 # "foo = /bar/*" makes every subrepo of /bar/ to be
27 27 # mounted as foo/subrepo
28 28 # and "foo = /bar/**" also recurses into the subdirectories,
29 29 # remember to use it without working dir.
30 30 try:
31 31 recurse = {'*': False, '**': True}[roottail]
32 32 except KeyError:
33 33 repos.append((prefix, root))
34 34 continue
35 roothead = os.path.normpath(roothead)
35 roothead = os.path.normpath(os.path.abspath(roothead))
36 36 for path in util.walkrepos(roothead, followsym=True, recurse=recurse):
37 37 path = os.path.normpath(path)
38 38 name = util.pconvert(path[len(roothead):]).strip('/')
39 39 if prefix:
40 40 name = prefix + '/' + name
41 41 repos.append((name, path))
42 42 return repos
43 43
44 44 class hgwebdir(object):
45 45 refreshinterval = 20
46 46
47 47 def __init__(self, conf, baseui=None):
48 48 self.conf = conf
49 49 self.baseui = baseui
50 50 self.lastrefresh = 0
51 51 self.motd = None
52 52 self.refresh()
53 53
54 54 def refresh(self):
55 55 if self.lastrefresh + self.refreshinterval > time.time():
56 56 return
57 57
58 58 if self.baseui:
59 59 u = self.baseui.copy()
60 60 else:
61 61 u = ui.ui()
62 62 u.setconfig('ui', 'report_untrusted', 'off')
63 63 u.setconfig('ui', 'interactive', 'off')
64 64
65 65 if not isinstance(self.conf, (dict, list, tuple)):
66 66 map = {'paths': 'hgweb-paths'}
67 67 u.readconfig(self.conf, remap=map, trust=True)
68 68 paths = u.configitems('hgweb-paths')
69 69 elif isinstance(self.conf, (list, tuple)):
70 70 paths = self.conf
71 71 elif isinstance(self.conf, dict):
72 72 paths = self.conf.items()
73 73
74 74 repos = findrepos(paths)
75 75 for prefix, root in u.configitems('collections'):
76 76 prefix = util.pconvert(prefix)
77 77 for path in util.walkrepos(root, followsym=True):
78 78 repo = os.path.normpath(path)
79 79 name = util.pconvert(repo)
80 80 if name.startswith(prefix):
81 81 name = name[len(prefix):]
82 82 repos.append((name.lstrip('/'), repo))
83 83
84 84 self.repos = repos
85 85 self.ui = u
86 86 encoding.encoding = self.ui.config('web', 'encoding',
87 87 encoding.encoding)
88 88 self.style = self.ui.config('web', 'style', 'paper')
89 89 self.templatepath = self.ui.config('web', 'templates', None)
90 90 self.stripecount = self.ui.config('web', 'stripes', 1)
91 91 if self.stripecount:
92 92 self.stripecount = int(self.stripecount)
93 93 self._baseurl = self.ui.config('web', 'baseurl')
94 94 self.lastrefresh = time.time()
95 95
96 96 def run(self):
97 97 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
98 98 raise RuntimeError("This function is only intended to be "
99 99 "called while running as a CGI script.")
100 100 import mercurial.hgweb.wsgicgi as wsgicgi
101 101 wsgicgi.launch(self)
102 102
103 103 def __call__(self, env, respond):
104 104 req = wsgirequest(env, respond)
105 105 return self.run_wsgi(req)
106 106
107 107 def read_allowed(self, ui, req):
108 108 """Check allow_read and deny_read config options of a repo's ui object
109 109 to determine user permissions. By default, with neither option set (or
110 110 both empty), allow all users to read the repo. There are two ways a
111 111 user can be denied read access: (1) deny_read is not empty, and the
112 112 user is unauthenticated or deny_read contains user (or *), and (2)
113 113 allow_read is not empty and the user is not in allow_read. Return True
114 114 if user is allowed to read the repo, else return False."""
115 115
116 116 user = req.env.get('REMOTE_USER')
117 117
118 118 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
119 119 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
120 120 return False
121 121
122 122 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
123 123 # by default, allow reading if no allow_read option has been set
124 124 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
125 125 return True
126 126
127 127 return False
128 128
129 129 def run_wsgi(self, req):
130 130 try:
131 131 try:
132 132 self.refresh()
133 133
134 134 virtual = req.env.get("PATH_INFO", "").strip('/')
135 135 tmpl = self.templater(req)
136 136 ctype = tmpl('mimetype', encoding=encoding.encoding)
137 137 ctype = templater.stringify(ctype)
138 138
139 139 # a static file
140 140 if virtual.startswith('static/') or 'static' in req.form:
141 141 if virtual.startswith('static/'):
142 142 fname = virtual[7:]
143 143 else:
144 144 fname = req.form['static'][0]
145 145 static = templater.templatepath('static')
146 146 return (staticfile(static, fname, req),)
147 147
148 148 # top-level index
149 149 elif not virtual:
150 150 req.respond(HTTP_OK, ctype)
151 151 return self.makeindex(req, tmpl)
152 152
153 153 # nested indexes and hgwebs
154 154
155 155 repos = dict(self.repos)
156 156 while virtual:
157 157 real = repos.get(virtual)
158 158 if real:
159 159 req.env['REPO_NAME'] = virtual
160 160 try:
161 161 repo = hg.repository(self.ui, real)
162 162 return hgweb(repo).run_wsgi(req)
163 163 except IOError, inst:
164 164 msg = inst.strerror
165 165 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
166 166 except error.RepoError, inst:
167 167 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
168 168
169 169 # browse subdirectories
170 170 subdir = virtual + '/'
171 171 if [r for r in repos if r.startswith(subdir)]:
172 172 req.respond(HTTP_OK, ctype)
173 173 return self.makeindex(req, tmpl, subdir)
174 174
175 175 up = virtual.rfind('/')
176 176 if up < 0:
177 177 break
178 178 virtual = virtual[:up]
179 179
180 180 # prefixes not found
181 181 req.respond(HTTP_NOT_FOUND, ctype)
182 182 return tmpl("notfound", repo=virtual)
183 183
184 184 except ErrorResponse, err:
185 185 req.respond(err, ctype)
186 186 return tmpl('error', error=err.message or '')
187 187 finally:
188 188 tmpl = None
189 189
190 190 def makeindex(self, req, tmpl, subdir=""):
191 191
192 192 def archivelist(ui, nodeid, url):
193 193 allowed = ui.configlist("web", "allow_archive", untrusted=True)
194 194 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
195 195 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
196 196 untrusted=True):
197 197 yield {"type" : i[0], "extension": i[1],
198 198 "node": nodeid, "url": url}
199 199
200 200 def rawentries(subdir="", **map):
201 201
202 202 descend = self.ui.configbool('web', 'descend', True)
203 203 for name, path in self.repos:
204 204
205 205 if not name.startswith(subdir):
206 206 continue
207 207 name = name[len(subdir):]
208 208 if not descend and '/' in name:
209 209 continue
210 210
211 211 u = self.ui.copy()
212 212 try:
213 213 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
214 214 except Exception, e:
215 215 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
216 216 continue
217 217 def get(section, name, default=None):
218 218 return u.config(section, name, default, untrusted=True)
219 219
220 220 if u.configbool("web", "hidden", untrusted=True):
221 221 continue
222 222
223 223 if not self.read_allowed(u, req):
224 224 continue
225 225
226 226 parts = [name]
227 227 if 'PATH_INFO' in req.env:
228 228 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
229 229 if req.env['SCRIPT_NAME']:
230 230 parts.insert(0, req.env['SCRIPT_NAME'])
231 231 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
232 232
233 233 # update time with local timezone
234 234 try:
235 235 r = hg.repository(self.ui, path)
236 236 d = (get_mtime(r.spath), util.makedate()[1])
237 237 except OSError:
238 238 continue
239 239
240 240 contact = get_contact(get)
241 241 description = get("web", "description", "")
242 242 name = get("web", "name", name)
243 243 row = dict(contact=contact or "unknown",
244 244 contact_sort=contact.upper() or "unknown",
245 245 name=name,
246 246 name_sort=name,
247 247 url=url,
248 248 description=description or "unknown",
249 249 description_sort=description.upper() or "unknown",
250 250 lastchange=d,
251 251 lastchange_sort=d[1]-d[0],
252 252 archives=archivelist(u, "tip", url))
253 253 yield row
254 254
255 255 sortdefault = None, False
256 256 def entries(sortcolumn="", descending=False, subdir="", **map):
257 257 rows = rawentries(subdir=subdir, **map)
258 258
259 259 if sortcolumn and sortdefault != (sortcolumn, descending):
260 260 sortkey = '%s_sort' % sortcolumn
261 261 rows = sorted(rows, key=lambda x: x[sortkey],
262 262 reverse=descending)
263 263 for row, parity in zip(rows, paritygen(self.stripecount)):
264 264 row['parity'] = parity
265 265 yield row
266 266
267 267 self.refresh()
268 268 sortable = ["name", "description", "contact", "lastchange"]
269 269 sortcolumn, descending = sortdefault
270 270 if 'sort' in req.form:
271 271 sortcolumn = req.form['sort'][0]
272 272 descending = sortcolumn.startswith('-')
273 273 if descending:
274 274 sortcolumn = sortcolumn[1:]
275 275 if sortcolumn not in sortable:
276 276 sortcolumn = ""
277 277
278 278 sort = [("sort_%s" % column,
279 279 "%s%s" % ((not descending and column == sortcolumn)
280 280 and "-" or "", column))
281 281 for column in sortable]
282 282
283 283 self.refresh()
284 284 self.updatereqenv(req.env)
285 285
286 286 return tmpl("index", entries=entries, subdir=subdir,
287 287 sortcolumn=sortcolumn, descending=descending,
288 288 **dict(sort))
289 289
290 290 def templater(self, req):
291 291
292 292 def header(**map):
293 293 yield tmpl('header', encoding=encoding.encoding, **map)
294 294
295 295 def footer(**map):
296 296 yield tmpl("footer", **map)
297 297
298 298 def motd(**map):
299 299 if self.motd is not None:
300 300 yield self.motd
301 301 else:
302 302 yield config('web', 'motd', '')
303 303
304 304 def config(section, name, default=None, untrusted=True):
305 305 return self.ui.config(section, name, default, untrusted)
306 306
307 307 self.updatereqenv(req.env)
308 308
309 309 url = req.env.get('SCRIPT_NAME', '')
310 310 if not url.endswith('/'):
311 311 url += '/'
312 312
313 313 vars = {}
314 314 styles = (
315 315 req.form.get('style', [None])[0],
316 316 config('web', 'style'),
317 317 'paper'
318 318 )
319 319 style, mapfile = templater.stylemap(styles, self.templatepath)
320 320 if style == styles[0]:
321 321 vars['style'] = style
322 322
323 323 start = url[-1] == '?' and '&' or '?'
324 324 sessionvars = webutil.sessionvars(vars, start)
325 325 staticurl = config('web', 'staticurl') or url + 'static/'
326 326 if not staticurl.endswith('/'):
327 327 staticurl += '/'
328 328
329 329 tmpl = templater.templater(mapfile,
330 330 defaults={"header": header,
331 331 "footer": footer,
332 332 "motd": motd,
333 333 "url": url,
334 334 "staticurl": staticurl,
335 335 "sessionvars": sessionvars})
336 336 return tmpl
337 337
338 338 def updatereqenv(self, env):
339 339 def splitnetloc(netloc):
340 340 if ':' in netloc:
341 341 return netloc.split(':', 1)
342 342 else:
343 343 return (netloc, None)
344 344
345 345 if self._baseurl is not None:
346 346 urlcomp = urlparse.urlparse(self._baseurl)
347 347 host, port = splitnetloc(urlcomp[1])
348 348 path = urlcomp[2]
349 349 env['SERVER_NAME'] = host
350 350 if port:
351 351 env['SERVER_PORT'] = port
352 352 env['SCRIPT_NAME'] = path
@@ -1,70 +1,75 b''
1 1 #!/bin/sh
2 2
3 3 cat >> $HGRCPATH <<EOF
4 4 [alias]
5 5 myinit = init
6 6 cleanstatus = status -c
7 7 unknown = bargle
8 8 ambiguous = s
9 9 recursive = recursive
10 10 nodefinition =
11 11 mylog = log
12 12 lognull = log -r null
13 13 shortlog = log --template '{rev} {node|short} | {date|isodate}\n'
14 14 dln = lognull --debug
15 15 nousage = rollback
16 16 put = export -r 0 -o "\$FOO/%R.diff"
17 17 echo = !echo
18 rt = root
18 19
19 20 [defaults]
20 21 mylog = -q
21 22 lognull = -q
22 23 log = -v
23 24 EOF
24 25
25 26 echo '% basic'
26 27 hg myinit alias
27 28
28 29 echo '% unknown'
29 30 hg unknown
30 31 hg help unknown
31 32
32 33 echo '% ambiguous'
33 34 hg ambiguous
34 35 hg help ambiguous
35 36
36 37 echo '% recursive'
37 38 hg recursive
38 39 hg help recursive
39 40
40 41 echo '% no definition'
41 42 hg nodef
42 43 hg help nodef
43 44
44 45 cd alias
45 46
46 47 echo '% no usage'
47 48 hg nousage
48 49
49 50 echo foo > foo
50 51 hg ci -Amfoo
51 52
52 53 echo '% with opts'
53 54 hg cleanst
54 55
55 56 echo '% with opts and whitespace'
56 57 hg shortlog
57 58
58 59 echo '% interaction with defaults'
59 60 hg mylog
60 61 hg lognull
61 62
62 63 echo '% properly recursive'
63 64 hg dln
64 65
65 66 echo '% path expanding'
66 67 FOO=`pwd` hg put
67 68 cat 0.diff
68 69
69 70 echo '% shell aliases'
70 71 hg echo foo
72 echo '% invalid arguments'
73 hg rt foo
74
75 exit 0
@@ -1,47 +1,60 b''
1 1 % basic
2 2 % unknown
3 3 alias 'unknown' resolves to unknown command 'bargle'
4 4 alias 'unknown' resolves to unknown command 'bargle'
5 5 % ambiguous
6 6 alias 'ambiguous' resolves to ambiguous command 's'
7 7 alias 'ambiguous' resolves to ambiguous command 's'
8 8 % recursive
9 9 alias 'recursive' resolves to unknown command 'recursive'
10 10 alias 'recursive' resolves to unknown command 'recursive'
11 11 % no definition
12 12 no definition for alias 'nodefinition'
13 13 no definition for alias 'nodefinition'
14 14 % no usage
15 15 no rollback information available
16 16 adding foo
17 17 % with opts
18 18 C foo
19 19 % with opts and whitespace
20 20 0 e63c23eaa88a | 1970-01-01 00:00 +0000
21 21 % interaction with defaults
22 22 0:e63c23eaa88a
23 23 -1:000000000000
24 24 % properly recursive
25 25 changeset: -1:0000000000000000000000000000000000000000
26 26 parent: -1:0000000000000000000000000000000000000000
27 27 parent: -1:0000000000000000000000000000000000000000
28 28 manifest: -1:0000000000000000000000000000000000000000
29 29 user:
30 30 date: Thu Jan 01 00:00:00 1970 +0000
31 31 extra: branch=default
32 32
33 33 % path expanding
34 34 # HG changeset patch
35 35 # User test
36 36 # Date 0 0
37 37 # Node ID e63c23eaa88ae77967edcf4ea194d31167c478b0
38 38 # Parent 0000000000000000000000000000000000000000
39 39 foo
40 40
41 41 diff -r 000000000000 -r e63c23eaa88a foo
42 42 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
43 43 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
44 44 @@ -0,0 +1,1 @@
45 45 +foo
46 46 % shell aliases
47 47 foo
48 % invalid arguments
49 hg rt: invalid arguments
50 hg rt
51
52 alias for: hg root
53
54 print the root (top) of the current working directory
55
56 Print the root directory of the current repository.
57
58 Returns 0 on success.
59
60 use "hg -v help rt" to show global options
@@ -1,27 +1,33 b''
1 1 #!/bin/sh
2 2 # test command parsing and dispatch
3 3
4 4 "$TESTDIR/hghave" no-outer-repo || exit 80
5 5
6 dir=`pwd`
7
6 8 hg init a
7 9 cd a
8 10 echo a > a
9 11 hg ci -Ama
10 12
11 13 echo "# missing arg"
12 14 hg cat
13 15
14 16 echo '% [defaults]'
15 17 hg cat a
16 18 cat >> $HGRCPATH <<EOF
17 19 [defaults]
18 20 cat = -r null
19 21 EOF
20 22 hg cat a
21 23
24 echo '% working directory removed'
25 rm -rf $dir/a
26 hg --version
27
22 28 echo '% no repo'
23 cd ..
29 cd $dir
24 30 hg cat
25 31
26 32 exit 0
27 33
@@ -1,37 +1,39 b''
1 1 adding a
2 2 # missing arg
3 3 hg cat: invalid arguments
4 4 hg cat [OPTION]... FILE...
5 5
6 6 output the current or given revision of files
7 7
8 8 Print the specified files as they were at the given revision. If no
9 9 revision is given, the parent of the working directory is used, or tip if
10 10 no revision is checked out.
11 11
12 12 Output may be to a file, in which case the name of the file is given using
13 13 a format string. The formatting rules are the same as for the export
14 14 command, with the following additions:
15 15
16 16 "%s" basename of file being printed
17 17 "%d" dirname of file being printed, or '.' if in repository root
18 18 "%p" root-relative path name of file being printed
19 19
20 20 Returns 0 on success.
21 21
22 22 options:
23 23
24 24 -o --output FORMAT print output to file with formatted name
25 25 -r --rev REV print the given revision
26 26 --decode apply any matching decode filter
27 27 -I --include PATTERN [+] include names matching the given patterns
28 28 -X --exclude PATTERN [+] exclude names matching the given patterns
29 29
30 30 [+] marked option can be specified multiple times
31 31
32 32 use "hg -v help cat" to show global options
33 33 % [defaults]
34 34 a
35 35 a: No such file in rev 000000000000
36 % working directory removed
37 abort: error getting current working directory: No such file or directory
36 38 % no repo
37 39 abort: There is no Mercurial repository here (.hg not found)!
@@ -1,123 +1,127 b''
1 1 #!/bin/sh
2 2
3 3 ########################################
4 4
5 5 HGENCODING=utf-8
6 6 export HGENCODING
7 7
8 8 hg init t
9 9 cd t
10 10
11 11 python << EOF
12 12 # (byte, width) = (6, 4)
13 13 s = "\xe7\x9f\xad\xe5\x90\x8d"
14 14 # (byte, width) = (7, 7): odd width is good for alignment test
15 15 m = "MIDDLE_"
16 16 # (byte, width) = (18, 12)
17 17 l = "\xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d"
18 18
19 19 f = file('s', 'w'); f.write(s); f.close()
20 20 f = file('m', 'w'); f.write(m); f.close()
21 21 f = file('l', 'w'); f.write(l); f.close()
22 22
23 23 # instant extension to show list of options
24 24 f = file('showoptlist.py', 'w'); f.write("""# encoding: utf-8
25 25 def showoptlist(ui, repo, *pats, **opts):
26 26 '''dummy command to show option descriptions'''
27 27 return 0
28 28
29 29 cmdtable = {
30 30 'showoptlist':
31 31 (showoptlist,
32 32 [('s', 'opt1', '', 'short width', '""" + s + """'),
33 33 ('m', 'opt2', '', 'middle width', '""" + m + """'),
34 34 ('l', 'opt3', '', 'long width', '""" + l + """')
35 35 ],
36 36 ""
37 37 )
38 38 }
39 39 """)
40 40 f.close()
41 41 EOF
42 42
43 43 S=`cat s`
44 44 M=`cat m`
45 45 L=`cat l`
46 46
47 47 ########################################
48 48 #### alignment of:
49 49 #### - option descriptions in help
50 50
51 51 cat <<EOF > .hg/hgrc
52 52 [extensions]
53 53 ja_ext = `pwd`/showoptlist.py
54 54 EOF
55 55 echo '% check alignment of option descriptions in help'
56 56 hg help showoptlist
57 57
58 58 ########################################
59 59 #### alignment of:
60 60 #### - user names in annotate
61 61 #### - file names in diffstat
62 62
63 rm -f s; touch s
64 rm -f m; touch m
65 rm -f l; touch l
66
63 67 #### add files
64 68
65 touch $S
69 cp s $S
66 70 hg add $S
67 touch $M
71 cp m $M
68 72 hg add $M
69 touch $L
73 cp l $L
70 74 hg add $L
71 75
72 76 #### commit(1)
73 77
74 echo 'first line(1)' >> $S
75 echo 'first line(2)' >> $M
76 echo 'first line(3)' >> $L
78 echo 'first line(1)' >> s; cp s $S
79 echo 'first line(2)' >> m; cp m $M
80 echo 'first line(3)' >> l; cp l $L
77 81 hg commit -m 'first commit' -u $S -d "1000000 0"
78 82
79 83 #### commit(2)
80 84
81 echo 'second line(1)' >> $S
82 echo 'second line(2)' >> $M
83 echo 'second line(3)' >> $L
85 echo 'second line(1)' >> s; cp s $S
86 echo 'second line(2)' >> m; cp m $M
87 echo 'second line(3)' >> l; cp l $L
84 88 hg commit -m 'second commit' -u $M -d "1000000 0"
85 89
86 90 #### commit(3)
87 91
88 echo 'third line(1)' >> $S
89 echo 'third line(2)' >> $M
90 echo 'third line(3)' >> $L
92 echo 'third line(1)' >> s; cp s $S
93 echo 'third line(2)' >> m; cp m $M
94 echo 'third line(3)' >> l; cp l $L
91 95 hg commit -m 'third commit' -u $L -d "1000000 0"
92 96
93 97 #### check
94 98
95 99 echo '% check alignment of user names in annotate'
96 100 hg annotate -u $M
97 101 echo '% check alignment of filenames in diffstat'
98 102 hg diff -c tip --stat
99 103
100 104 ########################################
101 105 #### alignment of:
102 106 #### - branch names in list
103 107 #### - tag names in list
104 108
105 109 #### add branches/tags
106 110
107 111 hg branch $S
108 112 hg tag -d "1000000 0" $S
109 113 hg branch $M
110 114 hg tag -d "1000000 0" $M
111 115 hg branch $L
112 116 hg tag -d "1000000 0" $L
113 117
114 118 #### check
115 119
116 120 echo '% check alignment of branches'
117 121 hg tags
118 122 echo '% check alignment of tags'
119 123 hg tags
120 124
121 125 ########################################
122 126
123 127 exit 0
@@ -1,161 +1,163 b''
1 1 #!/bin/sh
2 2 # Tests some basic hgwebdir functionality. Tests setting up paths and
3 3 # collection, different forms of 404s and the subdirectory support.
4 4
5 5 mkdir webdir
6 6 cd webdir
7 7
8 8 hg init a
9 9 echo a > a/a
10 10 hg --cwd a ci -Ama -d'1 0'
11 11 # create a mercurial queue repository
12 12 hg --cwd a qinit --config extensions.hgext.mq= -c
13 13
14 14 hg init b
15 15 echo b > b/b
16 16 hg --cwd b ci -Amb -d'2 0'
17 17
18 18 # create a nested repository
19 19 cd b
20 20 hg init d
21 21 echo d > d/d
22 22 hg --cwd d ci -Amd -d'3 0'
23 23 cd ..
24 24
25 25 hg init c
26 26 echo c > c/c
27 27 hg --cwd c ci -Amc -d'3 0'
28 28
29 29 root=`pwd`
30 30 cd ..
31 31
32 32
33 33 cat > paths.conf <<EOF
34 34 [paths]
35 35 a=$root/a
36 36 b=$root/b
37 37 EOF
38 38
39 39 hg serve -p $HGPORT -d --pid-file=hg.pid --webdir-conf paths.conf \
40 40 -A access-paths.log -E error-paths-1.log
41 41 cat hg.pid >> $DAEMON_PIDS
42 42
43 43 echo % should give a 404 - file does not exist
44 44 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/file/tip/bork?style=raw'
45 45
46 46 echo % should succeed
47 47 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/?style=raw'
48 48 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/file/tip/a?style=raw'
49 49 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/b/file/tip/b?style=raw'
50 50
51 51 echo % should give a 404 - repo is not published
52 52 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/c/file/tip/c?style=raw'
53 53
54 54 echo % atom-log without basedir
55 55 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/atom-log' \
56 56 | grep '<link' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
57 57
58 58 echo % rss-log without basedir
59 59 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/a/rss-log' \
60 60 | grep '<guid' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
61 61
62 62 cat > paths.conf <<EOF
63 63 [paths]
64 64 t/a/=$root/a
65 65 b=$root/b
66 66 coll=$root/*
67 67 rcoll=$root/**
68 star=*
69 starstar=**
68 70 EOF
69 71
70 72 hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
71 73 -A access-paths.log -E error-paths-2.log
72 74 cat hg.pid >> $DAEMON_PIDS
73 75
74 76 echo % should succeed, slashy names
75 77 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=raw'
76 78 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=paper' \
77 79 | sed "s/[0-9]\{1,\} seconds\{0,1\} ago/seconds ago/"
78 80 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t?style=raw'
79 81 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=raw'
80 82 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=paper' \
81 83 | sed "s/[0-9]\{1,\} seconds\{0,1\} ago/seconds ago/"
82 84 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a?style=atom' \
83 85 | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
84 86 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a/?style=atom' \
85 87 | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
86 88 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/a/file/tip/a?style=raw'
87 89 # Test [paths] '*' extension
88 90 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/coll/?style=raw'
89 91 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/coll/a/file/tip/a?style=raw'
90 92 #test [paths] '**' extension
91 93 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/rcoll/?style=raw'
92 94 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/rcoll/b/d/file/tip/d?style=raw'
93 95
94 96
95 97 "$TESTDIR/killdaemons.py"
96 98 cat > paths.conf <<EOF
97 99 [paths]
98 100 t/a = $root/a
99 101 t/b = $root/b
100 102 c = $root/c
101 103 [web]
102 104 descend=false
103 105 EOF
104 106
105 107 hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
106 108 -A access-paths.log -E error-paths-3.log
107 109 cat hg.pid >> $DAEMON_PIDS
108 110 echo % test descend = False
109 111 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=raw'
110 112 "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=raw'
111 113
112 114
113 115 cat > collections.conf <<EOF
114 116 [collections]
115 117 $root=$root
116 118 EOF
117 119
118 120 hg serve --config web.baseurl=http://hg.example.com:8080/ -p $HGPORT2 -d \
119 121 --pid-file=hg.pid --webdir-conf collections.conf \
120 122 -A access-collections.log -E error-collections.log
121 123 cat hg.pid >> $DAEMON_PIDS
122 124
123 125 echo % collections: should succeed
124 126 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/?style=raw'
125 127 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/file/tip/a?style=raw'
126 128 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/b/file/tip/b?style=raw'
127 129 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/c/file/tip/c?style=raw'
128 130
129 131 echo % atom-log with basedir /
130 132 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/atom-log' \
131 133 | grep '<link' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
132 134
133 135 echo % rss-log with basedir /
134 136 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/rss-log' \
135 137 | grep '<guid' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
136 138
137 139 "$TESTDIR/killdaemons.py"
138 140
139 141 hg serve --config web.baseurl=http://hg.example.com:8080/foo/ -p $HGPORT2 -d \
140 142 --pid-file=hg.pid --webdir-conf collections.conf \
141 143 -A access-collections-2.log -E error-collections-2.log
142 144 cat hg.pid >> $DAEMON_PIDS
143 145
144 146 echo % atom-log with basedir /foo/
145 147 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/atom-log' \
146 148 | grep '<link' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
147 149
148 150 echo % rss-log with basedir /foo/
149 151 "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '/a/rss-log' \
150 152 | grep '<guid' | sed 's|//[.a-zA-Z0-9_-]*:[0-9][0-9]*/|//example.com:8080/|'
151 153
152 154 echo % paths errors 1
153 155 cat error-paths-1.log
154 156 echo % paths errors 2
155 157 cat error-paths-2.log
156 158 echo % paths errors 3
157 159 cat error-paths-3.log
158 160 echo % collections errors
159 161 cat error-collections.log
160 162 echo % collections errors 2
161 163 cat error-collections-2.log
@@ -1,362 +1,443 b''
1 1 adding a
2 2 adding b
3 3 adding d
4 4 adding c
5 5 % should give a 404 - file does not exist
6 6 404 Not Found
7 7
8 8
9 9 error: bork@8580ff50825a: not found in manifest
10 10 % should succeed
11 11 200 Script output follows
12 12
13 13
14 14 /a/
15 15 /b/
16 16
17 17 200 Script output follows
18 18
19 19 a
20 20 200 Script output follows
21 21
22 22 b
23 23 % should give a 404 - repo is not published
24 24 404 Not Found
25 25
26 26
27 27 error: repository c not found
28 28 % atom-log without basedir
29 29 <link rel="self" href="http://example.com:8080/a/atom-log"/>
30 30 <link rel="alternate" href="http://example.com:8080/a/"/>
31 31 <link href="http://example.com:8080/a/rev/8580ff50825a"/>
32 32 % rss-log without basedir
33 33 <guid isPermaLink="true">http://example.com:8080/a/rev/8580ff50825a</guid>
34 34 % should succeed, slashy names
35 35 200 Script output follows
36 36
37 37
38 38 /t/a/
39 39 /b/
40 40 /coll/a/
41 41 /coll/a/.hg/patches/
42 42 /coll/b/
43 43 /coll/c/
44 44 /rcoll/a/
45 45 /rcoll/a/.hg/patches/
46 46 /rcoll/b/
47 47 /rcoll/b/d/
48 48 /rcoll/c/
49 /star/webdir/a/
50 /star/webdir/a/.hg/patches/
51 /star/webdir/b/
52 /star/webdir/c/
53 /starstar/webdir/a/
54 /starstar/webdir/a/.hg/patches/
55 /starstar/webdir/b/
56 /starstar/webdir/b/d/
57 /starstar/webdir/c/
49 58
50 59 200 Script output follows
51 60
52 61 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
53 62 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
54 63 <head>
55 64 <link rel="icon" href="/static/hgicon.png" type="image/png" />
56 65 <meta name="robots" content="index, nofollow" />
57 66 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
58 67
59 68 <title>Mercurial repositories index</title>
60 69 </head>
61 70 <body>
62 71
63 72 <div class="container">
64 73 <div class="menu">
65 74 <a href="http://mercurial.selenic.com/">
66 75 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
67 76 </div>
68 77 <div class="main">
69 78 <h2>Mercurial Repositories</h2>
70 79
71 80 <table class="bigtable">
72 81 <tr>
73 82 <th><a href="?sort=name">Name</a></th>
74 83 <th><a href="?sort=description">Description</a></th>
75 84 <th><a href="?sort=contact">Contact</a></th>
76 85 <th><a href="?sort=lastchange">Last modified</a></th>
77 86 <th>&nbsp;</th>
78 87 </tr>
79 88
80 89 <tr class="parity0">
81 90 <td><a href="/t/a/?style=paper">t/a</a></td>
82 91 <td>unknown</td>
83 92 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
84 93 <td class="age">seconds ago</td>
85 94 <td class="indexlinks"></td>
86 95 </tr>
87 96
88 97 <tr class="parity1">
89 98 <td><a href="/b/?style=paper">b</a></td>
90 99 <td>unknown</td>
91 100 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
92 101 <td class="age">seconds ago</td>
93 102 <td class="indexlinks"></td>
94 103 </tr>
95 104
96 105 <tr class="parity0">
97 106 <td><a href="/coll/a/?style=paper">coll/a</a></td>
98 107 <td>unknown</td>
99 108 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
100 109 <td class="age">seconds ago</td>
101 110 <td class="indexlinks"></td>
102 111 </tr>
103 112
104 113 <tr class="parity1">
105 114 <td><a href="/coll/a/.hg/patches/?style=paper">coll/a/.hg/patches</a></td>
106 115 <td>unknown</td>
107 116 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
108 117 <td class="age">seconds ago</td>
109 118 <td class="indexlinks"></td>
110 119 </tr>
111 120
112 121 <tr class="parity0">
113 122 <td><a href="/coll/b/?style=paper">coll/b</a></td>
114 123 <td>unknown</td>
115 124 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
116 125 <td class="age">seconds ago</td>
117 126 <td class="indexlinks"></td>
118 127 </tr>
119 128
120 129 <tr class="parity1">
121 130 <td><a href="/coll/c/?style=paper">coll/c</a></td>
122 131 <td>unknown</td>
123 132 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
124 133 <td class="age">seconds ago</td>
125 134 <td class="indexlinks"></td>
126 135 </tr>
127 136
128 137 <tr class="parity0">
129 138 <td><a href="/rcoll/a/?style=paper">rcoll/a</a></td>
130 139 <td>unknown</td>
131 140 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
132 141 <td class="age">seconds ago</td>
133 142 <td class="indexlinks"></td>
134 143 </tr>
135 144
136 145 <tr class="parity1">
137 146 <td><a href="/rcoll/a/.hg/patches/?style=paper">rcoll/a/.hg/patches</a></td>
138 147 <td>unknown</td>
139 148 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
140 149 <td class="age">seconds ago</td>
141 150 <td class="indexlinks"></td>
142 151 </tr>
143 152
144 153 <tr class="parity0">
145 154 <td><a href="/rcoll/b/?style=paper">rcoll/b</a></td>
146 155 <td>unknown</td>
147 156 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
148 157 <td class="age">seconds ago</td>
149 158 <td class="indexlinks"></td>
150 159 </tr>
151 160
152 161 <tr class="parity1">
153 162 <td><a href="/rcoll/b/d/?style=paper">rcoll/b/d</a></td>
154 163 <td>unknown</td>
155 164 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
156 165 <td class="age">seconds ago</td>
157 166 <td class="indexlinks"></td>
158 167 </tr>
159 168
160 169 <tr class="parity0">
161 170 <td><a href="/rcoll/c/?style=paper">rcoll/c</a></td>
162 171 <td>unknown</td>
163 172 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
164 173 <td class="age">seconds ago</td>
165 174 <td class="indexlinks"></td>
166 175 </tr>
167 176
177 <tr class="parity1">
178 <td><a href="/star/webdir/a/?style=paper">star/webdir/a</a></td>
179 <td>unknown</td>
180 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
181 <td class="age">seconds ago</td>
182 <td class="indexlinks"></td>
183 </tr>
184
185 <tr class="parity0">
186 <td><a href="/star/webdir/a/.hg/patches/?style=paper">star/webdir/a/.hg/patches</a></td>
187 <td>unknown</td>
188 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
189 <td class="age">seconds ago</td>
190 <td class="indexlinks"></td>
191 </tr>
192
193 <tr class="parity1">
194 <td><a href="/star/webdir/b/?style=paper">star/webdir/b</a></td>
195 <td>unknown</td>
196 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
197 <td class="age">seconds ago</td>
198 <td class="indexlinks"></td>
199 </tr>
200
201 <tr class="parity0">
202 <td><a href="/star/webdir/c/?style=paper">star/webdir/c</a></td>
203 <td>unknown</td>
204 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
205 <td class="age">seconds ago</td>
206 <td class="indexlinks"></td>
207 </tr>
208
209 <tr class="parity1">
210 <td><a href="/starstar/webdir/a/?style=paper">starstar/webdir/a</a></td>
211 <td>unknown</td>
212 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
213 <td class="age">seconds ago</td>
214 <td class="indexlinks"></td>
215 </tr>
216
217 <tr class="parity0">
218 <td><a href="/starstar/webdir/a/.hg/patches/?style=paper">starstar/webdir/a/.hg/patches</a></td>
219 <td>unknown</td>
220 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
221 <td class="age">seconds ago</td>
222 <td class="indexlinks"></td>
223 </tr>
224
225 <tr class="parity1">
226 <td><a href="/starstar/webdir/b/?style=paper">starstar/webdir/b</a></td>
227 <td>unknown</td>
228 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
229 <td class="age">seconds ago</td>
230 <td class="indexlinks"></td>
231 </tr>
232
233 <tr class="parity0">
234 <td><a href="/starstar/webdir/b/d/?style=paper">starstar/webdir/b/d</a></td>
235 <td>unknown</td>
236 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
237 <td class="age">seconds ago</td>
238 <td class="indexlinks"></td>
239 </tr>
240
241 <tr class="parity1">
242 <td><a href="/starstar/webdir/c/?style=paper">starstar/webdir/c</a></td>
243 <td>unknown</td>
244 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
245 <td class="age">seconds ago</td>
246 <td class="indexlinks"></td>
247 </tr>
248
168 249 </table>
169 250 </div>
170 251 </div>
171 252
172 253
173 254 </body>
174 255 </html>
175 256
176 257 200 Script output follows
177 258
178 259
179 260 /t/a/
180 261
181 262 200 Script output follows
182 263
183 264
184 265 /t/a/
185 266
186 267 200 Script output follows
187 268
188 269 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
189 270 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
190 271 <head>
191 272 <link rel="icon" href="/static/hgicon.png" type="image/png" />
192 273 <meta name="robots" content="index, nofollow" />
193 274 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
194 275
195 276 <title>Mercurial repositories index</title>
196 277 </head>
197 278 <body>
198 279
199 280 <div class="container">
200 281 <div class="menu">
201 282 <a href="http://mercurial.selenic.com/">
202 283 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
203 284 </div>
204 285 <div class="main">
205 286 <h2>Mercurial Repositories</h2>
206 287
207 288 <table class="bigtable">
208 289 <tr>
209 290 <th><a href="?sort=name">Name</a></th>
210 291 <th><a href="?sort=description">Description</a></th>
211 292 <th><a href="?sort=contact">Contact</a></th>
212 293 <th><a href="?sort=lastchange">Last modified</a></th>
213 294 <th>&nbsp;</th>
214 295 </tr>
215 296
216 297 <tr class="parity0">
217 298 <td><a href="/t/a/?style=paper">a</a></td>
218 299 <td>unknown</td>
219 300 <td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td>
220 301 <td class="age">seconds ago</td>
221 302 <td class="indexlinks"></td>
222 303 </tr>
223 304
224 305 </table>
225 306 </div>
226 307 </div>
227 308
228 309
229 310 </body>
230 311 </html>
231 312
232 313 200 Script output follows
233 314
234 315 <?xml version="1.0" encoding="ascii"?>
235 316 <feed xmlns="http://127.0.0.1/2005/Atom">
236 317 <!-- Changelog -->
237 318 <id>http://127.0.0.1/t/a/</id>
238 319 <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
239 320 <link rel="alternate" href="http://127.0.0.1/t/a/"/>
240 321 <title>t/a Changelog</title>
241 322 <updated>1970-01-01T00:00:01+00:00</updated>
242 323
243 324 <entry>
244 325 <title>a</title>
245 326 <id>http://127.0.0.1/t/a/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
246 327 <link href="http://127.0.0.1/t/a/rev/8580ff50825a"/>
247 328 <author>
248 329 <name>test</name>
249 330 <email>&#116;&#101;&#115;&#116;</email>
250 331 </author>
251 332 <updated>1970-01-01T00:00:01+00:00</updated>
252 333 <published>1970-01-01T00:00:01+00:00</published>
253 334 <content type="xhtml">
254 335 <div xmlns="http://127.0.0.1/1999/xhtml">
255 336 <pre xml:space="preserve">a</pre>
256 337 </div>
257 338 </content>
258 339 </entry>
259 340
260 341 </feed>
261 342 200 Script output follows
262 343
263 344 <?xml version="1.0" encoding="ascii"?>
264 345 <feed xmlns="http://127.0.0.1/2005/Atom">
265 346 <!-- Changelog -->
266 347 <id>http://127.0.0.1/t/a/</id>
267 348 <link rel="self" href="http://127.0.0.1/t/a/atom-log"/>
268 349 <link rel="alternate" href="http://127.0.0.1/t/a/"/>
269 350 <title>t/a Changelog</title>
270 351 <updated>1970-01-01T00:00:01+00:00</updated>
271 352
272 353 <entry>
273 354 <title>a</title>
274 355 <id>http://127.0.0.1/t/a/#changeset-8580ff50825a50c8f716709acdf8de0deddcd6ab</id>
275 356 <link href="http://127.0.0.1/t/a/rev/8580ff50825a"/>
276 357 <author>
277 358 <name>test</name>
278 359 <email>&#116;&#101;&#115;&#116;</email>
279 360 </author>
280 361 <updated>1970-01-01T00:00:01+00:00</updated>
281 362 <published>1970-01-01T00:00:01+00:00</published>
282 363 <content type="xhtml">
283 364 <div xmlns="http://127.0.0.1/1999/xhtml">
284 365 <pre xml:space="preserve">a</pre>
285 366 </div>
286 367 </content>
287 368 </entry>
288 369
289 370 </feed>
290 371 200 Script output follows
291 372
292 373 a
293 374 200 Script output follows
294 375
295 376
296 377 /coll/a/
297 378 /coll/a/.hg/patches/
298 379 /coll/b/
299 380 /coll/c/
300 381
301 382 200 Script output follows
302 383
303 384 a
304 385 200 Script output follows
305 386
306 387
307 388 /rcoll/a/
308 389 /rcoll/a/.hg/patches/
309 390 /rcoll/b/
310 391 /rcoll/b/d/
311 392 /rcoll/c/
312 393
313 394 200 Script output follows
314 395
315 396 d
316 397 % test descend = False
317 398 200 Script output follows
318 399
319 400
320 401 /c/
321 402
322 403 200 Script output follows
323 404
324 405
325 406 /t/a/
326 407 /t/b/
327 408
328 409 % collections: should succeed
329 410 200 Script output follows
330 411
331 412
332 413 /a/
333 414 /a/.hg/patches/
334 415 /b/
335 416 /c/
336 417
337 418 200 Script output follows
338 419
339 420 a
340 421 200 Script output follows
341 422
342 423 b
343 424 200 Script output follows
344 425
345 426 c
346 427 % atom-log with basedir /
347 428 <link rel="self" href="http://example.com:8080/a/atom-log"/>
348 429 <link rel="alternate" href="http://example.com:8080/a/"/>
349 430 <link href="http://example.com:8080/a/rev/8580ff50825a"/>
350 431 % rss-log with basedir /
351 432 <guid isPermaLink="true">http://example.com:8080/a/rev/8580ff50825a</guid>
352 433 % atom-log with basedir /foo/
353 434 <link rel="self" href="http://example.com:8080/foo/a/atom-log"/>
354 435 <link rel="alternate" href="http://example.com:8080/foo/a/"/>
355 436 <link href="http://example.com:8080/foo/a/rev/8580ff50825a"/>
356 437 % rss-log with basedir /foo/
357 438 <guid isPermaLink="true">http://example.com:8080/foo/a/rev/8580ff50825a</guid>
358 439 % paths errors 1
359 440 % paths errors 2
360 441 % paths errors 3
361 442 % collections errors
362 443 % collections errors 2
@@ -1,409 +1,418 b''
1 1 #!/bin/sh
2 2
3 3 cat <<EOF >> $HGRCPATH
4 4 [extensions]
5 5 keyword =
6 6 mq =
7 7 notify =
8 8 record =
9 9 transplant =
10 10 [ui]
11 11 interactive = true
12 12 EOF
13 13
14 14 # demo before [keyword] files are set up
15 15 # would succeed without uisetup otherwise
16 16 echo % hg kwdemo
17 17 hg --quiet kwdemo \
18 18 | sed -e 's![^ ][^ ]*demo.txt,v!/TMP/demo.txt,v!' \
19 19 -e 's/,v [a-z0-9][a-z0-9]* /,v xxxxxxxxxxxx /' \
20 20 -e '/[$]Revision/ s/: [a-z0-9][a-z0-9]* /: xxxxxxxxxxxx /' \
21 21 -e 's! 20[0-9][0-9]/[01][0-9]/[0-3][0-9] [0-2][0-9]:[0-6][0-9]:[0-6][0-9]! 2000/00/00 00:00:00!'
22 22
23 23 hg --quiet kwdemo "Branch = {branches}"
24 24
25 25 cat <<EOF >> $HGRCPATH
26 26 [keyword]
27 27 ** =
28 28 b = ignore
29 29 [hooks]
30 30 commit=
31 31 commit.test=cp a hooktest
32 32 EOF
33 33
34 34 hg init Test-bndl
35 35 cd Test-bndl
36 36
37 37 echo % kwshrink should exit silently in empty/invalid repo
38 38 hg kwshrink
39 39
40 40 # Symlinks cannot be created on Windows. The bundle was made with:
41 41 #
42 42 # hg init t
43 43 # cd t
44 44 # echo a > a
45 45 # ln -s a sym
46 46 # hg add sym
47 47 # hg ci -m addsym -u mercurial
48 48 # hg bundle --base null ../test-keyword.hg
49 49 #
50 50 hg pull -u "$TESTDIR/test-keyword.hg" \
51 51 | sed 's/pulling from.*test-keyword.hg/pulling from test-keyword.hg/'
52 52
53 53 echo 'expand $Id$' > a
54 54 echo 'do not process $Id:' >> a
55 55 echo 'xxx $' >> a
56 56 echo 'ignore $Id$' > b
57 57 echo % cat
58 58 cat a b
59 59
60 60 echo % no kwfiles
61 61 hg kwfiles
62 62 echo % untracked candidates
63 63 hg -v kwfiles --unknown
64 64
65 65 echo % addremove
66 66 hg addremove
67 67 echo % status
68 68 hg status
69 69
70 70 echo % default keyword expansion including commit hook
71 71 echo % interrupted commit should not change state or run commit hook
72 72 hg --debug commit
73 73 echo % status
74 74 hg status
75 75
76 76 echo % commit
77 77 hg --debug commit -mabsym -u 'User Name <user@example.com>'
78 78 echo % status
79 79 hg status
80 80 echo % identify
81 81 hg debugrebuildstate
82 82 hg --quiet identify
83 83 echo % cat
84 84 cat a b
85 85 echo % hg cat
86 86 hg cat sym a b
87 87
88 88 echo
89 89 echo % diff a hooktest
90 90 diff a hooktest
91 91
92 92 echo % removing commit hook from config
93 93 sed -e '/\[hooks\]/,$ d' "$HGRCPATH" > $HGRCPATH.nohook
94 94 mv "$HGRCPATH".nohook "$HGRCPATH"
95 95 rm hooktest
96 96
97 97 echo % bundle
98 98 hg bundle --base null ../kw.hg
99 99
100 100 cd ..
101 101 hg init Test
102 102 cd Test
103 103
104 104 echo % notify on pull to check whether keywords stay as is in email
105 105 echo % ie. if patch.diff wrapper acts as it should
106 106
107 107 cat <<EOF >> $HGRCPATH
108 108 [hooks]
109 109 incoming.notify = python:hgext.notify.hook
110 110 [notify]
111 111 sources = pull
112 112 diffstat = False
113 113 [reposubs]
114 114 * = Test
115 115 EOF
116 116
117 117 echo % pull from bundle
118 118 hg pull -u ../kw.hg 2>&1 | sed -e '/^Content-Type:/,/^diffs (/ d'
119 119
120 120 echo % remove notify config
121 121 sed -e '/\[hooks\]/,$ d' "$HGRCPATH" > $HGRCPATH.nonotify
122 122 mv "$HGRCPATH".nonotify "$HGRCPATH"
123 123
124 124 echo % touch
125 125 touch a b
126 126 echo % status
127 127 hg status
128 128
129 129 rm sym a b
130 130 echo % update
131 131 hg update -C
132 132 echo % cat
133 133 cat a b
134 134
135 135 echo % check whether expansion is filewise
136 136 echo '$Id$' > c
137 137 echo 'tests for different changenodes' >> c
138 138 echo % commit c
139 139 hg commit -A -mcndiff -d '1 0' -u 'User Name <user@example.com>'
140 140 echo % force expansion
141 141 hg -v kwexpand
142 142 echo % compare changenodes in a c
143 143 cat a c
144 144
145 145 echo % record chunk
146 146 python -c \
147 147 'l=open("a").readlines();l.insert(1,"foo\n");l.append("bar\n");open("a","w").writelines(l);'
148 148 hg record -d '1 10' -m rectest<<EOF
149 149 y
150 150 y
151 151 n
152 152 EOF
153 153 echo
154 154 hg identify
155 155 hg status
156 156 echo % cat modified file
157 157 cat a
158 158 hg diff | grep -v 'b/a'
159 159 hg rollback
160 160
161 161 echo % record file
162 162 echo foo > msg
163 163 # do not use "hg record -m" here!
164 164 hg record -l msg -d '1 11'<<EOF
165 165 y
166 166 y
167 167 y
168 168 EOF
169 169 echo % a should be clean
170 170 hg status -A a
171 171 rm msg
172 172 hg rollback
173 173 hg update -C
174 174
175 175 echo % init --mq
176 176 hg init --mq
177 177 echo % qimport
178 178 hg qimport -r tip -n mqtest.diff
179 179 echo % commit --mq
180 180 hg commit --mq -m mqtest
181 181 echo % keywords should not be expanded in patch
182 182 cat .hg/patches/mqtest.diff
183 183 echo % qpop
184 184 hg qpop
185 185 echo % qgoto - should imply qpush
186 186 hg qgoto mqtest.diff
187 187 echo % cat
188 188 cat c
189 189 echo % hg cat
190 190 hg cat c
191 191 echo % keyword should not be expanded in filelog
192 192 hg --config 'extensions.keyword=!' cat c
193 193 echo % qpop and move on
194 194 hg qpop
195 195
196 196 echo % copy
197 197 hg cp a c
198 198
199 199 echo % kwfiles added
200 200 hg kwfiles
201 201
202 202 echo % commit
203 203 hg --debug commit -ma2c -d '1 0' -u 'User Name <user@example.com>'
204 204 echo % cat a c
205 205 cat a c
206 206 echo % touch copied c
207 207 touch c
208 208 echo % status
209 209 hg status
210 210
211 211 echo % kwfiles
212 212 hg kwfiles
213 213 echo % ignored files
214 214 hg -v kwfiles --ignore
215 215 echo % all files
216 216 hg kwfiles --all
217 217
218 218 echo % diff --rev
219 219 hg diff --rev 1 | grep -v 'b/c'
220 220
221 221 echo % rollback
222 222 hg rollback
223 223 echo % status
224 224 hg status
225 225 echo % update -C
226 226 hg update --clean
227 227
228 228 echo % custom keyword expansion
229 229 echo % try with kwdemo
230 230 hg --quiet kwdemo "Xinfo = {author}: {desc}"
231 231
232 232 cat <<EOF >>$HGRCPATH
233 233 [keywordmaps]
234 234 Id = {file} {node|short} {date|rfc822date} {author|user}
235 235 Xinfo = {author}: {desc}
236 236 EOF
237 237
238 238 echo % cat
239 239 cat a b
240 240 echo % hg cat
241 241 hg cat sym a b
242 242
243 243 echo
244 244 echo '$Xinfo$' >> a
245 245 cat <<EOF >> log
246 246 firstline
247 247 secondline
248 248 EOF
249 249
250 250 echo % interrupted commit should not change state
251 251 hg commit
252 252 echo % status
253 253 hg status
254 254
255 255 echo % commit
256 256 hg --debug commit -l log -d '2 0' -u 'User Name <user@example.com>'
257 257 rm log
258 258 echo % status
259 259 hg status
260 260 echo % verify
261 261 hg verify
262 262
263 263 echo % cat
264 264 cat a b
265 265 echo % hg cat
266 266 hg cat sym a b
267 267 echo
268 268 echo % annotate
269 269 hg annotate a
270 270
271 271 echo % remove
272 272 hg debugrebuildstate
273 273 hg remove a
274 274 hg --debug commit -m rma
275 275 echo % status
276 276 hg status
277 277 echo % rollback
278 278 hg rollback
279 279 echo % status
280 280 hg status
281 281 echo % revert a
282 282 hg revert --no-backup --rev tip a
283 283 echo % cat a
284 284 cat a
285 285
286 echo % clone
287 cd ..
288
289 echo % expansion in dest
290 hg --quiet clone Test globalconf
291 cat globalconf/a
292 echo % no expansion in dest
293 hg --quiet --config 'keyword.**=ignore' clone Test localconf
294 cat localconf/a
295
286 296 echo % clone to test incoming
287 cd ..
288 297 hg clone -r1 Test Test-a
289 298 cd Test-a
290 299 cat <<EOF >> .hg/hgrc
291 300 [paths]
292 301 default = ../Test
293 302 EOF
294 303 echo % incoming
295 304 # remove path to temp dir
296 305 hg incoming | sed -e 's/^\(comparing with \).*\(test-keyword.*\)/\1\2/'
297 306
298 307 sed -e 's/Id.*/& rejecttest/' a > a.new
299 308 mv a.new a
300 309 echo % commit rejecttest
301 310 hg --debug commit -m'rejects?' -d '3 0' -u 'User Name <user@example.com>'
302 311 echo % export
303 312 hg export -o ../rejecttest.diff tip
304 313
305 314 cd ../Test
306 315 echo % import
307 316 hg import ../rejecttest.diff
308 317 echo % cat
309 318 cat a b
310 319 echo
311 320 echo % rollback
312 321 hg rollback
313 322 echo % clean update
314 323 hg update --clean
315 324
316 325 echo % kwexpand/kwshrink on selected files
317 326 mkdir x
318 327 echo % copy a x/a
319 328 hg copy a x/a
320 329 echo % kwexpand a
321 330 hg --verbose kwexpand a
322 331 echo % kwexpand x/a should abort
323 332 hg --verbose kwexpand x/a
324 333 cd x
325 334 hg --debug commit -m xa -d '3 0' -u 'User Name <user@example.com>'
326 335 echo % cat a
327 336 cat a
328 337 echo % kwshrink a inside directory x
329 338 hg --verbose kwshrink a
330 339 echo % cat a
331 340 cat a
332 341 cd ..
333 342
334 343 echo % kwexpand nonexistent
335 344 hg kwexpand nonexistent 2>&1 | sed 's/nonexistent:.*/nonexistent:/'
336 345
337 346 echo % hg serve
338 347 hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
339 348 cat hg.pid >> $DAEMON_PIDS
340 349 echo % expansion
341 350 echo % hgweb file
342 351 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/a/?style=raw')
343 352 echo % no expansion
344 353 echo % hgweb annotate
345 354 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/annotate/tip/a/?style=raw')
346 355 echo % hgweb changeset
347 356 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/rev/tip/?style=raw')
348 357 echo % hgweb filediff
349 358 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/diff/bb948857c743/a?style=raw')
350 359 echo % errors encountered
351 360 cat errors.log
352 361
353 362 echo % merge/resolve
354 363 echo '$Id$' > m
355 364 hg add m
356 365 hg commit -m 4kw
357 366 echo foo >> m
358 367 hg commit -m 5foo
359 368 echo % simplemerge
360 369 hg update 4
361 370 echo foo >> m
362 371 hg commit -m 6foo
363 372 hg merge
364 373 hg commit -m simplemerge
365 374 cat m
366 375 echo % conflict
367 376 hg update 4
368 377 echo bar >> m
369 378 hg commit -m 8bar
370 379 hg merge
371 380 echo % keyword stays outside conflict zone
372 381 cat m
373 382 echo % resolve to local
374 383 HGMERGE=internal:local hg resolve -a
375 384 hg commit -m localresolve
376 385 cat m
377 386
378 387 echo % test restricted mode with transplant -b
379 388 hg update 6
380 389 hg branch foo
381 390 mv a a.bak
382 391 echo foobranch > a
383 392 cat a.bak >> a
384 393 rm a.bak
385 394 hg commit -m 9foobranch
386 395 hg update default
387 396 hg -y transplant -b foo tip
388 397 echo % no expansion in changeset
389 398 hg tip -p
390 399 echo % expansion in file
391 400 head -n 2 a
392 401 hg -q rollback
393 402 hg -q update -C
394 403
395 404 echo % switch off expansion
396 405 echo % kwshrink with unknown file u
397 406 cp a u
398 407 hg --verbose kwshrink
399 408 echo % cat
400 409 cat a b
401 410 echo % hg cat
402 411 hg cat sym a b
403 412 echo
404 413 rm "$HGRCPATH"
405 414 echo % cat
406 415 cat a b
407 416 echo % hg cat
408 417 hg cat sym a b
409 418 echo
@@ -1,536 +1,547 b''
1 1 % hg kwdemo
2 2 [extensions]
3 3 keyword =
4 4 [keyword]
5 5 demo.txt =
6 6 [keywordmaps]
7 7 Author = {author|user}
8 8 Date = {date|utcdate}
9 9 Header = {root}/{file},v {node|short} {date|utcdate} {author|user}
10 10 Id = {file|basename},v {node|short} {date|utcdate} {author|user}
11 11 RCSFile = {file|basename},v
12 12 RCSfile = {file|basename},v
13 13 Revision = {node|short}
14 14 Source = {root}/{file},v
15 15 $Author: test $
16 16 $Date: 2000/00/00 00:00:00 $
17 17 $Header: /TMP/demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
18 18 $Id: demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
19 19 $RCSFile: demo.txt,v $
20 20 $RCSfile: demo.txt,v $
21 21 $Revision: xxxxxxxxxxxx $
22 22 $Source: /TMP/demo.txt,v $
23 23 [extensions]
24 24 keyword =
25 25 [keyword]
26 26 demo.txt =
27 27 [keywordmaps]
28 28 Branch = {branches}
29 29 $Branch: demobranch $
30 30 % kwshrink should exit silently in empty/invalid repo
31 31 pulling from test-keyword.hg
32 32 requesting all changes
33 33 adding changesets
34 34 adding manifests
35 35 adding file changes
36 36 added 1 changesets with 1 changes to 1 files
37 37 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 38 % cat
39 39 expand $Id$
40 40 do not process $Id:
41 41 xxx $
42 42 ignore $Id$
43 43 % no kwfiles
44 44 % untracked candidates
45 45 k a
46 46 % addremove
47 47 adding a
48 48 adding b
49 49 % status
50 50 A a
51 51 A b
52 52 % default keyword expansion including commit hook
53 53 % interrupted commit should not change state or run commit hook
54 54 abort: empty commit message
55 55 % status
56 56 A a
57 57 A b
58 58 % commit
59 59 a
60 60 b
61 61 overwriting a expanding keywords
62 62 running hook commit.test: cp a hooktest
63 63 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
64 64 % status
65 65 ? hooktest
66 66 % identify
67 67 ef63ca68695b
68 68 % cat
69 69 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
70 70 do not process $Id:
71 71 xxx $
72 72 ignore $Id$
73 73 % hg cat
74 74 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
75 75 do not process $Id:
76 76 xxx $
77 77 ignore $Id$
78 78 a
79 79 % diff a hooktest
80 80 % removing commit hook from config
81 81 % bundle
82 82 2 changesets found
83 83 % notify on pull to check whether keywords stay as is in email
84 84 % ie. if patch.diff wrapper acts as it should
85 85 % pull from bundle
86 86 pulling from ../kw.hg
87 87 requesting all changes
88 88 adding changesets
89 89 adding manifests
90 90 adding file changes
91 91 added 2 changesets with 3 changes to 3 files
92 92
93 93 diff -r 000000000000 -r a2392c293916 sym
94 94 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
95 95 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
96 96 @@ -0,0 +1,1 @@
97 97 +a
98 98 \ No newline at end of file
99 99
100 100 diff -r a2392c293916 -r ef63ca68695b a
101 101 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
102 102 +++ b/a Thu Jan 01 00:00:00 1970 +0000
103 103 @@ -0,0 +1,3 @@
104 104 +expand $Id$
105 105 +do not process $Id:
106 106 +xxx $
107 107 diff -r a2392c293916 -r ef63ca68695b b
108 108 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
109 109 +++ b/b Thu Jan 01 00:00:00 1970 +0000
110 110 @@ -0,0 +1,1 @@
111 111 +ignore $Id$
112 112 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
113 113 % remove notify config
114 114 % touch
115 115 % status
116 116 % update
117 117 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 118 % cat
119 119 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
120 120 do not process $Id:
121 121 xxx $
122 122 ignore $Id$
123 123 % check whether expansion is filewise
124 124 % commit c
125 125 adding c
126 126 % force expansion
127 127 overwriting a expanding keywords
128 128 overwriting c expanding keywords
129 129 % compare changenodes in a c
130 130 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
131 131 do not process $Id:
132 132 xxx $
133 133 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
134 134 tests for different changenodes
135 135 % record chunk
136 136 diff --git a/a b/a
137 137 2 hunks, 2 lines changed
138 138 examine changes to 'a'? [Ynsfdaq?]
139 139 @@ -1,3 +1,4 @@
140 140 expand $Id$
141 141 +foo
142 142 do not process $Id:
143 143 xxx $
144 144 record change 1/2 to 'a'? [Ynsfdaq?]
145 145 @@ -2,2 +3,3 @@
146 146 do not process $Id:
147 147 xxx $
148 148 +bar
149 149 record change 2/2 to 'a'? [Ynsfdaq?]
150 150
151 151 d17e03c92c97+ tip
152 152 M a
153 153 % cat modified file
154 154 expand $Id: a,v d17e03c92c97 1970/01/01 00:00:01 test $
155 155 foo
156 156 do not process $Id:
157 157 xxx $
158 158 bar
159 159 diff -r d17e03c92c97 a
160 160 --- a/a Wed Dec 31 23:59:51 1969 -0000
161 161 @@ -2,3 +2,4 @@
162 162 foo
163 163 do not process $Id:
164 164 xxx $
165 165 +bar
166 166 rolling back to revision 2 (undo commit)
167 167 % record file
168 168 diff --git a/a b/a
169 169 2 hunks, 2 lines changed
170 170 examine changes to 'a'? [Ynsfdaq?]
171 171 @@ -1,3 +1,4 @@
172 172 expand $Id$
173 173 +foo
174 174 do not process $Id:
175 175 xxx $
176 176 record change 1/2 to 'a'? [Ynsfdaq?]
177 177 @@ -2,2 +3,3 @@
178 178 do not process $Id:
179 179 xxx $
180 180 +bar
181 181 record change 2/2 to 'a'? [Ynsfdaq?]
182 182 % a should be clean
183 183 C a
184 184 rolling back to revision 2 (undo commit)
185 185 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
186 186 % init --mq
187 187 % qimport
188 188 % commit --mq
189 189 % keywords should not be expanded in patch
190 190 # HG changeset patch
191 191 # User User Name <user@example.com>
192 192 # Date 1 0
193 193 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
194 194 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
195 195 cndiff
196 196
197 197 diff -r ef63ca68695b -r 40a904bbbe4c c
198 198 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
199 199 +++ b/c Thu Jan 01 00:00:01 1970 +0000
200 200 @@ -0,0 +1,2 @@
201 201 +$Id$
202 202 +tests for different changenodes
203 203 % qpop
204 204 popping mqtest.diff
205 205 patch queue now empty
206 206 % qgoto - should imply qpush
207 207 applying mqtest.diff
208 208 now at: mqtest.diff
209 209 % cat
210 210 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
211 211 tests for different changenodes
212 212 % hg cat
213 213 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
214 214 tests for different changenodes
215 215 % keyword should not be expanded in filelog
216 216 $Id$
217 217 tests for different changenodes
218 218 % qpop and move on
219 219 popping mqtest.diff
220 220 patch queue now empty
221 221 % copy
222 222 % kwfiles added
223 223 a
224 224 c
225 225 % commit
226 226 c
227 227 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
228 228 overwriting c expanding keywords
229 229 committed changeset 2:25736cf2f5cbe41f6be4e6784ef6ecf9f3bbcc7d
230 230 % cat a c
231 231 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
232 232 do not process $Id:
233 233 xxx $
234 234 expand $Id: c,v 25736cf2f5cb 1970/01/01 00:00:01 user $
235 235 do not process $Id:
236 236 xxx $
237 237 % touch copied c
238 238 % status
239 239 % kwfiles
240 240 a
241 241 c
242 242 % ignored files
243 243 I b
244 244 I sym
245 245 % all files
246 246 K a
247 247 K c
248 248 I b
249 249 I sym
250 250 % diff --rev
251 251 diff -r ef63ca68695b c
252 252 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
253 253 @@ -0,0 +1,3 @@
254 254 +expand $Id$
255 255 +do not process $Id:
256 256 +xxx $
257 257 % rollback
258 258 rolling back to revision 1 (undo commit)
259 259 % status
260 260 A c
261 261 % update -C
262 262 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
263 263 % custom keyword expansion
264 264 % try with kwdemo
265 265 [extensions]
266 266 keyword =
267 267 [keyword]
268 268 ** =
269 269 b = ignore
270 270 demo.txt =
271 271 [keywordmaps]
272 272 Xinfo = {author}: {desc}
273 273 $Xinfo: test: hg keyword configuration and expansion example $
274 274 % cat
275 275 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
276 276 do not process $Id:
277 277 xxx $
278 278 ignore $Id$
279 279 % hg cat
280 280 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
281 281 do not process $Id:
282 282 xxx $
283 283 ignore $Id$
284 284 a
285 285 % interrupted commit should not change state
286 286 abort: empty commit message
287 287 % status
288 288 M a
289 289 ? c
290 290 ? log
291 291 % commit
292 292 a
293 293 overwriting a expanding keywords
294 294 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
295 295 % status
296 296 ? c
297 297 % verify
298 298 checking changesets
299 299 checking manifests
300 300 crosschecking files in changesets and manifests
301 301 checking files
302 302 3 files, 3 changesets, 4 total revisions
303 303 % cat
304 304 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
305 305 do not process $Id:
306 306 xxx $
307 307 $Xinfo: User Name <user@example.com>: firstline $
308 308 ignore $Id$
309 309 % hg cat
310 310 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
311 311 do not process $Id:
312 312 xxx $
313 313 $Xinfo: User Name <user@example.com>: firstline $
314 314 ignore $Id$
315 315 a
316 316 % annotate
317 317 1: expand $Id$
318 318 1: do not process $Id:
319 319 1: xxx $
320 320 2: $Xinfo$
321 321 % remove
322 322 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
323 323 % status
324 324 ? c
325 325 % rollback
326 326 rolling back to revision 2 (undo commit)
327 327 % status
328 328 R a
329 329 ? c
330 330 % revert a
331 331 % cat a
332 332 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
333 333 do not process $Id:
334 334 xxx $
335 335 $Xinfo: User Name <user@example.com>: firstline $
336 % clone
337 % expansion in dest
338 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
339 do not process $Id:
340 xxx $
341 $Xinfo: User Name <user@example.com>: firstline $
342 % no expansion in dest
343 expand $Id$
344 do not process $Id:
345 xxx $
346 $Xinfo$
336 347 % clone to test incoming
337 348 requesting all changes
338 349 adding changesets
339 350 adding manifests
340 351 adding file changes
341 352 added 2 changesets with 3 changes to 3 files
342 353 updating to branch default
343 354 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
344 355 % incoming
345 356 comparing with test-keyword/Test
346 357 searching for changes
347 358 changeset: 2:bb948857c743
348 359 tag: tip
349 360 user: User Name <user@example.com>
350 361 date: Thu Jan 01 00:00:02 1970 +0000
351 362 summary: firstline
352 363
353 364 % commit rejecttest
354 365 a
355 366 overwriting a expanding keywords
356 367 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
357 368 % export
358 369 % import
359 370 applying ../rejecttest.diff
360 371 % cat
361 372 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
362 373 do not process $Id: rejecttest
363 374 xxx $
364 375 $Xinfo: User Name <user@example.com>: rejects? $
365 376 ignore $Id$
366 377
367 378 % rollback
368 379 rolling back to revision 2 (undo commit)
369 380 % clean update
370 381 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
371 382 % kwexpand/kwshrink on selected files
372 383 % copy a x/a
373 384 % kwexpand a
374 385 overwriting a expanding keywords
375 386 % kwexpand x/a should abort
376 387 abort: outstanding uncommitted changes
377 388 x/a
378 389 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
379 390 overwriting x/a expanding keywords
380 391 committed changeset 3:b4560182a3f9a358179fd2d835c15e9da379c1e4
381 392 % cat a
382 393 expand $Id: x/a b4560182a3f9 Thu, 01 Jan 1970 00:00:03 +0000 user $
383 394 do not process $Id:
384 395 xxx $
385 396 $Xinfo: User Name <user@example.com>: xa $
386 397 % kwshrink a inside directory x
387 398 overwriting x/a shrinking keywords
388 399 % cat a
389 400 expand $Id$
390 401 do not process $Id:
391 402 xxx $
392 403 $Xinfo$
393 404 % kwexpand nonexistent
394 405 nonexistent:
395 406 % hg serve
396 407 % expansion
397 408 % hgweb file
398 409 200 Script output follows
399 410
400 411 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
401 412 do not process $Id:
402 413 xxx $
403 414 $Xinfo: User Name <user@example.com>: firstline $
404 415 % no expansion
405 416 % hgweb annotate
406 417 200 Script output follows
407 418
408 419
409 420 user@1: expand $Id$
410 421 user@1: do not process $Id:
411 422 user@1: xxx $
412 423 user@2: $Xinfo$
413 424
414 425
415 426
416 427
417 428 % hgweb changeset
418 429 200 Script output follows
419 430
420 431
421 432 # HG changeset patch
422 433 # User User Name <user@example.com>
423 434 # Date 3 0
424 435 # Node ID b4560182a3f9a358179fd2d835c15e9da379c1e4
425 436 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
426 437 xa
427 438
428 439 diff -r bb948857c743 -r b4560182a3f9 x/a
429 440 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
430 441 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
431 442 @@ -0,0 +1,4 @@
432 443 +expand $Id$
433 444 +do not process $Id:
434 445 +xxx $
435 446 +$Xinfo$
436 447
437 448 % hgweb filediff
438 449 200 Script output follows
439 450
440 451
441 452 diff -r ef63ca68695b -r bb948857c743 a
442 453 --- a/a Thu Jan 01 00:00:00 1970 +0000
443 454 +++ b/a Thu Jan 01 00:00:02 1970 +0000
444 455 @@ -1,3 +1,4 @@
445 456 expand $Id$
446 457 do not process $Id:
447 458 xxx $
448 459 +$Xinfo$
449 460
450 461
451 462
452 463
453 464 % errors encountered
454 465 % merge/resolve
455 466 % simplemerge
456 467 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
457 468 created new head
458 469 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
459 470 (branch merge, don't forget to commit)
460 471 $Id: m 27d48ee14f67 Thu, 01 Jan 1970 00:00:00 +0000 test $
461 472 foo
462 473 % conflict
463 474 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
464 475 created new head
465 476 merging m
466 477 warning: conflicts during merge.
467 478 merging m failed!
468 479 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
469 480 use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon
470 481 % keyword stays outside conflict zone
471 482 $Id$
472 483 <<<<<<< local
473 484 bar
474 485 =======
475 486 foo
476 487 >>>>>>> other
477 488 % resolve to local
478 489 $Id: m 41efa6d38e9b Thu, 01 Jan 1970 00:00:00 +0000 test $
479 490 bar
480 491 % test restricted mode with transplant -b
481 492 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
482 493 marked working directory as branch foo
483 494 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
484 495 applying 4aa30d025d50
485 496 4aa30d025d50 transplanted to 5a4da427c162
486 497 % no expansion in changeset
487 498 changeset: 11:5a4da427c162
488 499 tag: tip
489 500 parent: 9:41efa6d38e9b
490 501 user: test
491 502 date: Thu Jan 01 00:00:00 1970 +0000
492 503 summary: 9foobranch
493 504
494 505 diff -r 41efa6d38e9b -r 5a4da427c162 a
495 506 --- a/a Thu Jan 01 00:00:00 1970 +0000
496 507 +++ b/a Thu Jan 01 00:00:00 1970 +0000
497 508 @@ -1,3 +1,4 @@
498 509 +foobranch
499 510 expand $Id$
500 511 do not process $Id:
501 512 xxx $
502 513
503 514 % expansion in file
504 515 foobranch
505 516 expand $Id: a 5a4da427c162 Thu, 01 Jan 1970 00:00:00 +0000 test $
506 517 % switch off expansion
507 518 % kwshrink with unknown file u
508 519 overwriting a shrinking keywords
509 520 overwriting m shrinking keywords
510 521 overwriting x/a shrinking keywords
511 522 % cat
512 523 expand $Id$
513 524 do not process $Id:
514 525 xxx $
515 526 $Xinfo$
516 527 ignore $Id$
517 528 % hg cat
518 529 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
519 530 do not process $Id:
520 531 xxx $
521 532 $Xinfo: User Name <user@example.com>: firstline $
522 533 ignore $Id$
523 534 a
524 535 % cat
525 536 expand $Id$
526 537 do not process $Id:
527 538 xxx $
528 539 $Xinfo$
529 540 ignore $Id$
530 541 % hg cat
531 542 expand $Id$
532 543 do not process $Id:
533 544 xxx $
534 545 $Xinfo$
535 546 ignore $Id$
536 547 a
General Comments 0
You need to be logged in to leave comments. Login now