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