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