##// END OF EJS Templates
make_file: always return a fresh file handle that can be closed...
Dan Villiom Podlaski Christiansen -
r13121:bf763946 default
parent child Browse files
Show More
@@ -1,1379 +1,1380 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, glob, tempfile
10 import os, sys, errno, re, glob, tempfile
11 import util, templater, patch, error, encoding, templatekw
11 import util, templater, patch, error, encoding, templatekw
12 import match as matchmod
12 import match as matchmod
13 import similar, revset, subrepo
13 import similar, revset, subrepo
14
14
15 revrangesep = ':'
15 revrangesep = ':'
16
16
17 def parsealiases(cmd):
17 def parsealiases(cmd):
18 return cmd.lstrip("^").split("|")
18 return cmd.lstrip("^").split("|")
19
19
20 def findpossible(cmd, table, strict=False):
20 def findpossible(cmd, table, strict=False):
21 """
21 """
22 Return cmd -> (aliases, command table entry)
22 Return cmd -> (aliases, command table entry)
23 for each matching command.
23 for each matching command.
24 Return debug commands (or their aliases) only if no normal command matches.
24 Return debug commands (or their aliases) only if no normal command matches.
25 """
25 """
26 choice = {}
26 choice = {}
27 debugchoice = {}
27 debugchoice = {}
28 for e in table.keys():
28 for e in table.keys():
29 aliases = parsealiases(e)
29 aliases = parsealiases(e)
30 found = None
30 found = None
31 if cmd in aliases:
31 if cmd in aliases:
32 found = cmd
32 found = cmd
33 elif not strict:
33 elif not strict:
34 for a in aliases:
34 for a in aliases:
35 if a.startswith(cmd):
35 if a.startswith(cmd):
36 found = a
36 found = a
37 break
37 break
38 if found is not None:
38 if found is not None:
39 if aliases[0].startswith("debug") or found.startswith("debug"):
39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 debugchoice[found] = (aliases, table[e])
40 debugchoice[found] = (aliases, table[e])
41 else:
41 else:
42 choice[found] = (aliases, table[e])
42 choice[found] = (aliases, table[e])
43
43
44 if not choice and debugchoice:
44 if not choice and debugchoice:
45 choice = debugchoice
45 choice = debugchoice
46
46
47 return choice
47 return choice
48
48
49 def findcmd(cmd, table, strict=True):
49 def findcmd(cmd, table, strict=True):
50 """Return (aliases, command table entry) for command string."""
50 """Return (aliases, command table entry) for command string."""
51 choice = findpossible(cmd, table, strict)
51 choice = findpossible(cmd, table, strict)
52
52
53 if cmd in choice:
53 if cmd in choice:
54 return choice[cmd]
54 return choice[cmd]
55
55
56 if len(choice) > 1:
56 if len(choice) > 1:
57 clist = choice.keys()
57 clist = choice.keys()
58 clist.sort()
58 clist.sort()
59 raise error.AmbiguousCommand(cmd, clist)
59 raise error.AmbiguousCommand(cmd, clist)
60
60
61 if choice:
61 if choice:
62 return choice.values()[0]
62 return choice.values()[0]
63
63
64 raise error.UnknownCommand(cmd)
64 raise error.UnknownCommand(cmd)
65
65
66 def findrepo(p):
66 def findrepo(p):
67 while not os.path.isdir(os.path.join(p, ".hg")):
67 while not os.path.isdir(os.path.join(p, ".hg")):
68 oldp, p = p, os.path.dirname(p)
68 oldp, p = p, os.path.dirname(p)
69 if p == oldp:
69 if p == oldp:
70 return None
70 return None
71
71
72 return p
72 return p
73
73
74 def bail_if_changed(repo):
74 def bail_if_changed(repo):
75 if repo.dirstate.parents()[1] != nullid:
75 if repo.dirstate.parents()[1] != nullid:
76 raise util.Abort(_('outstanding uncommitted merge'))
76 raise util.Abort(_('outstanding uncommitted merge'))
77 modified, added, removed, deleted = repo.status()[:4]
77 modified, added, removed, deleted = repo.status()[:4]
78 if modified or added or removed or deleted:
78 if modified or added or removed or deleted:
79 raise util.Abort(_("outstanding uncommitted changes"))
79 raise util.Abort(_("outstanding uncommitted changes"))
80
80
81 def logmessage(opts):
81 def logmessage(opts):
82 """ get the log message according to -m and -l option """
82 """ get the log message according to -m and -l option """
83 message = opts.get('message')
83 message = opts.get('message')
84 logfile = opts.get('logfile')
84 logfile = opts.get('logfile')
85
85
86 if message and logfile:
86 if message and logfile:
87 raise util.Abort(_('options --message and --logfile are mutually '
87 raise util.Abort(_('options --message and --logfile are mutually '
88 'exclusive'))
88 'exclusive'))
89 if not message and logfile:
89 if not message and logfile:
90 try:
90 try:
91 if logfile == '-':
91 if logfile == '-':
92 message = sys.stdin.read()
92 message = sys.stdin.read()
93 else:
93 else:
94 message = open(logfile).read()
94 message = open(logfile).read()
95 except IOError, inst:
95 except IOError, inst:
96 raise util.Abort(_("can't read commit message '%s': %s") %
96 raise util.Abort(_("can't read commit message '%s': %s") %
97 (logfile, inst.strerror))
97 (logfile, inst.strerror))
98 return message
98 return message
99
99
100 def loglimit(opts):
100 def loglimit(opts):
101 """get the log limit according to option -l/--limit"""
101 """get the log limit according to option -l/--limit"""
102 limit = opts.get('limit')
102 limit = opts.get('limit')
103 if limit:
103 if limit:
104 try:
104 try:
105 limit = int(limit)
105 limit = int(limit)
106 except ValueError:
106 except ValueError:
107 raise util.Abort(_('limit must be a positive integer'))
107 raise util.Abort(_('limit must be a positive integer'))
108 if limit <= 0:
108 if limit <= 0:
109 raise util.Abort(_('limit must be positive'))
109 raise util.Abort(_('limit must be positive'))
110 else:
110 else:
111 limit = None
111 limit = None
112 return limit
112 return limit
113
113
114 def revsingle(repo, revspec, default='.'):
114 def revsingle(repo, revspec, default='.'):
115 if not revspec:
115 if not revspec:
116 return repo[default]
116 return repo[default]
117
117
118 l = revrange(repo, [revspec])
118 l = revrange(repo, [revspec])
119 if len(l) < 1:
119 if len(l) < 1:
120 raise util.Abort(_('empty revision set'))
120 raise util.Abort(_('empty revision set'))
121 return repo[l[-1]]
121 return repo[l[-1]]
122
122
123 def revpair(repo, revs):
123 def revpair(repo, revs):
124 if not revs:
124 if not revs:
125 return repo.dirstate.parents()[0], None
125 return repo.dirstate.parents()[0], None
126
126
127 l = revrange(repo, revs)
127 l = revrange(repo, revs)
128
128
129 if len(l) == 0:
129 if len(l) == 0:
130 return repo.dirstate.parents()[0], None
130 return repo.dirstate.parents()[0], None
131
131
132 if len(l) == 1:
132 if len(l) == 1:
133 return repo.lookup(l[0]), None
133 return repo.lookup(l[0]), None
134
134
135 return repo.lookup(l[0]), repo.lookup(l[-1])
135 return repo.lookup(l[0]), repo.lookup(l[-1])
136
136
137 def revrange(repo, revs):
137 def revrange(repo, revs):
138 """Yield revision as strings from a list of revision specifications."""
138 """Yield revision as strings from a list of revision specifications."""
139
139
140 def revfix(repo, val, defval):
140 def revfix(repo, val, defval):
141 if not val and val != 0 and defval is not None:
141 if not val and val != 0 and defval is not None:
142 return defval
142 return defval
143 return repo.changelog.rev(repo.lookup(val))
143 return repo.changelog.rev(repo.lookup(val))
144
144
145 seen, l = set(), []
145 seen, l = set(), []
146 for spec in revs:
146 for spec in revs:
147 # attempt to parse old-style ranges first to deal with
147 # attempt to parse old-style ranges first to deal with
148 # things like old-tag which contain query metacharacters
148 # things like old-tag which contain query metacharacters
149 try:
149 try:
150 if isinstance(spec, int):
150 if isinstance(spec, int):
151 seen.add(spec)
151 seen.add(spec)
152 l.append(spec)
152 l.append(spec)
153 continue
153 continue
154
154
155 if revrangesep in spec:
155 if revrangesep in spec:
156 start, end = spec.split(revrangesep, 1)
156 start, end = spec.split(revrangesep, 1)
157 start = revfix(repo, start, 0)
157 start = revfix(repo, start, 0)
158 end = revfix(repo, end, len(repo) - 1)
158 end = revfix(repo, end, len(repo) - 1)
159 step = start > end and -1 or 1
159 step = start > end and -1 or 1
160 for rev in xrange(start, end + step, step):
160 for rev in xrange(start, end + step, step):
161 if rev in seen:
161 if rev in seen:
162 continue
162 continue
163 seen.add(rev)
163 seen.add(rev)
164 l.append(rev)
164 l.append(rev)
165 continue
165 continue
166 elif spec and spec in repo: # single unquoted rev
166 elif spec and spec in repo: # single unquoted rev
167 rev = revfix(repo, spec, None)
167 rev = revfix(repo, spec, None)
168 if rev in seen:
168 if rev in seen:
169 continue
169 continue
170 seen.add(rev)
170 seen.add(rev)
171 l.append(rev)
171 l.append(rev)
172 continue
172 continue
173 except error.RepoLookupError:
173 except error.RepoLookupError:
174 pass
174 pass
175
175
176 # fall through to new-style queries if old-style fails
176 # fall through to new-style queries if old-style fails
177 m = revset.match(spec)
177 m = revset.match(spec)
178 for r in m(repo, range(len(repo))):
178 for r in m(repo, range(len(repo))):
179 if r not in seen:
179 if r not in seen:
180 l.append(r)
180 l.append(r)
181 seen.update(l)
181 seen.update(l)
182
182
183 return l
183 return l
184
184
185 def make_filename(repo, pat, node,
185 def make_filename(repo, pat, node,
186 total=None, seqno=None, revwidth=None, pathname=None):
186 total=None, seqno=None, revwidth=None, pathname=None):
187 node_expander = {
187 node_expander = {
188 'H': lambda: hex(node),
188 'H': lambda: hex(node),
189 'R': lambda: str(repo.changelog.rev(node)),
189 'R': lambda: str(repo.changelog.rev(node)),
190 'h': lambda: short(node),
190 'h': lambda: short(node),
191 }
191 }
192 expander = {
192 expander = {
193 '%': lambda: '%',
193 '%': lambda: '%',
194 'b': lambda: os.path.basename(repo.root),
194 'b': lambda: os.path.basename(repo.root),
195 }
195 }
196
196
197 try:
197 try:
198 if node:
198 if node:
199 expander.update(node_expander)
199 expander.update(node_expander)
200 if node:
200 if node:
201 expander['r'] = (lambda:
201 expander['r'] = (lambda:
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
203 if total is not None:
203 if total is not None:
204 expander['N'] = lambda: str(total)
204 expander['N'] = lambda: str(total)
205 if seqno is not None:
205 if seqno is not None:
206 expander['n'] = lambda: str(seqno)
206 expander['n'] = lambda: str(seqno)
207 if total is not None and seqno is not None:
207 if total is not None and seqno is not None:
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
209 if pathname is not None:
209 if pathname is not None:
210 expander['s'] = lambda: os.path.basename(pathname)
210 expander['s'] = lambda: os.path.basename(pathname)
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
212 expander['p'] = lambda: pathname
212 expander['p'] = lambda: pathname
213
213
214 newname = []
214 newname = []
215 patlen = len(pat)
215 patlen = len(pat)
216 i = 0
216 i = 0
217 while i < patlen:
217 while i < patlen:
218 c = pat[i]
218 c = pat[i]
219 if c == '%':
219 if c == '%':
220 i += 1
220 i += 1
221 c = pat[i]
221 c = pat[i]
222 c = expander[c]()
222 c = expander[c]()
223 newname.append(c)
223 newname.append(c)
224 i += 1
224 i += 1
225 return ''.join(newname)
225 return ''.join(newname)
226 except KeyError, inst:
226 except KeyError, inst:
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
228 inst.args[0])
228 inst.args[0])
229
229
230 def make_file(repo, pat, node=None,
230 def make_file(repo, pat, node=None,
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
232
232
233 writable = 'w' in mode or 'a' in mode
233 writable = 'w' in mode or 'a' in mode
234
234
235 if not pat or pat == '-':
235 if not pat or pat == '-':
236 return writable and sys.stdout or sys.stdin
236 fp = writable and sys.stdout or sys.stdin
237 return os.fdopen(os.dup(fp.fileno()), mode)
237 if hasattr(pat, 'write') and writable:
238 if hasattr(pat, 'write') and writable:
238 return pat
239 return pat
239 if hasattr(pat, 'read') and 'r' in mode:
240 if hasattr(pat, 'read') and 'r' in mode:
240 return pat
241 return pat
241 return open(make_filename(repo, pat, node, total, seqno, revwidth,
242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
242 pathname),
243 pathname),
243 mode)
244 mode)
244
245
245 def expandpats(pats):
246 def expandpats(pats):
246 if not util.expandglobs:
247 if not util.expandglobs:
247 return list(pats)
248 return list(pats)
248 ret = []
249 ret = []
249 for p in pats:
250 for p in pats:
250 kind, name = matchmod._patsplit(p, None)
251 kind, name = matchmod._patsplit(p, None)
251 if kind is None:
252 if kind is None:
252 try:
253 try:
253 globbed = glob.glob(name)
254 globbed = glob.glob(name)
254 except re.error:
255 except re.error:
255 globbed = [name]
256 globbed = [name]
256 if globbed:
257 if globbed:
257 ret.extend(globbed)
258 ret.extend(globbed)
258 continue
259 continue
259 ret.append(p)
260 ret.append(p)
260 return ret
261 return ret
261
262
262 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
263 if not globbed and default == 'relpath':
264 if not globbed and default == 'relpath':
264 pats = expandpats(pats or [])
265 pats = expandpats(pats or [])
265 m = matchmod.match(repo.root, repo.getcwd(), pats,
266 m = matchmod.match(repo.root, repo.getcwd(), pats,
266 opts.get('include'), opts.get('exclude'), default,
267 opts.get('include'), opts.get('exclude'), default,
267 auditor=repo.auditor)
268 auditor=repo.auditor)
268 def badfn(f, msg):
269 def badfn(f, msg):
269 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
270 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
270 m.bad = badfn
271 m.bad = badfn
271 return m
272 return m
272
273
273 def matchall(repo):
274 def matchall(repo):
274 return matchmod.always(repo.root, repo.getcwd())
275 return matchmod.always(repo.root, repo.getcwd())
275
276
276 def matchfiles(repo, files):
277 def matchfiles(repo, files):
277 return matchmod.exact(repo.root, repo.getcwd(), files)
278 return matchmod.exact(repo.root, repo.getcwd(), files)
278
279
279 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
280 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
280 if dry_run is None:
281 if dry_run is None:
281 dry_run = opts.get('dry_run')
282 dry_run = opts.get('dry_run')
282 if similarity is None:
283 if similarity is None:
283 similarity = float(opts.get('similarity') or 0)
284 similarity = float(opts.get('similarity') or 0)
284 # we'd use status here, except handling of symlinks and ignore is tricky
285 # we'd use status here, except handling of symlinks and ignore is tricky
285 added, unknown, deleted, removed = [], [], [], []
286 added, unknown, deleted, removed = [], [], [], []
286 audit_path = util.path_auditor(repo.root)
287 audit_path = util.path_auditor(repo.root)
287 m = match(repo, pats, opts)
288 m = match(repo, pats, opts)
288 for abs in repo.walk(m):
289 for abs in repo.walk(m):
289 target = repo.wjoin(abs)
290 target = repo.wjoin(abs)
290 good = True
291 good = True
291 try:
292 try:
292 audit_path(abs)
293 audit_path(abs)
293 except:
294 except:
294 good = False
295 good = False
295 rel = m.rel(abs)
296 rel = m.rel(abs)
296 exact = m.exact(abs)
297 exact = m.exact(abs)
297 if good and abs not in repo.dirstate:
298 if good and abs not in repo.dirstate:
298 unknown.append(abs)
299 unknown.append(abs)
299 if repo.ui.verbose or not exact:
300 if repo.ui.verbose or not exact:
300 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
301 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
301 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
302 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
302 or (os.path.isdir(target) and not os.path.islink(target))):
303 or (os.path.isdir(target) and not os.path.islink(target))):
303 deleted.append(abs)
304 deleted.append(abs)
304 if repo.ui.verbose or not exact:
305 if repo.ui.verbose or not exact:
305 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
306 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
306 # for finding renames
307 # for finding renames
307 elif repo.dirstate[abs] == 'r':
308 elif repo.dirstate[abs] == 'r':
308 removed.append(abs)
309 removed.append(abs)
309 elif repo.dirstate[abs] == 'a':
310 elif repo.dirstate[abs] == 'a':
310 added.append(abs)
311 added.append(abs)
311 copies = {}
312 copies = {}
312 if similarity > 0:
313 if similarity > 0:
313 for old, new, score in similar.findrenames(repo,
314 for old, new, score in similar.findrenames(repo,
314 added + unknown, removed + deleted, similarity):
315 added + unknown, removed + deleted, similarity):
315 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
316 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
316 repo.ui.status(_('recording removal of %s as rename to %s '
317 repo.ui.status(_('recording removal of %s as rename to %s '
317 '(%d%% similar)\n') %
318 '(%d%% similar)\n') %
318 (m.rel(old), m.rel(new), score * 100))
319 (m.rel(old), m.rel(new), score * 100))
319 copies[new] = old
320 copies[new] = old
320
321
321 if not dry_run:
322 if not dry_run:
322 wctx = repo[None]
323 wctx = repo[None]
323 wlock = repo.wlock()
324 wlock = repo.wlock()
324 try:
325 try:
325 wctx.remove(deleted)
326 wctx.remove(deleted)
326 wctx.add(unknown)
327 wctx.add(unknown)
327 for new, old in copies.iteritems():
328 for new, old in copies.iteritems():
328 wctx.copy(old, new)
329 wctx.copy(old, new)
329 finally:
330 finally:
330 wlock.release()
331 wlock.release()
331
332
332 def updatedir(ui, repo, patches, similarity=0):
333 def updatedir(ui, repo, patches, similarity=0):
333 '''Update dirstate after patch application according to metadata'''
334 '''Update dirstate after patch application according to metadata'''
334 if not patches:
335 if not patches:
335 return
336 return
336 copies = []
337 copies = []
337 removes = set()
338 removes = set()
338 cfiles = patches.keys()
339 cfiles = patches.keys()
339 cwd = repo.getcwd()
340 cwd = repo.getcwd()
340 if cwd:
341 if cwd:
341 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
342 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
342 for f in patches:
343 for f in patches:
343 gp = patches[f]
344 gp = patches[f]
344 if not gp:
345 if not gp:
345 continue
346 continue
346 if gp.op == 'RENAME':
347 if gp.op == 'RENAME':
347 copies.append((gp.oldpath, gp.path))
348 copies.append((gp.oldpath, gp.path))
348 removes.add(gp.oldpath)
349 removes.add(gp.oldpath)
349 elif gp.op == 'COPY':
350 elif gp.op == 'COPY':
350 copies.append((gp.oldpath, gp.path))
351 copies.append((gp.oldpath, gp.path))
351 elif gp.op == 'DELETE':
352 elif gp.op == 'DELETE':
352 removes.add(gp.path)
353 removes.add(gp.path)
353
354
354 wctx = repo[None]
355 wctx = repo[None]
355 for src, dst in copies:
356 for src, dst in copies:
356 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
357 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
357 if (not similarity) and removes:
358 if (not similarity) and removes:
358 wctx.remove(sorted(removes), True)
359 wctx.remove(sorted(removes), True)
359
360
360 for f in patches:
361 for f in patches:
361 gp = patches[f]
362 gp = patches[f]
362 if gp and gp.mode:
363 if gp and gp.mode:
363 islink, isexec = gp.mode
364 islink, isexec = gp.mode
364 dst = repo.wjoin(gp.path)
365 dst = repo.wjoin(gp.path)
365 # patch won't create empty files
366 # patch won't create empty files
366 if gp.op == 'ADD' and not os.path.lexists(dst):
367 if gp.op == 'ADD' and not os.path.lexists(dst):
367 flags = (isexec and 'x' or '') + (islink and 'l' or '')
368 flags = (isexec and 'x' or '') + (islink and 'l' or '')
368 repo.wwrite(gp.path, '', flags)
369 repo.wwrite(gp.path, '', flags)
369 util.set_flags(dst, islink, isexec)
370 util.set_flags(dst, islink, isexec)
370 addremove(repo, cfiles, similarity=similarity)
371 addremove(repo, cfiles, similarity=similarity)
371 files = patches.keys()
372 files = patches.keys()
372 files.extend([r for r in removes if r not in files])
373 files.extend([r for r in removes if r not in files])
373 return sorted(files)
374 return sorted(files)
374
375
375 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
376 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
376 """Update the dirstate to reflect the intent of copying src to dst. For
377 """Update the dirstate to reflect the intent of copying src to dst. For
377 different reasons it might not end with dst being marked as copied from src.
378 different reasons it might not end with dst being marked as copied from src.
378 """
379 """
379 origsrc = repo.dirstate.copied(src) or src
380 origsrc = repo.dirstate.copied(src) or src
380 if dst == origsrc: # copying back a copy?
381 if dst == origsrc: # copying back a copy?
381 if repo.dirstate[dst] not in 'mn' and not dryrun:
382 if repo.dirstate[dst] not in 'mn' and not dryrun:
382 repo.dirstate.normallookup(dst)
383 repo.dirstate.normallookup(dst)
383 else:
384 else:
384 if repo.dirstate[origsrc] == 'a' and origsrc == src:
385 if repo.dirstate[origsrc] == 'a' and origsrc == src:
385 if not ui.quiet:
386 if not ui.quiet:
386 ui.warn(_("%s has not been committed yet, so no copy "
387 ui.warn(_("%s has not been committed yet, so no copy "
387 "data will be stored for %s.\n")
388 "data will be stored for %s.\n")
388 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
389 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
389 if repo.dirstate[dst] in '?r' and not dryrun:
390 if repo.dirstate[dst] in '?r' and not dryrun:
390 wctx.add([dst])
391 wctx.add([dst])
391 elif not dryrun:
392 elif not dryrun:
392 wctx.copy(origsrc, dst)
393 wctx.copy(origsrc, dst)
393
394
394 def copy(ui, repo, pats, opts, rename=False):
395 def copy(ui, repo, pats, opts, rename=False):
395 # called with the repo lock held
396 # called with the repo lock held
396 #
397 #
397 # hgsep => pathname that uses "/" to separate directories
398 # hgsep => pathname that uses "/" to separate directories
398 # ossep => pathname that uses os.sep to separate directories
399 # ossep => pathname that uses os.sep to separate directories
399 cwd = repo.getcwd()
400 cwd = repo.getcwd()
400 targets = {}
401 targets = {}
401 after = opts.get("after")
402 after = opts.get("after")
402 dryrun = opts.get("dry_run")
403 dryrun = opts.get("dry_run")
403 wctx = repo[None]
404 wctx = repo[None]
404
405
405 def walkpat(pat):
406 def walkpat(pat):
406 srcs = []
407 srcs = []
407 badstates = after and '?' or '?r'
408 badstates = after and '?' or '?r'
408 m = match(repo, [pat], opts, globbed=True)
409 m = match(repo, [pat], opts, globbed=True)
409 for abs in repo.walk(m):
410 for abs in repo.walk(m):
410 state = repo.dirstate[abs]
411 state = repo.dirstate[abs]
411 rel = m.rel(abs)
412 rel = m.rel(abs)
412 exact = m.exact(abs)
413 exact = m.exact(abs)
413 if state in badstates:
414 if state in badstates:
414 if exact and state == '?':
415 if exact and state == '?':
415 ui.warn(_('%s: not copying - file is not managed\n') % rel)
416 ui.warn(_('%s: not copying - file is not managed\n') % rel)
416 if exact and state == 'r':
417 if exact and state == 'r':
417 ui.warn(_('%s: not copying - file has been marked for'
418 ui.warn(_('%s: not copying - file has been marked for'
418 ' remove\n') % rel)
419 ' remove\n') % rel)
419 continue
420 continue
420 # abs: hgsep
421 # abs: hgsep
421 # rel: ossep
422 # rel: ossep
422 srcs.append((abs, rel, exact))
423 srcs.append((abs, rel, exact))
423 return srcs
424 return srcs
424
425
425 # abssrc: hgsep
426 # abssrc: hgsep
426 # relsrc: ossep
427 # relsrc: ossep
427 # otarget: ossep
428 # otarget: ossep
428 def copyfile(abssrc, relsrc, otarget, exact):
429 def copyfile(abssrc, relsrc, otarget, exact):
429 abstarget = util.canonpath(repo.root, cwd, otarget)
430 abstarget = util.canonpath(repo.root, cwd, otarget)
430 reltarget = repo.pathto(abstarget, cwd)
431 reltarget = repo.pathto(abstarget, cwd)
431 target = repo.wjoin(abstarget)
432 target = repo.wjoin(abstarget)
432 src = repo.wjoin(abssrc)
433 src = repo.wjoin(abssrc)
433 state = repo.dirstate[abstarget]
434 state = repo.dirstate[abstarget]
434
435
435 # check for collisions
436 # check for collisions
436 prevsrc = targets.get(abstarget)
437 prevsrc = targets.get(abstarget)
437 if prevsrc is not None:
438 if prevsrc is not None:
438 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
439 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
439 (reltarget, repo.pathto(abssrc, cwd),
440 (reltarget, repo.pathto(abssrc, cwd),
440 repo.pathto(prevsrc, cwd)))
441 repo.pathto(prevsrc, cwd)))
441 return
442 return
442
443
443 # check for overwrites
444 # check for overwrites
444 exists = os.path.lexists(target)
445 exists = os.path.lexists(target)
445 if not after and exists or after and state in 'mn':
446 if not after and exists or after and state in 'mn':
446 if not opts['force']:
447 if not opts['force']:
447 ui.warn(_('%s: not overwriting - file exists\n') %
448 ui.warn(_('%s: not overwriting - file exists\n') %
448 reltarget)
449 reltarget)
449 return
450 return
450
451
451 if after:
452 if after:
452 if not exists:
453 if not exists:
453 if rename:
454 if rename:
454 ui.warn(_('%s: not recording move - %s does not exist\n') %
455 ui.warn(_('%s: not recording move - %s does not exist\n') %
455 (relsrc, reltarget))
456 (relsrc, reltarget))
456 else:
457 else:
457 ui.warn(_('%s: not recording copy - %s does not exist\n') %
458 ui.warn(_('%s: not recording copy - %s does not exist\n') %
458 (relsrc, reltarget))
459 (relsrc, reltarget))
459 return
460 return
460 elif not dryrun:
461 elif not dryrun:
461 try:
462 try:
462 if exists:
463 if exists:
463 os.unlink(target)
464 os.unlink(target)
464 targetdir = os.path.dirname(target) or '.'
465 targetdir = os.path.dirname(target) or '.'
465 if not os.path.isdir(targetdir):
466 if not os.path.isdir(targetdir):
466 os.makedirs(targetdir)
467 os.makedirs(targetdir)
467 util.copyfile(src, target)
468 util.copyfile(src, target)
468 except IOError, inst:
469 except IOError, inst:
469 if inst.errno == errno.ENOENT:
470 if inst.errno == errno.ENOENT:
470 ui.warn(_('%s: deleted in working copy\n') % relsrc)
471 ui.warn(_('%s: deleted in working copy\n') % relsrc)
471 else:
472 else:
472 ui.warn(_('%s: cannot copy - %s\n') %
473 ui.warn(_('%s: cannot copy - %s\n') %
473 (relsrc, inst.strerror))
474 (relsrc, inst.strerror))
474 return True # report a failure
475 return True # report a failure
475
476
476 if ui.verbose or not exact:
477 if ui.verbose or not exact:
477 if rename:
478 if rename:
478 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
479 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
479 else:
480 else:
480 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
481 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
481
482
482 targets[abstarget] = abssrc
483 targets[abstarget] = abssrc
483
484
484 # fix up dirstate
485 # fix up dirstate
485 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
486 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
486 if rename and not dryrun:
487 if rename and not dryrun:
487 wctx.remove([abssrc], not after)
488 wctx.remove([abssrc], not after)
488
489
489 # pat: ossep
490 # pat: ossep
490 # dest ossep
491 # dest ossep
491 # srcs: list of (hgsep, hgsep, ossep, bool)
492 # srcs: list of (hgsep, hgsep, ossep, bool)
492 # return: function that takes hgsep and returns ossep
493 # return: function that takes hgsep and returns ossep
493 def targetpathfn(pat, dest, srcs):
494 def targetpathfn(pat, dest, srcs):
494 if os.path.isdir(pat):
495 if os.path.isdir(pat):
495 abspfx = util.canonpath(repo.root, cwd, pat)
496 abspfx = util.canonpath(repo.root, cwd, pat)
496 abspfx = util.localpath(abspfx)
497 abspfx = util.localpath(abspfx)
497 if destdirexists:
498 if destdirexists:
498 striplen = len(os.path.split(abspfx)[0])
499 striplen = len(os.path.split(abspfx)[0])
499 else:
500 else:
500 striplen = len(abspfx)
501 striplen = len(abspfx)
501 if striplen:
502 if striplen:
502 striplen += len(os.sep)
503 striplen += len(os.sep)
503 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
504 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
504 elif destdirexists:
505 elif destdirexists:
505 res = lambda p: os.path.join(dest,
506 res = lambda p: os.path.join(dest,
506 os.path.basename(util.localpath(p)))
507 os.path.basename(util.localpath(p)))
507 else:
508 else:
508 res = lambda p: dest
509 res = lambda p: dest
509 return res
510 return res
510
511
511 # pat: ossep
512 # pat: ossep
512 # dest ossep
513 # dest ossep
513 # srcs: list of (hgsep, hgsep, ossep, bool)
514 # srcs: list of (hgsep, hgsep, ossep, bool)
514 # return: function that takes hgsep and returns ossep
515 # return: function that takes hgsep and returns ossep
515 def targetpathafterfn(pat, dest, srcs):
516 def targetpathafterfn(pat, dest, srcs):
516 if matchmod.patkind(pat):
517 if matchmod.patkind(pat):
517 # a mercurial pattern
518 # a mercurial pattern
518 res = lambda p: os.path.join(dest,
519 res = lambda p: os.path.join(dest,
519 os.path.basename(util.localpath(p)))
520 os.path.basename(util.localpath(p)))
520 else:
521 else:
521 abspfx = util.canonpath(repo.root, cwd, pat)
522 abspfx = util.canonpath(repo.root, cwd, pat)
522 if len(abspfx) < len(srcs[0][0]):
523 if len(abspfx) < len(srcs[0][0]):
523 # A directory. Either the target path contains the last
524 # A directory. Either the target path contains the last
524 # component of the source path or it does not.
525 # component of the source path or it does not.
525 def evalpath(striplen):
526 def evalpath(striplen):
526 score = 0
527 score = 0
527 for s in srcs:
528 for s in srcs:
528 t = os.path.join(dest, util.localpath(s[0])[striplen:])
529 t = os.path.join(dest, util.localpath(s[0])[striplen:])
529 if os.path.lexists(t):
530 if os.path.lexists(t):
530 score += 1
531 score += 1
531 return score
532 return score
532
533
533 abspfx = util.localpath(abspfx)
534 abspfx = util.localpath(abspfx)
534 striplen = len(abspfx)
535 striplen = len(abspfx)
535 if striplen:
536 if striplen:
536 striplen += len(os.sep)
537 striplen += len(os.sep)
537 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
538 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
538 score = evalpath(striplen)
539 score = evalpath(striplen)
539 striplen1 = len(os.path.split(abspfx)[0])
540 striplen1 = len(os.path.split(abspfx)[0])
540 if striplen1:
541 if striplen1:
541 striplen1 += len(os.sep)
542 striplen1 += len(os.sep)
542 if evalpath(striplen1) > score:
543 if evalpath(striplen1) > score:
543 striplen = striplen1
544 striplen = striplen1
544 res = lambda p: os.path.join(dest,
545 res = lambda p: os.path.join(dest,
545 util.localpath(p)[striplen:])
546 util.localpath(p)[striplen:])
546 else:
547 else:
547 # a file
548 # a file
548 if destdirexists:
549 if destdirexists:
549 res = lambda p: os.path.join(dest,
550 res = lambda p: os.path.join(dest,
550 os.path.basename(util.localpath(p)))
551 os.path.basename(util.localpath(p)))
551 else:
552 else:
552 res = lambda p: dest
553 res = lambda p: dest
553 return res
554 return res
554
555
555
556
556 pats = expandpats(pats)
557 pats = expandpats(pats)
557 if not pats:
558 if not pats:
558 raise util.Abort(_('no source or destination specified'))
559 raise util.Abort(_('no source or destination specified'))
559 if len(pats) == 1:
560 if len(pats) == 1:
560 raise util.Abort(_('no destination specified'))
561 raise util.Abort(_('no destination specified'))
561 dest = pats.pop()
562 dest = pats.pop()
562 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
563 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
563 if not destdirexists:
564 if not destdirexists:
564 if len(pats) > 1 or matchmod.patkind(pats[0]):
565 if len(pats) > 1 or matchmod.patkind(pats[0]):
565 raise util.Abort(_('with multiple sources, destination must be an '
566 raise util.Abort(_('with multiple sources, destination must be an '
566 'existing directory'))
567 'existing directory'))
567 if util.endswithsep(dest):
568 if util.endswithsep(dest):
568 raise util.Abort(_('destination %s is not a directory') % dest)
569 raise util.Abort(_('destination %s is not a directory') % dest)
569
570
570 tfn = targetpathfn
571 tfn = targetpathfn
571 if after:
572 if after:
572 tfn = targetpathafterfn
573 tfn = targetpathafterfn
573 copylist = []
574 copylist = []
574 for pat in pats:
575 for pat in pats:
575 srcs = walkpat(pat)
576 srcs = walkpat(pat)
576 if not srcs:
577 if not srcs:
577 continue
578 continue
578 copylist.append((tfn(pat, dest, srcs), srcs))
579 copylist.append((tfn(pat, dest, srcs), srcs))
579 if not copylist:
580 if not copylist:
580 raise util.Abort(_('no files to copy'))
581 raise util.Abort(_('no files to copy'))
581
582
582 errors = 0
583 errors = 0
583 for targetpath, srcs in copylist:
584 for targetpath, srcs in copylist:
584 for abssrc, relsrc, exact in srcs:
585 for abssrc, relsrc, exact in srcs:
585 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
586 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
586 errors += 1
587 errors += 1
587
588
588 if errors:
589 if errors:
589 ui.warn(_('(consider using --after)\n'))
590 ui.warn(_('(consider using --after)\n'))
590
591
591 return errors != 0
592 return errors != 0
592
593
593 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
594 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
594 runargs=None, appendpid=False):
595 runargs=None, appendpid=False):
595 '''Run a command as a service.'''
596 '''Run a command as a service.'''
596
597
597 if opts['daemon'] and not opts['daemon_pipefds']:
598 if opts['daemon'] and not opts['daemon_pipefds']:
598 # Signal child process startup with file removal
599 # Signal child process startup with file removal
599 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
600 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
600 os.close(lockfd)
601 os.close(lockfd)
601 try:
602 try:
602 if not runargs:
603 if not runargs:
603 runargs = util.hgcmd() + sys.argv[1:]
604 runargs = util.hgcmd() + sys.argv[1:]
604 runargs.append('--daemon-pipefds=%s' % lockpath)
605 runargs.append('--daemon-pipefds=%s' % lockpath)
605 # Don't pass --cwd to the child process, because we've already
606 # Don't pass --cwd to the child process, because we've already
606 # changed directory.
607 # changed directory.
607 for i in xrange(1, len(runargs)):
608 for i in xrange(1, len(runargs)):
608 if runargs[i].startswith('--cwd='):
609 if runargs[i].startswith('--cwd='):
609 del runargs[i]
610 del runargs[i]
610 break
611 break
611 elif runargs[i].startswith('--cwd'):
612 elif runargs[i].startswith('--cwd'):
612 del runargs[i:i + 2]
613 del runargs[i:i + 2]
613 break
614 break
614 def condfn():
615 def condfn():
615 return not os.path.exists(lockpath)
616 return not os.path.exists(lockpath)
616 pid = util.rundetached(runargs, condfn)
617 pid = util.rundetached(runargs, condfn)
617 if pid < 0:
618 if pid < 0:
618 raise util.Abort(_('child process failed to start'))
619 raise util.Abort(_('child process failed to start'))
619 finally:
620 finally:
620 try:
621 try:
621 os.unlink(lockpath)
622 os.unlink(lockpath)
622 except OSError, e:
623 except OSError, e:
623 if e.errno != errno.ENOENT:
624 if e.errno != errno.ENOENT:
624 raise
625 raise
625 if parentfn:
626 if parentfn:
626 return parentfn(pid)
627 return parentfn(pid)
627 else:
628 else:
628 return
629 return
629
630
630 if initfn:
631 if initfn:
631 initfn()
632 initfn()
632
633
633 if opts['pid_file']:
634 if opts['pid_file']:
634 mode = appendpid and 'a' or 'w'
635 mode = appendpid and 'a' or 'w'
635 fp = open(opts['pid_file'], mode)
636 fp = open(opts['pid_file'], mode)
636 fp.write(str(os.getpid()) + '\n')
637 fp.write(str(os.getpid()) + '\n')
637 fp.close()
638 fp.close()
638
639
639 if opts['daemon_pipefds']:
640 if opts['daemon_pipefds']:
640 lockpath = opts['daemon_pipefds']
641 lockpath = opts['daemon_pipefds']
641 try:
642 try:
642 os.setsid()
643 os.setsid()
643 except AttributeError:
644 except AttributeError:
644 pass
645 pass
645 os.unlink(lockpath)
646 os.unlink(lockpath)
646 util.hidewindow()
647 util.hidewindow()
647 sys.stdout.flush()
648 sys.stdout.flush()
648 sys.stderr.flush()
649 sys.stderr.flush()
649
650
650 nullfd = os.open(util.nulldev, os.O_RDWR)
651 nullfd = os.open(util.nulldev, os.O_RDWR)
651 logfilefd = nullfd
652 logfilefd = nullfd
652 if logfile:
653 if logfile:
653 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
654 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
654 os.dup2(nullfd, 0)
655 os.dup2(nullfd, 0)
655 os.dup2(logfilefd, 1)
656 os.dup2(logfilefd, 1)
656 os.dup2(logfilefd, 2)
657 os.dup2(logfilefd, 2)
657 if nullfd not in (0, 1, 2):
658 if nullfd not in (0, 1, 2):
658 os.close(nullfd)
659 os.close(nullfd)
659 if logfile and logfilefd not in (0, 1, 2):
660 if logfile and logfilefd not in (0, 1, 2):
660 os.close(logfilefd)
661 os.close(logfilefd)
661
662
662 if runfn:
663 if runfn:
663 return runfn()
664 return runfn()
664
665
665 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
666 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
666 opts=None):
667 opts=None):
667 '''export changesets as hg patches.'''
668 '''export changesets as hg patches.'''
668
669
669 total = len(revs)
670 total = len(revs)
670 revwidth = max([len(str(rev)) for rev in revs])
671 revwidth = max([len(str(rev)) for rev in revs])
671
672
672 def single(rev, seqno, fp):
673 def single(rev, seqno, fp):
673 ctx = repo[rev]
674 ctx = repo[rev]
674 node = ctx.node()
675 node = ctx.node()
675 parents = [p.node() for p in ctx.parents() if p]
676 parents = [p.node() for p in ctx.parents() if p]
676 branch = ctx.branch()
677 branch = ctx.branch()
677 if switch_parent:
678 if switch_parent:
678 parents.reverse()
679 parents.reverse()
679 prev = (parents and parents[0]) or nullid
680 prev = (parents and parents[0]) or nullid
680
681
681 if not fp:
682 if not fp:
682 fp = make_file(repo, template, node, total=total, seqno=seqno,
683 fp = make_file(repo, template, node, total=total, seqno=seqno,
683 revwidth=revwidth, mode='ab')
684 revwidth=revwidth, mode='ab')
684 if fp != sys.stdout and hasattr(fp, 'name'):
685 if fp != sys.stdout and hasattr(fp, 'name'):
685 repo.ui.note("%s\n" % fp.name)
686 repo.ui.note("%s\n" % fp.name)
686
687
687 fp.write("# HG changeset patch\n")
688 fp.write("# HG changeset patch\n")
688 fp.write("# User %s\n" % ctx.user())
689 fp.write("# User %s\n" % ctx.user())
689 fp.write("# Date %d %d\n" % ctx.date())
690 fp.write("# Date %d %d\n" % ctx.date())
690 if branch and branch != 'default':
691 if branch and branch != 'default':
691 fp.write("# Branch %s\n" % branch)
692 fp.write("# Branch %s\n" % branch)
692 fp.write("# Node ID %s\n" % hex(node))
693 fp.write("# Node ID %s\n" % hex(node))
693 fp.write("# Parent %s\n" % hex(prev))
694 fp.write("# Parent %s\n" % hex(prev))
694 if len(parents) > 1:
695 if len(parents) > 1:
695 fp.write("# Parent %s\n" % hex(parents[1]))
696 fp.write("# Parent %s\n" % hex(parents[1]))
696 fp.write(ctx.description().rstrip())
697 fp.write(ctx.description().rstrip())
697 fp.write("\n\n")
698 fp.write("\n\n")
698
699
699 for chunk in patch.diff(repo, prev, node, opts=opts):
700 for chunk in patch.diff(repo, prev, node, opts=opts):
700 fp.write(chunk)
701 fp.write(chunk)
701
702
702 fp.flush()
703 fp.flush()
703
704
704 for seqno, rev in enumerate(revs):
705 for seqno, rev in enumerate(revs):
705 single(rev, seqno + 1, fp)
706 single(rev, seqno + 1, fp)
706
707
707 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
708 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
708 changes=None, stat=False, fp=None, prefix='',
709 changes=None, stat=False, fp=None, prefix='',
709 listsubrepos=False):
710 listsubrepos=False):
710 '''show diff or diffstat.'''
711 '''show diff or diffstat.'''
711 if fp is None:
712 if fp is None:
712 write = ui.write
713 write = ui.write
713 else:
714 else:
714 def write(s, **kw):
715 def write(s, **kw):
715 fp.write(s)
716 fp.write(s)
716
717
717 if stat:
718 if stat:
718 diffopts = diffopts.copy(context=0)
719 diffopts = diffopts.copy(context=0)
719 width = 80
720 width = 80
720 if not ui.plain():
721 if not ui.plain():
721 width = ui.termwidth()
722 width = ui.termwidth()
722 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
723 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
723 prefix=prefix)
724 prefix=prefix)
724 for chunk, label in patch.diffstatui(util.iterlines(chunks),
725 for chunk, label in patch.diffstatui(util.iterlines(chunks),
725 width=width,
726 width=width,
726 git=diffopts.git):
727 git=diffopts.git):
727 write(chunk, label=label)
728 write(chunk, label=label)
728 else:
729 else:
729 for chunk, label in patch.diffui(repo, node1, node2, match,
730 for chunk, label in patch.diffui(repo, node1, node2, match,
730 changes, diffopts, prefix=prefix):
731 changes, diffopts, prefix=prefix):
731 write(chunk, label=label)
732 write(chunk, label=label)
732
733
733 if listsubrepos:
734 if listsubrepos:
734 ctx1 = repo[node1]
735 ctx1 = repo[node1]
735 ctx2 = repo[node2]
736 ctx2 = repo[node2]
736 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
737 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
737 if node2 is not None:
738 if node2 is not None:
738 node2 = ctx2.substate[subpath][1]
739 node2 = ctx2.substate[subpath][1]
739 submatch = matchmod.narrowmatcher(subpath, match)
740 submatch = matchmod.narrowmatcher(subpath, match)
740 sub.diff(diffopts, node2, submatch, changes=changes,
741 sub.diff(diffopts, node2, submatch, changes=changes,
741 stat=stat, fp=fp, prefix=prefix)
742 stat=stat, fp=fp, prefix=prefix)
742
743
743 class changeset_printer(object):
744 class changeset_printer(object):
744 '''show changeset information when templating not requested.'''
745 '''show changeset information when templating not requested.'''
745
746
746 def __init__(self, ui, repo, patch, diffopts, buffered):
747 def __init__(self, ui, repo, patch, diffopts, buffered):
747 self.ui = ui
748 self.ui = ui
748 self.repo = repo
749 self.repo = repo
749 self.buffered = buffered
750 self.buffered = buffered
750 self.patch = patch
751 self.patch = patch
751 self.diffopts = diffopts
752 self.diffopts = diffopts
752 self.header = {}
753 self.header = {}
753 self.hunk = {}
754 self.hunk = {}
754 self.lastheader = None
755 self.lastheader = None
755 self.footer = None
756 self.footer = None
756
757
757 def flush(self, rev):
758 def flush(self, rev):
758 if rev in self.header:
759 if rev in self.header:
759 h = self.header[rev]
760 h = self.header[rev]
760 if h != self.lastheader:
761 if h != self.lastheader:
761 self.lastheader = h
762 self.lastheader = h
762 self.ui.write(h)
763 self.ui.write(h)
763 del self.header[rev]
764 del self.header[rev]
764 if rev in self.hunk:
765 if rev in self.hunk:
765 self.ui.write(self.hunk[rev])
766 self.ui.write(self.hunk[rev])
766 del self.hunk[rev]
767 del self.hunk[rev]
767 return 1
768 return 1
768 return 0
769 return 0
769
770
770 def close(self):
771 def close(self):
771 if self.footer:
772 if self.footer:
772 self.ui.write(self.footer)
773 self.ui.write(self.footer)
773
774
774 def show(self, ctx, copies=None, matchfn=None, **props):
775 def show(self, ctx, copies=None, matchfn=None, **props):
775 if self.buffered:
776 if self.buffered:
776 self.ui.pushbuffer()
777 self.ui.pushbuffer()
777 self._show(ctx, copies, matchfn, props)
778 self._show(ctx, copies, matchfn, props)
778 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
779 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
779 else:
780 else:
780 self._show(ctx, copies, matchfn, props)
781 self._show(ctx, copies, matchfn, props)
781
782
782 def _show(self, ctx, copies, matchfn, props):
783 def _show(self, ctx, copies, matchfn, props):
783 '''show a single changeset or file revision'''
784 '''show a single changeset or file revision'''
784 changenode = ctx.node()
785 changenode = ctx.node()
785 rev = ctx.rev()
786 rev = ctx.rev()
786
787
787 if self.ui.quiet:
788 if self.ui.quiet:
788 self.ui.write("%d:%s\n" % (rev, short(changenode)),
789 self.ui.write("%d:%s\n" % (rev, short(changenode)),
789 label='log.node')
790 label='log.node')
790 return
791 return
791
792
792 log = self.repo.changelog
793 log = self.repo.changelog
793 date = util.datestr(ctx.date())
794 date = util.datestr(ctx.date())
794
795
795 hexfunc = self.ui.debugflag and hex or short
796 hexfunc = self.ui.debugflag and hex or short
796
797
797 parents = [(p, hexfunc(log.node(p)))
798 parents = [(p, hexfunc(log.node(p)))
798 for p in self._meaningful_parentrevs(log, rev)]
799 for p in self._meaningful_parentrevs(log, rev)]
799
800
800 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
801 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
801 label='log.changeset')
802 label='log.changeset')
802
803
803 branch = ctx.branch()
804 branch = ctx.branch()
804 # don't show the default branch name
805 # don't show the default branch name
805 if branch != 'default':
806 if branch != 'default':
806 self.ui.write(_("branch: %s\n") % branch,
807 self.ui.write(_("branch: %s\n") % branch,
807 label='log.branch')
808 label='log.branch')
808 for tag in self.repo.nodetags(changenode):
809 for tag in self.repo.nodetags(changenode):
809 self.ui.write(_("tag: %s\n") % tag,
810 self.ui.write(_("tag: %s\n") % tag,
810 label='log.tag')
811 label='log.tag')
811 for parent in parents:
812 for parent in parents:
812 self.ui.write(_("parent: %d:%s\n") % parent,
813 self.ui.write(_("parent: %d:%s\n") % parent,
813 label='log.parent')
814 label='log.parent')
814
815
815 if self.ui.debugflag:
816 if self.ui.debugflag:
816 mnode = ctx.manifestnode()
817 mnode = ctx.manifestnode()
817 self.ui.write(_("manifest: %d:%s\n") %
818 self.ui.write(_("manifest: %d:%s\n") %
818 (self.repo.manifest.rev(mnode), hex(mnode)),
819 (self.repo.manifest.rev(mnode), hex(mnode)),
819 label='ui.debug log.manifest')
820 label='ui.debug log.manifest')
820 self.ui.write(_("user: %s\n") % ctx.user(),
821 self.ui.write(_("user: %s\n") % ctx.user(),
821 label='log.user')
822 label='log.user')
822 self.ui.write(_("date: %s\n") % date,
823 self.ui.write(_("date: %s\n") % date,
823 label='log.date')
824 label='log.date')
824
825
825 if self.ui.debugflag:
826 if self.ui.debugflag:
826 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
827 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
827 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
828 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
828 files):
829 files):
829 if value:
830 if value:
830 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
831 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
831 label='ui.debug log.files')
832 label='ui.debug log.files')
832 elif ctx.files() and self.ui.verbose:
833 elif ctx.files() and self.ui.verbose:
833 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
834 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
834 label='ui.note log.files')
835 label='ui.note log.files')
835 if copies and self.ui.verbose:
836 if copies and self.ui.verbose:
836 copies = ['%s (%s)' % c for c in copies]
837 copies = ['%s (%s)' % c for c in copies]
837 self.ui.write(_("copies: %s\n") % ' '.join(copies),
838 self.ui.write(_("copies: %s\n") % ' '.join(copies),
838 label='ui.note log.copies')
839 label='ui.note log.copies')
839
840
840 extra = ctx.extra()
841 extra = ctx.extra()
841 if extra and self.ui.debugflag:
842 if extra and self.ui.debugflag:
842 for key, value in sorted(extra.items()):
843 for key, value in sorted(extra.items()):
843 self.ui.write(_("extra: %s=%s\n")
844 self.ui.write(_("extra: %s=%s\n")
844 % (key, value.encode('string_escape')),
845 % (key, value.encode('string_escape')),
845 label='ui.debug log.extra')
846 label='ui.debug log.extra')
846
847
847 description = ctx.description().strip()
848 description = ctx.description().strip()
848 if description:
849 if description:
849 if self.ui.verbose:
850 if self.ui.verbose:
850 self.ui.write(_("description:\n"),
851 self.ui.write(_("description:\n"),
851 label='ui.note log.description')
852 label='ui.note log.description')
852 self.ui.write(description,
853 self.ui.write(description,
853 label='ui.note log.description')
854 label='ui.note log.description')
854 self.ui.write("\n\n")
855 self.ui.write("\n\n")
855 else:
856 else:
856 self.ui.write(_("summary: %s\n") %
857 self.ui.write(_("summary: %s\n") %
857 description.splitlines()[0],
858 description.splitlines()[0],
858 label='log.summary')
859 label='log.summary')
859 self.ui.write("\n")
860 self.ui.write("\n")
860
861
861 self.showpatch(changenode, matchfn)
862 self.showpatch(changenode, matchfn)
862
863
863 def showpatch(self, node, matchfn):
864 def showpatch(self, node, matchfn):
864 if not matchfn:
865 if not matchfn:
865 matchfn = self.patch
866 matchfn = self.patch
866 if matchfn:
867 if matchfn:
867 stat = self.diffopts.get('stat')
868 stat = self.diffopts.get('stat')
868 diff = self.diffopts.get('patch')
869 diff = self.diffopts.get('patch')
869 diffopts = patch.diffopts(self.ui, self.diffopts)
870 diffopts = patch.diffopts(self.ui, self.diffopts)
870 prev = self.repo.changelog.parents(node)[0]
871 prev = self.repo.changelog.parents(node)[0]
871 if stat:
872 if stat:
872 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
873 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
873 match=matchfn, stat=True)
874 match=matchfn, stat=True)
874 if diff:
875 if diff:
875 if stat:
876 if stat:
876 self.ui.write("\n")
877 self.ui.write("\n")
877 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
878 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
878 match=matchfn, stat=False)
879 match=matchfn, stat=False)
879 self.ui.write("\n")
880 self.ui.write("\n")
880
881
881 def _meaningful_parentrevs(self, log, rev):
882 def _meaningful_parentrevs(self, log, rev):
882 """Return list of meaningful (or all if debug) parentrevs for rev.
883 """Return list of meaningful (or all if debug) parentrevs for rev.
883
884
884 For merges (two non-nullrev revisions) both parents are meaningful.
885 For merges (two non-nullrev revisions) both parents are meaningful.
885 Otherwise the first parent revision is considered meaningful if it
886 Otherwise the first parent revision is considered meaningful if it
886 is not the preceding revision.
887 is not the preceding revision.
887 """
888 """
888 parents = log.parentrevs(rev)
889 parents = log.parentrevs(rev)
889 if not self.ui.debugflag and parents[1] == nullrev:
890 if not self.ui.debugflag and parents[1] == nullrev:
890 if parents[0] >= rev - 1:
891 if parents[0] >= rev - 1:
891 parents = []
892 parents = []
892 else:
893 else:
893 parents = [parents[0]]
894 parents = [parents[0]]
894 return parents
895 return parents
895
896
896
897
897 class changeset_templater(changeset_printer):
898 class changeset_templater(changeset_printer):
898 '''format changeset information.'''
899 '''format changeset information.'''
899
900
900 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
901 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
901 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
902 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
902 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
903 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
903 defaulttempl = {
904 defaulttempl = {
904 'parent': '{rev}:{node|formatnode} ',
905 'parent': '{rev}:{node|formatnode} ',
905 'manifest': '{rev}:{node|formatnode}',
906 'manifest': '{rev}:{node|formatnode}',
906 'file_copy': '{name} ({source})',
907 'file_copy': '{name} ({source})',
907 'extra': '{key}={value|stringescape}'
908 'extra': '{key}={value|stringescape}'
908 }
909 }
909 # filecopy is preserved for compatibility reasons
910 # filecopy is preserved for compatibility reasons
910 defaulttempl['filecopy'] = defaulttempl['file_copy']
911 defaulttempl['filecopy'] = defaulttempl['file_copy']
911 self.t = templater.templater(mapfile, {'formatnode': formatnode},
912 self.t = templater.templater(mapfile, {'formatnode': formatnode},
912 cache=defaulttempl)
913 cache=defaulttempl)
913 self.cache = {}
914 self.cache = {}
914
915
915 def use_template(self, t):
916 def use_template(self, t):
916 '''set template string to use'''
917 '''set template string to use'''
917 self.t.cache['changeset'] = t
918 self.t.cache['changeset'] = t
918
919
919 def _meaningful_parentrevs(self, ctx):
920 def _meaningful_parentrevs(self, ctx):
920 """Return list of meaningful (or all if debug) parentrevs for rev.
921 """Return list of meaningful (or all if debug) parentrevs for rev.
921 """
922 """
922 parents = ctx.parents()
923 parents = ctx.parents()
923 if len(parents) > 1:
924 if len(parents) > 1:
924 return parents
925 return parents
925 if self.ui.debugflag:
926 if self.ui.debugflag:
926 return [parents[0], self.repo['null']]
927 return [parents[0], self.repo['null']]
927 if parents[0].rev() >= ctx.rev() - 1:
928 if parents[0].rev() >= ctx.rev() - 1:
928 return []
929 return []
929 return parents
930 return parents
930
931
931 def _show(self, ctx, copies, matchfn, props):
932 def _show(self, ctx, copies, matchfn, props):
932 '''show a single changeset or file revision'''
933 '''show a single changeset or file revision'''
933
934
934 showlist = templatekw.showlist
935 showlist = templatekw.showlist
935
936
936 # showparents() behaviour depends on ui trace level which
937 # showparents() behaviour depends on ui trace level which
937 # causes unexpected behaviours at templating level and makes
938 # causes unexpected behaviours at templating level and makes
938 # it harder to extract it in a standalone function. Its
939 # it harder to extract it in a standalone function. Its
939 # behaviour cannot be changed so leave it here for now.
940 # behaviour cannot be changed so leave it here for now.
940 def showparents(**args):
941 def showparents(**args):
941 ctx = args['ctx']
942 ctx = args['ctx']
942 parents = [[('rev', p.rev()), ('node', p.hex())]
943 parents = [[('rev', p.rev()), ('node', p.hex())]
943 for p in self._meaningful_parentrevs(ctx)]
944 for p in self._meaningful_parentrevs(ctx)]
944 return showlist('parent', parents, **args)
945 return showlist('parent', parents, **args)
945
946
946 props = props.copy()
947 props = props.copy()
947 props.update(templatekw.keywords)
948 props.update(templatekw.keywords)
948 props['parents'] = showparents
949 props['parents'] = showparents
949 props['templ'] = self.t
950 props['templ'] = self.t
950 props['ctx'] = ctx
951 props['ctx'] = ctx
951 props['repo'] = self.repo
952 props['repo'] = self.repo
952 props['revcache'] = {'copies': copies}
953 props['revcache'] = {'copies': copies}
953 props['cache'] = self.cache
954 props['cache'] = self.cache
954
955
955 # find correct templates for current mode
956 # find correct templates for current mode
956
957
957 tmplmodes = [
958 tmplmodes = [
958 (True, None),
959 (True, None),
959 (self.ui.verbose, 'verbose'),
960 (self.ui.verbose, 'verbose'),
960 (self.ui.quiet, 'quiet'),
961 (self.ui.quiet, 'quiet'),
961 (self.ui.debugflag, 'debug'),
962 (self.ui.debugflag, 'debug'),
962 ]
963 ]
963
964
964 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
965 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
965 for mode, postfix in tmplmodes:
966 for mode, postfix in tmplmodes:
966 for type in types:
967 for type in types:
967 cur = postfix and ('%s_%s' % (type, postfix)) or type
968 cur = postfix and ('%s_%s' % (type, postfix)) or type
968 if mode and cur in self.t:
969 if mode and cur in self.t:
969 types[type] = cur
970 types[type] = cur
970
971
971 try:
972 try:
972
973
973 # write header
974 # write header
974 if types['header']:
975 if types['header']:
975 h = templater.stringify(self.t(types['header'], **props))
976 h = templater.stringify(self.t(types['header'], **props))
976 if self.buffered:
977 if self.buffered:
977 self.header[ctx.rev()] = h
978 self.header[ctx.rev()] = h
978 else:
979 else:
979 if self.lastheader != h:
980 if self.lastheader != h:
980 self.lastheader = h
981 self.lastheader = h
981 self.ui.write(h)
982 self.ui.write(h)
982
983
983 # write changeset metadata, then patch if requested
984 # write changeset metadata, then patch if requested
984 key = types['changeset']
985 key = types['changeset']
985 self.ui.write(templater.stringify(self.t(key, **props)))
986 self.ui.write(templater.stringify(self.t(key, **props)))
986 self.showpatch(ctx.node(), matchfn)
987 self.showpatch(ctx.node(), matchfn)
987
988
988 if types['footer']:
989 if types['footer']:
989 if not self.footer:
990 if not self.footer:
990 self.footer = templater.stringify(self.t(types['footer'],
991 self.footer = templater.stringify(self.t(types['footer'],
991 **props))
992 **props))
992
993
993 except KeyError, inst:
994 except KeyError, inst:
994 msg = _("%s: no key named '%s'")
995 msg = _("%s: no key named '%s'")
995 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
996 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
996 except SyntaxError, inst:
997 except SyntaxError, inst:
997 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
998 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
998
999
999 def show_changeset(ui, repo, opts, buffered=False):
1000 def show_changeset(ui, repo, opts, buffered=False):
1000 """show one changeset using template or regular display.
1001 """show one changeset using template or regular display.
1001
1002
1002 Display format will be the first non-empty hit of:
1003 Display format will be the first non-empty hit of:
1003 1. option 'template'
1004 1. option 'template'
1004 2. option 'style'
1005 2. option 'style'
1005 3. [ui] setting 'logtemplate'
1006 3. [ui] setting 'logtemplate'
1006 4. [ui] setting 'style'
1007 4. [ui] setting 'style'
1007 If all of these values are either the unset or the empty string,
1008 If all of these values are either the unset or the empty string,
1008 regular display via changeset_printer() is done.
1009 regular display via changeset_printer() is done.
1009 """
1010 """
1010 # options
1011 # options
1011 patch = False
1012 patch = False
1012 if opts.get('patch') or opts.get('stat'):
1013 if opts.get('patch') or opts.get('stat'):
1013 patch = matchall(repo)
1014 patch = matchall(repo)
1014
1015
1015 tmpl = opts.get('template')
1016 tmpl = opts.get('template')
1016 style = None
1017 style = None
1017 if tmpl:
1018 if tmpl:
1018 tmpl = templater.parsestring(tmpl, quoted=False)
1019 tmpl = templater.parsestring(tmpl, quoted=False)
1019 else:
1020 else:
1020 style = opts.get('style')
1021 style = opts.get('style')
1021
1022
1022 # ui settings
1023 # ui settings
1023 if not (tmpl or style):
1024 if not (tmpl or style):
1024 tmpl = ui.config('ui', 'logtemplate')
1025 tmpl = ui.config('ui', 'logtemplate')
1025 if tmpl:
1026 if tmpl:
1026 tmpl = templater.parsestring(tmpl)
1027 tmpl = templater.parsestring(tmpl)
1027 else:
1028 else:
1028 style = util.expandpath(ui.config('ui', 'style', ''))
1029 style = util.expandpath(ui.config('ui', 'style', ''))
1029
1030
1030 if not (tmpl or style):
1031 if not (tmpl or style):
1031 return changeset_printer(ui, repo, patch, opts, buffered)
1032 return changeset_printer(ui, repo, patch, opts, buffered)
1032
1033
1033 mapfile = None
1034 mapfile = None
1034 if style and not tmpl:
1035 if style and not tmpl:
1035 mapfile = style
1036 mapfile = style
1036 if not os.path.split(mapfile)[0]:
1037 if not os.path.split(mapfile)[0]:
1037 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1038 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1038 or templater.templatepath(mapfile))
1039 or templater.templatepath(mapfile))
1039 if mapname:
1040 if mapname:
1040 mapfile = mapname
1041 mapfile = mapname
1041
1042
1042 try:
1043 try:
1043 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1044 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1044 except SyntaxError, inst:
1045 except SyntaxError, inst:
1045 raise util.Abort(inst.args[0])
1046 raise util.Abort(inst.args[0])
1046 if tmpl:
1047 if tmpl:
1047 t.use_template(tmpl)
1048 t.use_template(tmpl)
1048 return t
1049 return t
1049
1050
1050 def finddate(ui, repo, date):
1051 def finddate(ui, repo, date):
1051 """Find the tipmost changeset that matches the given date spec"""
1052 """Find the tipmost changeset that matches the given date spec"""
1052
1053
1053 df = util.matchdate(date)
1054 df = util.matchdate(date)
1054 m = matchall(repo)
1055 m = matchall(repo)
1055 results = {}
1056 results = {}
1056
1057
1057 def prep(ctx, fns):
1058 def prep(ctx, fns):
1058 d = ctx.date()
1059 d = ctx.date()
1059 if df(d[0]):
1060 if df(d[0]):
1060 results[ctx.rev()] = d
1061 results[ctx.rev()] = d
1061
1062
1062 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1063 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1063 rev = ctx.rev()
1064 rev = ctx.rev()
1064 if rev in results:
1065 if rev in results:
1065 ui.status(_("Found revision %s from %s\n") %
1066 ui.status(_("Found revision %s from %s\n") %
1066 (rev, util.datestr(results[rev])))
1067 (rev, util.datestr(results[rev])))
1067 return str(rev)
1068 return str(rev)
1068
1069
1069 raise util.Abort(_("revision matching date not found"))
1070 raise util.Abort(_("revision matching date not found"))
1070
1071
1071 def walkchangerevs(repo, match, opts, prepare):
1072 def walkchangerevs(repo, match, opts, prepare):
1072 '''Iterate over files and the revs in which they changed.
1073 '''Iterate over files and the revs in which they changed.
1073
1074
1074 Callers most commonly need to iterate backwards over the history
1075 Callers most commonly need to iterate backwards over the history
1075 in which they are interested. Doing so has awful (quadratic-looking)
1076 in which they are interested. Doing so has awful (quadratic-looking)
1076 performance, so we use iterators in a "windowed" way.
1077 performance, so we use iterators in a "windowed" way.
1077
1078
1078 We walk a window of revisions in the desired order. Within the
1079 We walk a window of revisions in the desired order. Within the
1079 window, we first walk forwards to gather data, then in the desired
1080 window, we first walk forwards to gather data, then in the desired
1080 order (usually backwards) to display it.
1081 order (usually backwards) to display it.
1081
1082
1082 This function returns an iterator yielding contexts. Before
1083 This function returns an iterator yielding contexts. Before
1083 yielding each context, the iterator will first call the prepare
1084 yielding each context, the iterator will first call the prepare
1084 function on each context in the window in forward order.'''
1085 function on each context in the window in forward order.'''
1085
1086
1086 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1087 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1087 if start < end:
1088 if start < end:
1088 while start < end:
1089 while start < end:
1089 yield start, min(windowsize, end - start)
1090 yield start, min(windowsize, end - start)
1090 start += windowsize
1091 start += windowsize
1091 if windowsize < sizelimit:
1092 if windowsize < sizelimit:
1092 windowsize *= 2
1093 windowsize *= 2
1093 else:
1094 else:
1094 while start > end:
1095 while start > end:
1095 yield start, min(windowsize, start - end - 1)
1096 yield start, min(windowsize, start - end - 1)
1096 start -= windowsize
1097 start -= windowsize
1097 if windowsize < sizelimit:
1098 if windowsize < sizelimit:
1098 windowsize *= 2
1099 windowsize *= 2
1099
1100
1100 follow = opts.get('follow') or opts.get('follow_first')
1101 follow = opts.get('follow') or opts.get('follow_first')
1101
1102
1102 if not len(repo):
1103 if not len(repo):
1103 return []
1104 return []
1104
1105
1105 if follow:
1106 if follow:
1106 defrange = '%s:0' % repo['.'].rev()
1107 defrange = '%s:0' % repo['.'].rev()
1107 else:
1108 else:
1108 defrange = '-1:0'
1109 defrange = '-1:0'
1109 revs = revrange(repo, opts['rev'] or [defrange])
1110 revs = revrange(repo, opts['rev'] or [defrange])
1110 if not revs:
1111 if not revs:
1111 return []
1112 return []
1112 wanted = set()
1113 wanted = set()
1113 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1114 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1114 fncache = {}
1115 fncache = {}
1115 change = util.cachefunc(repo.changectx)
1116 change = util.cachefunc(repo.changectx)
1116
1117
1117 # First step is to fill wanted, the set of revisions that we want to yield.
1118 # First step is to fill wanted, the set of revisions that we want to yield.
1118 # When it does not induce extra cost, we also fill fncache for revisions in
1119 # When it does not induce extra cost, we also fill fncache for revisions in
1119 # wanted: a cache of filenames that were changed (ctx.files()) and that
1120 # wanted: a cache of filenames that were changed (ctx.files()) and that
1120 # match the file filtering conditions.
1121 # match the file filtering conditions.
1121
1122
1122 if not slowpath and not match.files():
1123 if not slowpath and not match.files():
1123 # No files, no patterns. Display all revs.
1124 # No files, no patterns. Display all revs.
1124 wanted = set(revs)
1125 wanted = set(revs)
1125 copies = []
1126 copies = []
1126
1127
1127 if not slowpath:
1128 if not slowpath:
1128 # We only have to read through the filelog to find wanted revisions
1129 # We only have to read through the filelog to find wanted revisions
1129
1130
1130 minrev, maxrev = min(revs), max(revs)
1131 minrev, maxrev = min(revs), max(revs)
1131 def filerevgen(filelog, last):
1132 def filerevgen(filelog, last):
1132 """
1133 """
1133 Only files, no patterns. Check the history of each file.
1134 Only files, no patterns. Check the history of each file.
1134
1135
1135 Examines filelog entries within minrev, maxrev linkrev range
1136 Examines filelog entries within minrev, maxrev linkrev range
1136 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1137 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1137 tuples in backwards order
1138 tuples in backwards order
1138 """
1139 """
1139 cl_count = len(repo)
1140 cl_count = len(repo)
1140 revs = []
1141 revs = []
1141 for j in xrange(0, last + 1):
1142 for j in xrange(0, last + 1):
1142 linkrev = filelog.linkrev(j)
1143 linkrev = filelog.linkrev(j)
1143 if linkrev < minrev:
1144 if linkrev < minrev:
1144 continue
1145 continue
1145 # only yield rev for which we have the changelog, it can
1146 # only yield rev for which we have the changelog, it can
1146 # happen while doing "hg log" during a pull or commit
1147 # happen while doing "hg log" during a pull or commit
1147 if linkrev >= cl_count:
1148 if linkrev >= cl_count:
1148 break
1149 break
1149
1150
1150 parentlinkrevs = []
1151 parentlinkrevs = []
1151 for p in filelog.parentrevs(j):
1152 for p in filelog.parentrevs(j):
1152 if p != nullrev:
1153 if p != nullrev:
1153 parentlinkrevs.append(filelog.linkrev(p))
1154 parentlinkrevs.append(filelog.linkrev(p))
1154 n = filelog.node(j)
1155 n = filelog.node(j)
1155 revs.append((linkrev, parentlinkrevs,
1156 revs.append((linkrev, parentlinkrevs,
1156 follow and filelog.renamed(n)))
1157 follow and filelog.renamed(n)))
1157
1158
1158 return reversed(revs)
1159 return reversed(revs)
1159 def iterfiles():
1160 def iterfiles():
1160 for filename in match.files():
1161 for filename in match.files():
1161 yield filename, None
1162 yield filename, None
1162 for filename_node in copies:
1163 for filename_node in copies:
1163 yield filename_node
1164 yield filename_node
1164 for file_, node in iterfiles():
1165 for file_, node in iterfiles():
1165 filelog = repo.file(file_)
1166 filelog = repo.file(file_)
1166 if not len(filelog):
1167 if not len(filelog):
1167 if node is None:
1168 if node is None:
1168 # A zero count may be a directory or deleted file, so
1169 # A zero count may be a directory or deleted file, so
1169 # try to find matching entries on the slow path.
1170 # try to find matching entries on the slow path.
1170 if follow:
1171 if follow:
1171 raise util.Abort(
1172 raise util.Abort(
1172 _('cannot follow nonexistent file: "%s"') % file_)
1173 _('cannot follow nonexistent file: "%s"') % file_)
1173 slowpath = True
1174 slowpath = True
1174 break
1175 break
1175 else:
1176 else:
1176 continue
1177 continue
1177
1178
1178 if node is None:
1179 if node is None:
1179 last = len(filelog) - 1
1180 last = len(filelog) - 1
1180 else:
1181 else:
1181 last = filelog.rev(node)
1182 last = filelog.rev(node)
1182
1183
1183
1184
1184 # keep track of all ancestors of the file
1185 # keep track of all ancestors of the file
1185 ancestors = set([filelog.linkrev(last)])
1186 ancestors = set([filelog.linkrev(last)])
1186
1187
1187 # iterate from latest to oldest revision
1188 # iterate from latest to oldest revision
1188 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1189 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1189 if not follow:
1190 if not follow:
1190 if rev > maxrev:
1191 if rev > maxrev:
1191 continue
1192 continue
1192 else:
1193 else:
1193 # Note that last might not be the first interesting
1194 # Note that last might not be the first interesting
1194 # rev to us:
1195 # rev to us:
1195 # if the file has been changed after maxrev, we'll
1196 # if the file has been changed after maxrev, we'll
1196 # have linkrev(last) > maxrev, and we still need
1197 # have linkrev(last) > maxrev, and we still need
1197 # to explore the file graph
1198 # to explore the file graph
1198 if rev not in ancestors:
1199 if rev not in ancestors:
1199 continue
1200 continue
1200 # XXX insert 1327 fix here
1201 # XXX insert 1327 fix here
1201 if flparentlinkrevs:
1202 if flparentlinkrevs:
1202 ancestors.update(flparentlinkrevs)
1203 ancestors.update(flparentlinkrevs)
1203
1204
1204 fncache.setdefault(rev, []).append(file_)
1205 fncache.setdefault(rev, []).append(file_)
1205 wanted.add(rev)
1206 wanted.add(rev)
1206 if copied:
1207 if copied:
1207 copies.append(copied)
1208 copies.append(copied)
1208 if slowpath:
1209 if slowpath:
1209 # We have to read the changelog to match filenames against
1210 # We have to read the changelog to match filenames against
1210 # changed files
1211 # changed files
1211
1212
1212 if follow:
1213 if follow:
1213 raise util.Abort(_('can only follow copies/renames for explicit '
1214 raise util.Abort(_('can only follow copies/renames for explicit '
1214 'filenames'))
1215 'filenames'))
1215
1216
1216 # The slow path checks files modified in every changeset.
1217 # The slow path checks files modified in every changeset.
1217 for i in sorted(revs):
1218 for i in sorted(revs):
1218 ctx = change(i)
1219 ctx = change(i)
1219 matches = filter(match, ctx.files())
1220 matches = filter(match, ctx.files())
1220 if matches:
1221 if matches:
1221 fncache[i] = matches
1222 fncache[i] = matches
1222 wanted.add(i)
1223 wanted.add(i)
1223
1224
1224 class followfilter(object):
1225 class followfilter(object):
1225 def __init__(self, onlyfirst=False):
1226 def __init__(self, onlyfirst=False):
1226 self.startrev = nullrev
1227 self.startrev = nullrev
1227 self.roots = set()
1228 self.roots = set()
1228 self.onlyfirst = onlyfirst
1229 self.onlyfirst = onlyfirst
1229
1230
1230 def match(self, rev):
1231 def match(self, rev):
1231 def realparents(rev):
1232 def realparents(rev):
1232 if self.onlyfirst:
1233 if self.onlyfirst:
1233 return repo.changelog.parentrevs(rev)[0:1]
1234 return repo.changelog.parentrevs(rev)[0:1]
1234 else:
1235 else:
1235 return filter(lambda x: x != nullrev,
1236 return filter(lambda x: x != nullrev,
1236 repo.changelog.parentrevs(rev))
1237 repo.changelog.parentrevs(rev))
1237
1238
1238 if self.startrev == nullrev:
1239 if self.startrev == nullrev:
1239 self.startrev = rev
1240 self.startrev = rev
1240 return True
1241 return True
1241
1242
1242 if rev > self.startrev:
1243 if rev > self.startrev:
1243 # forward: all descendants
1244 # forward: all descendants
1244 if not self.roots:
1245 if not self.roots:
1245 self.roots.add(self.startrev)
1246 self.roots.add(self.startrev)
1246 for parent in realparents(rev):
1247 for parent in realparents(rev):
1247 if parent in self.roots:
1248 if parent in self.roots:
1248 self.roots.add(rev)
1249 self.roots.add(rev)
1249 return True
1250 return True
1250 else:
1251 else:
1251 # backwards: all parents
1252 # backwards: all parents
1252 if not self.roots:
1253 if not self.roots:
1253 self.roots.update(realparents(self.startrev))
1254 self.roots.update(realparents(self.startrev))
1254 if rev in self.roots:
1255 if rev in self.roots:
1255 self.roots.remove(rev)
1256 self.roots.remove(rev)
1256 self.roots.update(realparents(rev))
1257 self.roots.update(realparents(rev))
1257 return True
1258 return True
1258
1259
1259 return False
1260 return False
1260
1261
1261 # it might be worthwhile to do this in the iterator if the rev range
1262 # it might be worthwhile to do this in the iterator if the rev range
1262 # is descending and the prune args are all within that range
1263 # is descending and the prune args are all within that range
1263 for rev in opts.get('prune', ()):
1264 for rev in opts.get('prune', ()):
1264 rev = repo.changelog.rev(repo.lookup(rev))
1265 rev = repo.changelog.rev(repo.lookup(rev))
1265 ff = followfilter()
1266 ff = followfilter()
1266 stop = min(revs[0], revs[-1])
1267 stop = min(revs[0], revs[-1])
1267 for x in xrange(rev, stop - 1, -1):
1268 for x in xrange(rev, stop - 1, -1):
1268 if ff.match(x):
1269 if ff.match(x):
1269 wanted.discard(x)
1270 wanted.discard(x)
1270
1271
1271 # Now that wanted is correctly initialized, we can iterate over the
1272 # Now that wanted is correctly initialized, we can iterate over the
1272 # revision range, yielding only revisions in wanted.
1273 # revision range, yielding only revisions in wanted.
1273 def iterate():
1274 def iterate():
1274 if follow and not match.files():
1275 if follow and not match.files():
1275 ff = followfilter(onlyfirst=opts.get('follow_first'))
1276 ff = followfilter(onlyfirst=opts.get('follow_first'))
1276 def want(rev):
1277 def want(rev):
1277 return ff.match(rev) and rev in wanted
1278 return ff.match(rev) and rev in wanted
1278 else:
1279 else:
1279 def want(rev):
1280 def want(rev):
1280 return rev in wanted
1281 return rev in wanted
1281
1282
1282 for i, window in increasing_windows(0, len(revs)):
1283 for i, window in increasing_windows(0, len(revs)):
1283 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1284 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1284 for rev in sorted(nrevs):
1285 for rev in sorted(nrevs):
1285 fns = fncache.get(rev)
1286 fns = fncache.get(rev)
1286 ctx = change(rev)
1287 ctx = change(rev)
1287 if not fns:
1288 if not fns:
1288 def fns_generator():
1289 def fns_generator():
1289 for f in ctx.files():
1290 for f in ctx.files():
1290 if match(f):
1291 if match(f):
1291 yield f
1292 yield f
1292 fns = fns_generator()
1293 fns = fns_generator()
1293 prepare(ctx, fns)
1294 prepare(ctx, fns)
1294 for rev in nrevs:
1295 for rev in nrevs:
1295 yield change(rev)
1296 yield change(rev)
1296 return iterate()
1297 return iterate()
1297
1298
1298 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1299 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1299 join = lambda f: os.path.join(prefix, f)
1300 join = lambda f: os.path.join(prefix, f)
1300 bad = []
1301 bad = []
1301 oldbad = match.bad
1302 oldbad = match.bad
1302 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1303 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1303 names = []
1304 names = []
1304 wctx = repo[None]
1305 wctx = repo[None]
1305 for f in repo.walk(match):
1306 for f in repo.walk(match):
1306 exact = match.exact(f)
1307 exact = match.exact(f)
1307 if exact or f not in repo.dirstate:
1308 if exact or f not in repo.dirstate:
1308 names.append(f)
1309 names.append(f)
1309 if ui.verbose or not exact:
1310 if ui.verbose or not exact:
1310 ui.status(_('adding %s\n') % match.rel(join(f)))
1311 ui.status(_('adding %s\n') % match.rel(join(f)))
1311
1312
1312 if listsubrepos:
1313 if listsubrepos:
1313 for subpath in wctx.substate:
1314 for subpath in wctx.substate:
1314 sub = wctx.sub(subpath)
1315 sub = wctx.sub(subpath)
1315 try:
1316 try:
1316 submatch = matchmod.narrowmatcher(subpath, match)
1317 submatch = matchmod.narrowmatcher(subpath, match)
1317 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1318 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1318 except error.LookupError:
1319 except error.LookupError:
1319 ui.status(_("skipping missing subrepository: %s\n")
1320 ui.status(_("skipping missing subrepository: %s\n")
1320 % join(subpath))
1321 % join(subpath))
1321
1322
1322 if not dryrun:
1323 if not dryrun:
1323 rejected = wctx.add(names, prefix)
1324 rejected = wctx.add(names, prefix)
1324 bad.extend(f for f in rejected if f in match.files())
1325 bad.extend(f for f in rejected if f in match.files())
1325 return bad
1326 return bad
1326
1327
1327 def commit(ui, repo, commitfunc, pats, opts):
1328 def commit(ui, repo, commitfunc, pats, opts):
1328 '''commit the specified files or all outstanding changes'''
1329 '''commit the specified files or all outstanding changes'''
1329 date = opts.get('date')
1330 date = opts.get('date')
1330 if date:
1331 if date:
1331 opts['date'] = util.parsedate(date)
1332 opts['date'] = util.parsedate(date)
1332 message = logmessage(opts)
1333 message = logmessage(opts)
1333
1334
1334 # extract addremove carefully -- this function can be called from a command
1335 # extract addremove carefully -- this function can be called from a command
1335 # that doesn't support addremove
1336 # that doesn't support addremove
1336 if opts.get('addremove'):
1337 if opts.get('addremove'):
1337 addremove(repo, pats, opts)
1338 addremove(repo, pats, opts)
1338
1339
1339 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1340 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1340
1341
1341 def commiteditor(repo, ctx, subs):
1342 def commiteditor(repo, ctx, subs):
1342 if ctx.description():
1343 if ctx.description():
1343 return ctx.description()
1344 return ctx.description()
1344 return commitforceeditor(repo, ctx, subs)
1345 return commitforceeditor(repo, ctx, subs)
1345
1346
1346 def commitforceeditor(repo, ctx, subs):
1347 def commitforceeditor(repo, ctx, subs):
1347 edittext = []
1348 edittext = []
1348 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1349 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1349 if ctx.description():
1350 if ctx.description():
1350 edittext.append(ctx.description())
1351 edittext.append(ctx.description())
1351 edittext.append("")
1352 edittext.append("")
1352 edittext.append("") # Empty line between message and comments.
1353 edittext.append("") # Empty line between message and comments.
1353 edittext.append(_("HG: Enter commit message."
1354 edittext.append(_("HG: Enter commit message."
1354 " Lines beginning with 'HG:' are removed."))
1355 " Lines beginning with 'HG:' are removed."))
1355 edittext.append(_("HG: Leave message empty to abort commit."))
1356 edittext.append(_("HG: Leave message empty to abort commit."))
1356 edittext.append("HG: --")
1357 edittext.append("HG: --")
1357 edittext.append(_("HG: user: %s") % ctx.user())
1358 edittext.append(_("HG: user: %s") % ctx.user())
1358 if ctx.p2():
1359 if ctx.p2():
1359 edittext.append(_("HG: branch merge"))
1360 edittext.append(_("HG: branch merge"))
1360 if ctx.branch():
1361 if ctx.branch():
1361 edittext.append(_("HG: branch '%s'") % ctx.branch())
1362 edittext.append(_("HG: branch '%s'") % ctx.branch())
1362 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1363 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1363 edittext.extend([_("HG: added %s") % f for f in added])
1364 edittext.extend([_("HG: added %s") % f for f in added])
1364 edittext.extend([_("HG: changed %s") % f for f in modified])
1365 edittext.extend([_("HG: changed %s") % f for f in modified])
1365 edittext.extend([_("HG: removed %s") % f for f in removed])
1366 edittext.extend([_("HG: removed %s") % f for f in removed])
1366 if not added and not modified and not removed:
1367 if not added and not modified and not removed:
1367 edittext.append(_("HG: no files changed"))
1368 edittext.append(_("HG: no files changed"))
1368 edittext.append("")
1369 edittext.append("")
1369 # run editor in the repository root
1370 # run editor in the repository root
1370 olddir = os.getcwd()
1371 olddir = os.getcwd()
1371 os.chdir(repo.root)
1372 os.chdir(repo.root)
1372 text = repo.ui.edit("\n".join(edittext), ctx.user())
1373 text = repo.ui.edit("\n".join(edittext), ctx.user())
1373 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1374 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1374 os.chdir(olddir)
1375 os.chdir(olddir)
1375
1376
1376 if not text.strip():
1377 if not text.strip():
1377 raise util.Abort(_("empty commit message"))
1378 raise util.Abort(_("empty commit message"))
1378
1379
1379 return text
1380 return text
@@ -1,483 +1,483 b''
1 $ cp "$TESTDIR"/printenv.py .
1 $ cp "$TESTDIR"/printenv.py .
2
2
3 commit hooks can see env vars
3 commit hooks can see env vars
4
4
5 $ hg init a
5 $ hg init a
6 $ cd a
6 $ cd a
7 $ echo "[hooks]" > .hg/hgrc
7 $ echo "[hooks]" > .hg/hgrc
8 $ echo 'commit = unset HG_LOCAL HG_TAG; python ../printenv.py commit' >> .hg/hgrc
8 $ echo 'commit = unset HG_LOCAL HG_TAG; python ../printenv.py commit' >> .hg/hgrc
9 $ echo 'commit.b = unset HG_LOCAL HG_TAG; python ../printenv.py commit.b' >> .hg/hgrc
9 $ echo 'commit.b = unset HG_LOCAL HG_TAG; python ../printenv.py commit.b' >> .hg/hgrc
10 $ echo 'precommit = unset HG_LOCAL HG_NODE HG_TAG; python ../printenv.py precommit' >> .hg/hgrc
10 $ echo 'precommit = unset HG_LOCAL HG_NODE HG_TAG; python ../printenv.py precommit' >> .hg/hgrc
11 $ echo 'pretxncommit = unset HG_LOCAL HG_TAG; python ../printenv.py pretxncommit' >> .hg/hgrc
11 $ echo 'pretxncommit = unset HG_LOCAL HG_TAG; python ../printenv.py pretxncommit' >> .hg/hgrc
12 $ echo 'pretxncommit.tip = hg -q tip' >> .hg/hgrc
12 $ echo 'pretxncommit.tip = hg -q tip' >> .hg/hgrc
13 $ echo 'pre-identify = python ../printenv.py pre-identify 1' >> .hg/hgrc
13 $ echo 'pre-identify = python ../printenv.py pre-identify 1' >> .hg/hgrc
14 $ echo 'pre-cat = python ../printenv.py pre-cat' >> .hg/hgrc
14 $ echo 'pre-cat = python ../printenv.py pre-cat' >> .hg/hgrc
15 $ echo 'post-cat = python ../printenv.py post-cat' >> .hg/hgrc
15 $ echo 'post-cat = python ../printenv.py post-cat' >> .hg/hgrc
16 $ echo a > a
16 $ echo a > a
17 $ hg add a
17 $ hg add a
18 $ hg commit -m a
18 $ hg commit -m a
19 precommit hook: HG_PARENT1=0000000000000000000000000000000000000000
19 precommit hook: HG_PARENT1=0000000000000000000000000000000000000000
20 pretxncommit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
20 pretxncommit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
21 0:cb9a9f314b8b
21 0:cb9a9f314b8b
22 commit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
22 commit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
23 commit.b hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
23 commit.b hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
24
24
25 $ hg clone . ../b
25 $ hg clone . ../b
26 updating to branch default
26 updating to branch default
27 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
27 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
28 $ cd ../b
28 $ cd ../b
29
29
30 changegroup hooks can see env vars
30 changegroup hooks can see env vars
31
31
32 $ echo '[hooks]' > .hg/hgrc
32 $ echo '[hooks]' > .hg/hgrc
33 $ echo 'prechangegroup = python ../printenv.py prechangegroup' >> .hg/hgrc
33 $ echo 'prechangegroup = python ../printenv.py prechangegroup' >> .hg/hgrc
34 $ echo 'changegroup = python ../printenv.py changegroup' >> .hg/hgrc
34 $ echo 'changegroup = python ../printenv.py changegroup' >> .hg/hgrc
35 $ echo 'incoming = python ../printenv.py incoming' >> .hg/hgrc
35 $ echo 'incoming = python ../printenv.py incoming' >> .hg/hgrc
36
36
37 pretxncommit and commit hooks can see both parents of merge
37 pretxncommit and commit hooks can see both parents of merge
38
38
39 $ cd ../a
39 $ cd ../a
40 $ echo b >> a
40 $ echo b >> a
41 $ hg commit -m a1 -d "1 0"
41 $ hg commit -m a1 -d "1 0"
42 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
42 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
43 pretxncommit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
43 pretxncommit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
44 1:ab228980c14d
44 1:ab228980c14d
45 commit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
45 commit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
46 commit.b hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
46 commit.b hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
47 $ hg update -C 0
47 $ hg update -C 0
48 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
48 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 $ echo b > b
49 $ echo b > b
50 $ hg add b
50 $ hg add b
51 $ hg commit -m b -d '1 0'
51 $ hg commit -m b -d '1 0'
52 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
52 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
53 pretxncommit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
53 pretxncommit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
54 2:ee9deb46ab31
54 2:ee9deb46ab31
55 commit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
55 commit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
56 commit.b hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
56 commit.b hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
57 created new head
57 created new head
58 $ hg merge 1
58 $ hg merge 1
59 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 (branch merge, don't forget to commit)
60 (branch merge, don't forget to commit)
61 $ hg commit -m merge -d '2 0'
61 $ hg commit -m merge -d '2 0'
62 precommit hook: HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
62 precommit hook: HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
63 pretxncommit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
63 pretxncommit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
64 3:07f3376c1e65
64 3:07f3376c1e65
65 commit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
65 commit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
66 commit.b hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
66 commit.b hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
67
67
68 test generic hooks
68 test generic hooks
69
69
70 $ hg id
70 $ hg id
71 pre-identify hook: HG_ARGS=id HG_OPTS={'tags': None, 'rev': '', 'num': None, 'branch': None, 'id': None} HG_PATS=[]
71 pre-identify hook: HG_ARGS=id HG_OPTS={'tags': None, 'rev': '', 'num': None, 'branch': None, 'id': None} HG_PATS=[]
72 warning: pre-identify hook exited with status 1
72 warning: pre-identify hook exited with status 1
73 [1]
73 [1]
74 $ hg cat b
74 $ hg cat b
75 pre-cat hook: HG_ARGS=cat b HG_OPTS={'rev': '', 'decode': None, 'exclude': [], 'output': '', 'include': []} HG_PATS=['b']
75 pre-cat hook: HG_ARGS=cat b HG_OPTS={'rev': '', 'decode': None, 'exclude': [], 'output': '', 'include': []} HG_PATS=['b']
76 b
76 post-cat hook: HG_ARGS=cat b HG_OPTS={'rev': '', 'decode': None, 'exclude': [], 'output': '', 'include': []} HG_PATS=['b'] HG_RESULT=0
77 post-cat hook: HG_ARGS=cat b HG_OPTS={'rev': '', 'decode': None, 'exclude': [], 'output': '', 'include': []} HG_PATS=['b'] HG_RESULT=0
77 b
78
78
79 $ cd ../b
79 $ cd ../b
80 $ hg pull ../a
80 $ hg pull ../a
81 prechangegroup hook: HG_SOURCE=pull HG_URL=file:
81 prechangegroup hook: HG_SOURCE=pull HG_URL=file:
82 changegroup hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_URL=file:
82 changegroup hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_URL=file:
83 incoming hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_URL=file:
83 incoming hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_URL=file:
84 incoming hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_URL=file:
84 incoming hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_URL=file:
85 incoming hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_URL=file:
85 incoming hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_URL=file:
86 pulling from ../a
86 pulling from ../a
87 searching for changes
87 searching for changes
88 adding changesets
88 adding changesets
89 adding manifests
89 adding manifests
90 adding file changes
90 adding file changes
91 added 3 changesets with 2 changes to 2 files
91 added 3 changesets with 2 changes to 2 files
92 (run 'hg update' to get a working copy)
92 (run 'hg update' to get a working copy)
93
93
94 tag hooks can see env vars
94 tag hooks can see env vars
95
95
96 $ cd ../a
96 $ cd ../a
97 $ echo 'pretag = python ../printenv.py pretag' >> .hg/hgrc
97 $ echo 'pretag = python ../printenv.py pretag' >> .hg/hgrc
98 $ echo 'tag = unset HG_PARENT1 HG_PARENT2; python ../printenv.py tag' >> .hg/hgrc
98 $ echo 'tag = unset HG_PARENT1 HG_PARENT2; python ../printenv.py tag' >> .hg/hgrc
99 $ hg tag -d '3 0' a
99 $ hg tag -d '3 0' a
100 pretag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
100 pretag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
101 precommit hook: HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
101 precommit hook: HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
102 pretxncommit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
102 pretxncommit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
103 4:539e4b31b6dc
103 4:539e4b31b6dc
104 commit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
104 commit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
105 commit.b hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
105 commit.b hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
106 tag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
106 tag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
107 $ hg tag -l la
107 $ hg tag -l la
108 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
108 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
109 tag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
109 tag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
110
110
111 pretag hook can forbid tagging
111 pretag hook can forbid tagging
112
112
113 $ echo 'pretag.forbid = python ../printenv.py pretag.forbid 1' >> .hg/hgrc
113 $ echo 'pretag.forbid = python ../printenv.py pretag.forbid 1' >> .hg/hgrc
114 $ hg tag -d '4 0' fa
114 $ hg tag -d '4 0' fa
115 pretag hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
115 pretag hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
116 pretag.forbid hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
116 pretag.forbid hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
117 abort: pretag.forbid hook exited with status 1
117 abort: pretag.forbid hook exited with status 1
118 [255]
118 [255]
119 $ hg tag -l fla
119 $ hg tag -l fla
120 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
120 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
121 pretag.forbid hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
121 pretag.forbid hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
122 abort: pretag.forbid hook exited with status 1
122 abort: pretag.forbid hook exited with status 1
123 [255]
123 [255]
124
124
125 pretxncommit hook can see changeset, can roll back txn, changeset no
125 pretxncommit hook can see changeset, can roll back txn, changeset no
126 more there after
126 more there after
127
127
128 $ echo 'pretxncommit.forbid0 = hg tip -q' >> .hg/hgrc
128 $ echo 'pretxncommit.forbid0 = hg tip -q' >> .hg/hgrc
129 $ echo 'pretxncommit.forbid1 = python ../printenv.py pretxncommit.forbid 1' >> .hg/hgrc
129 $ echo 'pretxncommit.forbid1 = python ../printenv.py pretxncommit.forbid 1' >> .hg/hgrc
130 $ echo z > z
130 $ echo z > z
131 $ hg add z
131 $ hg add z
132 $ hg -q tip
132 $ hg -q tip
133 4:539e4b31b6dc
133 4:539e4b31b6dc
134 $ hg commit -m 'fail' -d '4 0'
134 $ hg commit -m 'fail' -d '4 0'
135 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
135 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
136 pretxncommit hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
136 pretxncommit hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
137 5:6f611f8018c1
137 5:6f611f8018c1
138 5:6f611f8018c1
138 5:6f611f8018c1
139 pretxncommit.forbid hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
139 pretxncommit.forbid hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
140 transaction abort!
140 transaction abort!
141 rollback completed
141 rollback completed
142 abort: pretxncommit.forbid1 hook exited with status 1
142 abort: pretxncommit.forbid1 hook exited with status 1
143 [255]
143 [255]
144 $ hg -q tip
144 $ hg -q tip
145 4:539e4b31b6dc
145 4:539e4b31b6dc
146
146
147 precommit hook can prevent commit
147 precommit hook can prevent commit
148
148
149 $ echo 'precommit.forbid = python ../printenv.py precommit.forbid 1' >> .hg/hgrc
149 $ echo 'precommit.forbid = python ../printenv.py precommit.forbid 1' >> .hg/hgrc
150 $ hg commit -m 'fail' -d '4 0'
150 $ hg commit -m 'fail' -d '4 0'
151 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
151 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
152 precommit.forbid hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
152 precommit.forbid hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
153 abort: precommit.forbid hook exited with status 1
153 abort: precommit.forbid hook exited with status 1
154 [255]
154 [255]
155 $ hg -q tip
155 $ hg -q tip
156 4:539e4b31b6dc
156 4:539e4b31b6dc
157
157
158 preupdate hook can prevent update
158 preupdate hook can prevent update
159
159
160 $ echo 'preupdate = python ../printenv.py preupdate' >> .hg/hgrc
160 $ echo 'preupdate = python ../printenv.py preupdate' >> .hg/hgrc
161 $ hg update 1
161 $ hg update 1
162 preupdate hook: HG_PARENT1=ab228980c14d
162 preupdate hook: HG_PARENT1=ab228980c14d
163 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
163 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
164
164
165 update hook
165 update hook
166
166
167 $ echo 'update = python ../printenv.py update' >> .hg/hgrc
167 $ echo 'update = python ../printenv.py update' >> .hg/hgrc
168 $ hg update
168 $ hg update
169 preupdate hook: HG_PARENT1=539e4b31b6dc
169 preupdate hook: HG_PARENT1=539e4b31b6dc
170 update hook: HG_ERROR=0 HG_PARENT1=539e4b31b6dc
170 update hook: HG_ERROR=0 HG_PARENT1=539e4b31b6dc
171 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
171 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
172
172
173 prechangegroup hook can prevent incoming changes
173 prechangegroup hook can prevent incoming changes
174
174
175 $ cd ../b
175 $ cd ../b
176 $ hg -q tip
176 $ hg -q tip
177 3:07f3376c1e65
177 3:07f3376c1e65
178 $ echo '[hooks]' > .hg/hgrc
178 $ echo '[hooks]' > .hg/hgrc
179 $ echo 'prechangegroup.forbid = python ../printenv.py prechangegroup.forbid 1' >> .hg/hgrc
179 $ echo 'prechangegroup.forbid = python ../printenv.py prechangegroup.forbid 1' >> .hg/hgrc
180 $ hg pull ../a
180 $ hg pull ../a
181 prechangegroup.forbid hook: HG_SOURCE=pull HG_URL=file:
181 prechangegroup.forbid hook: HG_SOURCE=pull HG_URL=file:
182 pulling from ../a
182 pulling from ../a
183 searching for changes
183 searching for changes
184 abort: prechangegroup.forbid hook exited with status 1
184 abort: prechangegroup.forbid hook exited with status 1
185 [255]
185 [255]
186
186
187 pretxnchangegroup hook can see incoming changes, can roll back txn,
187 pretxnchangegroup hook can see incoming changes, can roll back txn,
188 incoming changes no longer there after
188 incoming changes no longer there after
189
189
190 $ echo '[hooks]' > .hg/hgrc
190 $ echo '[hooks]' > .hg/hgrc
191 $ echo 'pretxnchangegroup.forbid0 = hg tip -q' >> .hg/hgrc
191 $ echo 'pretxnchangegroup.forbid0 = hg tip -q' >> .hg/hgrc
192 $ echo 'pretxnchangegroup.forbid1 = python ../printenv.py pretxnchangegroup.forbid 1' >> .hg/hgrc
192 $ echo 'pretxnchangegroup.forbid1 = python ../printenv.py pretxnchangegroup.forbid 1' >> .hg/hgrc
193 $ hg pull ../a
193 $ hg pull ../a
194 4:539e4b31b6dc
194 4:539e4b31b6dc
195 pretxnchangegroup.forbid hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/b HG_SOURCE=pull HG_URL=file:
195 pretxnchangegroup.forbid hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/b HG_SOURCE=pull HG_URL=file:
196 pulling from ../a
196 pulling from ../a
197 searching for changes
197 searching for changes
198 adding changesets
198 adding changesets
199 adding manifests
199 adding manifests
200 adding file changes
200 adding file changes
201 added 1 changesets with 1 changes to 1 files
201 added 1 changesets with 1 changes to 1 files
202 transaction abort!
202 transaction abort!
203 rollback completed
203 rollback completed
204 abort: pretxnchangegroup.forbid1 hook exited with status 1
204 abort: pretxnchangegroup.forbid1 hook exited with status 1
205 [255]
205 [255]
206 $ hg -q tip
206 $ hg -q tip
207 3:07f3376c1e65
207 3:07f3376c1e65
208
208
209 outgoing hooks can see env vars
209 outgoing hooks can see env vars
210
210
211 $ rm .hg/hgrc
211 $ rm .hg/hgrc
212 $ echo '[hooks]' > ../a/.hg/hgrc
212 $ echo '[hooks]' > ../a/.hg/hgrc
213 $ echo 'preoutgoing = python ../printenv.py preoutgoing' >> ../a/.hg/hgrc
213 $ echo 'preoutgoing = python ../printenv.py preoutgoing' >> ../a/.hg/hgrc
214 $ echo 'outgoing = python ../printenv.py outgoing' >> ../a/.hg/hgrc
214 $ echo 'outgoing = python ../printenv.py outgoing' >> ../a/.hg/hgrc
215 $ hg pull ../a
215 $ hg pull ../a
216 preoutgoing hook: HG_SOURCE=pull
216 preoutgoing hook: HG_SOURCE=pull
217 outgoing hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull
217 outgoing hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull
218 pulling from ../a
218 pulling from ../a
219 searching for changes
219 searching for changes
220 adding changesets
220 adding changesets
221 adding manifests
221 adding manifests
222 adding file changes
222 adding file changes
223 added 1 changesets with 1 changes to 1 files
223 added 1 changesets with 1 changes to 1 files
224 (run 'hg update' to get a working copy)
224 (run 'hg update' to get a working copy)
225 $ hg rollback
225 $ hg rollback
226 rolling back to revision 3 (undo pull)
226 rolling back to revision 3 (undo pull)
227
227
228 preoutgoing hook can prevent outgoing changes
228 preoutgoing hook can prevent outgoing changes
229
229
230 $ echo 'preoutgoing.forbid = python ../printenv.py preoutgoing.forbid 1' >> ../a/.hg/hgrc
230 $ echo 'preoutgoing.forbid = python ../printenv.py preoutgoing.forbid 1' >> ../a/.hg/hgrc
231 $ hg pull ../a
231 $ hg pull ../a
232 preoutgoing hook: HG_SOURCE=pull
232 preoutgoing hook: HG_SOURCE=pull
233 preoutgoing.forbid hook: HG_SOURCE=pull
233 preoutgoing.forbid hook: HG_SOURCE=pull
234 pulling from ../a
234 pulling from ../a
235 searching for changes
235 searching for changes
236 abort: preoutgoing.forbid hook exited with status 1
236 abort: preoutgoing.forbid hook exited with status 1
237 [255]
237 [255]
238
238
239 outgoing hooks work for local clones
239 outgoing hooks work for local clones
240
240
241 $ cd ..
241 $ cd ..
242 $ echo '[hooks]' > a/.hg/hgrc
242 $ echo '[hooks]' > a/.hg/hgrc
243 $ echo 'preoutgoing = python ../printenv.py preoutgoing' >> a/.hg/hgrc
243 $ echo 'preoutgoing = python ../printenv.py preoutgoing' >> a/.hg/hgrc
244 $ echo 'outgoing = python ../printenv.py outgoing' >> a/.hg/hgrc
244 $ echo 'outgoing = python ../printenv.py outgoing' >> a/.hg/hgrc
245 $ hg clone a c
245 $ hg clone a c
246 preoutgoing hook: HG_SOURCE=clone
246 preoutgoing hook: HG_SOURCE=clone
247 outgoing hook: HG_NODE=0000000000000000000000000000000000000000 HG_SOURCE=clone
247 outgoing hook: HG_NODE=0000000000000000000000000000000000000000 HG_SOURCE=clone
248 updating to branch default
248 updating to branch default
249 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
249 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
250 $ rm -rf c
250 $ rm -rf c
251
251
252 preoutgoing hook can prevent outgoing changes for local clones
252 preoutgoing hook can prevent outgoing changes for local clones
253
253
254 $ echo 'preoutgoing.forbid = python ../printenv.py preoutgoing.forbid 1' >> a/.hg/hgrc
254 $ echo 'preoutgoing.forbid = python ../printenv.py preoutgoing.forbid 1' >> a/.hg/hgrc
255 $ hg clone a zzz
255 $ hg clone a zzz
256 preoutgoing hook: HG_SOURCE=clone
256 preoutgoing hook: HG_SOURCE=clone
257 preoutgoing.forbid hook: HG_SOURCE=clone
257 preoutgoing.forbid hook: HG_SOURCE=clone
258 abort: preoutgoing.forbid hook exited with status 1
258 abort: preoutgoing.forbid hook exited with status 1
259 [255]
259 [255]
260 $ cd b
260 $ cd b
261
261
262 $ cat > hooktests.py <<EOF
262 $ cat > hooktests.py <<EOF
263 > from mercurial import util
263 > from mercurial import util
264 >
264 >
265 > uncallable = 0
265 > uncallable = 0
266 >
266 >
267 > def printargs(args):
267 > def printargs(args):
268 > args.pop('ui', None)
268 > args.pop('ui', None)
269 > args.pop('repo', None)
269 > args.pop('repo', None)
270 > a = list(args.items())
270 > a = list(args.items())
271 > a.sort()
271 > a.sort()
272 > print 'hook args:'
272 > print 'hook args:'
273 > for k, v in a:
273 > for k, v in a:
274 > print ' ', k, v
274 > print ' ', k, v
275 >
275 >
276 > def passhook(**args):
276 > def passhook(**args):
277 > printargs(args)
277 > printargs(args)
278 >
278 >
279 > def failhook(**args):
279 > def failhook(**args):
280 > printargs(args)
280 > printargs(args)
281 > return True
281 > return True
282 >
282 >
283 > class LocalException(Exception):
283 > class LocalException(Exception):
284 > pass
284 > pass
285 >
285 >
286 > def raisehook(**args):
286 > def raisehook(**args):
287 > raise LocalException('exception from hook')
287 > raise LocalException('exception from hook')
288 >
288 >
289 > def aborthook(**args):
289 > def aborthook(**args):
290 > raise util.Abort('raise abort from hook')
290 > raise util.Abort('raise abort from hook')
291 >
291 >
292 > def brokenhook(**args):
292 > def brokenhook(**args):
293 > return 1 + {}
293 > return 1 + {}
294 >
294 >
295 > class container:
295 > class container:
296 > unreachable = 1
296 > unreachable = 1
297 > EOF
297 > EOF
298
298
299 test python hooks
299 test python hooks
300
300
301 $ PYTHONPATH="`pwd`:$PYTHONPATH"
301 $ PYTHONPATH="`pwd`:$PYTHONPATH"
302 $ export PYTHONPATH
302 $ export PYTHONPATH
303
303
304 $ echo '[hooks]' > ../a/.hg/hgrc
304 $ echo '[hooks]' > ../a/.hg/hgrc
305 $ echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
305 $ echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
306 $ hg pull ../a 2>&1 | grep 'raised an exception'
306 $ hg pull ../a 2>&1 | grep 'raised an exception'
307 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
307 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
308
308
309 $ echo '[hooks]' > ../a/.hg/hgrc
309 $ echo '[hooks]' > ../a/.hg/hgrc
310 $ echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
310 $ echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
311 $ hg pull ../a 2>&1 | grep 'raised an exception'
311 $ hg pull ../a 2>&1 | grep 'raised an exception'
312 error: preoutgoing.raise hook raised an exception: exception from hook
312 error: preoutgoing.raise hook raised an exception: exception from hook
313
313
314 $ echo '[hooks]' > ../a/.hg/hgrc
314 $ echo '[hooks]' > ../a/.hg/hgrc
315 $ echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
315 $ echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
316 $ hg pull ../a
316 $ hg pull ../a
317 pulling from ../a
317 pulling from ../a
318 searching for changes
318 searching for changes
319 error: preoutgoing.abort hook failed: raise abort from hook
319 error: preoutgoing.abort hook failed: raise abort from hook
320 abort: raise abort from hook
320 abort: raise abort from hook
321 [255]
321 [255]
322
322
323 $ echo '[hooks]' > ../a/.hg/hgrc
323 $ echo '[hooks]' > ../a/.hg/hgrc
324 $ echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
324 $ echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
325 $ hg pull ../a
325 $ hg pull ../a
326 pulling from ../a
326 pulling from ../a
327 searching for changes
327 searching for changes
328 hook args:
328 hook args:
329 hooktype preoutgoing
329 hooktype preoutgoing
330 source pull
330 source pull
331 abort: preoutgoing.fail hook failed
331 abort: preoutgoing.fail hook failed
332 [255]
332 [255]
333
333
334 $ echo '[hooks]' > ../a/.hg/hgrc
334 $ echo '[hooks]' > ../a/.hg/hgrc
335 $ echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
335 $ echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
336 $ hg pull ../a
336 $ hg pull ../a
337 pulling from ../a
337 pulling from ../a
338 searching for changes
338 searching for changes
339 abort: preoutgoing.uncallable hook is invalid ("hooktests.uncallable" is not callable)
339 abort: preoutgoing.uncallable hook is invalid ("hooktests.uncallable" is not callable)
340 [255]
340 [255]
341
341
342 $ echo '[hooks]' > ../a/.hg/hgrc
342 $ echo '[hooks]' > ../a/.hg/hgrc
343 $ echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
343 $ echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
344 $ hg pull ../a
344 $ hg pull ../a
345 pulling from ../a
345 pulling from ../a
346 searching for changes
346 searching for changes
347 abort: preoutgoing.nohook hook is invalid ("hooktests.nohook" is not defined)
347 abort: preoutgoing.nohook hook is invalid ("hooktests.nohook" is not defined)
348 [255]
348 [255]
349
349
350 $ echo '[hooks]' > ../a/.hg/hgrc
350 $ echo '[hooks]' > ../a/.hg/hgrc
351 $ echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
351 $ echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
352 $ hg pull ../a
352 $ hg pull ../a
353 pulling from ../a
353 pulling from ../a
354 searching for changes
354 searching for changes
355 abort: preoutgoing.nomodule hook is invalid ("nomodule" not in a module)
355 abort: preoutgoing.nomodule hook is invalid ("nomodule" not in a module)
356 [255]
356 [255]
357
357
358 $ echo '[hooks]' > ../a/.hg/hgrc
358 $ echo '[hooks]' > ../a/.hg/hgrc
359 $ echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
359 $ echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
360 $ hg pull ../a
360 $ hg pull ../a
361 pulling from ../a
361 pulling from ../a
362 searching for changes
362 searching for changes
363 abort: preoutgoing.badmodule hook is invalid (import of "nomodule" failed)
363 abort: preoutgoing.badmodule hook is invalid (import of "nomodule" failed)
364 [255]
364 [255]
365
365
366 $ echo '[hooks]' > ../a/.hg/hgrc
366 $ echo '[hooks]' > ../a/.hg/hgrc
367 $ echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
367 $ echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
368 $ hg pull ../a
368 $ hg pull ../a
369 pulling from ../a
369 pulling from ../a
370 searching for changes
370 searching for changes
371 abort: preoutgoing.unreachable hook is invalid (import of "hooktests.container" failed)
371 abort: preoutgoing.unreachable hook is invalid (import of "hooktests.container" failed)
372 [255]
372 [255]
373
373
374 $ echo '[hooks]' > ../a/.hg/hgrc
374 $ echo '[hooks]' > ../a/.hg/hgrc
375 $ echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
375 $ echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
376 $ hg pull ../a
376 $ hg pull ../a
377 pulling from ../a
377 pulling from ../a
378 searching for changes
378 searching for changes
379 hook args:
379 hook args:
380 hooktype preoutgoing
380 hooktype preoutgoing
381 source pull
381 source pull
382 adding changesets
382 adding changesets
383 adding manifests
383 adding manifests
384 adding file changes
384 adding file changes
385 added 1 changesets with 1 changes to 1 files
385 added 1 changesets with 1 changes to 1 files
386 (run 'hg update' to get a working copy)
386 (run 'hg update' to get a working copy)
387
387
388 make sure --traceback works
388 make sure --traceback works
389
389
390 $ echo '[hooks]' > .hg/hgrc
390 $ echo '[hooks]' > .hg/hgrc
391 $ echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
391 $ echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
392
392
393 $ echo aa > a
393 $ echo aa > a
394 $ hg --traceback commit -d '0 0' -ma 2>&1 | grep '^Traceback'
394 $ hg --traceback commit -d '0 0' -ma 2>&1 | grep '^Traceback'
395 Traceback (most recent call last):
395 Traceback (most recent call last):
396
396
397 $ cd ..
397 $ cd ..
398 $ hg init c
398 $ hg init c
399 $ cd c
399 $ cd c
400
400
401 $ cat > hookext.py <<EOF
401 $ cat > hookext.py <<EOF
402 > def autohook(**args):
402 > def autohook(**args):
403 > print "Automatically installed hook"
403 > print "Automatically installed hook"
404 >
404 >
405 > def reposetup(ui, repo):
405 > def reposetup(ui, repo):
406 > repo.ui.setconfig("hooks", "commit.auto", autohook)
406 > repo.ui.setconfig("hooks", "commit.auto", autohook)
407 > EOF
407 > EOF
408 $ echo '[extensions]' >> .hg/hgrc
408 $ echo '[extensions]' >> .hg/hgrc
409 $ echo 'hookext = hookext.py' >> .hg/hgrc
409 $ echo 'hookext = hookext.py' >> .hg/hgrc
410
410
411 $ touch foo
411 $ touch foo
412 $ hg add foo
412 $ hg add foo
413 $ hg ci -d '0 0' -m 'add foo'
413 $ hg ci -d '0 0' -m 'add foo'
414 Automatically installed hook
414 Automatically installed hook
415 $ echo >> foo
415 $ echo >> foo
416 $ hg ci --debug -d '0 0' -m 'change foo'
416 $ hg ci --debug -d '0 0' -m 'change foo'
417 foo
417 foo
418 calling hook commit.auto: <function autohook at *> (glob)
418 calling hook commit.auto: <function autohook at *> (glob)
419 Automatically installed hook
419 Automatically installed hook
420 committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708
420 committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708
421
421
422 $ hg showconfig hooks
422 $ hg showconfig hooks
423 hooks.commit.auto=<function autohook at *> (glob)
423 hooks.commit.auto=<function autohook at *> (glob)
424
424
425 test python hook configured with python:[file]:[hook] syntax
425 test python hook configured with python:[file]:[hook] syntax
426
426
427 $ cd ..
427 $ cd ..
428 $ mkdir d
428 $ mkdir d
429 $ cd d
429 $ cd d
430 $ hg init repo
430 $ hg init repo
431 $ mkdir hooks
431 $ mkdir hooks
432
432
433 $ cd hooks
433 $ cd hooks
434 $ cat > testhooks.py <<EOF
434 $ cat > testhooks.py <<EOF
435 > def testhook(**args):
435 > def testhook(**args):
436 > print 'hook works'
436 > print 'hook works'
437 > EOF
437 > EOF
438 $ echo '[hooks]' > ../repo/.hg/hgrc
438 $ echo '[hooks]' > ../repo/.hg/hgrc
439 $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
439 $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
440
440
441 $ cd ../repo
441 $ cd ../repo
442 $ hg commit -d '0 0'
442 $ hg commit -d '0 0'
443 hook works
443 hook works
444 nothing changed
444 nothing changed
445 [1]
445 [1]
446
446
447 $ cd ../../b
447 $ cd ../../b
448
448
449 make sure --traceback works on hook import failure
449 make sure --traceback works on hook import failure
450
450
451 $ cat > importfail.py <<EOF
451 $ cat > importfail.py <<EOF
452 > import somebogusmodule
452 > import somebogusmodule
453 > # dereference something in the module to force demandimport to load it
453 > # dereference something in the module to force demandimport to load it
454 > somebogusmodule.whatever
454 > somebogusmodule.whatever
455 > EOF
455 > EOF
456
456
457 $ echo '[hooks]' > .hg/hgrc
457 $ echo '[hooks]' > .hg/hgrc
458 $ echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
458 $ echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
459
459
460 $ echo a >> a
460 $ echo a >> a
461 $ hg --traceback commit -ma 2>&1 | egrep '^(exception|Traceback|ImportError)'
461 $ hg --traceback commit -ma 2>&1 | egrep '^(exception|Traceback|ImportError)'
462 exception from first failed import attempt:
462 exception from first failed import attempt:
463 Traceback (most recent call last):
463 Traceback (most recent call last):
464 ImportError: No module named somebogusmodule
464 ImportError: No module named somebogusmodule
465 exception from second failed import attempt:
465 exception from second failed import attempt:
466 Traceback (most recent call last):
466 Traceback (most recent call last):
467 ImportError: No module named hgext_importfail
467 ImportError: No module named hgext_importfail
468 Traceback (most recent call last):
468 Traceback (most recent call last):
469
469
470 Issue1827: Hooks Update & Commit not completely post operation
470 Issue1827: Hooks Update & Commit not completely post operation
471
471
472 commit and update hooks should run after command completion
472 commit and update hooks should run after command completion
473
473
474 $ echo '[hooks]' > .hg/hgrc
474 $ echo '[hooks]' > .hg/hgrc
475 $ echo 'commit = hg id' >> .hg/hgrc
475 $ echo 'commit = hg id' >> .hg/hgrc
476 $ echo 'update = hg id' >> .hg/hgrc
476 $ echo 'update = hg id' >> .hg/hgrc
477 $ echo bb > a
477 $ echo bb > a
478 $ hg ci -ma
478 $ hg ci -ma
479 223eafe2750c tip
479 223eafe2750c tip
480 $ hg up 0
480 $ hg up 0
481 cb9a9f314b8b
481 cb9a9f314b8b
482 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
482 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
483
483
General Comments 0
You need to be logged in to leave comments. Login now