##// END OF EJS Templates
match: ignore "" patterns...
Matt Mackall -
r13534:4ec34de8 stable
parent child Browse files
Show More
@@ -1,1387 +1,1389 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 fp = 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 return os.fdopen(os.dup(fp.fileno()), mode)
238 if hasattr(pat, 'write') and writable:
238 if hasattr(pat, 'write') and writable:
239 return pat
239 return pat
240 if hasattr(pat, 'read') and 'r' in mode:
240 if hasattr(pat, 'read') and 'r' in mode:
241 return pat
241 return pat
242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
243 pathname),
243 pathname),
244 mode)
244 mode)
245
245
246 def expandpats(pats):
246 def expandpats(pats):
247 if not util.expandglobs:
247 if not util.expandglobs:
248 return list(pats)
248 return list(pats)
249 ret = []
249 ret = []
250 for p in pats:
250 for p in pats:
251 kind, name = matchmod._patsplit(p, None)
251 kind, name = matchmod._patsplit(p, None)
252 if kind is None:
252 if kind is None:
253 try:
253 try:
254 globbed = glob.glob(name)
254 globbed = glob.glob(name)
255 except re.error:
255 except re.error:
256 globbed = [name]
256 globbed = [name]
257 if globbed:
257 if globbed:
258 ret.extend(globbed)
258 ret.extend(globbed)
259 continue
259 continue
260 ret.append(p)
260 ret.append(p)
261 return ret
261 return ret
262
262
263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
264 if pats == ("",):
265 pats = []
264 if not globbed and default == 'relpath':
266 if not globbed and default == 'relpath':
265 pats = expandpats(pats or [])
267 pats = expandpats(pats or [])
266 m = matchmod.match(repo.root, repo.getcwd(), pats,
268 m = matchmod.match(repo.root, repo.getcwd(), pats,
267 opts.get('include'), opts.get('exclude'), default,
269 opts.get('include'), opts.get('exclude'), default,
268 auditor=repo.auditor)
270 auditor=repo.auditor)
269 def badfn(f, msg):
271 def badfn(f, msg):
270 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
272 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
271 m.bad = badfn
273 m.bad = badfn
272 return m
274 return m
273
275
274 def matchall(repo):
276 def matchall(repo):
275 return matchmod.always(repo.root, repo.getcwd())
277 return matchmod.always(repo.root, repo.getcwd())
276
278
277 def matchfiles(repo, files):
279 def matchfiles(repo, files):
278 return matchmod.exact(repo.root, repo.getcwd(), files)
280 return matchmod.exact(repo.root, repo.getcwd(), files)
279
281
280 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
282 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
281 if dry_run is None:
283 if dry_run is None:
282 dry_run = opts.get('dry_run')
284 dry_run = opts.get('dry_run')
283 if similarity is None:
285 if similarity is None:
284 similarity = float(opts.get('similarity') or 0)
286 similarity = float(opts.get('similarity') or 0)
285 # we'd use status here, except handling of symlinks and ignore is tricky
287 # we'd use status here, except handling of symlinks and ignore is tricky
286 added, unknown, deleted, removed = [], [], [], []
288 added, unknown, deleted, removed = [], [], [], []
287 audit_path = util.path_auditor(repo.root)
289 audit_path = util.path_auditor(repo.root)
288 m = match(repo, pats, opts)
290 m = match(repo, pats, opts)
289 for abs in repo.walk(m):
291 for abs in repo.walk(m):
290 target = repo.wjoin(abs)
292 target = repo.wjoin(abs)
291 good = True
293 good = True
292 try:
294 try:
293 audit_path(abs)
295 audit_path(abs)
294 except:
296 except:
295 good = False
297 good = False
296 rel = m.rel(abs)
298 rel = m.rel(abs)
297 exact = m.exact(abs)
299 exact = m.exact(abs)
298 if good and abs not in repo.dirstate:
300 if good and abs not in repo.dirstate:
299 unknown.append(abs)
301 unknown.append(abs)
300 if repo.ui.verbose or not exact:
302 if repo.ui.verbose or not exact:
301 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
303 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
302 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
304 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
303 or (os.path.isdir(target) and not os.path.islink(target))):
305 or (os.path.isdir(target) and not os.path.islink(target))):
304 deleted.append(abs)
306 deleted.append(abs)
305 if repo.ui.verbose or not exact:
307 if repo.ui.verbose or not exact:
306 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
308 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
307 # for finding renames
309 # for finding renames
308 elif repo.dirstate[abs] == 'r':
310 elif repo.dirstate[abs] == 'r':
309 removed.append(abs)
311 removed.append(abs)
310 elif repo.dirstate[abs] == 'a':
312 elif repo.dirstate[abs] == 'a':
311 added.append(abs)
313 added.append(abs)
312 copies = {}
314 copies = {}
313 if similarity > 0:
315 if similarity > 0:
314 for old, new, score in similar.findrenames(repo,
316 for old, new, score in similar.findrenames(repo,
315 added + unknown, removed + deleted, similarity):
317 added + unknown, removed + deleted, similarity):
316 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
318 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
317 repo.ui.status(_('recording removal of %s as rename to %s '
319 repo.ui.status(_('recording removal of %s as rename to %s '
318 '(%d%% similar)\n') %
320 '(%d%% similar)\n') %
319 (m.rel(old), m.rel(new), score * 100))
321 (m.rel(old), m.rel(new), score * 100))
320 copies[new] = old
322 copies[new] = old
321
323
322 if not dry_run:
324 if not dry_run:
323 wctx = repo[None]
325 wctx = repo[None]
324 wlock = repo.wlock()
326 wlock = repo.wlock()
325 try:
327 try:
326 wctx.remove(deleted)
328 wctx.remove(deleted)
327 wctx.add(unknown)
329 wctx.add(unknown)
328 for new, old in copies.iteritems():
330 for new, old in copies.iteritems():
329 wctx.copy(old, new)
331 wctx.copy(old, new)
330 finally:
332 finally:
331 wlock.release()
333 wlock.release()
332
334
333 def updatedir(ui, repo, patches, similarity=0):
335 def updatedir(ui, repo, patches, similarity=0):
334 '''Update dirstate after patch application according to metadata'''
336 '''Update dirstate after patch application according to metadata'''
335 if not patches:
337 if not patches:
336 return
338 return
337 copies = []
339 copies = []
338 removes = set()
340 removes = set()
339 cfiles = patches.keys()
341 cfiles = patches.keys()
340 cwd = repo.getcwd()
342 cwd = repo.getcwd()
341 if cwd:
343 if cwd:
342 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
344 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
343 for f in patches:
345 for f in patches:
344 gp = patches[f]
346 gp = patches[f]
345 if not gp:
347 if not gp:
346 continue
348 continue
347 if gp.op == 'RENAME':
349 if gp.op == 'RENAME':
348 copies.append((gp.oldpath, gp.path))
350 copies.append((gp.oldpath, gp.path))
349 removes.add(gp.oldpath)
351 removes.add(gp.oldpath)
350 elif gp.op == 'COPY':
352 elif gp.op == 'COPY':
351 copies.append((gp.oldpath, gp.path))
353 copies.append((gp.oldpath, gp.path))
352 elif gp.op == 'DELETE':
354 elif gp.op == 'DELETE':
353 removes.add(gp.path)
355 removes.add(gp.path)
354
356
355 wctx = repo[None]
357 wctx = repo[None]
356 for src, dst in copies:
358 for src, dst in copies:
357 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
359 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
358 if (not similarity) and removes:
360 if (not similarity) and removes:
359 wctx.remove(sorted(removes), True)
361 wctx.remove(sorted(removes), True)
360
362
361 for f in patches:
363 for f in patches:
362 gp = patches[f]
364 gp = patches[f]
363 if gp and gp.mode:
365 if gp and gp.mode:
364 islink, isexec = gp.mode
366 islink, isexec = gp.mode
365 dst = repo.wjoin(gp.path)
367 dst = repo.wjoin(gp.path)
366 # patch won't create empty files
368 # patch won't create empty files
367 if gp.op == 'ADD' and not os.path.lexists(dst):
369 if gp.op == 'ADD' and not os.path.lexists(dst):
368 flags = (isexec and 'x' or '') + (islink and 'l' or '')
370 flags = (isexec and 'x' or '') + (islink and 'l' or '')
369 repo.wwrite(gp.path, '', flags)
371 repo.wwrite(gp.path, '', flags)
370 util.set_flags(dst, islink, isexec)
372 util.set_flags(dst, islink, isexec)
371 addremove(repo, cfiles, similarity=similarity)
373 addremove(repo, cfiles, similarity=similarity)
372 files = patches.keys()
374 files = patches.keys()
373 files.extend([r for r in removes if r not in files])
375 files.extend([r for r in removes if r not in files])
374 return sorted(files)
376 return sorted(files)
375
377
376 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
378 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
377 """Update the dirstate to reflect the intent of copying src to dst. For
379 """Update the dirstate to reflect the intent of copying src to dst. For
378 different reasons it might not end with dst being marked as copied from src.
380 different reasons it might not end with dst being marked as copied from src.
379 """
381 """
380 origsrc = repo.dirstate.copied(src) or src
382 origsrc = repo.dirstate.copied(src) or src
381 if dst == origsrc: # copying back a copy?
383 if dst == origsrc: # copying back a copy?
382 if repo.dirstate[dst] not in 'mn' and not dryrun:
384 if repo.dirstate[dst] not in 'mn' and not dryrun:
383 repo.dirstate.normallookup(dst)
385 repo.dirstate.normallookup(dst)
384 else:
386 else:
385 if repo.dirstate[origsrc] == 'a' and origsrc == src:
387 if repo.dirstate[origsrc] == 'a' and origsrc == src:
386 if not ui.quiet:
388 if not ui.quiet:
387 ui.warn(_("%s has not been committed yet, so no copy "
389 ui.warn(_("%s has not been committed yet, so no copy "
388 "data will be stored for %s.\n")
390 "data will be stored for %s.\n")
389 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
391 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
390 if repo.dirstate[dst] in '?r' and not dryrun:
392 if repo.dirstate[dst] in '?r' and not dryrun:
391 wctx.add([dst])
393 wctx.add([dst])
392 elif not dryrun:
394 elif not dryrun:
393 wctx.copy(origsrc, dst)
395 wctx.copy(origsrc, dst)
394
396
395 def copy(ui, repo, pats, opts, rename=False):
397 def copy(ui, repo, pats, opts, rename=False):
396 # called with the repo lock held
398 # called with the repo lock held
397 #
399 #
398 # hgsep => pathname that uses "/" to separate directories
400 # hgsep => pathname that uses "/" to separate directories
399 # ossep => pathname that uses os.sep to separate directories
401 # ossep => pathname that uses os.sep to separate directories
400 cwd = repo.getcwd()
402 cwd = repo.getcwd()
401 targets = {}
403 targets = {}
402 after = opts.get("after")
404 after = opts.get("after")
403 dryrun = opts.get("dry_run")
405 dryrun = opts.get("dry_run")
404 wctx = repo[None]
406 wctx = repo[None]
405
407
406 def walkpat(pat):
408 def walkpat(pat):
407 srcs = []
409 srcs = []
408 badstates = after and '?' or '?r'
410 badstates = after and '?' or '?r'
409 m = match(repo, [pat], opts, globbed=True)
411 m = match(repo, [pat], opts, globbed=True)
410 for abs in repo.walk(m):
412 for abs in repo.walk(m):
411 state = repo.dirstate[abs]
413 state = repo.dirstate[abs]
412 rel = m.rel(abs)
414 rel = m.rel(abs)
413 exact = m.exact(abs)
415 exact = m.exact(abs)
414 if state in badstates:
416 if state in badstates:
415 if exact and state == '?':
417 if exact and state == '?':
416 ui.warn(_('%s: not copying - file is not managed\n') % rel)
418 ui.warn(_('%s: not copying - file is not managed\n') % rel)
417 if exact and state == 'r':
419 if exact and state == 'r':
418 ui.warn(_('%s: not copying - file has been marked for'
420 ui.warn(_('%s: not copying - file has been marked for'
419 ' remove\n') % rel)
421 ' remove\n') % rel)
420 continue
422 continue
421 # abs: hgsep
423 # abs: hgsep
422 # rel: ossep
424 # rel: ossep
423 srcs.append((abs, rel, exact))
425 srcs.append((abs, rel, exact))
424 return srcs
426 return srcs
425
427
426 # abssrc: hgsep
428 # abssrc: hgsep
427 # relsrc: ossep
429 # relsrc: ossep
428 # otarget: ossep
430 # otarget: ossep
429 def copyfile(abssrc, relsrc, otarget, exact):
431 def copyfile(abssrc, relsrc, otarget, exact):
430 abstarget = util.canonpath(repo.root, cwd, otarget)
432 abstarget = util.canonpath(repo.root, cwd, otarget)
431 reltarget = repo.pathto(abstarget, cwd)
433 reltarget = repo.pathto(abstarget, cwd)
432 target = repo.wjoin(abstarget)
434 target = repo.wjoin(abstarget)
433 src = repo.wjoin(abssrc)
435 src = repo.wjoin(abssrc)
434 state = repo.dirstate[abstarget]
436 state = repo.dirstate[abstarget]
435
437
436 # check for collisions
438 # check for collisions
437 prevsrc = targets.get(abstarget)
439 prevsrc = targets.get(abstarget)
438 if prevsrc is not None:
440 if prevsrc is not None:
439 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
441 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
440 (reltarget, repo.pathto(abssrc, cwd),
442 (reltarget, repo.pathto(abssrc, cwd),
441 repo.pathto(prevsrc, cwd)))
443 repo.pathto(prevsrc, cwd)))
442 return
444 return
443
445
444 # check for overwrites
446 # check for overwrites
445 exists = os.path.lexists(target)
447 exists = os.path.lexists(target)
446 if not after and exists or after and state in 'mn':
448 if not after and exists or after and state in 'mn':
447 if not opts['force']:
449 if not opts['force']:
448 ui.warn(_('%s: not overwriting - file exists\n') %
450 ui.warn(_('%s: not overwriting - file exists\n') %
449 reltarget)
451 reltarget)
450 return
452 return
451
453
452 if after:
454 if after:
453 if not exists:
455 if not exists:
454 if rename:
456 if rename:
455 ui.warn(_('%s: not recording move - %s does not exist\n') %
457 ui.warn(_('%s: not recording move - %s does not exist\n') %
456 (relsrc, reltarget))
458 (relsrc, reltarget))
457 else:
459 else:
458 ui.warn(_('%s: not recording copy - %s does not exist\n') %
460 ui.warn(_('%s: not recording copy - %s does not exist\n') %
459 (relsrc, reltarget))
461 (relsrc, reltarget))
460 return
462 return
461 elif not dryrun:
463 elif not dryrun:
462 try:
464 try:
463 if exists:
465 if exists:
464 os.unlink(target)
466 os.unlink(target)
465 targetdir = os.path.dirname(target) or '.'
467 targetdir = os.path.dirname(target) or '.'
466 if not os.path.isdir(targetdir):
468 if not os.path.isdir(targetdir):
467 os.makedirs(targetdir)
469 os.makedirs(targetdir)
468 util.copyfile(src, target)
470 util.copyfile(src, target)
469 except IOError, inst:
471 except IOError, inst:
470 if inst.errno == errno.ENOENT:
472 if inst.errno == errno.ENOENT:
471 ui.warn(_('%s: deleted in working copy\n') % relsrc)
473 ui.warn(_('%s: deleted in working copy\n') % relsrc)
472 else:
474 else:
473 ui.warn(_('%s: cannot copy - %s\n') %
475 ui.warn(_('%s: cannot copy - %s\n') %
474 (relsrc, inst.strerror))
476 (relsrc, inst.strerror))
475 return True # report a failure
477 return True # report a failure
476
478
477 if ui.verbose or not exact:
479 if ui.verbose or not exact:
478 if rename:
480 if rename:
479 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
481 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
480 else:
482 else:
481 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
483 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
482
484
483 targets[abstarget] = abssrc
485 targets[abstarget] = abssrc
484
486
485 # fix up dirstate
487 # fix up dirstate
486 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
488 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
487 if rename and not dryrun:
489 if rename and not dryrun:
488 wctx.remove([abssrc], not after)
490 wctx.remove([abssrc], not after)
489
491
490 # pat: ossep
492 # pat: ossep
491 # dest ossep
493 # dest ossep
492 # srcs: list of (hgsep, hgsep, ossep, bool)
494 # srcs: list of (hgsep, hgsep, ossep, bool)
493 # return: function that takes hgsep and returns ossep
495 # return: function that takes hgsep and returns ossep
494 def targetpathfn(pat, dest, srcs):
496 def targetpathfn(pat, dest, srcs):
495 if os.path.isdir(pat):
497 if os.path.isdir(pat):
496 abspfx = util.canonpath(repo.root, cwd, pat)
498 abspfx = util.canonpath(repo.root, cwd, pat)
497 abspfx = util.localpath(abspfx)
499 abspfx = util.localpath(abspfx)
498 if destdirexists:
500 if destdirexists:
499 striplen = len(os.path.split(abspfx)[0])
501 striplen = len(os.path.split(abspfx)[0])
500 else:
502 else:
501 striplen = len(abspfx)
503 striplen = len(abspfx)
502 if striplen:
504 if striplen:
503 striplen += len(os.sep)
505 striplen += len(os.sep)
504 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
506 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
505 elif destdirexists:
507 elif destdirexists:
506 res = lambda p: os.path.join(dest,
508 res = lambda p: os.path.join(dest,
507 os.path.basename(util.localpath(p)))
509 os.path.basename(util.localpath(p)))
508 else:
510 else:
509 res = lambda p: dest
511 res = lambda p: dest
510 return res
512 return res
511
513
512 # pat: ossep
514 # pat: ossep
513 # dest ossep
515 # dest ossep
514 # srcs: list of (hgsep, hgsep, ossep, bool)
516 # srcs: list of (hgsep, hgsep, ossep, bool)
515 # return: function that takes hgsep and returns ossep
517 # return: function that takes hgsep and returns ossep
516 def targetpathafterfn(pat, dest, srcs):
518 def targetpathafterfn(pat, dest, srcs):
517 if matchmod.patkind(pat):
519 if matchmod.patkind(pat):
518 # a mercurial pattern
520 # a mercurial pattern
519 res = lambda p: os.path.join(dest,
521 res = lambda p: os.path.join(dest,
520 os.path.basename(util.localpath(p)))
522 os.path.basename(util.localpath(p)))
521 else:
523 else:
522 abspfx = util.canonpath(repo.root, cwd, pat)
524 abspfx = util.canonpath(repo.root, cwd, pat)
523 if len(abspfx) < len(srcs[0][0]):
525 if len(abspfx) < len(srcs[0][0]):
524 # A directory. Either the target path contains the last
526 # A directory. Either the target path contains the last
525 # component of the source path or it does not.
527 # component of the source path or it does not.
526 def evalpath(striplen):
528 def evalpath(striplen):
527 score = 0
529 score = 0
528 for s in srcs:
530 for s in srcs:
529 t = os.path.join(dest, util.localpath(s[0])[striplen:])
531 t = os.path.join(dest, util.localpath(s[0])[striplen:])
530 if os.path.lexists(t):
532 if os.path.lexists(t):
531 score += 1
533 score += 1
532 return score
534 return score
533
535
534 abspfx = util.localpath(abspfx)
536 abspfx = util.localpath(abspfx)
535 striplen = len(abspfx)
537 striplen = len(abspfx)
536 if striplen:
538 if striplen:
537 striplen += len(os.sep)
539 striplen += len(os.sep)
538 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
540 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
539 score = evalpath(striplen)
541 score = evalpath(striplen)
540 striplen1 = len(os.path.split(abspfx)[0])
542 striplen1 = len(os.path.split(abspfx)[0])
541 if striplen1:
543 if striplen1:
542 striplen1 += len(os.sep)
544 striplen1 += len(os.sep)
543 if evalpath(striplen1) > score:
545 if evalpath(striplen1) > score:
544 striplen = striplen1
546 striplen = striplen1
545 res = lambda p: os.path.join(dest,
547 res = lambda p: os.path.join(dest,
546 util.localpath(p)[striplen:])
548 util.localpath(p)[striplen:])
547 else:
549 else:
548 # a file
550 # a file
549 if destdirexists:
551 if destdirexists:
550 res = lambda p: os.path.join(dest,
552 res = lambda p: os.path.join(dest,
551 os.path.basename(util.localpath(p)))
553 os.path.basename(util.localpath(p)))
552 else:
554 else:
553 res = lambda p: dest
555 res = lambda p: dest
554 return res
556 return res
555
557
556
558
557 pats = expandpats(pats)
559 pats = expandpats(pats)
558 if not pats:
560 if not pats:
559 raise util.Abort(_('no source or destination specified'))
561 raise util.Abort(_('no source or destination specified'))
560 if len(pats) == 1:
562 if len(pats) == 1:
561 raise util.Abort(_('no destination specified'))
563 raise util.Abort(_('no destination specified'))
562 dest = pats.pop()
564 dest = pats.pop()
563 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
565 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
564 if not destdirexists:
566 if not destdirexists:
565 if len(pats) > 1 or matchmod.patkind(pats[0]):
567 if len(pats) > 1 or matchmod.patkind(pats[0]):
566 raise util.Abort(_('with multiple sources, destination must be an '
568 raise util.Abort(_('with multiple sources, destination must be an '
567 'existing directory'))
569 'existing directory'))
568 if util.endswithsep(dest):
570 if util.endswithsep(dest):
569 raise util.Abort(_('destination %s is not a directory') % dest)
571 raise util.Abort(_('destination %s is not a directory') % dest)
570
572
571 tfn = targetpathfn
573 tfn = targetpathfn
572 if after:
574 if after:
573 tfn = targetpathafterfn
575 tfn = targetpathafterfn
574 copylist = []
576 copylist = []
575 for pat in pats:
577 for pat in pats:
576 srcs = walkpat(pat)
578 srcs = walkpat(pat)
577 if not srcs:
579 if not srcs:
578 continue
580 continue
579 copylist.append((tfn(pat, dest, srcs), srcs))
581 copylist.append((tfn(pat, dest, srcs), srcs))
580 if not copylist:
582 if not copylist:
581 raise util.Abort(_('no files to copy'))
583 raise util.Abort(_('no files to copy'))
582
584
583 errors = 0
585 errors = 0
584 for targetpath, srcs in copylist:
586 for targetpath, srcs in copylist:
585 for abssrc, relsrc, exact in srcs:
587 for abssrc, relsrc, exact in srcs:
586 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
588 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
587 errors += 1
589 errors += 1
588
590
589 if errors:
591 if errors:
590 ui.warn(_('(consider using --after)\n'))
592 ui.warn(_('(consider using --after)\n'))
591
593
592 return errors != 0
594 return errors != 0
593
595
594 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
596 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
595 runargs=None, appendpid=False):
597 runargs=None, appendpid=False):
596 '''Run a command as a service.'''
598 '''Run a command as a service.'''
597
599
598 if opts['daemon'] and not opts['daemon_pipefds']:
600 if opts['daemon'] and not opts['daemon_pipefds']:
599 # Signal child process startup with file removal
601 # Signal child process startup with file removal
600 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
602 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
601 os.close(lockfd)
603 os.close(lockfd)
602 try:
604 try:
603 if not runargs:
605 if not runargs:
604 runargs = util.hgcmd() + sys.argv[1:]
606 runargs = util.hgcmd() + sys.argv[1:]
605 runargs.append('--daemon-pipefds=%s' % lockpath)
607 runargs.append('--daemon-pipefds=%s' % lockpath)
606 # Don't pass --cwd to the child process, because we've already
608 # Don't pass --cwd to the child process, because we've already
607 # changed directory.
609 # changed directory.
608 for i in xrange(1, len(runargs)):
610 for i in xrange(1, len(runargs)):
609 if runargs[i].startswith('--cwd='):
611 if runargs[i].startswith('--cwd='):
610 del runargs[i]
612 del runargs[i]
611 break
613 break
612 elif runargs[i].startswith('--cwd'):
614 elif runargs[i].startswith('--cwd'):
613 del runargs[i:i + 2]
615 del runargs[i:i + 2]
614 break
616 break
615 def condfn():
617 def condfn():
616 return not os.path.exists(lockpath)
618 return not os.path.exists(lockpath)
617 pid = util.rundetached(runargs, condfn)
619 pid = util.rundetached(runargs, condfn)
618 if pid < 0:
620 if pid < 0:
619 raise util.Abort(_('child process failed to start'))
621 raise util.Abort(_('child process failed to start'))
620 finally:
622 finally:
621 try:
623 try:
622 os.unlink(lockpath)
624 os.unlink(lockpath)
623 except OSError, e:
625 except OSError, e:
624 if e.errno != errno.ENOENT:
626 if e.errno != errno.ENOENT:
625 raise
627 raise
626 if parentfn:
628 if parentfn:
627 return parentfn(pid)
629 return parentfn(pid)
628 else:
630 else:
629 return
631 return
630
632
631 if initfn:
633 if initfn:
632 initfn()
634 initfn()
633
635
634 if opts['pid_file']:
636 if opts['pid_file']:
635 mode = appendpid and 'a' or 'w'
637 mode = appendpid and 'a' or 'w'
636 fp = open(opts['pid_file'], mode)
638 fp = open(opts['pid_file'], mode)
637 fp.write(str(os.getpid()) + '\n')
639 fp.write(str(os.getpid()) + '\n')
638 fp.close()
640 fp.close()
639
641
640 if opts['daemon_pipefds']:
642 if opts['daemon_pipefds']:
641 lockpath = opts['daemon_pipefds']
643 lockpath = opts['daemon_pipefds']
642 try:
644 try:
643 os.setsid()
645 os.setsid()
644 except AttributeError:
646 except AttributeError:
645 pass
647 pass
646 os.unlink(lockpath)
648 os.unlink(lockpath)
647 util.hidewindow()
649 util.hidewindow()
648 sys.stdout.flush()
650 sys.stdout.flush()
649 sys.stderr.flush()
651 sys.stderr.flush()
650
652
651 nullfd = os.open(util.nulldev, os.O_RDWR)
653 nullfd = os.open(util.nulldev, os.O_RDWR)
652 logfilefd = nullfd
654 logfilefd = nullfd
653 if logfile:
655 if logfile:
654 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
656 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
655 os.dup2(nullfd, 0)
657 os.dup2(nullfd, 0)
656 os.dup2(logfilefd, 1)
658 os.dup2(logfilefd, 1)
657 os.dup2(logfilefd, 2)
659 os.dup2(logfilefd, 2)
658 if nullfd not in (0, 1, 2):
660 if nullfd not in (0, 1, 2):
659 os.close(nullfd)
661 os.close(nullfd)
660 if logfile and logfilefd not in (0, 1, 2):
662 if logfile and logfilefd not in (0, 1, 2):
661 os.close(logfilefd)
663 os.close(logfilefd)
662
664
663 if runfn:
665 if runfn:
664 return runfn()
666 return runfn()
665
667
666 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
668 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
667 opts=None):
669 opts=None):
668 '''export changesets as hg patches.'''
670 '''export changesets as hg patches.'''
669
671
670 total = len(revs)
672 total = len(revs)
671 revwidth = max([len(str(rev)) for rev in revs])
673 revwidth = max([len(str(rev)) for rev in revs])
672
674
673 def single(rev, seqno, fp):
675 def single(rev, seqno, fp):
674 ctx = repo[rev]
676 ctx = repo[rev]
675 node = ctx.node()
677 node = ctx.node()
676 parents = [p.node() for p in ctx.parents() if p]
678 parents = [p.node() for p in ctx.parents() if p]
677 branch = ctx.branch()
679 branch = ctx.branch()
678 if switch_parent:
680 if switch_parent:
679 parents.reverse()
681 parents.reverse()
680 prev = (parents and parents[0]) or nullid
682 prev = (parents and parents[0]) or nullid
681
683
682 shouldclose = False
684 shouldclose = False
683 if not fp:
685 if not fp:
684 fp = make_file(repo, template, node, total=total, seqno=seqno,
686 fp = make_file(repo, template, node, total=total, seqno=seqno,
685 revwidth=revwidth, mode='ab')
687 revwidth=revwidth, mode='ab')
686 if fp != template:
688 if fp != template:
687 shouldclose = True
689 shouldclose = True
688 if fp != sys.stdout and hasattr(fp, 'name'):
690 if fp != sys.stdout and hasattr(fp, 'name'):
689 repo.ui.note("%s\n" % fp.name)
691 repo.ui.note("%s\n" % fp.name)
690
692
691 fp.write("# HG changeset patch\n")
693 fp.write("# HG changeset patch\n")
692 fp.write("# User %s\n" % ctx.user())
694 fp.write("# User %s\n" % ctx.user())
693 fp.write("# Date %d %d\n" % ctx.date())
695 fp.write("# Date %d %d\n" % ctx.date())
694 if branch and branch != 'default':
696 if branch and branch != 'default':
695 fp.write("# Branch %s\n" % branch)
697 fp.write("# Branch %s\n" % branch)
696 fp.write("# Node ID %s\n" % hex(node))
698 fp.write("# Node ID %s\n" % hex(node))
697 fp.write("# Parent %s\n" % hex(prev))
699 fp.write("# Parent %s\n" % hex(prev))
698 if len(parents) > 1:
700 if len(parents) > 1:
699 fp.write("# Parent %s\n" % hex(parents[1]))
701 fp.write("# Parent %s\n" % hex(parents[1]))
700 fp.write(ctx.description().rstrip())
702 fp.write(ctx.description().rstrip())
701 fp.write("\n\n")
703 fp.write("\n\n")
702
704
703 for chunk in patch.diff(repo, prev, node, opts=opts):
705 for chunk in patch.diff(repo, prev, node, opts=opts):
704 fp.write(chunk)
706 fp.write(chunk)
705
707
706 if shouldclose:
708 if shouldclose:
707 fp.close()
709 fp.close()
708
710
709 for seqno, rev in enumerate(revs):
711 for seqno, rev in enumerate(revs):
710 single(rev, seqno + 1, fp)
712 single(rev, seqno + 1, fp)
711
713
712 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
714 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
713 changes=None, stat=False, fp=None, prefix='',
715 changes=None, stat=False, fp=None, prefix='',
714 listsubrepos=False):
716 listsubrepos=False):
715 '''show diff or diffstat.'''
717 '''show diff or diffstat.'''
716 if fp is None:
718 if fp is None:
717 write = ui.write
719 write = ui.write
718 else:
720 else:
719 def write(s, **kw):
721 def write(s, **kw):
720 fp.write(s)
722 fp.write(s)
721
723
722 if stat:
724 if stat:
723 diffopts = diffopts.copy(context=0)
725 diffopts = diffopts.copy(context=0)
724 width = 80
726 width = 80
725 if not ui.plain():
727 if not ui.plain():
726 width = ui.termwidth()
728 width = ui.termwidth()
727 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
729 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
728 prefix=prefix)
730 prefix=prefix)
729 for chunk, label in patch.diffstatui(util.iterlines(chunks),
731 for chunk, label in patch.diffstatui(util.iterlines(chunks),
730 width=width,
732 width=width,
731 git=diffopts.git):
733 git=diffopts.git):
732 write(chunk, label=label)
734 write(chunk, label=label)
733 else:
735 else:
734 for chunk, label in patch.diffui(repo, node1, node2, match,
736 for chunk, label in patch.diffui(repo, node1, node2, match,
735 changes, diffopts, prefix=prefix):
737 changes, diffopts, prefix=prefix):
736 write(chunk, label=label)
738 write(chunk, label=label)
737
739
738 if listsubrepos:
740 if listsubrepos:
739 ctx1 = repo[node1]
741 ctx1 = repo[node1]
740 ctx2 = repo[node2]
742 ctx2 = repo[node2]
741 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
743 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
742 if node2 is not None:
744 if node2 is not None:
743 node2 = ctx2.substate[subpath][1]
745 node2 = ctx2.substate[subpath][1]
744 submatch = matchmod.narrowmatcher(subpath, match)
746 submatch = matchmod.narrowmatcher(subpath, match)
745 sub.diff(diffopts, node2, submatch, changes=changes,
747 sub.diff(diffopts, node2, submatch, changes=changes,
746 stat=stat, fp=fp, prefix=prefix)
748 stat=stat, fp=fp, prefix=prefix)
747
749
748 class changeset_printer(object):
750 class changeset_printer(object):
749 '''show changeset information when templating not requested.'''
751 '''show changeset information when templating not requested.'''
750
752
751 def __init__(self, ui, repo, patch, diffopts, buffered):
753 def __init__(self, ui, repo, patch, diffopts, buffered):
752 self.ui = ui
754 self.ui = ui
753 self.repo = repo
755 self.repo = repo
754 self.buffered = buffered
756 self.buffered = buffered
755 self.patch = patch
757 self.patch = patch
756 self.diffopts = diffopts
758 self.diffopts = diffopts
757 self.header = {}
759 self.header = {}
758 self.hunk = {}
760 self.hunk = {}
759 self.lastheader = None
761 self.lastheader = None
760 self.footer = None
762 self.footer = None
761
763
762 def flush(self, rev):
764 def flush(self, rev):
763 if rev in self.header:
765 if rev in self.header:
764 h = self.header[rev]
766 h = self.header[rev]
765 if h != self.lastheader:
767 if h != self.lastheader:
766 self.lastheader = h
768 self.lastheader = h
767 self.ui.write(h)
769 self.ui.write(h)
768 del self.header[rev]
770 del self.header[rev]
769 if rev in self.hunk:
771 if rev in self.hunk:
770 self.ui.write(self.hunk[rev])
772 self.ui.write(self.hunk[rev])
771 del self.hunk[rev]
773 del self.hunk[rev]
772 return 1
774 return 1
773 return 0
775 return 0
774
776
775 def close(self):
777 def close(self):
776 if self.footer:
778 if self.footer:
777 self.ui.write(self.footer)
779 self.ui.write(self.footer)
778
780
779 def show(self, ctx, copies=None, matchfn=None, **props):
781 def show(self, ctx, copies=None, matchfn=None, **props):
780 if self.buffered:
782 if self.buffered:
781 self.ui.pushbuffer()
783 self.ui.pushbuffer()
782 self._show(ctx, copies, matchfn, props)
784 self._show(ctx, copies, matchfn, props)
783 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
785 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
784 else:
786 else:
785 self._show(ctx, copies, matchfn, props)
787 self._show(ctx, copies, matchfn, props)
786
788
787 def _show(self, ctx, copies, matchfn, props):
789 def _show(self, ctx, copies, matchfn, props):
788 '''show a single changeset or file revision'''
790 '''show a single changeset or file revision'''
789 changenode = ctx.node()
791 changenode = ctx.node()
790 rev = ctx.rev()
792 rev = ctx.rev()
791
793
792 if self.ui.quiet:
794 if self.ui.quiet:
793 self.ui.write("%d:%s\n" % (rev, short(changenode)),
795 self.ui.write("%d:%s\n" % (rev, short(changenode)),
794 label='log.node')
796 label='log.node')
795 return
797 return
796
798
797 log = self.repo.changelog
799 log = self.repo.changelog
798 date = util.datestr(ctx.date())
800 date = util.datestr(ctx.date())
799
801
800 hexfunc = self.ui.debugflag and hex or short
802 hexfunc = self.ui.debugflag and hex or short
801
803
802 parents = [(p, hexfunc(log.node(p)))
804 parents = [(p, hexfunc(log.node(p)))
803 for p in self._meaningful_parentrevs(log, rev)]
805 for p in self._meaningful_parentrevs(log, rev)]
804
806
805 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
807 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
806 label='log.changeset')
808 label='log.changeset')
807
809
808 branch = ctx.branch()
810 branch = ctx.branch()
809 # don't show the default branch name
811 # don't show the default branch name
810 if branch != 'default':
812 if branch != 'default':
811 self.ui.write(_("branch: %s\n") % branch,
813 self.ui.write(_("branch: %s\n") % branch,
812 label='log.branch')
814 label='log.branch')
813 for bookmark in self.repo.nodebookmarks(changenode):
815 for bookmark in self.repo.nodebookmarks(changenode):
814 self.ui.write(_("bookmark: %s\n") % bookmark,
816 self.ui.write(_("bookmark: %s\n") % bookmark,
815 label='log.bookmark')
817 label='log.bookmark')
816 for tag in self.repo.nodetags(changenode):
818 for tag in self.repo.nodetags(changenode):
817 self.ui.write(_("tag: %s\n") % tag,
819 self.ui.write(_("tag: %s\n") % tag,
818 label='log.tag')
820 label='log.tag')
819 for parent in parents:
821 for parent in parents:
820 self.ui.write(_("parent: %d:%s\n") % parent,
822 self.ui.write(_("parent: %d:%s\n") % parent,
821 label='log.parent')
823 label='log.parent')
822
824
823 if self.ui.debugflag:
825 if self.ui.debugflag:
824 mnode = ctx.manifestnode()
826 mnode = ctx.manifestnode()
825 self.ui.write(_("manifest: %d:%s\n") %
827 self.ui.write(_("manifest: %d:%s\n") %
826 (self.repo.manifest.rev(mnode), hex(mnode)),
828 (self.repo.manifest.rev(mnode), hex(mnode)),
827 label='ui.debug log.manifest')
829 label='ui.debug log.manifest')
828 self.ui.write(_("user: %s\n") % ctx.user(),
830 self.ui.write(_("user: %s\n") % ctx.user(),
829 label='log.user')
831 label='log.user')
830 self.ui.write(_("date: %s\n") % date,
832 self.ui.write(_("date: %s\n") % date,
831 label='log.date')
833 label='log.date')
832
834
833 if self.ui.debugflag:
835 if self.ui.debugflag:
834 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
836 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
835 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
837 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
836 files):
838 files):
837 if value:
839 if value:
838 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
840 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
839 label='ui.debug log.files')
841 label='ui.debug log.files')
840 elif ctx.files() and self.ui.verbose:
842 elif ctx.files() and self.ui.verbose:
841 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
843 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
842 label='ui.note log.files')
844 label='ui.note log.files')
843 if copies and self.ui.verbose:
845 if copies and self.ui.verbose:
844 copies = ['%s (%s)' % c for c in copies]
846 copies = ['%s (%s)' % c for c in copies]
845 self.ui.write(_("copies: %s\n") % ' '.join(copies),
847 self.ui.write(_("copies: %s\n") % ' '.join(copies),
846 label='ui.note log.copies')
848 label='ui.note log.copies')
847
849
848 extra = ctx.extra()
850 extra = ctx.extra()
849 if extra and self.ui.debugflag:
851 if extra and self.ui.debugflag:
850 for key, value in sorted(extra.items()):
852 for key, value in sorted(extra.items()):
851 self.ui.write(_("extra: %s=%s\n")
853 self.ui.write(_("extra: %s=%s\n")
852 % (key, value.encode('string_escape')),
854 % (key, value.encode('string_escape')),
853 label='ui.debug log.extra')
855 label='ui.debug log.extra')
854
856
855 description = ctx.description().strip()
857 description = ctx.description().strip()
856 if description:
858 if description:
857 if self.ui.verbose:
859 if self.ui.verbose:
858 self.ui.write(_("description:\n"),
860 self.ui.write(_("description:\n"),
859 label='ui.note log.description')
861 label='ui.note log.description')
860 self.ui.write(description,
862 self.ui.write(description,
861 label='ui.note log.description')
863 label='ui.note log.description')
862 self.ui.write("\n\n")
864 self.ui.write("\n\n")
863 else:
865 else:
864 self.ui.write(_("summary: %s\n") %
866 self.ui.write(_("summary: %s\n") %
865 description.splitlines()[0],
867 description.splitlines()[0],
866 label='log.summary')
868 label='log.summary')
867 self.ui.write("\n")
869 self.ui.write("\n")
868
870
869 self.showpatch(changenode, matchfn)
871 self.showpatch(changenode, matchfn)
870
872
871 def showpatch(self, node, matchfn):
873 def showpatch(self, node, matchfn):
872 if not matchfn:
874 if not matchfn:
873 matchfn = self.patch
875 matchfn = self.patch
874 if matchfn:
876 if matchfn:
875 stat = self.diffopts.get('stat')
877 stat = self.diffopts.get('stat')
876 diff = self.diffopts.get('patch')
878 diff = self.diffopts.get('patch')
877 diffopts = patch.diffopts(self.ui, self.diffopts)
879 diffopts = patch.diffopts(self.ui, self.diffopts)
878 prev = self.repo.changelog.parents(node)[0]
880 prev = self.repo.changelog.parents(node)[0]
879 if stat:
881 if stat:
880 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
882 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
881 match=matchfn, stat=True)
883 match=matchfn, stat=True)
882 if diff:
884 if diff:
883 if stat:
885 if stat:
884 self.ui.write("\n")
886 self.ui.write("\n")
885 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
887 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
886 match=matchfn, stat=False)
888 match=matchfn, stat=False)
887 self.ui.write("\n")
889 self.ui.write("\n")
888
890
889 def _meaningful_parentrevs(self, log, rev):
891 def _meaningful_parentrevs(self, log, rev):
890 """Return list of meaningful (or all if debug) parentrevs for rev.
892 """Return list of meaningful (or all if debug) parentrevs for rev.
891
893
892 For merges (two non-nullrev revisions) both parents are meaningful.
894 For merges (two non-nullrev revisions) both parents are meaningful.
893 Otherwise the first parent revision is considered meaningful if it
895 Otherwise the first parent revision is considered meaningful if it
894 is not the preceding revision.
896 is not the preceding revision.
895 """
897 """
896 parents = log.parentrevs(rev)
898 parents = log.parentrevs(rev)
897 if not self.ui.debugflag and parents[1] == nullrev:
899 if not self.ui.debugflag and parents[1] == nullrev:
898 if parents[0] >= rev - 1:
900 if parents[0] >= rev - 1:
899 parents = []
901 parents = []
900 else:
902 else:
901 parents = [parents[0]]
903 parents = [parents[0]]
902 return parents
904 return parents
903
905
904
906
905 class changeset_templater(changeset_printer):
907 class changeset_templater(changeset_printer):
906 '''format changeset information.'''
908 '''format changeset information.'''
907
909
908 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
910 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
909 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
911 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
910 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
912 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
911 defaulttempl = {
913 defaulttempl = {
912 'parent': '{rev}:{node|formatnode} ',
914 'parent': '{rev}:{node|formatnode} ',
913 'manifest': '{rev}:{node|formatnode}',
915 'manifest': '{rev}:{node|formatnode}',
914 'file_copy': '{name} ({source})',
916 'file_copy': '{name} ({source})',
915 'extra': '{key}={value|stringescape}'
917 'extra': '{key}={value|stringescape}'
916 }
918 }
917 # filecopy is preserved for compatibility reasons
919 # filecopy is preserved for compatibility reasons
918 defaulttempl['filecopy'] = defaulttempl['file_copy']
920 defaulttempl['filecopy'] = defaulttempl['file_copy']
919 self.t = templater.templater(mapfile, {'formatnode': formatnode},
921 self.t = templater.templater(mapfile, {'formatnode': formatnode},
920 cache=defaulttempl)
922 cache=defaulttempl)
921 self.cache = {}
923 self.cache = {}
922
924
923 def use_template(self, t):
925 def use_template(self, t):
924 '''set template string to use'''
926 '''set template string to use'''
925 self.t.cache['changeset'] = t
927 self.t.cache['changeset'] = t
926
928
927 def _meaningful_parentrevs(self, ctx):
929 def _meaningful_parentrevs(self, ctx):
928 """Return list of meaningful (or all if debug) parentrevs for rev.
930 """Return list of meaningful (or all if debug) parentrevs for rev.
929 """
931 """
930 parents = ctx.parents()
932 parents = ctx.parents()
931 if len(parents) > 1:
933 if len(parents) > 1:
932 return parents
934 return parents
933 if self.ui.debugflag:
935 if self.ui.debugflag:
934 return [parents[0], self.repo['null']]
936 return [parents[0], self.repo['null']]
935 if parents[0].rev() >= ctx.rev() - 1:
937 if parents[0].rev() >= ctx.rev() - 1:
936 return []
938 return []
937 return parents
939 return parents
938
940
939 def _show(self, ctx, copies, matchfn, props):
941 def _show(self, ctx, copies, matchfn, props):
940 '''show a single changeset or file revision'''
942 '''show a single changeset or file revision'''
941
943
942 showlist = templatekw.showlist
944 showlist = templatekw.showlist
943
945
944 # showparents() behaviour depends on ui trace level which
946 # showparents() behaviour depends on ui trace level which
945 # causes unexpected behaviours at templating level and makes
947 # causes unexpected behaviours at templating level and makes
946 # it harder to extract it in a standalone function. Its
948 # it harder to extract it in a standalone function. Its
947 # behaviour cannot be changed so leave it here for now.
949 # behaviour cannot be changed so leave it here for now.
948 def showparents(**args):
950 def showparents(**args):
949 ctx = args['ctx']
951 ctx = args['ctx']
950 parents = [[('rev', p.rev()), ('node', p.hex())]
952 parents = [[('rev', p.rev()), ('node', p.hex())]
951 for p in self._meaningful_parentrevs(ctx)]
953 for p in self._meaningful_parentrevs(ctx)]
952 return showlist('parent', parents, **args)
954 return showlist('parent', parents, **args)
953
955
954 props = props.copy()
956 props = props.copy()
955 props.update(templatekw.keywords)
957 props.update(templatekw.keywords)
956 props['parents'] = showparents
958 props['parents'] = showparents
957 props['templ'] = self.t
959 props['templ'] = self.t
958 props['ctx'] = ctx
960 props['ctx'] = ctx
959 props['repo'] = self.repo
961 props['repo'] = self.repo
960 props['revcache'] = {'copies': copies}
962 props['revcache'] = {'copies': copies}
961 props['cache'] = self.cache
963 props['cache'] = self.cache
962
964
963 # find correct templates for current mode
965 # find correct templates for current mode
964
966
965 tmplmodes = [
967 tmplmodes = [
966 (True, None),
968 (True, None),
967 (self.ui.verbose, 'verbose'),
969 (self.ui.verbose, 'verbose'),
968 (self.ui.quiet, 'quiet'),
970 (self.ui.quiet, 'quiet'),
969 (self.ui.debugflag, 'debug'),
971 (self.ui.debugflag, 'debug'),
970 ]
972 ]
971
973
972 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
974 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
973 for mode, postfix in tmplmodes:
975 for mode, postfix in tmplmodes:
974 for type in types:
976 for type in types:
975 cur = postfix and ('%s_%s' % (type, postfix)) or type
977 cur = postfix and ('%s_%s' % (type, postfix)) or type
976 if mode and cur in self.t:
978 if mode and cur in self.t:
977 types[type] = cur
979 types[type] = cur
978
980
979 try:
981 try:
980
982
981 # write header
983 # write header
982 if types['header']:
984 if types['header']:
983 h = templater.stringify(self.t(types['header'], **props))
985 h = templater.stringify(self.t(types['header'], **props))
984 if self.buffered:
986 if self.buffered:
985 self.header[ctx.rev()] = h
987 self.header[ctx.rev()] = h
986 else:
988 else:
987 if self.lastheader != h:
989 if self.lastheader != h:
988 self.lastheader = h
990 self.lastheader = h
989 self.ui.write(h)
991 self.ui.write(h)
990
992
991 # write changeset metadata, then patch if requested
993 # write changeset metadata, then patch if requested
992 key = types['changeset']
994 key = types['changeset']
993 self.ui.write(templater.stringify(self.t(key, **props)))
995 self.ui.write(templater.stringify(self.t(key, **props)))
994 self.showpatch(ctx.node(), matchfn)
996 self.showpatch(ctx.node(), matchfn)
995
997
996 if types['footer']:
998 if types['footer']:
997 if not self.footer:
999 if not self.footer:
998 self.footer = templater.stringify(self.t(types['footer'],
1000 self.footer = templater.stringify(self.t(types['footer'],
999 **props))
1001 **props))
1000
1002
1001 except KeyError, inst:
1003 except KeyError, inst:
1002 msg = _("%s: no key named '%s'")
1004 msg = _("%s: no key named '%s'")
1003 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1005 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1004 except SyntaxError, inst:
1006 except SyntaxError, inst:
1005 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1007 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1006
1008
1007 def show_changeset(ui, repo, opts, buffered=False):
1009 def show_changeset(ui, repo, opts, buffered=False):
1008 """show one changeset using template or regular display.
1010 """show one changeset using template or regular display.
1009
1011
1010 Display format will be the first non-empty hit of:
1012 Display format will be the first non-empty hit of:
1011 1. option 'template'
1013 1. option 'template'
1012 2. option 'style'
1014 2. option 'style'
1013 3. [ui] setting 'logtemplate'
1015 3. [ui] setting 'logtemplate'
1014 4. [ui] setting 'style'
1016 4. [ui] setting 'style'
1015 If all of these values are either the unset or the empty string,
1017 If all of these values are either the unset or the empty string,
1016 regular display via changeset_printer() is done.
1018 regular display via changeset_printer() is done.
1017 """
1019 """
1018 # options
1020 # options
1019 patch = False
1021 patch = False
1020 if opts.get('patch') or opts.get('stat'):
1022 if opts.get('patch') or opts.get('stat'):
1021 patch = matchall(repo)
1023 patch = matchall(repo)
1022
1024
1023 tmpl = opts.get('template')
1025 tmpl = opts.get('template')
1024 style = None
1026 style = None
1025 if tmpl:
1027 if tmpl:
1026 tmpl = templater.parsestring(tmpl, quoted=False)
1028 tmpl = templater.parsestring(tmpl, quoted=False)
1027 else:
1029 else:
1028 style = opts.get('style')
1030 style = opts.get('style')
1029
1031
1030 # ui settings
1032 # ui settings
1031 if not (tmpl or style):
1033 if not (tmpl or style):
1032 tmpl = ui.config('ui', 'logtemplate')
1034 tmpl = ui.config('ui', 'logtemplate')
1033 if tmpl:
1035 if tmpl:
1034 tmpl = templater.parsestring(tmpl)
1036 tmpl = templater.parsestring(tmpl)
1035 else:
1037 else:
1036 style = util.expandpath(ui.config('ui', 'style', ''))
1038 style = util.expandpath(ui.config('ui', 'style', ''))
1037
1039
1038 if not (tmpl or style):
1040 if not (tmpl or style):
1039 return changeset_printer(ui, repo, patch, opts, buffered)
1041 return changeset_printer(ui, repo, patch, opts, buffered)
1040
1042
1041 mapfile = None
1043 mapfile = None
1042 if style and not tmpl:
1044 if style and not tmpl:
1043 mapfile = style
1045 mapfile = style
1044 if not os.path.split(mapfile)[0]:
1046 if not os.path.split(mapfile)[0]:
1045 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1047 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1046 or templater.templatepath(mapfile))
1048 or templater.templatepath(mapfile))
1047 if mapname:
1049 if mapname:
1048 mapfile = mapname
1050 mapfile = mapname
1049
1051
1050 try:
1052 try:
1051 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1053 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1052 except SyntaxError, inst:
1054 except SyntaxError, inst:
1053 raise util.Abort(inst.args[0])
1055 raise util.Abort(inst.args[0])
1054 if tmpl:
1056 if tmpl:
1055 t.use_template(tmpl)
1057 t.use_template(tmpl)
1056 return t
1058 return t
1057
1059
1058 def finddate(ui, repo, date):
1060 def finddate(ui, repo, date):
1059 """Find the tipmost changeset that matches the given date spec"""
1061 """Find the tipmost changeset that matches the given date spec"""
1060
1062
1061 df = util.matchdate(date)
1063 df = util.matchdate(date)
1062 m = matchall(repo)
1064 m = matchall(repo)
1063 results = {}
1065 results = {}
1064
1066
1065 def prep(ctx, fns):
1067 def prep(ctx, fns):
1066 d = ctx.date()
1068 d = ctx.date()
1067 if df(d[0]):
1069 if df(d[0]):
1068 results[ctx.rev()] = d
1070 results[ctx.rev()] = d
1069
1071
1070 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1072 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1071 rev = ctx.rev()
1073 rev = ctx.rev()
1072 if rev in results:
1074 if rev in results:
1073 ui.status(_("Found revision %s from %s\n") %
1075 ui.status(_("Found revision %s from %s\n") %
1074 (rev, util.datestr(results[rev])))
1076 (rev, util.datestr(results[rev])))
1075 return str(rev)
1077 return str(rev)
1076
1078
1077 raise util.Abort(_("revision matching date not found"))
1079 raise util.Abort(_("revision matching date not found"))
1078
1080
1079 def walkchangerevs(repo, match, opts, prepare):
1081 def walkchangerevs(repo, match, opts, prepare):
1080 '''Iterate over files and the revs in which they changed.
1082 '''Iterate over files and the revs in which they changed.
1081
1083
1082 Callers most commonly need to iterate backwards over the history
1084 Callers most commonly need to iterate backwards over the history
1083 in which they are interested. Doing so has awful (quadratic-looking)
1085 in which they are interested. Doing so has awful (quadratic-looking)
1084 performance, so we use iterators in a "windowed" way.
1086 performance, so we use iterators in a "windowed" way.
1085
1087
1086 We walk a window of revisions in the desired order. Within the
1088 We walk a window of revisions in the desired order. Within the
1087 window, we first walk forwards to gather data, then in the desired
1089 window, we first walk forwards to gather data, then in the desired
1088 order (usually backwards) to display it.
1090 order (usually backwards) to display it.
1089
1091
1090 This function returns an iterator yielding contexts. Before
1092 This function returns an iterator yielding contexts. Before
1091 yielding each context, the iterator will first call the prepare
1093 yielding each context, the iterator will first call the prepare
1092 function on each context in the window in forward order.'''
1094 function on each context in the window in forward order.'''
1093
1095
1094 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1096 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1095 if start < end:
1097 if start < end:
1096 while start < end:
1098 while start < end:
1097 yield start, min(windowsize, end - start)
1099 yield start, min(windowsize, end - start)
1098 start += windowsize
1100 start += windowsize
1099 if windowsize < sizelimit:
1101 if windowsize < sizelimit:
1100 windowsize *= 2
1102 windowsize *= 2
1101 else:
1103 else:
1102 while start > end:
1104 while start > end:
1103 yield start, min(windowsize, start - end - 1)
1105 yield start, min(windowsize, start - end - 1)
1104 start -= windowsize
1106 start -= windowsize
1105 if windowsize < sizelimit:
1107 if windowsize < sizelimit:
1106 windowsize *= 2
1108 windowsize *= 2
1107
1109
1108 follow = opts.get('follow') or opts.get('follow_first')
1110 follow = opts.get('follow') or opts.get('follow_first')
1109
1111
1110 if not len(repo):
1112 if not len(repo):
1111 return []
1113 return []
1112
1114
1113 if follow:
1115 if follow:
1114 defrange = '%s:0' % repo['.'].rev()
1116 defrange = '%s:0' % repo['.'].rev()
1115 else:
1117 else:
1116 defrange = '-1:0'
1118 defrange = '-1:0'
1117 revs = revrange(repo, opts['rev'] or [defrange])
1119 revs = revrange(repo, opts['rev'] or [defrange])
1118 if not revs:
1120 if not revs:
1119 return []
1121 return []
1120 wanted = set()
1122 wanted = set()
1121 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1123 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1122 fncache = {}
1124 fncache = {}
1123 change = util.cachefunc(repo.changectx)
1125 change = util.cachefunc(repo.changectx)
1124
1126
1125 # First step is to fill wanted, the set of revisions that we want to yield.
1127 # First step is to fill wanted, the set of revisions that we want to yield.
1126 # When it does not induce extra cost, we also fill fncache for revisions in
1128 # When it does not induce extra cost, we also fill fncache for revisions in
1127 # wanted: a cache of filenames that were changed (ctx.files()) and that
1129 # wanted: a cache of filenames that were changed (ctx.files()) and that
1128 # match the file filtering conditions.
1130 # match the file filtering conditions.
1129
1131
1130 if not slowpath and not match.files():
1132 if not slowpath and not match.files():
1131 # No files, no patterns. Display all revs.
1133 # No files, no patterns. Display all revs.
1132 wanted = set(revs)
1134 wanted = set(revs)
1133 copies = []
1135 copies = []
1134
1136
1135 if not slowpath:
1137 if not slowpath:
1136 # We only have to read through the filelog to find wanted revisions
1138 # We only have to read through the filelog to find wanted revisions
1137
1139
1138 minrev, maxrev = min(revs), max(revs)
1140 minrev, maxrev = min(revs), max(revs)
1139 def filerevgen(filelog, last):
1141 def filerevgen(filelog, last):
1140 """
1142 """
1141 Only files, no patterns. Check the history of each file.
1143 Only files, no patterns. Check the history of each file.
1142
1144
1143 Examines filelog entries within minrev, maxrev linkrev range
1145 Examines filelog entries within minrev, maxrev linkrev range
1144 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1146 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1145 tuples in backwards order
1147 tuples in backwards order
1146 """
1148 """
1147 cl_count = len(repo)
1149 cl_count = len(repo)
1148 revs = []
1150 revs = []
1149 for j in xrange(0, last + 1):
1151 for j in xrange(0, last + 1):
1150 linkrev = filelog.linkrev(j)
1152 linkrev = filelog.linkrev(j)
1151 if linkrev < minrev:
1153 if linkrev < minrev:
1152 continue
1154 continue
1153 # only yield rev for which we have the changelog, it can
1155 # only yield rev for which we have the changelog, it can
1154 # happen while doing "hg log" during a pull or commit
1156 # happen while doing "hg log" during a pull or commit
1155 if linkrev >= cl_count:
1157 if linkrev >= cl_count:
1156 break
1158 break
1157
1159
1158 parentlinkrevs = []
1160 parentlinkrevs = []
1159 for p in filelog.parentrevs(j):
1161 for p in filelog.parentrevs(j):
1160 if p != nullrev:
1162 if p != nullrev:
1161 parentlinkrevs.append(filelog.linkrev(p))
1163 parentlinkrevs.append(filelog.linkrev(p))
1162 n = filelog.node(j)
1164 n = filelog.node(j)
1163 revs.append((linkrev, parentlinkrevs,
1165 revs.append((linkrev, parentlinkrevs,
1164 follow and filelog.renamed(n)))
1166 follow and filelog.renamed(n)))
1165
1167
1166 return reversed(revs)
1168 return reversed(revs)
1167 def iterfiles():
1169 def iterfiles():
1168 for filename in match.files():
1170 for filename in match.files():
1169 yield filename, None
1171 yield filename, None
1170 for filename_node in copies:
1172 for filename_node in copies:
1171 yield filename_node
1173 yield filename_node
1172 for file_, node in iterfiles():
1174 for file_, node in iterfiles():
1173 filelog = repo.file(file_)
1175 filelog = repo.file(file_)
1174 if not len(filelog):
1176 if not len(filelog):
1175 if node is None:
1177 if node is None:
1176 # A zero count may be a directory or deleted file, so
1178 # A zero count may be a directory or deleted file, so
1177 # try to find matching entries on the slow path.
1179 # try to find matching entries on the slow path.
1178 if follow:
1180 if follow:
1179 raise util.Abort(
1181 raise util.Abort(
1180 _('cannot follow nonexistent file: "%s"') % file_)
1182 _('cannot follow nonexistent file: "%s"') % file_)
1181 slowpath = True
1183 slowpath = True
1182 break
1184 break
1183 else:
1185 else:
1184 continue
1186 continue
1185
1187
1186 if node is None:
1188 if node is None:
1187 last = len(filelog) - 1
1189 last = len(filelog) - 1
1188 else:
1190 else:
1189 last = filelog.rev(node)
1191 last = filelog.rev(node)
1190
1192
1191
1193
1192 # keep track of all ancestors of the file
1194 # keep track of all ancestors of the file
1193 ancestors = set([filelog.linkrev(last)])
1195 ancestors = set([filelog.linkrev(last)])
1194
1196
1195 # iterate from latest to oldest revision
1197 # iterate from latest to oldest revision
1196 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1198 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1197 if not follow:
1199 if not follow:
1198 if rev > maxrev:
1200 if rev > maxrev:
1199 continue
1201 continue
1200 else:
1202 else:
1201 # Note that last might not be the first interesting
1203 # Note that last might not be the first interesting
1202 # rev to us:
1204 # rev to us:
1203 # if the file has been changed after maxrev, we'll
1205 # if the file has been changed after maxrev, we'll
1204 # have linkrev(last) > maxrev, and we still need
1206 # have linkrev(last) > maxrev, and we still need
1205 # to explore the file graph
1207 # to explore the file graph
1206 if rev not in ancestors:
1208 if rev not in ancestors:
1207 continue
1209 continue
1208 # XXX insert 1327 fix here
1210 # XXX insert 1327 fix here
1209 if flparentlinkrevs:
1211 if flparentlinkrevs:
1210 ancestors.update(flparentlinkrevs)
1212 ancestors.update(flparentlinkrevs)
1211
1213
1212 fncache.setdefault(rev, []).append(file_)
1214 fncache.setdefault(rev, []).append(file_)
1213 wanted.add(rev)
1215 wanted.add(rev)
1214 if copied:
1216 if copied:
1215 copies.append(copied)
1217 copies.append(copied)
1216 if slowpath:
1218 if slowpath:
1217 # We have to read the changelog to match filenames against
1219 # We have to read the changelog to match filenames against
1218 # changed files
1220 # changed files
1219
1221
1220 if follow:
1222 if follow:
1221 raise util.Abort(_('can only follow copies/renames for explicit '
1223 raise util.Abort(_('can only follow copies/renames for explicit '
1222 'filenames'))
1224 'filenames'))
1223
1225
1224 # The slow path checks files modified in every changeset.
1226 # The slow path checks files modified in every changeset.
1225 for i in sorted(revs):
1227 for i in sorted(revs):
1226 ctx = change(i)
1228 ctx = change(i)
1227 matches = filter(match, ctx.files())
1229 matches = filter(match, ctx.files())
1228 if matches:
1230 if matches:
1229 fncache[i] = matches
1231 fncache[i] = matches
1230 wanted.add(i)
1232 wanted.add(i)
1231
1233
1232 class followfilter(object):
1234 class followfilter(object):
1233 def __init__(self, onlyfirst=False):
1235 def __init__(self, onlyfirst=False):
1234 self.startrev = nullrev
1236 self.startrev = nullrev
1235 self.roots = set()
1237 self.roots = set()
1236 self.onlyfirst = onlyfirst
1238 self.onlyfirst = onlyfirst
1237
1239
1238 def match(self, rev):
1240 def match(self, rev):
1239 def realparents(rev):
1241 def realparents(rev):
1240 if self.onlyfirst:
1242 if self.onlyfirst:
1241 return repo.changelog.parentrevs(rev)[0:1]
1243 return repo.changelog.parentrevs(rev)[0:1]
1242 else:
1244 else:
1243 return filter(lambda x: x != nullrev,
1245 return filter(lambda x: x != nullrev,
1244 repo.changelog.parentrevs(rev))
1246 repo.changelog.parentrevs(rev))
1245
1247
1246 if self.startrev == nullrev:
1248 if self.startrev == nullrev:
1247 self.startrev = rev
1249 self.startrev = rev
1248 return True
1250 return True
1249
1251
1250 if rev > self.startrev:
1252 if rev > self.startrev:
1251 # forward: all descendants
1253 # forward: all descendants
1252 if not self.roots:
1254 if not self.roots:
1253 self.roots.add(self.startrev)
1255 self.roots.add(self.startrev)
1254 for parent in realparents(rev):
1256 for parent in realparents(rev):
1255 if parent in self.roots:
1257 if parent in self.roots:
1256 self.roots.add(rev)
1258 self.roots.add(rev)
1257 return True
1259 return True
1258 else:
1260 else:
1259 # backwards: all parents
1261 # backwards: all parents
1260 if not self.roots:
1262 if not self.roots:
1261 self.roots.update(realparents(self.startrev))
1263 self.roots.update(realparents(self.startrev))
1262 if rev in self.roots:
1264 if rev in self.roots:
1263 self.roots.remove(rev)
1265 self.roots.remove(rev)
1264 self.roots.update(realparents(rev))
1266 self.roots.update(realparents(rev))
1265 return True
1267 return True
1266
1268
1267 return False
1269 return False
1268
1270
1269 # it might be worthwhile to do this in the iterator if the rev range
1271 # it might be worthwhile to do this in the iterator if the rev range
1270 # is descending and the prune args are all within that range
1272 # is descending and the prune args are all within that range
1271 for rev in opts.get('prune', ()):
1273 for rev in opts.get('prune', ()):
1272 rev = repo.changelog.rev(repo.lookup(rev))
1274 rev = repo.changelog.rev(repo.lookup(rev))
1273 ff = followfilter()
1275 ff = followfilter()
1274 stop = min(revs[0], revs[-1])
1276 stop = min(revs[0], revs[-1])
1275 for x in xrange(rev, stop - 1, -1):
1277 for x in xrange(rev, stop - 1, -1):
1276 if ff.match(x):
1278 if ff.match(x):
1277 wanted.discard(x)
1279 wanted.discard(x)
1278
1280
1279 # Now that wanted is correctly initialized, we can iterate over the
1281 # Now that wanted is correctly initialized, we can iterate over the
1280 # revision range, yielding only revisions in wanted.
1282 # revision range, yielding only revisions in wanted.
1281 def iterate():
1283 def iterate():
1282 if follow and not match.files():
1284 if follow and not match.files():
1283 ff = followfilter(onlyfirst=opts.get('follow_first'))
1285 ff = followfilter(onlyfirst=opts.get('follow_first'))
1284 def want(rev):
1286 def want(rev):
1285 return ff.match(rev) and rev in wanted
1287 return ff.match(rev) and rev in wanted
1286 else:
1288 else:
1287 def want(rev):
1289 def want(rev):
1288 return rev in wanted
1290 return rev in wanted
1289
1291
1290 for i, window in increasing_windows(0, len(revs)):
1292 for i, window in increasing_windows(0, len(revs)):
1291 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1293 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1292 for rev in sorted(nrevs):
1294 for rev in sorted(nrevs):
1293 fns = fncache.get(rev)
1295 fns = fncache.get(rev)
1294 ctx = change(rev)
1296 ctx = change(rev)
1295 if not fns:
1297 if not fns:
1296 def fns_generator():
1298 def fns_generator():
1297 for f in ctx.files():
1299 for f in ctx.files():
1298 if match(f):
1300 if match(f):
1299 yield f
1301 yield f
1300 fns = fns_generator()
1302 fns = fns_generator()
1301 prepare(ctx, fns)
1303 prepare(ctx, fns)
1302 for rev in nrevs:
1304 for rev in nrevs:
1303 yield change(rev)
1305 yield change(rev)
1304 return iterate()
1306 return iterate()
1305
1307
1306 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1308 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1307 join = lambda f: os.path.join(prefix, f)
1309 join = lambda f: os.path.join(prefix, f)
1308 bad = []
1310 bad = []
1309 oldbad = match.bad
1311 oldbad = match.bad
1310 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1312 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1311 names = []
1313 names = []
1312 wctx = repo[None]
1314 wctx = repo[None]
1313 for f in repo.walk(match):
1315 for f in repo.walk(match):
1314 exact = match.exact(f)
1316 exact = match.exact(f)
1315 if exact or f not in repo.dirstate:
1317 if exact or f not in repo.dirstate:
1316 names.append(f)
1318 names.append(f)
1317 if ui.verbose or not exact:
1319 if ui.verbose or not exact:
1318 ui.status(_('adding %s\n') % match.rel(join(f)))
1320 ui.status(_('adding %s\n') % match.rel(join(f)))
1319
1321
1320 if listsubrepos:
1322 if listsubrepos:
1321 for subpath in wctx.substate:
1323 for subpath in wctx.substate:
1322 sub = wctx.sub(subpath)
1324 sub = wctx.sub(subpath)
1323 try:
1325 try:
1324 submatch = matchmod.narrowmatcher(subpath, match)
1326 submatch = matchmod.narrowmatcher(subpath, match)
1325 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1327 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1326 except error.LookupError:
1328 except error.LookupError:
1327 ui.status(_("skipping missing subrepository: %s\n")
1329 ui.status(_("skipping missing subrepository: %s\n")
1328 % join(subpath))
1330 % join(subpath))
1329
1331
1330 if not dryrun:
1332 if not dryrun:
1331 rejected = wctx.add(names, prefix)
1333 rejected = wctx.add(names, prefix)
1332 bad.extend(f for f in rejected if f in match.files())
1334 bad.extend(f for f in rejected if f in match.files())
1333 return bad
1335 return bad
1334
1336
1335 def commit(ui, repo, commitfunc, pats, opts):
1337 def commit(ui, repo, commitfunc, pats, opts):
1336 '''commit the specified files or all outstanding changes'''
1338 '''commit the specified files or all outstanding changes'''
1337 date = opts.get('date')
1339 date = opts.get('date')
1338 if date:
1340 if date:
1339 opts['date'] = util.parsedate(date)
1341 opts['date'] = util.parsedate(date)
1340 message = logmessage(opts)
1342 message = logmessage(opts)
1341
1343
1342 # extract addremove carefully -- this function can be called from a command
1344 # extract addremove carefully -- this function can be called from a command
1343 # that doesn't support addremove
1345 # that doesn't support addremove
1344 if opts.get('addremove'):
1346 if opts.get('addremove'):
1345 addremove(repo, pats, opts)
1347 addremove(repo, pats, opts)
1346
1348
1347 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1349 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1348
1350
1349 def commiteditor(repo, ctx, subs):
1351 def commiteditor(repo, ctx, subs):
1350 if ctx.description():
1352 if ctx.description():
1351 return ctx.description()
1353 return ctx.description()
1352 return commitforceeditor(repo, ctx, subs)
1354 return commitforceeditor(repo, ctx, subs)
1353
1355
1354 def commitforceeditor(repo, ctx, subs):
1356 def commitforceeditor(repo, ctx, subs):
1355 edittext = []
1357 edittext = []
1356 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1358 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1357 if ctx.description():
1359 if ctx.description():
1358 edittext.append(ctx.description())
1360 edittext.append(ctx.description())
1359 edittext.append("")
1361 edittext.append("")
1360 edittext.append("") # Empty line between message and comments.
1362 edittext.append("") # Empty line between message and comments.
1361 edittext.append(_("HG: Enter commit message."
1363 edittext.append(_("HG: Enter commit message."
1362 " Lines beginning with 'HG:' are removed."))
1364 " Lines beginning with 'HG:' are removed."))
1363 edittext.append(_("HG: Leave message empty to abort commit."))
1365 edittext.append(_("HG: Leave message empty to abort commit."))
1364 edittext.append("HG: --")
1366 edittext.append("HG: --")
1365 edittext.append(_("HG: user: %s") % ctx.user())
1367 edittext.append(_("HG: user: %s") % ctx.user())
1366 if ctx.p2():
1368 if ctx.p2():
1367 edittext.append(_("HG: branch merge"))
1369 edittext.append(_("HG: branch merge"))
1368 if ctx.branch():
1370 if ctx.branch():
1369 edittext.append(_("HG: branch '%s'") % ctx.branch())
1371 edittext.append(_("HG: branch '%s'") % ctx.branch())
1370 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1372 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1371 edittext.extend([_("HG: added %s") % f for f in added])
1373 edittext.extend([_("HG: added %s") % f for f in added])
1372 edittext.extend([_("HG: changed %s") % f for f in modified])
1374 edittext.extend([_("HG: changed %s") % f for f in modified])
1373 edittext.extend([_("HG: removed %s") % f for f in removed])
1375 edittext.extend([_("HG: removed %s") % f for f in removed])
1374 if not added and not modified and not removed:
1376 if not added and not modified and not removed:
1375 edittext.append(_("HG: no files changed"))
1377 edittext.append(_("HG: no files changed"))
1376 edittext.append("")
1378 edittext.append("")
1377 # run editor in the repository root
1379 # run editor in the repository root
1378 olddir = os.getcwd()
1380 olddir = os.getcwd()
1379 os.chdir(repo.root)
1381 os.chdir(repo.root)
1380 text = repo.ui.edit("\n".join(edittext), ctx.user())
1382 text = repo.ui.edit("\n".join(edittext), ctx.user())
1381 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1383 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1382 os.chdir(olddir)
1384 os.chdir(olddir)
1383
1385
1384 if not text.strip():
1386 if not text.strip():
1385 raise util.Abort(_("empty commit message"))
1387 raise util.Abort(_("empty commit message"))
1386
1388
1387 return text
1389 return text
General Comments 0
You need to be logged in to leave comments. Login now