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