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