##// END OF EJS Templates
remoteui: copy http_proxy settings...
Yuya Nishihara -
r10953:f1250e2e stable
parent child Browse files
Show More
@@ -1,1191 +1,1192 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 mdiff, bdiff, util, templater, patch, error, encoding, templatekw
11 import mdiff, bdiff, util, templater, patch, error, encoding, templatekw
12 import match as _match
12 import match as _match
13
13
14 revrangesep = ':'
14 revrangesep = ':'
15
15
16 def parsealiases(cmd):
16 def parsealiases(cmd):
17 return cmd.lstrip("^").split("|")
17 return cmd.lstrip("^").split("|")
18
18
19 def findpossible(cmd, table, strict=False):
19 def findpossible(cmd, table, strict=False):
20 """
20 """
21 Return cmd -> (aliases, command table entry)
21 Return cmd -> (aliases, command table entry)
22 for each matching command.
22 for each matching command.
23 Return debug commands (or their aliases) only if no normal command matches.
23 Return debug commands (or their aliases) only if no normal command matches.
24 """
24 """
25 choice = {}
25 choice = {}
26 debugchoice = {}
26 debugchoice = {}
27 for e in table.keys():
27 for e in table.keys():
28 aliases = parsealiases(e)
28 aliases = parsealiases(e)
29 found = None
29 found = None
30 if cmd in aliases:
30 if cmd in aliases:
31 found = cmd
31 found = cmd
32 elif not strict:
32 elif not strict:
33 for a in aliases:
33 for a in aliases:
34 if a.startswith(cmd):
34 if a.startswith(cmd):
35 found = a
35 found = a
36 break
36 break
37 if found is not None:
37 if found is not None:
38 if aliases[0].startswith("debug") or found.startswith("debug"):
38 if aliases[0].startswith("debug") or found.startswith("debug"):
39 debugchoice[found] = (aliases, table[e])
39 debugchoice[found] = (aliases, table[e])
40 else:
40 else:
41 choice[found] = (aliases, table[e])
41 choice[found] = (aliases, table[e])
42
42
43 if not choice and debugchoice:
43 if not choice and debugchoice:
44 choice = debugchoice
44 choice = debugchoice
45
45
46 return choice
46 return choice
47
47
48 def findcmd(cmd, table, strict=True):
48 def findcmd(cmd, table, strict=True):
49 """Return (aliases, command table entry) for command string."""
49 """Return (aliases, command table entry) for command string."""
50 choice = findpossible(cmd, table, strict)
50 choice = findpossible(cmd, table, strict)
51
51
52 if cmd in choice:
52 if cmd in choice:
53 return choice[cmd]
53 return choice[cmd]
54
54
55 if len(choice) > 1:
55 if len(choice) > 1:
56 clist = choice.keys()
56 clist = choice.keys()
57 clist.sort()
57 clist.sort()
58 raise error.AmbiguousCommand(cmd, clist)
58 raise error.AmbiguousCommand(cmd, clist)
59
59
60 if choice:
60 if choice:
61 return choice.values()[0]
61 return choice.values()[0]
62
62
63 raise error.UnknownCommand(cmd)
63 raise error.UnknownCommand(cmd)
64
64
65 def findrepo(p):
65 def findrepo(p):
66 while not os.path.isdir(os.path.join(p, ".hg")):
66 while not os.path.isdir(os.path.join(p, ".hg")):
67 oldp, p = p, os.path.dirname(p)
67 oldp, p = p, os.path.dirname(p)
68 if p == oldp:
68 if p == oldp:
69 return None
69 return None
70
70
71 return p
71 return p
72
72
73 def bail_if_changed(repo):
73 def bail_if_changed(repo):
74 if repo.dirstate.parents()[1] != nullid:
74 if repo.dirstate.parents()[1] != nullid:
75 raise util.Abort(_('outstanding uncommitted merge'))
75 raise util.Abort(_('outstanding uncommitted merge'))
76 modified, added, removed, deleted = repo.status()[:4]
76 modified, added, removed, deleted = repo.status()[:4]
77 if modified or added or removed or deleted:
77 if modified or added or removed or deleted:
78 raise util.Abort(_("outstanding uncommitted changes"))
78 raise util.Abort(_("outstanding uncommitted changes"))
79
79
80 def logmessage(opts):
80 def logmessage(opts):
81 """ get the log message according to -m and -l option """
81 """ get the log message according to -m and -l option """
82 message = opts.get('message')
82 message = opts.get('message')
83 logfile = opts.get('logfile')
83 logfile = opts.get('logfile')
84
84
85 if message and logfile:
85 if message and logfile:
86 raise util.Abort(_('options --message and --logfile are mutually '
86 raise util.Abort(_('options --message and --logfile are mutually '
87 'exclusive'))
87 'exclusive'))
88 if not message and logfile:
88 if not message and logfile:
89 try:
89 try:
90 if logfile == '-':
90 if logfile == '-':
91 message = sys.stdin.read()
91 message = sys.stdin.read()
92 else:
92 else:
93 message = open(logfile).read()
93 message = open(logfile).read()
94 except IOError, inst:
94 except IOError, inst:
95 raise util.Abort(_("can't read commit message '%s': %s") %
95 raise util.Abort(_("can't read commit message '%s': %s") %
96 (logfile, inst.strerror))
96 (logfile, inst.strerror))
97 return message
97 return message
98
98
99 def loglimit(opts):
99 def loglimit(opts):
100 """get the log limit according to option -l/--limit"""
100 """get the log limit according to option -l/--limit"""
101 limit = opts.get('limit')
101 limit = opts.get('limit')
102 if limit:
102 if limit:
103 try:
103 try:
104 limit = int(limit)
104 limit = int(limit)
105 except ValueError:
105 except ValueError:
106 raise util.Abort(_('limit must be a positive integer'))
106 raise util.Abort(_('limit must be a positive integer'))
107 if limit <= 0:
107 if limit <= 0:
108 raise util.Abort(_('limit must be positive'))
108 raise util.Abort(_('limit must be positive'))
109 else:
109 else:
110 limit = None
110 limit = None
111 return limit
111 return limit
112
112
113 def remoteui(src, opts):
113 def remoteui(src, opts):
114 'build a remote ui from ui or repo and opts'
114 'build a remote ui from ui or repo and opts'
115 if hasattr(src, 'baseui'): # looks like a repository
115 if hasattr(src, 'baseui'): # looks like a repository
116 dst = src.baseui.copy() # drop repo-specific config
116 dst = src.baseui.copy() # drop repo-specific config
117 src = src.ui # copy target options from repo
117 src = src.ui # copy target options from repo
118 else: # assume it's a global ui object
118 else: # assume it's a global ui object
119 dst = src.copy() # keep all global options
119 dst = src.copy() # keep all global options
120
120
121 # copy ssh-specific options
121 # copy ssh-specific options
122 for o in 'ssh', 'remotecmd':
122 for o in 'ssh', 'remotecmd':
123 v = opts.get(o) or src.config('ui', o)
123 v = opts.get(o) or src.config('ui', o)
124 if v:
124 if v:
125 dst.setconfig("ui", o, v)
125 dst.setconfig("ui", o, v)
126
126
127 # copy bundle-specific options
127 # copy bundle-specific options
128 r = src.config('bundle', 'mainreporoot')
128 r = src.config('bundle', 'mainreporoot')
129 if r:
129 if r:
130 dst.setconfig('bundle', 'mainreporoot', r)
130 dst.setconfig('bundle', 'mainreporoot', r)
131
131
132 # copy auth section settings
132 # copy auth and http_proxy section settings
133 for key, val in src.configitems('auth'):
133 for sect in ('auth', 'http_proxy'):
134 dst.setconfig('auth', key, val)
134 for key, val in src.configitems(sect):
135 dst.setconfig(sect, key, val)
135
136
136 return dst
137 return dst
137
138
138 def revpair(repo, revs):
139 def revpair(repo, revs):
139 '''return pair of nodes, given list of revisions. second item can
140 '''return pair of nodes, given list of revisions. second item can
140 be None, meaning use working dir.'''
141 be None, meaning use working dir.'''
141
142
142 def revfix(repo, val, defval):
143 def revfix(repo, val, defval):
143 if not val and val != 0 and defval is not None:
144 if not val and val != 0 and defval is not None:
144 val = defval
145 val = defval
145 return repo.lookup(val)
146 return repo.lookup(val)
146
147
147 if not revs:
148 if not revs:
148 return repo.dirstate.parents()[0], None
149 return repo.dirstate.parents()[0], None
149 end = None
150 end = None
150 if len(revs) == 1:
151 if len(revs) == 1:
151 if revrangesep in revs[0]:
152 if revrangesep in revs[0]:
152 start, end = revs[0].split(revrangesep, 1)
153 start, end = revs[0].split(revrangesep, 1)
153 start = revfix(repo, start, 0)
154 start = revfix(repo, start, 0)
154 end = revfix(repo, end, len(repo) - 1)
155 end = revfix(repo, end, len(repo) - 1)
155 else:
156 else:
156 start = revfix(repo, revs[0], None)
157 start = revfix(repo, revs[0], None)
157 elif len(revs) == 2:
158 elif len(revs) == 2:
158 if revrangesep in revs[0] or revrangesep in revs[1]:
159 if revrangesep in revs[0] or revrangesep in revs[1]:
159 raise util.Abort(_('too many revisions specified'))
160 raise util.Abort(_('too many revisions specified'))
160 start = revfix(repo, revs[0], None)
161 start = revfix(repo, revs[0], None)
161 end = revfix(repo, revs[1], None)
162 end = revfix(repo, revs[1], None)
162 else:
163 else:
163 raise util.Abort(_('too many revisions specified'))
164 raise util.Abort(_('too many revisions specified'))
164 return start, end
165 return start, end
165
166
166 def revrange(repo, revs):
167 def revrange(repo, revs):
167 """Yield revision as strings from a list of revision specifications."""
168 """Yield revision as strings from a list of revision specifications."""
168
169
169 def revfix(repo, val, defval):
170 def revfix(repo, val, defval):
170 if not val and val != 0 and defval is not None:
171 if not val and val != 0 and defval is not None:
171 return defval
172 return defval
172 return repo.changelog.rev(repo.lookup(val))
173 return repo.changelog.rev(repo.lookup(val))
173
174
174 seen, l = set(), []
175 seen, l = set(), []
175 for spec in revs:
176 for spec in revs:
176 if revrangesep in spec:
177 if revrangesep in spec:
177 start, end = spec.split(revrangesep, 1)
178 start, end = spec.split(revrangesep, 1)
178 start = revfix(repo, start, 0)
179 start = revfix(repo, start, 0)
179 end = revfix(repo, end, len(repo) - 1)
180 end = revfix(repo, end, len(repo) - 1)
180 step = start > end and -1 or 1
181 step = start > end and -1 or 1
181 for rev in xrange(start, end + step, step):
182 for rev in xrange(start, end + step, step):
182 if rev in seen:
183 if rev in seen:
183 continue
184 continue
184 seen.add(rev)
185 seen.add(rev)
185 l.append(rev)
186 l.append(rev)
186 else:
187 else:
187 rev = revfix(repo, spec, None)
188 rev = revfix(repo, spec, None)
188 if rev in seen:
189 if rev in seen:
189 continue
190 continue
190 seen.add(rev)
191 seen.add(rev)
191 l.append(rev)
192 l.append(rev)
192
193
193 return l
194 return l
194
195
195 def make_filename(repo, pat, node,
196 def make_filename(repo, pat, node,
196 total=None, seqno=None, revwidth=None, pathname=None):
197 total=None, seqno=None, revwidth=None, pathname=None):
197 node_expander = {
198 node_expander = {
198 'H': lambda: hex(node),
199 'H': lambda: hex(node),
199 'R': lambda: str(repo.changelog.rev(node)),
200 'R': lambda: str(repo.changelog.rev(node)),
200 'h': lambda: short(node),
201 'h': lambda: short(node),
201 }
202 }
202 expander = {
203 expander = {
203 '%': lambda: '%',
204 '%': lambda: '%',
204 'b': lambda: os.path.basename(repo.root),
205 'b': lambda: os.path.basename(repo.root),
205 }
206 }
206
207
207 try:
208 try:
208 if node:
209 if node:
209 expander.update(node_expander)
210 expander.update(node_expander)
210 if node:
211 if node:
211 expander['r'] = (lambda:
212 expander['r'] = (lambda:
212 str(repo.changelog.rev(node)).zfill(revwidth or 0))
213 str(repo.changelog.rev(node)).zfill(revwidth or 0))
213 if total is not None:
214 if total is not None:
214 expander['N'] = lambda: str(total)
215 expander['N'] = lambda: str(total)
215 if seqno is not None:
216 if seqno is not None:
216 expander['n'] = lambda: str(seqno)
217 expander['n'] = lambda: str(seqno)
217 if total is not None and seqno is not None:
218 if total is not None and seqno is not None:
218 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
219 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
219 if pathname is not None:
220 if pathname is not None:
220 expander['s'] = lambda: os.path.basename(pathname)
221 expander['s'] = lambda: os.path.basename(pathname)
221 expander['d'] = lambda: os.path.dirname(pathname) or '.'
222 expander['d'] = lambda: os.path.dirname(pathname) or '.'
222 expander['p'] = lambda: pathname
223 expander['p'] = lambda: pathname
223
224
224 newname = []
225 newname = []
225 patlen = len(pat)
226 patlen = len(pat)
226 i = 0
227 i = 0
227 while i < patlen:
228 while i < patlen:
228 c = pat[i]
229 c = pat[i]
229 if c == '%':
230 if c == '%':
230 i += 1
231 i += 1
231 c = pat[i]
232 c = pat[i]
232 c = expander[c]()
233 c = expander[c]()
233 newname.append(c)
234 newname.append(c)
234 i += 1
235 i += 1
235 return ''.join(newname)
236 return ''.join(newname)
236 except KeyError, inst:
237 except KeyError, inst:
237 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
238 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
238 inst.args[0])
239 inst.args[0])
239
240
240 def make_file(repo, pat, node=None,
241 def make_file(repo, pat, node=None,
241 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
242 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
242
243
243 writable = 'w' in mode or 'a' in mode
244 writable = 'w' in mode or 'a' in mode
244
245
245 if not pat or pat == '-':
246 if not pat or pat == '-':
246 return writable and sys.stdout or sys.stdin
247 return writable and sys.stdout or sys.stdin
247 if hasattr(pat, 'write') and writable:
248 if hasattr(pat, 'write') and writable:
248 return pat
249 return pat
249 if hasattr(pat, 'read') and 'r' in mode:
250 if hasattr(pat, 'read') and 'r' in mode:
250 return pat
251 return pat
251 return open(make_filename(repo, pat, node, total, seqno, revwidth,
252 return open(make_filename(repo, pat, node, total, seqno, revwidth,
252 pathname),
253 pathname),
253 mode)
254 mode)
254
255
255 def expandpats(pats):
256 def expandpats(pats):
256 if not util.expandglobs:
257 if not util.expandglobs:
257 return list(pats)
258 return list(pats)
258 ret = []
259 ret = []
259 for p in pats:
260 for p in pats:
260 kind, name = _match._patsplit(p, None)
261 kind, name = _match._patsplit(p, None)
261 if kind is None:
262 if kind is None:
262 try:
263 try:
263 globbed = glob.glob(name)
264 globbed = glob.glob(name)
264 except re.error:
265 except re.error:
265 globbed = [name]
266 globbed = [name]
266 if globbed:
267 if globbed:
267 ret.extend(globbed)
268 ret.extend(globbed)
268 continue
269 continue
269 ret.append(p)
270 ret.append(p)
270 return ret
271 return ret
271
272
272 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
273 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
273 if not globbed and default == 'relpath':
274 if not globbed and default == 'relpath':
274 pats = expandpats(pats or [])
275 pats = expandpats(pats or [])
275 m = _match.match(repo.root, repo.getcwd(), pats,
276 m = _match.match(repo.root, repo.getcwd(), pats,
276 opts.get('include'), opts.get('exclude'), default)
277 opts.get('include'), opts.get('exclude'), default)
277 def badfn(f, msg):
278 def badfn(f, msg):
278 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
279 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
279 m.bad = badfn
280 m.bad = badfn
280 return m
281 return m
281
282
282 def matchall(repo):
283 def matchall(repo):
283 return _match.always(repo.root, repo.getcwd())
284 return _match.always(repo.root, repo.getcwd())
284
285
285 def matchfiles(repo, files):
286 def matchfiles(repo, files):
286 return _match.exact(repo.root, repo.getcwd(), files)
287 return _match.exact(repo.root, repo.getcwd(), files)
287
288
288 def findrenames(repo, added, removed, threshold):
289 def findrenames(repo, added, removed, threshold):
289 '''find renamed files -- yields (before, after, score) tuples'''
290 '''find renamed files -- yields (before, after, score) tuples'''
290 copies = {}
291 copies = {}
291 ctx = repo['.']
292 ctx = repo['.']
292 for r in removed:
293 for r in removed:
293 if r not in ctx:
294 if r not in ctx:
294 continue
295 continue
295 fctx = ctx.filectx(r)
296 fctx = ctx.filectx(r)
296
297
297 def score(text):
298 def score(text):
298 if not len(text):
299 if not len(text):
299 return 0.0
300 return 0.0
300 if not fctx.cmp(text):
301 if not fctx.cmp(text):
301 return 1.0
302 return 1.0
302 if threshold == 1.0:
303 if threshold == 1.0:
303 return 0.0
304 return 0.0
304 orig = fctx.data()
305 orig = fctx.data()
305 # bdiff.blocks() returns blocks of matching lines
306 # bdiff.blocks() returns blocks of matching lines
306 # count the number of bytes in each
307 # count the number of bytes in each
307 equal = 0
308 equal = 0
308 alines = mdiff.splitnewlines(text)
309 alines = mdiff.splitnewlines(text)
309 matches = bdiff.blocks(text, orig)
310 matches = bdiff.blocks(text, orig)
310 for x1, x2, y1, y2 in matches:
311 for x1, x2, y1, y2 in matches:
311 for line in alines[x1:x2]:
312 for line in alines[x1:x2]:
312 equal += len(line)
313 equal += len(line)
313
314
314 lengths = len(text) + len(orig)
315 lengths = len(text) + len(orig)
315 return equal * 2.0 / lengths
316 return equal * 2.0 / lengths
316
317
317 for a in added:
318 for a in added:
318 bestscore = copies.get(a, (None, threshold))[1]
319 bestscore = copies.get(a, (None, threshold))[1]
319 myscore = score(repo.wread(a))
320 myscore = score(repo.wread(a))
320 if myscore >= bestscore:
321 if myscore >= bestscore:
321 copies[a] = (r, myscore)
322 copies[a] = (r, myscore)
322
323
323 for dest, v in copies.iteritems():
324 for dest, v in copies.iteritems():
324 source, score = v
325 source, score = v
325 yield source, dest, score
326 yield source, dest, score
326
327
327 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
328 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
328 if dry_run is None:
329 if dry_run is None:
329 dry_run = opts.get('dry_run')
330 dry_run = opts.get('dry_run')
330 if similarity is None:
331 if similarity is None:
331 similarity = float(opts.get('similarity') or 0)
332 similarity = float(opts.get('similarity') or 0)
332 # we'd use status here, except handling of symlinks and ignore is tricky
333 # we'd use status here, except handling of symlinks and ignore is tricky
333 added, unknown, deleted, removed = [], [], [], []
334 added, unknown, deleted, removed = [], [], [], []
334 audit_path = util.path_auditor(repo.root)
335 audit_path = util.path_auditor(repo.root)
335 m = match(repo, pats, opts)
336 m = match(repo, pats, opts)
336 for abs in repo.walk(m):
337 for abs in repo.walk(m):
337 target = repo.wjoin(abs)
338 target = repo.wjoin(abs)
338 good = True
339 good = True
339 try:
340 try:
340 audit_path(abs)
341 audit_path(abs)
341 except:
342 except:
342 good = False
343 good = False
343 rel = m.rel(abs)
344 rel = m.rel(abs)
344 exact = m.exact(abs)
345 exact = m.exact(abs)
345 if good and abs not in repo.dirstate:
346 if good and abs not in repo.dirstate:
346 unknown.append(abs)
347 unknown.append(abs)
347 if repo.ui.verbose or not exact:
348 if repo.ui.verbose or not exact:
348 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
349 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
349 elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
350 elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
350 or (os.path.isdir(target) and not os.path.islink(target))):
351 or (os.path.isdir(target) and not os.path.islink(target))):
351 deleted.append(abs)
352 deleted.append(abs)
352 if repo.ui.verbose or not exact:
353 if repo.ui.verbose or not exact:
353 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
354 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
354 # for finding renames
355 # for finding renames
355 elif repo.dirstate[abs] == 'r':
356 elif repo.dirstate[abs] == 'r':
356 removed.append(abs)
357 removed.append(abs)
357 elif repo.dirstate[abs] == 'a':
358 elif repo.dirstate[abs] == 'a':
358 added.append(abs)
359 added.append(abs)
359 if not dry_run:
360 if not dry_run:
360 repo.remove(deleted)
361 repo.remove(deleted)
361 repo.add(unknown)
362 repo.add(unknown)
362 if similarity > 0:
363 if similarity > 0:
363 for old, new, score in findrenames(repo, added + unknown,
364 for old, new, score in findrenames(repo, added + unknown,
364 removed + deleted, similarity):
365 removed + deleted, similarity):
365 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
366 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
366 repo.ui.status(_('recording removal of %s as rename to %s '
367 repo.ui.status(_('recording removal of %s as rename to %s '
367 '(%d%% similar)\n') %
368 '(%d%% similar)\n') %
368 (m.rel(old), m.rel(new), score * 100))
369 (m.rel(old), m.rel(new), score * 100))
369 if not dry_run:
370 if not dry_run:
370 repo.copy(old, new)
371 repo.copy(old, new)
371
372
372 def copy(ui, repo, pats, opts, rename=False):
373 def copy(ui, repo, pats, opts, rename=False):
373 # called with the repo lock held
374 # called with the repo lock held
374 #
375 #
375 # hgsep => pathname that uses "/" to separate directories
376 # hgsep => pathname that uses "/" to separate directories
376 # ossep => pathname that uses os.sep to separate directories
377 # ossep => pathname that uses os.sep to separate directories
377 cwd = repo.getcwd()
378 cwd = repo.getcwd()
378 targets = {}
379 targets = {}
379 after = opts.get("after")
380 after = opts.get("after")
380 dryrun = opts.get("dry_run")
381 dryrun = opts.get("dry_run")
381
382
382 def walkpat(pat):
383 def walkpat(pat):
383 srcs = []
384 srcs = []
384 m = match(repo, [pat], opts, globbed=True)
385 m = match(repo, [pat], opts, globbed=True)
385 for abs in repo.walk(m):
386 for abs in repo.walk(m):
386 state = repo.dirstate[abs]
387 state = repo.dirstate[abs]
387 rel = m.rel(abs)
388 rel = m.rel(abs)
388 exact = m.exact(abs)
389 exact = m.exact(abs)
389 if state in '?r':
390 if state in '?r':
390 if exact and state == '?':
391 if exact and state == '?':
391 ui.warn(_('%s: not copying - file is not managed\n') % rel)
392 ui.warn(_('%s: not copying - file is not managed\n') % rel)
392 if exact and state == 'r':
393 if exact and state == 'r':
393 ui.warn(_('%s: not copying - file has been marked for'
394 ui.warn(_('%s: not copying - file has been marked for'
394 ' remove\n') % rel)
395 ' remove\n') % rel)
395 continue
396 continue
396 # abs: hgsep
397 # abs: hgsep
397 # rel: ossep
398 # rel: ossep
398 srcs.append((abs, rel, exact))
399 srcs.append((abs, rel, exact))
399 return srcs
400 return srcs
400
401
401 # abssrc: hgsep
402 # abssrc: hgsep
402 # relsrc: ossep
403 # relsrc: ossep
403 # otarget: ossep
404 # otarget: ossep
404 def copyfile(abssrc, relsrc, otarget, exact):
405 def copyfile(abssrc, relsrc, otarget, exact):
405 abstarget = util.canonpath(repo.root, cwd, otarget)
406 abstarget = util.canonpath(repo.root, cwd, otarget)
406 reltarget = repo.pathto(abstarget, cwd)
407 reltarget = repo.pathto(abstarget, cwd)
407 target = repo.wjoin(abstarget)
408 target = repo.wjoin(abstarget)
408 src = repo.wjoin(abssrc)
409 src = repo.wjoin(abssrc)
409 state = repo.dirstate[abstarget]
410 state = repo.dirstate[abstarget]
410
411
411 # check for collisions
412 # check for collisions
412 prevsrc = targets.get(abstarget)
413 prevsrc = targets.get(abstarget)
413 if prevsrc is not None:
414 if prevsrc is not None:
414 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
415 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
415 (reltarget, repo.pathto(abssrc, cwd),
416 (reltarget, repo.pathto(abssrc, cwd),
416 repo.pathto(prevsrc, cwd)))
417 repo.pathto(prevsrc, cwd)))
417 return
418 return
418
419
419 # check for overwrites
420 # check for overwrites
420 exists = os.path.exists(target)
421 exists = os.path.exists(target)
421 if not after and exists or after and state in 'mn':
422 if not after and exists or after and state in 'mn':
422 if not opts['force']:
423 if not opts['force']:
423 ui.warn(_('%s: not overwriting - file exists\n') %
424 ui.warn(_('%s: not overwriting - file exists\n') %
424 reltarget)
425 reltarget)
425 return
426 return
426
427
427 if after:
428 if after:
428 if not exists:
429 if not exists:
429 return
430 return
430 elif not dryrun:
431 elif not dryrun:
431 try:
432 try:
432 if exists:
433 if exists:
433 os.unlink(target)
434 os.unlink(target)
434 targetdir = os.path.dirname(target) or '.'
435 targetdir = os.path.dirname(target) or '.'
435 if not os.path.isdir(targetdir):
436 if not os.path.isdir(targetdir):
436 os.makedirs(targetdir)
437 os.makedirs(targetdir)
437 util.copyfile(src, target)
438 util.copyfile(src, target)
438 except IOError, inst:
439 except IOError, inst:
439 if inst.errno == errno.ENOENT:
440 if inst.errno == errno.ENOENT:
440 ui.warn(_('%s: deleted in working copy\n') % relsrc)
441 ui.warn(_('%s: deleted in working copy\n') % relsrc)
441 else:
442 else:
442 ui.warn(_('%s: cannot copy - %s\n') %
443 ui.warn(_('%s: cannot copy - %s\n') %
443 (relsrc, inst.strerror))
444 (relsrc, inst.strerror))
444 return True # report a failure
445 return True # report a failure
445
446
446 if ui.verbose or not exact:
447 if ui.verbose or not exact:
447 if rename:
448 if rename:
448 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
449 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
449 else:
450 else:
450 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
451 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
451
452
452 targets[abstarget] = abssrc
453 targets[abstarget] = abssrc
453
454
454 # fix up dirstate
455 # fix up dirstate
455 origsrc = repo.dirstate.copied(abssrc) or abssrc
456 origsrc = repo.dirstate.copied(abssrc) or abssrc
456 if abstarget == origsrc: # copying back a copy?
457 if abstarget == origsrc: # copying back a copy?
457 if state not in 'mn' and not dryrun:
458 if state not in 'mn' and not dryrun:
458 repo.dirstate.normallookup(abstarget)
459 repo.dirstate.normallookup(abstarget)
459 else:
460 else:
460 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
461 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
461 if not ui.quiet:
462 if not ui.quiet:
462 ui.warn(_("%s has not been committed yet, so no copy "
463 ui.warn(_("%s has not been committed yet, so no copy "
463 "data will be stored for %s.\n")
464 "data will be stored for %s.\n")
464 % (repo.pathto(origsrc, cwd), reltarget))
465 % (repo.pathto(origsrc, cwd), reltarget))
465 if repo.dirstate[abstarget] in '?r' and not dryrun:
466 if repo.dirstate[abstarget] in '?r' and not dryrun:
466 repo.add([abstarget])
467 repo.add([abstarget])
467 elif not dryrun:
468 elif not dryrun:
468 repo.copy(origsrc, abstarget)
469 repo.copy(origsrc, abstarget)
469
470
470 if rename and not dryrun:
471 if rename and not dryrun:
471 repo.remove([abssrc], not after)
472 repo.remove([abssrc], not after)
472
473
473 # pat: ossep
474 # pat: ossep
474 # dest ossep
475 # dest ossep
475 # srcs: list of (hgsep, hgsep, ossep, bool)
476 # srcs: list of (hgsep, hgsep, ossep, bool)
476 # return: function that takes hgsep and returns ossep
477 # return: function that takes hgsep and returns ossep
477 def targetpathfn(pat, dest, srcs):
478 def targetpathfn(pat, dest, srcs):
478 if os.path.isdir(pat):
479 if os.path.isdir(pat):
479 abspfx = util.canonpath(repo.root, cwd, pat)
480 abspfx = util.canonpath(repo.root, cwd, pat)
480 abspfx = util.localpath(abspfx)
481 abspfx = util.localpath(abspfx)
481 if destdirexists:
482 if destdirexists:
482 striplen = len(os.path.split(abspfx)[0])
483 striplen = len(os.path.split(abspfx)[0])
483 else:
484 else:
484 striplen = len(abspfx)
485 striplen = len(abspfx)
485 if striplen:
486 if striplen:
486 striplen += len(os.sep)
487 striplen += len(os.sep)
487 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
488 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
488 elif destdirexists:
489 elif destdirexists:
489 res = lambda p: os.path.join(dest,
490 res = lambda p: os.path.join(dest,
490 os.path.basename(util.localpath(p)))
491 os.path.basename(util.localpath(p)))
491 else:
492 else:
492 res = lambda p: dest
493 res = lambda p: dest
493 return res
494 return res
494
495
495 # pat: ossep
496 # pat: ossep
496 # dest ossep
497 # dest ossep
497 # srcs: list of (hgsep, hgsep, ossep, bool)
498 # srcs: list of (hgsep, hgsep, ossep, bool)
498 # return: function that takes hgsep and returns ossep
499 # return: function that takes hgsep and returns ossep
499 def targetpathafterfn(pat, dest, srcs):
500 def targetpathafterfn(pat, dest, srcs):
500 if _match.patkind(pat):
501 if _match.patkind(pat):
501 # a mercurial pattern
502 # a mercurial pattern
502 res = lambda p: os.path.join(dest,
503 res = lambda p: os.path.join(dest,
503 os.path.basename(util.localpath(p)))
504 os.path.basename(util.localpath(p)))
504 else:
505 else:
505 abspfx = util.canonpath(repo.root, cwd, pat)
506 abspfx = util.canonpath(repo.root, cwd, pat)
506 if len(abspfx) < len(srcs[0][0]):
507 if len(abspfx) < len(srcs[0][0]):
507 # A directory. Either the target path contains the last
508 # A directory. Either the target path contains the last
508 # component of the source path or it does not.
509 # component of the source path or it does not.
509 def evalpath(striplen):
510 def evalpath(striplen):
510 score = 0
511 score = 0
511 for s in srcs:
512 for s in srcs:
512 t = os.path.join(dest, util.localpath(s[0])[striplen:])
513 t = os.path.join(dest, util.localpath(s[0])[striplen:])
513 if os.path.exists(t):
514 if os.path.exists(t):
514 score += 1
515 score += 1
515 return score
516 return score
516
517
517 abspfx = util.localpath(abspfx)
518 abspfx = util.localpath(abspfx)
518 striplen = len(abspfx)
519 striplen = len(abspfx)
519 if striplen:
520 if striplen:
520 striplen += len(os.sep)
521 striplen += len(os.sep)
521 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
522 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
522 score = evalpath(striplen)
523 score = evalpath(striplen)
523 striplen1 = len(os.path.split(abspfx)[0])
524 striplen1 = len(os.path.split(abspfx)[0])
524 if striplen1:
525 if striplen1:
525 striplen1 += len(os.sep)
526 striplen1 += len(os.sep)
526 if evalpath(striplen1) > score:
527 if evalpath(striplen1) > score:
527 striplen = striplen1
528 striplen = striplen1
528 res = lambda p: os.path.join(dest,
529 res = lambda p: os.path.join(dest,
529 util.localpath(p)[striplen:])
530 util.localpath(p)[striplen:])
530 else:
531 else:
531 # a file
532 # a file
532 if destdirexists:
533 if destdirexists:
533 res = lambda p: os.path.join(dest,
534 res = lambda p: os.path.join(dest,
534 os.path.basename(util.localpath(p)))
535 os.path.basename(util.localpath(p)))
535 else:
536 else:
536 res = lambda p: dest
537 res = lambda p: dest
537 return res
538 return res
538
539
539
540
540 pats = expandpats(pats)
541 pats = expandpats(pats)
541 if not pats:
542 if not pats:
542 raise util.Abort(_('no source or destination specified'))
543 raise util.Abort(_('no source or destination specified'))
543 if len(pats) == 1:
544 if len(pats) == 1:
544 raise util.Abort(_('no destination specified'))
545 raise util.Abort(_('no destination specified'))
545 dest = pats.pop()
546 dest = pats.pop()
546 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
547 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
547 if not destdirexists:
548 if not destdirexists:
548 if len(pats) > 1 or _match.patkind(pats[0]):
549 if len(pats) > 1 or _match.patkind(pats[0]):
549 raise util.Abort(_('with multiple sources, destination must be an '
550 raise util.Abort(_('with multiple sources, destination must be an '
550 'existing directory'))
551 'existing directory'))
551 if util.endswithsep(dest):
552 if util.endswithsep(dest):
552 raise util.Abort(_('destination %s is not a directory') % dest)
553 raise util.Abort(_('destination %s is not a directory') % dest)
553
554
554 tfn = targetpathfn
555 tfn = targetpathfn
555 if after:
556 if after:
556 tfn = targetpathafterfn
557 tfn = targetpathafterfn
557 copylist = []
558 copylist = []
558 for pat in pats:
559 for pat in pats:
559 srcs = walkpat(pat)
560 srcs = walkpat(pat)
560 if not srcs:
561 if not srcs:
561 continue
562 continue
562 copylist.append((tfn(pat, dest, srcs), srcs))
563 copylist.append((tfn(pat, dest, srcs), srcs))
563 if not copylist:
564 if not copylist:
564 raise util.Abort(_('no files to copy'))
565 raise util.Abort(_('no files to copy'))
565
566
566 errors = 0
567 errors = 0
567 for targetpath, srcs in copylist:
568 for targetpath, srcs in copylist:
568 for abssrc, relsrc, exact in srcs:
569 for abssrc, relsrc, exact in srcs:
569 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
570 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
570 errors += 1
571 errors += 1
571
572
572 if errors:
573 if errors:
573 ui.warn(_('(consider using --after)\n'))
574 ui.warn(_('(consider using --after)\n'))
574
575
575 return errors
576 return errors
576
577
577 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
578 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
578 runargs=None, appendpid=False):
579 runargs=None, appendpid=False):
579 '''Run a command as a service.'''
580 '''Run a command as a service.'''
580
581
581 if opts['daemon'] and not opts['daemon_pipefds']:
582 if opts['daemon'] and not opts['daemon_pipefds']:
582 # Signal child process startup with file removal
583 # Signal child process startup with file removal
583 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
584 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
584 os.close(lockfd)
585 os.close(lockfd)
585 try:
586 try:
586 if not runargs:
587 if not runargs:
587 runargs = util.hgcmd() + sys.argv[1:]
588 runargs = util.hgcmd() + sys.argv[1:]
588 runargs.append('--daemon-pipefds=%s' % lockpath)
589 runargs.append('--daemon-pipefds=%s' % lockpath)
589 # Don't pass --cwd to the child process, because we've already
590 # Don't pass --cwd to the child process, because we've already
590 # changed directory.
591 # changed directory.
591 for i in xrange(1, len(runargs)):
592 for i in xrange(1, len(runargs)):
592 if runargs[i].startswith('--cwd='):
593 if runargs[i].startswith('--cwd='):
593 del runargs[i]
594 del runargs[i]
594 break
595 break
595 elif runargs[i].startswith('--cwd'):
596 elif runargs[i].startswith('--cwd'):
596 del runargs[i:i + 2]
597 del runargs[i:i + 2]
597 break
598 break
598 def condfn():
599 def condfn():
599 return not os.path.exists(lockpath)
600 return not os.path.exists(lockpath)
600 pid = util.rundetached(runargs, condfn)
601 pid = util.rundetached(runargs, condfn)
601 if pid < 0:
602 if pid < 0:
602 raise util.Abort(_('child process failed to start'))
603 raise util.Abort(_('child process failed to start'))
603 finally:
604 finally:
604 try:
605 try:
605 os.unlink(lockpath)
606 os.unlink(lockpath)
606 except OSError, e:
607 except OSError, e:
607 if e.errno != errno.ENOENT:
608 if e.errno != errno.ENOENT:
608 raise
609 raise
609 if parentfn:
610 if parentfn:
610 return parentfn(pid)
611 return parentfn(pid)
611 else:
612 else:
612 return
613 return
613
614
614 if initfn:
615 if initfn:
615 initfn()
616 initfn()
616
617
617 if opts['pid_file']:
618 if opts['pid_file']:
618 mode = appendpid and 'a' or 'w'
619 mode = appendpid and 'a' or 'w'
619 fp = open(opts['pid_file'], mode)
620 fp = open(opts['pid_file'], mode)
620 fp.write(str(os.getpid()) + '\n')
621 fp.write(str(os.getpid()) + '\n')
621 fp.close()
622 fp.close()
622
623
623 if opts['daemon_pipefds']:
624 if opts['daemon_pipefds']:
624 lockpath = opts['daemon_pipefds']
625 lockpath = opts['daemon_pipefds']
625 try:
626 try:
626 os.setsid()
627 os.setsid()
627 except AttributeError:
628 except AttributeError:
628 pass
629 pass
629 os.unlink(lockpath)
630 os.unlink(lockpath)
630 util.hidewindow()
631 util.hidewindow()
631 sys.stdout.flush()
632 sys.stdout.flush()
632 sys.stderr.flush()
633 sys.stderr.flush()
633
634
634 nullfd = os.open(util.nulldev, os.O_RDWR)
635 nullfd = os.open(util.nulldev, os.O_RDWR)
635 logfilefd = nullfd
636 logfilefd = nullfd
636 if logfile:
637 if logfile:
637 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
638 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
638 os.dup2(nullfd, 0)
639 os.dup2(nullfd, 0)
639 os.dup2(logfilefd, 1)
640 os.dup2(logfilefd, 1)
640 os.dup2(logfilefd, 2)
641 os.dup2(logfilefd, 2)
641 if nullfd not in (0, 1, 2):
642 if nullfd not in (0, 1, 2):
642 os.close(nullfd)
643 os.close(nullfd)
643 if logfile and logfilefd not in (0, 1, 2):
644 if logfile and logfilefd not in (0, 1, 2):
644 os.close(logfilefd)
645 os.close(logfilefd)
645
646
646 if runfn:
647 if runfn:
647 return runfn()
648 return runfn()
648
649
649 class changeset_printer(object):
650 class changeset_printer(object):
650 '''show changeset information when templating not requested.'''
651 '''show changeset information when templating not requested.'''
651
652
652 def __init__(self, ui, repo, patch, diffopts, buffered):
653 def __init__(self, ui, repo, patch, diffopts, buffered):
653 self.ui = ui
654 self.ui = ui
654 self.repo = repo
655 self.repo = repo
655 self.buffered = buffered
656 self.buffered = buffered
656 self.patch = patch
657 self.patch = patch
657 self.diffopts = diffopts
658 self.diffopts = diffopts
658 self.header = {}
659 self.header = {}
659 self.hunk = {}
660 self.hunk = {}
660 self.lastheader = None
661 self.lastheader = None
661 self.footer = None
662 self.footer = None
662
663
663 def flush(self, rev):
664 def flush(self, rev):
664 if rev in self.header:
665 if rev in self.header:
665 h = self.header[rev]
666 h = self.header[rev]
666 if h != self.lastheader:
667 if h != self.lastheader:
667 self.lastheader = h
668 self.lastheader = h
668 self.ui.write(h)
669 self.ui.write(h)
669 del self.header[rev]
670 del self.header[rev]
670 if rev in self.hunk:
671 if rev in self.hunk:
671 self.ui.write(self.hunk[rev])
672 self.ui.write(self.hunk[rev])
672 del self.hunk[rev]
673 del self.hunk[rev]
673 return 1
674 return 1
674 return 0
675 return 0
675
676
676 def close(self):
677 def close(self):
677 if self.footer:
678 if self.footer:
678 self.ui.write(self.footer)
679 self.ui.write(self.footer)
679
680
680 def show(self, ctx, copies=None, **props):
681 def show(self, ctx, copies=None, **props):
681 if self.buffered:
682 if self.buffered:
682 self.ui.pushbuffer()
683 self.ui.pushbuffer()
683 self._show(ctx, copies, props)
684 self._show(ctx, copies, props)
684 self.hunk[ctx.rev()] = self.ui.popbuffer()
685 self.hunk[ctx.rev()] = self.ui.popbuffer()
685 else:
686 else:
686 self._show(ctx, copies, props)
687 self._show(ctx, copies, props)
687
688
688 def _show(self, ctx, copies, props):
689 def _show(self, ctx, copies, props):
689 '''show a single changeset or file revision'''
690 '''show a single changeset or file revision'''
690 changenode = ctx.node()
691 changenode = ctx.node()
691 rev = ctx.rev()
692 rev = ctx.rev()
692
693
693 if self.ui.quiet:
694 if self.ui.quiet:
694 self.ui.write("%d:%s\n" % (rev, short(changenode)))
695 self.ui.write("%d:%s\n" % (rev, short(changenode)))
695 return
696 return
696
697
697 log = self.repo.changelog
698 log = self.repo.changelog
698 date = util.datestr(ctx.date())
699 date = util.datestr(ctx.date())
699
700
700 hexfunc = self.ui.debugflag and hex or short
701 hexfunc = self.ui.debugflag and hex or short
701
702
702 parents = [(p, hexfunc(log.node(p)))
703 parents = [(p, hexfunc(log.node(p)))
703 for p in self._meaningful_parentrevs(log, rev)]
704 for p in self._meaningful_parentrevs(log, rev)]
704
705
705 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
706 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
706
707
707 branch = ctx.branch()
708 branch = ctx.branch()
708 # don't show the default branch name
709 # don't show the default branch name
709 if branch != 'default':
710 if branch != 'default':
710 branch = encoding.tolocal(branch)
711 branch = encoding.tolocal(branch)
711 self.ui.write(_("branch: %s\n") % branch)
712 self.ui.write(_("branch: %s\n") % branch)
712 for tag in self.repo.nodetags(changenode):
713 for tag in self.repo.nodetags(changenode):
713 self.ui.write(_("tag: %s\n") % tag)
714 self.ui.write(_("tag: %s\n") % tag)
714 for parent in parents:
715 for parent in parents:
715 self.ui.write(_("parent: %d:%s\n") % parent)
716 self.ui.write(_("parent: %d:%s\n") % parent)
716
717
717 if self.ui.debugflag:
718 if self.ui.debugflag:
718 mnode = ctx.manifestnode()
719 mnode = ctx.manifestnode()
719 self.ui.write(_("manifest: %d:%s\n") %
720 self.ui.write(_("manifest: %d:%s\n") %
720 (self.repo.manifest.rev(mnode), hex(mnode)))
721 (self.repo.manifest.rev(mnode), hex(mnode)))
721 self.ui.write(_("user: %s\n") % ctx.user())
722 self.ui.write(_("user: %s\n") % ctx.user())
722 self.ui.write(_("date: %s\n") % date)
723 self.ui.write(_("date: %s\n") % date)
723
724
724 if self.ui.debugflag:
725 if self.ui.debugflag:
725 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
726 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
726 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
727 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
727 files):
728 files):
728 if value:
729 if value:
729 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
730 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
730 elif ctx.files() and self.ui.verbose:
731 elif ctx.files() and self.ui.verbose:
731 self.ui.write(_("files: %s\n") % " ".join(ctx.files()))
732 self.ui.write(_("files: %s\n") % " ".join(ctx.files()))
732 if copies and self.ui.verbose:
733 if copies and self.ui.verbose:
733 copies = ['%s (%s)' % c for c in copies]
734 copies = ['%s (%s)' % c for c in copies]
734 self.ui.write(_("copies: %s\n") % ' '.join(copies))
735 self.ui.write(_("copies: %s\n") % ' '.join(copies))
735
736
736 extra = ctx.extra()
737 extra = ctx.extra()
737 if extra and self.ui.debugflag:
738 if extra and self.ui.debugflag:
738 for key, value in sorted(extra.items()):
739 for key, value in sorted(extra.items()):
739 self.ui.write(_("extra: %s=%s\n")
740 self.ui.write(_("extra: %s=%s\n")
740 % (key, value.encode('string_escape')))
741 % (key, value.encode('string_escape')))
741
742
742 description = ctx.description().strip()
743 description = ctx.description().strip()
743 if description:
744 if description:
744 if self.ui.verbose:
745 if self.ui.verbose:
745 self.ui.write(_("description:\n"))
746 self.ui.write(_("description:\n"))
746 self.ui.write(description)
747 self.ui.write(description)
747 self.ui.write("\n\n")
748 self.ui.write("\n\n")
748 else:
749 else:
749 self.ui.write(_("summary: %s\n") %
750 self.ui.write(_("summary: %s\n") %
750 description.splitlines()[0])
751 description.splitlines()[0])
751 self.ui.write("\n")
752 self.ui.write("\n")
752
753
753 self.showpatch(changenode)
754 self.showpatch(changenode)
754
755
755 def showpatch(self, node):
756 def showpatch(self, node):
756 if self.patch:
757 if self.patch:
757 prev = self.repo.changelog.parents(node)[0]
758 prev = self.repo.changelog.parents(node)[0]
758 chunks = patch.diff(self.repo, prev, node, match=self.patch,
759 chunks = patch.diff(self.repo, prev, node, match=self.patch,
759 opts=patch.diffopts(self.ui, self.diffopts))
760 opts=patch.diffopts(self.ui, self.diffopts))
760 for chunk in chunks:
761 for chunk in chunks:
761 self.ui.write(chunk)
762 self.ui.write(chunk)
762 self.ui.write("\n")
763 self.ui.write("\n")
763
764
764 def _meaningful_parentrevs(self, log, rev):
765 def _meaningful_parentrevs(self, log, rev):
765 """Return list of meaningful (or all if debug) parentrevs for rev.
766 """Return list of meaningful (or all if debug) parentrevs for rev.
766
767
767 For merges (two non-nullrev revisions) both parents are meaningful.
768 For merges (two non-nullrev revisions) both parents are meaningful.
768 Otherwise the first parent revision is considered meaningful if it
769 Otherwise the first parent revision is considered meaningful if it
769 is not the preceding revision.
770 is not the preceding revision.
770 """
771 """
771 parents = log.parentrevs(rev)
772 parents = log.parentrevs(rev)
772 if not self.ui.debugflag and parents[1] == nullrev:
773 if not self.ui.debugflag and parents[1] == nullrev:
773 if parents[0] >= rev - 1:
774 if parents[0] >= rev - 1:
774 parents = []
775 parents = []
775 else:
776 else:
776 parents = [parents[0]]
777 parents = [parents[0]]
777 return parents
778 return parents
778
779
779
780
780 class changeset_templater(changeset_printer):
781 class changeset_templater(changeset_printer):
781 '''format changeset information.'''
782 '''format changeset information.'''
782
783
783 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
784 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
784 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
785 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
785 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
786 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
786 defaulttempl = {
787 defaulttempl = {
787 'parent': '{rev}:{node|formatnode} ',
788 'parent': '{rev}:{node|formatnode} ',
788 'manifest': '{rev}:{node|formatnode}',
789 'manifest': '{rev}:{node|formatnode}',
789 'file_copy': '{name} ({source})',
790 'file_copy': '{name} ({source})',
790 'extra': '{key}={value|stringescape}'
791 'extra': '{key}={value|stringescape}'
791 }
792 }
792 # filecopy is preserved for compatibility reasons
793 # filecopy is preserved for compatibility reasons
793 defaulttempl['filecopy'] = defaulttempl['file_copy']
794 defaulttempl['filecopy'] = defaulttempl['file_copy']
794 self.t = templater.templater(mapfile, {'formatnode': formatnode},
795 self.t = templater.templater(mapfile, {'formatnode': formatnode},
795 cache=defaulttempl)
796 cache=defaulttempl)
796 self.cache = {}
797 self.cache = {}
797
798
798 def use_template(self, t):
799 def use_template(self, t):
799 '''set template string to use'''
800 '''set template string to use'''
800 self.t.cache['changeset'] = t
801 self.t.cache['changeset'] = t
801
802
802 def _meaningful_parentrevs(self, ctx):
803 def _meaningful_parentrevs(self, ctx):
803 """Return list of meaningful (or all if debug) parentrevs for rev.
804 """Return list of meaningful (or all if debug) parentrevs for rev.
804 """
805 """
805 parents = ctx.parents()
806 parents = ctx.parents()
806 if len(parents) > 1:
807 if len(parents) > 1:
807 return parents
808 return parents
808 if self.ui.debugflag:
809 if self.ui.debugflag:
809 return [parents[0], self.repo['null']]
810 return [parents[0], self.repo['null']]
810 if parents[0].rev() >= ctx.rev() - 1:
811 if parents[0].rev() >= ctx.rev() - 1:
811 return []
812 return []
812 return parents
813 return parents
813
814
814 def _show(self, ctx, copies, props):
815 def _show(self, ctx, copies, props):
815 '''show a single changeset or file revision'''
816 '''show a single changeset or file revision'''
816
817
817 showlist = templatekw.showlist
818 showlist = templatekw.showlist
818
819
819 # showparents() behaviour depends on ui trace level which
820 # showparents() behaviour depends on ui trace level which
820 # causes unexpected behaviours at templating level and makes
821 # causes unexpected behaviours at templating level and makes
821 # it harder to extract it in a standalone function. Its
822 # it harder to extract it in a standalone function. Its
822 # behaviour cannot be changed so leave it here for now.
823 # behaviour cannot be changed so leave it here for now.
823 def showparents(**args):
824 def showparents(**args):
824 ctx = args['ctx']
825 ctx = args['ctx']
825 parents = [[('rev', p.rev()), ('node', p.hex())]
826 parents = [[('rev', p.rev()), ('node', p.hex())]
826 for p in self._meaningful_parentrevs(ctx)]
827 for p in self._meaningful_parentrevs(ctx)]
827 return showlist('parent', parents, **args)
828 return showlist('parent', parents, **args)
828
829
829 props = props.copy()
830 props = props.copy()
830 props.update(templatekw.keywords)
831 props.update(templatekw.keywords)
831 props['parents'] = showparents
832 props['parents'] = showparents
832 props['templ'] = self.t
833 props['templ'] = self.t
833 props['ctx'] = ctx
834 props['ctx'] = ctx
834 props['repo'] = self.repo
835 props['repo'] = self.repo
835 props['revcache'] = {'copies': copies}
836 props['revcache'] = {'copies': copies}
836 props['cache'] = self.cache
837 props['cache'] = self.cache
837
838
838 # find correct templates for current mode
839 # find correct templates for current mode
839
840
840 tmplmodes = [
841 tmplmodes = [
841 (True, None),
842 (True, None),
842 (self.ui.verbose, 'verbose'),
843 (self.ui.verbose, 'verbose'),
843 (self.ui.quiet, 'quiet'),
844 (self.ui.quiet, 'quiet'),
844 (self.ui.debugflag, 'debug'),
845 (self.ui.debugflag, 'debug'),
845 ]
846 ]
846
847
847 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
848 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
848 for mode, postfix in tmplmodes:
849 for mode, postfix in tmplmodes:
849 for type in types:
850 for type in types:
850 cur = postfix and ('%s_%s' % (type, postfix)) or type
851 cur = postfix and ('%s_%s' % (type, postfix)) or type
851 if mode and cur in self.t:
852 if mode and cur in self.t:
852 types[type] = cur
853 types[type] = cur
853
854
854 try:
855 try:
855
856
856 # write header
857 # write header
857 if types['header']:
858 if types['header']:
858 h = templater.stringify(self.t(types['header'], **props))
859 h = templater.stringify(self.t(types['header'], **props))
859 if self.buffered:
860 if self.buffered:
860 self.header[ctx.rev()] = h
861 self.header[ctx.rev()] = h
861 else:
862 else:
862 self.ui.write(h)
863 self.ui.write(h)
863
864
864 # write changeset metadata, then patch if requested
865 # write changeset metadata, then patch if requested
865 key = types['changeset']
866 key = types['changeset']
866 self.ui.write(templater.stringify(self.t(key, **props)))
867 self.ui.write(templater.stringify(self.t(key, **props)))
867 self.showpatch(ctx.node())
868 self.showpatch(ctx.node())
868
869
869 if types['footer']:
870 if types['footer']:
870 if not self.footer:
871 if not self.footer:
871 self.footer = templater.stringify(self.t(types['footer'],
872 self.footer = templater.stringify(self.t(types['footer'],
872 **props))
873 **props))
873
874
874 except KeyError, inst:
875 except KeyError, inst:
875 msg = _("%s: no key named '%s'")
876 msg = _("%s: no key named '%s'")
876 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
877 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
877 except SyntaxError, inst:
878 except SyntaxError, inst:
878 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
879 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
879
880
880 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
881 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
881 """show one changeset using template or regular display.
882 """show one changeset using template or regular display.
882
883
883 Display format will be the first non-empty hit of:
884 Display format will be the first non-empty hit of:
884 1. option 'template'
885 1. option 'template'
885 2. option 'style'
886 2. option 'style'
886 3. [ui] setting 'logtemplate'
887 3. [ui] setting 'logtemplate'
887 4. [ui] setting 'style'
888 4. [ui] setting 'style'
888 If all of these values are either the unset or the empty string,
889 If all of these values are either the unset or the empty string,
889 regular display via changeset_printer() is done.
890 regular display via changeset_printer() is done.
890 """
891 """
891 # options
892 # options
892 patch = False
893 patch = False
893 if opts.get('patch'):
894 if opts.get('patch'):
894 patch = matchfn or matchall(repo)
895 patch = matchfn or matchall(repo)
895
896
896 tmpl = opts.get('template')
897 tmpl = opts.get('template')
897 style = None
898 style = None
898 if tmpl:
899 if tmpl:
899 tmpl = templater.parsestring(tmpl, quoted=False)
900 tmpl = templater.parsestring(tmpl, quoted=False)
900 else:
901 else:
901 style = opts.get('style')
902 style = opts.get('style')
902
903
903 # ui settings
904 # ui settings
904 if not (tmpl or style):
905 if not (tmpl or style):
905 tmpl = ui.config('ui', 'logtemplate')
906 tmpl = ui.config('ui', 'logtemplate')
906 if tmpl:
907 if tmpl:
907 tmpl = templater.parsestring(tmpl)
908 tmpl = templater.parsestring(tmpl)
908 else:
909 else:
909 style = util.expandpath(ui.config('ui', 'style', ''))
910 style = util.expandpath(ui.config('ui', 'style', ''))
910
911
911 if not (tmpl or style):
912 if not (tmpl or style):
912 return changeset_printer(ui, repo, patch, opts, buffered)
913 return changeset_printer(ui, repo, patch, opts, buffered)
913
914
914 mapfile = None
915 mapfile = None
915 if style and not tmpl:
916 if style and not tmpl:
916 mapfile = style
917 mapfile = style
917 if not os.path.split(mapfile)[0]:
918 if not os.path.split(mapfile)[0]:
918 mapname = (templater.templatepath('map-cmdline.' + mapfile)
919 mapname = (templater.templatepath('map-cmdline.' + mapfile)
919 or templater.templatepath(mapfile))
920 or templater.templatepath(mapfile))
920 if mapname:
921 if mapname:
921 mapfile = mapname
922 mapfile = mapname
922
923
923 try:
924 try:
924 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
925 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
925 except SyntaxError, inst:
926 except SyntaxError, inst:
926 raise util.Abort(inst.args[0])
927 raise util.Abort(inst.args[0])
927 if tmpl:
928 if tmpl:
928 t.use_template(tmpl)
929 t.use_template(tmpl)
929 return t
930 return t
930
931
931 def finddate(ui, repo, date):
932 def finddate(ui, repo, date):
932 """Find the tipmost changeset that matches the given date spec"""
933 """Find the tipmost changeset that matches the given date spec"""
933
934
934 df = util.matchdate(date)
935 df = util.matchdate(date)
935 m = matchall(repo)
936 m = matchall(repo)
936 results = {}
937 results = {}
937
938
938 def prep(ctx, fns):
939 def prep(ctx, fns):
939 d = ctx.date()
940 d = ctx.date()
940 if df(d[0]):
941 if df(d[0]):
941 results[ctx.rev()] = d
942 results[ctx.rev()] = d
942
943
943 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
944 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
944 rev = ctx.rev()
945 rev = ctx.rev()
945 if rev in results:
946 if rev in results:
946 ui.status(_("Found revision %s from %s\n") %
947 ui.status(_("Found revision %s from %s\n") %
947 (rev, util.datestr(results[rev])))
948 (rev, util.datestr(results[rev])))
948 return str(rev)
949 return str(rev)
949
950
950 raise util.Abort(_("revision matching date not found"))
951 raise util.Abort(_("revision matching date not found"))
951
952
952 def walkchangerevs(repo, match, opts, prepare):
953 def walkchangerevs(repo, match, opts, prepare):
953 '''Iterate over files and the revs in which they changed.
954 '''Iterate over files and the revs in which they changed.
954
955
955 Callers most commonly need to iterate backwards over the history
956 Callers most commonly need to iterate backwards over the history
956 in which they are interested. Doing so has awful (quadratic-looking)
957 in which they are interested. Doing so has awful (quadratic-looking)
957 performance, so we use iterators in a "windowed" way.
958 performance, so we use iterators in a "windowed" way.
958
959
959 We walk a window of revisions in the desired order. Within the
960 We walk a window of revisions in the desired order. Within the
960 window, we first walk forwards to gather data, then in the desired
961 window, we first walk forwards to gather data, then in the desired
961 order (usually backwards) to display it.
962 order (usually backwards) to display it.
962
963
963 This function returns an iterator yielding contexts. Before
964 This function returns an iterator yielding contexts. Before
964 yielding each context, the iterator will first call the prepare
965 yielding each context, the iterator will first call the prepare
965 function on each context in the window in forward order.'''
966 function on each context in the window in forward order.'''
966
967
967 def increasing_windows(start, end, windowsize=8, sizelimit=512):
968 def increasing_windows(start, end, windowsize=8, sizelimit=512):
968 if start < end:
969 if start < end:
969 while start < end:
970 while start < end:
970 yield start, min(windowsize, end - start)
971 yield start, min(windowsize, end - start)
971 start += windowsize
972 start += windowsize
972 if windowsize < sizelimit:
973 if windowsize < sizelimit:
973 windowsize *= 2
974 windowsize *= 2
974 else:
975 else:
975 while start > end:
976 while start > end:
976 yield start, min(windowsize, start - end - 1)
977 yield start, min(windowsize, start - end - 1)
977 start -= windowsize
978 start -= windowsize
978 if windowsize < sizelimit:
979 if windowsize < sizelimit:
979 windowsize *= 2
980 windowsize *= 2
980
981
981 follow = opts.get('follow') or opts.get('follow_first')
982 follow = opts.get('follow') or opts.get('follow_first')
982
983
983 if not len(repo):
984 if not len(repo):
984 return []
985 return []
985
986
986 if follow:
987 if follow:
987 defrange = '%s:0' % repo['.'].rev()
988 defrange = '%s:0' % repo['.'].rev()
988 else:
989 else:
989 defrange = '-1:0'
990 defrange = '-1:0'
990 revs = revrange(repo, opts['rev'] or [defrange])
991 revs = revrange(repo, opts['rev'] or [defrange])
991 wanted = set()
992 wanted = set()
992 slowpath = match.anypats() or (match.files() and opts.get('removed'))
993 slowpath = match.anypats() or (match.files() and opts.get('removed'))
993 fncache = {}
994 fncache = {}
994 change = util.cachefunc(repo.changectx)
995 change = util.cachefunc(repo.changectx)
995
996
996 if not slowpath and not match.files():
997 if not slowpath and not match.files():
997 # No files, no patterns. Display all revs.
998 # No files, no patterns. Display all revs.
998 wanted = set(revs)
999 wanted = set(revs)
999 copies = []
1000 copies = []
1000
1001
1001 if not slowpath:
1002 if not slowpath:
1002 # Only files, no patterns. Check the history of each file.
1003 # Only files, no patterns. Check the history of each file.
1003 def filerevgen(filelog, node):
1004 def filerevgen(filelog, node):
1004 cl_count = len(repo)
1005 cl_count = len(repo)
1005 if node is None:
1006 if node is None:
1006 last = len(filelog) - 1
1007 last = len(filelog) - 1
1007 else:
1008 else:
1008 last = filelog.rev(node)
1009 last = filelog.rev(node)
1009 for i, window in increasing_windows(last, nullrev):
1010 for i, window in increasing_windows(last, nullrev):
1010 revs = []
1011 revs = []
1011 for j in xrange(i - window, i + 1):
1012 for j in xrange(i - window, i + 1):
1012 n = filelog.node(j)
1013 n = filelog.node(j)
1013 revs.append((filelog.linkrev(j),
1014 revs.append((filelog.linkrev(j),
1014 follow and filelog.renamed(n)))
1015 follow and filelog.renamed(n)))
1015 for rev in reversed(revs):
1016 for rev in reversed(revs):
1016 # only yield rev for which we have the changelog, it can
1017 # only yield rev for which we have the changelog, it can
1017 # happen while doing "hg log" during a pull or commit
1018 # happen while doing "hg log" during a pull or commit
1018 if rev[0] < cl_count:
1019 if rev[0] < cl_count:
1019 yield rev
1020 yield rev
1020 def iterfiles():
1021 def iterfiles():
1021 for filename in match.files():
1022 for filename in match.files():
1022 yield filename, None
1023 yield filename, None
1023 for filename_node in copies:
1024 for filename_node in copies:
1024 yield filename_node
1025 yield filename_node
1025 minrev, maxrev = min(revs), max(revs)
1026 minrev, maxrev = min(revs), max(revs)
1026 for file_, node in iterfiles():
1027 for file_, node in iterfiles():
1027 filelog = repo.file(file_)
1028 filelog = repo.file(file_)
1028 if not len(filelog):
1029 if not len(filelog):
1029 if node is None:
1030 if node is None:
1030 # A zero count may be a directory or deleted file, so
1031 # A zero count may be a directory or deleted file, so
1031 # try to find matching entries on the slow path.
1032 # try to find matching entries on the slow path.
1032 if follow:
1033 if follow:
1033 raise util.Abort(
1034 raise util.Abort(
1034 _('cannot follow nonexistent file: "%s"') % file_)
1035 _('cannot follow nonexistent file: "%s"') % file_)
1035 slowpath = True
1036 slowpath = True
1036 break
1037 break
1037 else:
1038 else:
1038 continue
1039 continue
1039 for rev, copied in filerevgen(filelog, node):
1040 for rev, copied in filerevgen(filelog, node):
1040 if rev <= maxrev:
1041 if rev <= maxrev:
1041 if rev < minrev:
1042 if rev < minrev:
1042 break
1043 break
1043 fncache.setdefault(rev, [])
1044 fncache.setdefault(rev, [])
1044 fncache[rev].append(file_)
1045 fncache[rev].append(file_)
1045 wanted.add(rev)
1046 wanted.add(rev)
1046 if follow and copied:
1047 if follow and copied:
1047 copies.append(copied)
1048 copies.append(copied)
1048 if slowpath:
1049 if slowpath:
1049 if follow:
1050 if follow:
1050 raise util.Abort(_('can only follow copies/renames for explicit '
1051 raise util.Abort(_('can only follow copies/renames for explicit '
1051 'filenames'))
1052 'filenames'))
1052
1053
1053 # The slow path checks files modified in every changeset.
1054 # The slow path checks files modified in every changeset.
1054 def changerevgen():
1055 def changerevgen():
1055 for i, window in increasing_windows(len(repo) - 1, nullrev):
1056 for i, window in increasing_windows(len(repo) - 1, nullrev):
1056 for j in xrange(i - window, i + 1):
1057 for j in xrange(i - window, i + 1):
1057 yield change(j)
1058 yield change(j)
1058
1059
1059 for ctx in changerevgen():
1060 for ctx in changerevgen():
1060 matches = filter(match, ctx.files())
1061 matches = filter(match, ctx.files())
1061 if matches:
1062 if matches:
1062 fncache[ctx.rev()] = matches
1063 fncache[ctx.rev()] = matches
1063 wanted.add(ctx.rev())
1064 wanted.add(ctx.rev())
1064
1065
1065 class followfilter(object):
1066 class followfilter(object):
1066 def __init__(self, onlyfirst=False):
1067 def __init__(self, onlyfirst=False):
1067 self.startrev = nullrev
1068 self.startrev = nullrev
1068 self.roots = set()
1069 self.roots = set()
1069 self.onlyfirst = onlyfirst
1070 self.onlyfirst = onlyfirst
1070
1071
1071 def match(self, rev):
1072 def match(self, rev):
1072 def realparents(rev):
1073 def realparents(rev):
1073 if self.onlyfirst:
1074 if self.onlyfirst:
1074 return repo.changelog.parentrevs(rev)[0:1]
1075 return repo.changelog.parentrevs(rev)[0:1]
1075 else:
1076 else:
1076 return filter(lambda x: x != nullrev,
1077 return filter(lambda x: x != nullrev,
1077 repo.changelog.parentrevs(rev))
1078 repo.changelog.parentrevs(rev))
1078
1079
1079 if self.startrev == nullrev:
1080 if self.startrev == nullrev:
1080 self.startrev = rev
1081 self.startrev = rev
1081 return True
1082 return True
1082
1083
1083 if rev > self.startrev:
1084 if rev > self.startrev:
1084 # forward: all descendants
1085 # forward: all descendants
1085 if not self.roots:
1086 if not self.roots:
1086 self.roots.add(self.startrev)
1087 self.roots.add(self.startrev)
1087 for parent in realparents(rev):
1088 for parent in realparents(rev):
1088 if parent in self.roots:
1089 if parent in self.roots:
1089 self.roots.add(rev)
1090 self.roots.add(rev)
1090 return True
1091 return True
1091 else:
1092 else:
1092 # backwards: all parents
1093 # backwards: all parents
1093 if not self.roots:
1094 if not self.roots:
1094 self.roots.update(realparents(self.startrev))
1095 self.roots.update(realparents(self.startrev))
1095 if rev in self.roots:
1096 if rev in self.roots:
1096 self.roots.remove(rev)
1097 self.roots.remove(rev)
1097 self.roots.update(realparents(rev))
1098 self.roots.update(realparents(rev))
1098 return True
1099 return True
1099
1100
1100 return False
1101 return False
1101
1102
1102 # it might be worthwhile to do this in the iterator if the rev range
1103 # it might be worthwhile to do this in the iterator if the rev range
1103 # is descending and the prune args are all within that range
1104 # is descending and the prune args are all within that range
1104 for rev in opts.get('prune', ()):
1105 for rev in opts.get('prune', ()):
1105 rev = repo.changelog.rev(repo.lookup(rev))
1106 rev = repo.changelog.rev(repo.lookup(rev))
1106 ff = followfilter()
1107 ff = followfilter()
1107 stop = min(revs[0], revs[-1])
1108 stop = min(revs[0], revs[-1])
1108 for x in xrange(rev, stop - 1, -1):
1109 for x in xrange(rev, stop - 1, -1):
1109 if ff.match(x):
1110 if ff.match(x):
1110 wanted.discard(x)
1111 wanted.discard(x)
1111
1112
1112 def iterate():
1113 def iterate():
1113 if follow and not match.files():
1114 if follow and not match.files():
1114 ff = followfilter(onlyfirst=opts.get('follow_first'))
1115 ff = followfilter(onlyfirst=opts.get('follow_first'))
1115 def want(rev):
1116 def want(rev):
1116 return ff.match(rev) and rev in wanted
1117 return ff.match(rev) and rev in wanted
1117 else:
1118 else:
1118 def want(rev):
1119 def want(rev):
1119 return rev in wanted
1120 return rev in wanted
1120
1121
1121 for i, window in increasing_windows(0, len(revs)):
1122 for i, window in increasing_windows(0, len(revs)):
1122 change = util.cachefunc(repo.changectx)
1123 change = util.cachefunc(repo.changectx)
1123 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1124 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1124 for rev in sorted(nrevs):
1125 for rev in sorted(nrevs):
1125 fns = fncache.get(rev)
1126 fns = fncache.get(rev)
1126 ctx = change(rev)
1127 ctx = change(rev)
1127 if not fns:
1128 if not fns:
1128 def fns_generator():
1129 def fns_generator():
1129 for f in ctx.files():
1130 for f in ctx.files():
1130 if match(f):
1131 if match(f):
1131 yield f
1132 yield f
1132 fns = fns_generator()
1133 fns = fns_generator()
1133 prepare(ctx, fns)
1134 prepare(ctx, fns)
1134 for rev in nrevs:
1135 for rev in nrevs:
1135 yield change(rev)
1136 yield change(rev)
1136 return iterate()
1137 return iterate()
1137
1138
1138 def commit(ui, repo, commitfunc, pats, opts):
1139 def commit(ui, repo, commitfunc, pats, opts):
1139 '''commit the specified files or all outstanding changes'''
1140 '''commit the specified files or all outstanding changes'''
1140 date = opts.get('date')
1141 date = opts.get('date')
1141 if date:
1142 if date:
1142 opts['date'] = util.parsedate(date)
1143 opts['date'] = util.parsedate(date)
1143 message = logmessage(opts)
1144 message = logmessage(opts)
1144
1145
1145 # extract addremove carefully -- this function can be called from a command
1146 # extract addremove carefully -- this function can be called from a command
1146 # that doesn't support addremove
1147 # that doesn't support addremove
1147 if opts.get('addremove'):
1148 if opts.get('addremove'):
1148 addremove(repo, pats, opts)
1149 addremove(repo, pats, opts)
1149
1150
1150 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1151 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1151
1152
1152 def commiteditor(repo, ctx, subs):
1153 def commiteditor(repo, ctx, subs):
1153 if ctx.description():
1154 if ctx.description():
1154 return ctx.description()
1155 return ctx.description()
1155 return commitforceeditor(repo, ctx, subs)
1156 return commitforceeditor(repo, ctx, subs)
1156
1157
1157 def commitforceeditor(repo, ctx, subs):
1158 def commitforceeditor(repo, ctx, subs):
1158 edittext = []
1159 edittext = []
1159 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1160 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1160 if ctx.description():
1161 if ctx.description():
1161 edittext.append(ctx.description())
1162 edittext.append(ctx.description())
1162 edittext.append("")
1163 edittext.append("")
1163 edittext.append("") # Empty line between message and comments.
1164 edittext.append("") # Empty line between message and comments.
1164 edittext.append(_("HG: Enter commit message."
1165 edittext.append(_("HG: Enter commit message."
1165 " Lines beginning with 'HG:' are removed."))
1166 " Lines beginning with 'HG:' are removed."))
1166 edittext.append(_("HG: Leave message empty to abort commit."))
1167 edittext.append(_("HG: Leave message empty to abort commit."))
1167 edittext.append("HG: --")
1168 edittext.append("HG: --")
1168 edittext.append(_("HG: user: %s") % ctx.user())
1169 edittext.append(_("HG: user: %s") % ctx.user())
1169 if ctx.p2():
1170 if ctx.p2():
1170 edittext.append(_("HG: branch merge"))
1171 edittext.append(_("HG: branch merge"))
1171 if ctx.branch():
1172 if ctx.branch():
1172 edittext.append(_("HG: branch '%s'")
1173 edittext.append(_("HG: branch '%s'")
1173 % encoding.tolocal(ctx.branch()))
1174 % encoding.tolocal(ctx.branch()))
1174 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1175 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1175 edittext.extend([_("HG: added %s") % f for f in added])
1176 edittext.extend([_("HG: added %s") % f for f in added])
1176 edittext.extend([_("HG: changed %s") % f for f in modified])
1177 edittext.extend([_("HG: changed %s") % f for f in modified])
1177 edittext.extend([_("HG: removed %s") % f for f in removed])
1178 edittext.extend([_("HG: removed %s") % f for f in removed])
1178 if not added and not modified and not removed:
1179 if not added and not modified and not removed:
1179 edittext.append(_("HG: no files changed"))
1180 edittext.append(_("HG: no files changed"))
1180 edittext.append("")
1181 edittext.append("")
1181 # run editor in the repository root
1182 # run editor in the repository root
1182 olddir = os.getcwd()
1183 olddir = os.getcwd()
1183 os.chdir(repo.root)
1184 os.chdir(repo.root)
1184 text = repo.ui.edit("\n".join(edittext), ctx.user())
1185 text = repo.ui.edit("\n".join(edittext), ctx.user())
1185 text = re.sub("(?m)^HG:.*\n", "", text)
1186 text = re.sub("(?m)^HG:.*\n", "", text)
1186 os.chdir(olddir)
1187 os.chdir(olddir)
1187
1188
1188 if not text.strip():
1189 if not text.strip():
1189 raise util.Abort(_("empty commit message"))
1190 raise util.Abort(_("empty commit message"))
1190
1191
1191 return text
1192 return text
General Comments 0
You need to be logged in to leave comments. Login now