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