##// END OF EJS Templates
cmdutil.tryimportone: allow importing relative patches into the working dir...
Siddharth Agarwal -
r24259:5ac8ce04 default
parent child Browse files
Show More
@@ -1,2969 +1,2970 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, tempfile
10 import os, sys, errno, re, tempfile
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod
12 import match as matchmod
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 import changelog
14 import changelog
15 import bookmarks
15 import bookmarks
16 import encoding
16 import encoding
17 import lock as lockmod
17 import lock as lockmod
18
18
19 def parsealiases(cmd):
19 def parsealiases(cmd):
20 return cmd.lstrip("^").split("|")
20 return cmd.lstrip("^").split("|")
21
21
22 def findpossible(cmd, table, strict=False):
22 def findpossible(cmd, table, strict=False):
23 """
23 """
24 Return cmd -> (aliases, command table entry)
24 Return cmd -> (aliases, command table entry)
25 for each matching command.
25 for each matching command.
26 Return debug commands (or their aliases) only if no normal command matches.
26 Return debug commands (or their aliases) only if no normal command matches.
27 """
27 """
28 choice = {}
28 choice = {}
29 debugchoice = {}
29 debugchoice = {}
30
30
31 if cmd in table:
31 if cmd in table:
32 # short-circuit exact matches, "log" alias beats "^log|history"
32 # short-circuit exact matches, "log" alias beats "^log|history"
33 keys = [cmd]
33 keys = [cmd]
34 else:
34 else:
35 keys = table.keys()
35 keys = table.keys()
36
36
37 allcmds = []
37 allcmds = []
38 for e in keys:
38 for e in keys:
39 aliases = parsealiases(e)
39 aliases = parsealiases(e)
40 allcmds.extend(aliases)
40 allcmds.extend(aliases)
41 found = None
41 found = None
42 if cmd in aliases:
42 if cmd in aliases:
43 found = cmd
43 found = cmd
44 elif not strict:
44 elif not strict:
45 for a in aliases:
45 for a in aliases:
46 if a.startswith(cmd):
46 if a.startswith(cmd):
47 found = a
47 found = a
48 break
48 break
49 if found is not None:
49 if found is not None:
50 if aliases[0].startswith("debug") or found.startswith("debug"):
50 if aliases[0].startswith("debug") or found.startswith("debug"):
51 debugchoice[found] = (aliases, table[e])
51 debugchoice[found] = (aliases, table[e])
52 else:
52 else:
53 choice[found] = (aliases, table[e])
53 choice[found] = (aliases, table[e])
54
54
55 if not choice and debugchoice:
55 if not choice and debugchoice:
56 choice = debugchoice
56 choice = debugchoice
57
57
58 return choice, allcmds
58 return choice, allcmds
59
59
60 def findcmd(cmd, table, strict=True):
60 def findcmd(cmd, table, strict=True):
61 """Return (aliases, command table entry) for command string."""
61 """Return (aliases, command table entry) for command string."""
62 choice, allcmds = findpossible(cmd, table, strict)
62 choice, allcmds = findpossible(cmd, table, strict)
63
63
64 if cmd in choice:
64 if cmd in choice:
65 return choice[cmd]
65 return choice[cmd]
66
66
67 if len(choice) > 1:
67 if len(choice) > 1:
68 clist = choice.keys()
68 clist = choice.keys()
69 clist.sort()
69 clist.sort()
70 raise error.AmbiguousCommand(cmd, clist)
70 raise error.AmbiguousCommand(cmd, clist)
71
71
72 if choice:
72 if choice:
73 return choice.values()[0]
73 return choice.values()[0]
74
74
75 raise error.UnknownCommand(cmd, allcmds)
75 raise error.UnknownCommand(cmd, allcmds)
76
76
77 def findrepo(p):
77 def findrepo(p):
78 while not os.path.isdir(os.path.join(p, ".hg")):
78 while not os.path.isdir(os.path.join(p, ".hg")):
79 oldp, p = p, os.path.dirname(p)
79 oldp, p = p, os.path.dirname(p)
80 if p == oldp:
80 if p == oldp:
81 return None
81 return None
82
82
83 return p
83 return p
84
84
85 def bailifchanged(repo):
85 def bailifchanged(repo):
86 if repo.dirstate.p2() != nullid:
86 if repo.dirstate.p2() != nullid:
87 raise util.Abort(_('outstanding uncommitted merge'))
87 raise util.Abort(_('outstanding uncommitted merge'))
88 modified, added, removed, deleted = repo.status()[:4]
88 modified, added, removed, deleted = repo.status()[:4]
89 if modified or added or removed or deleted:
89 if modified or added or removed or deleted:
90 raise util.Abort(_('uncommitted changes'))
90 raise util.Abort(_('uncommitted changes'))
91 ctx = repo[None]
91 ctx = repo[None]
92 for s in sorted(ctx.substate):
92 for s in sorted(ctx.substate):
93 if ctx.sub(s).dirty():
93 if ctx.sub(s).dirty():
94 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
94 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
95
95
96 def logmessage(ui, opts):
96 def logmessage(ui, opts):
97 """ get the log message according to -m and -l option """
97 """ get the log message according to -m and -l option """
98 message = opts.get('message')
98 message = opts.get('message')
99 logfile = opts.get('logfile')
99 logfile = opts.get('logfile')
100
100
101 if message and logfile:
101 if message and logfile:
102 raise util.Abort(_('options --message and --logfile are mutually '
102 raise util.Abort(_('options --message and --logfile are mutually '
103 'exclusive'))
103 'exclusive'))
104 if not message and logfile:
104 if not message and logfile:
105 try:
105 try:
106 if logfile == '-':
106 if logfile == '-':
107 message = ui.fin.read()
107 message = ui.fin.read()
108 else:
108 else:
109 message = '\n'.join(util.readfile(logfile).splitlines())
109 message = '\n'.join(util.readfile(logfile).splitlines())
110 except IOError, inst:
110 except IOError, inst:
111 raise util.Abort(_("can't read commit message '%s': %s") %
111 raise util.Abort(_("can't read commit message '%s': %s") %
112 (logfile, inst.strerror))
112 (logfile, inst.strerror))
113 return message
113 return message
114
114
115 def mergeeditform(ctxorbool, baseformname):
115 def mergeeditform(ctxorbool, baseformname):
116 """return appropriate editform name (referencing a committemplate)
116 """return appropriate editform name (referencing a committemplate)
117
117
118 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
118 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
119 merging is committed.
119 merging is committed.
120
120
121 This returns baseformname with '.merge' appended if it is a merge,
121 This returns baseformname with '.merge' appended if it is a merge,
122 otherwise '.normal' is appended.
122 otherwise '.normal' is appended.
123 """
123 """
124 if isinstance(ctxorbool, bool):
124 if isinstance(ctxorbool, bool):
125 if ctxorbool:
125 if ctxorbool:
126 return baseformname + ".merge"
126 return baseformname + ".merge"
127 elif 1 < len(ctxorbool.parents()):
127 elif 1 < len(ctxorbool.parents()):
128 return baseformname + ".merge"
128 return baseformname + ".merge"
129
129
130 return baseformname + ".normal"
130 return baseformname + ".normal"
131
131
132 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
132 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
133 editform='', **opts):
133 editform='', **opts):
134 """get appropriate commit message editor according to '--edit' option
134 """get appropriate commit message editor according to '--edit' option
135
135
136 'finishdesc' is a function to be called with edited commit message
136 'finishdesc' is a function to be called with edited commit message
137 (= 'description' of the new changeset) just after editing, but
137 (= 'description' of the new changeset) just after editing, but
138 before checking empty-ness. It should return actual text to be
138 before checking empty-ness. It should return actual text to be
139 stored into history. This allows to change description before
139 stored into history. This allows to change description before
140 storing.
140 storing.
141
141
142 'extramsg' is a extra message to be shown in the editor instead of
142 'extramsg' is a extra message to be shown in the editor instead of
143 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
143 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
144 is automatically added.
144 is automatically added.
145
145
146 'editform' is a dot-separated list of names, to distinguish
146 'editform' is a dot-separated list of names, to distinguish
147 the purpose of commit text editing.
147 the purpose of commit text editing.
148
148
149 'getcommiteditor' returns 'commitforceeditor' regardless of
149 'getcommiteditor' returns 'commitforceeditor' regardless of
150 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
150 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
151 they are specific for usage in MQ.
151 they are specific for usage in MQ.
152 """
152 """
153 if edit or finishdesc or extramsg:
153 if edit or finishdesc or extramsg:
154 return lambda r, c, s: commitforceeditor(r, c, s,
154 return lambda r, c, s: commitforceeditor(r, c, s,
155 finishdesc=finishdesc,
155 finishdesc=finishdesc,
156 extramsg=extramsg,
156 extramsg=extramsg,
157 editform=editform)
157 editform=editform)
158 elif editform:
158 elif editform:
159 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
159 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
160 else:
160 else:
161 return commiteditor
161 return commiteditor
162
162
163 def loglimit(opts):
163 def loglimit(opts):
164 """get the log limit according to option -l/--limit"""
164 """get the log limit according to option -l/--limit"""
165 limit = opts.get('limit')
165 limit = opts.get('limit')
166 if limit:
166 if limit:
167 try:
167 try:
168 limit = int(limit)
168 limit = int(limit)
169 except ValueError:
169 except ValueError:
170 raise util.Abort(_('limit must be a positive integer'))
170 raise util.Abort(_('limit must be a positive integer'))
171 if limit <= 0:
171 if limit <= 0:
172 raise util.Abort(_('limit must be positive'))
172 raise util.Abort(_('limit must be positive'))
173 else:
173 else:
174 limit = None
174 limit = None
175 return limit
175 return limit
176
176
177 def makefilename(repo, pat, node, desc=None,
177 def makefilename(repo, pat, node, desc=None,
178 total=None, seqno=None, revwidth=None, pathname=None):
178 total=None, seqno=None, revwidth=None, pathname=None):
179 node_expander = {
179 node_expander = {
180 'H': lambda: hex(node),
180 'H': lambda: hex(node),
181 'R': lambda: str(repo.changelog.rev(node)),
181 'R': lambda: str(repo.changelog.rev(node)),
182 'h': lambda: short(node),
182 'h': lambda: short(node),
183 'm': lambda: re.sub('[^\w]', '_', str(desc))
183 'm': lambda: re.sub('[^\w]', '_', str(desc))
184 }
184 }
185 expander = {
185 expander = {
186 '%': lambda: '%',
186 '%': lambda: '%',
187 'b': lambda: os.path.basename(repo.root),
187 'b': lambda: os.path.basename(repo.root),
188 }
188 }
189
189
190 try:
190 try:
191 if node:
191 if node:
192 expander.update(node_expander)
192 expander.update(node_expander)
193 if node:
193 if node:
194 expander['r'] = (lambda:
194 expander['r'] = (lambda:
195 str(repo.changelog.rev(node)).zfill(revwidth or 0))
195 str(repo.changelog.rev(node)).zfill(revwidth or 0))
196 if total is not None:
196 if total is not None:
197 expander['N'] = lambda: str(total)
197 expander['N'] = lambda: str(total)
198 if seqno is not None:
198 if seqno is not None:
199 expander['n'] = lambda: str(seqno)
199 expander['n'] = lambda: str(seqno)
200 if total is not None and seqno is not None:
200 if total is not None and seqno is not None:
201 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
201 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
202 if pathname is not None:
202 if pathname is not None:
203 expander['s'] = lambda: os.path.basename(pathname)
203 expander['s'] = lambda: os.path.basename(pathname)
204 expander['d'] = lambda: os.path.dirname(pathname) or '.'
204 expander['d'] = lambda: os.path.dirname(pathname) or '.'
205 expander['p'] = lambda: pathname
205 expander['p'] = lambda: pathname
206
206
207 newname = []
207 newname = []
208 patlen = len(pat)
208 patlen = len(pat)
209 i = 0
209 i = 0
210 while i < patlen:
210 while i < patlen:
211 c = pat[i]
211 c = pat[i]
212 if c == '%':
212 if c == '%':
213 i += 1
213 i += 1
214 c = pat[i]
214 c = pat[i]
215 c = expander[c]()
215 c = expander[c]()
216 newname.append(c)
216 newname.append(c)
217 i += 1
217 i += 1
218 return ''.join(newname)
218 return ''.join(newname)
219 except KeyError, inst:
219 except KeyError, inst:
220 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
220 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
221 inst.args[0])
221 inst.args[0])
222
222
223 def makefileobj(repo, pat, node=None, desc=None, total=None,
223 def makefileobj(repo, pat, node=None, desc=None, total=None,
224 seqno=None, revwidth=None, mode='wb', modemap=None,
224 seqno=None, revwidth=None, mode='wb', modemap=None,
225 pathname=None):
225 pathname=None):
226
226
227 writable = mode not in ('r', 'rb')
227 writable = mode not in ('r', 'rb')
228
228
229 if not pat or pat == '-':
229 if not pat or pat == '-':
230 fp = writable and repo.ui.fout or repo.ui.fin
230 fp = writable and repo.ui.fout or repo.ui.fin
231 if util.safehasattr(fp, 'fileno'):
231 if util.safehasattr(fp, 'fileno'):
232 return os.fdopen(os.dup(fp.fileno()), mode)
232 return os.fdopen(os.dup(fp.fileno()), mode)
233 else:
233 else:
234 # if this fp can't be duped properly, return
234 # if this fp can't be duped properly, return
235 # a dummy object that can be closed
235 # a dummy object that can be closed
236 class wrappedfileobj(object):
236 class wrappedfileobj(object):
237 noop = lambda x: None
237 noop = lambda x: None
238 def __init__(self, f):
238 def __init__(self, f):
239 self.f = f
239 self.f = f
240 def __getattr__(self, attr):
240 def __getattr__(self, attr):
241 if attr == 'close':
241 if attr == 'close':
242 return self.noop
242 return self.noop
243 else:
243 else:
244 return getattr(self.f, attr)
244 return getattr(self.f, attr)
245
245
246 return wrappedfileobj(fp)
246 return wrappedfileobj(fp)
247 if util.safehasattr(pat, 'write') and writable:
247 if util.safehasattr(pat, 'write') and writable:
248 return pat
248 return pat
249 if util.safehasattr(pat, 'read') and 'r' in mode:
249 if util.safehasattr(pat, 'read') and 'r' in mode:
250 return pat
250 return pat
251 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
251 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
252 if modemap is not None:
252 if modemap is not None:
253 mode = modemap.get(fn, mode)
253 mode = modemap.get(fn, mode)
254 if mode == 'wb':
254 if mode == 'wb':
255 modemap[fn] = 'ab'
255 modemap[fn] = 'ab'
256 return open(fn, mode)
256 return open(fn, mode)
257
257
258 def openrevlog(repo, cmd, file_, opts):
258 def openrevlog(repo, cmd, file_, opts):
259 """opens the changelog, manifest, a filelog or a given revlog"""
259 """opens the changelog, manifest, a filelog or a given revlog"""
260 cl = opts['changelog']
260 cl = opts['changelog']
261 mf = opts['manifest']
261 mf = opts['manifest']
262 msg = None
262 msg = None
263 if cl and mf:
263 if cl and mf:
264 msg = _('cannot specify --changelog and --manifest at the same time')
264 msg = _('cannot specify --changelog and --manifest at the same time')
265 elif cl or mf:
265 elif cl or mf:
266 if file_:
266 if file_:
267 msg = _('cannot specify filename with --changelog or --manifest')
267 msg = _('cannot specify filename with --changelog or --manifest')
268 elif not repo:
268 elif not repo:
269 msg = _('cannot specify --changelog or --manifest '
269 msg = _('cannot specify --changelog or --manifest '
270 'without a repository')
270 'without a repository')
271 if msg:
271 if msg:
272 raise util.Abort(msg)
272 raise util.Abort(msg)
273
273
274 r = None
274 r = None
275 if repo:
275 if repo:
276 if cl:
276 if cl:
277 r = repo.unfiltered().changelog
277 r = repo.unfiltered().changelog
278 elif mf:
278 elif mf:
279 r = repo.manifest
279 r = repo.manifest
280 elif file_:
280 elif file_:
281 filelog = repo.file(file_)
281 filelog = repo.file(file_)
282 if len(filelog):
282 if len(filelog):
283 r = filelog
283 r = filelog
284 if not r:
284 if not r:
285 if not file_:
285 if not file_:
286 raise error.CommandError(cmd, _('invalid arguments'))
286 raise error.CommandError(cmd, _('invalid arguments'))
287 if not os.path.isfile(file_):
287 if not os.path.isfile(file_):
288 raise util.Abort(_("revlog '%s' not found") % file_)
288 raise util.Abort(_("revlog '%s' not found") % file_)
289 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
289 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
290 file_[:-2] + ".i")
290 file_[:-2] + ".i")
291 return r
291 return r
292
292
293 def copy(ui, repo, pats, opts, rename=False):
293 def copy(ui, repo, pats, opts, rename=False):
294 # called with the repo lock held
294 # called with the repo lock held
295 #
295 #
296 # hgsep => pathname that uses "/" to separate directories
296 # hgsep => pathname that uses "/" to separate directories
297 # ossep => pathname that uses os.sep to separate directories
297 # ossep => pathname that uses os.sep to separate directories
298 cwd = repo.getcwd()
298 cwd = repo.getcwd()
299 targets = {}
299 targets = {}
300 after = opts.get("after")
300 after = opts.get("after")
301 dryrun = opts.get("dry_run")
301 dryrun = opts.get("dry_run")
302 wctx = repo[None]
302 wctx = repo[None]
303
303
304 def walkpat(pat):
304 def walkpat(pat):
305 srcs = []
305 srcs = []
306 badstates = after and '?' or '?r'
306 badstates = after and '?' or '?r'
307 m = scmutil.match(repo[None], [pat], opts, globbed=True)
307 m = scmutil.match(repo[None], [pat], opts, globbed=True)
308 for abs in repo.walk(m):
308 for abs in repo.walk(m):
309 state = repo.dirstate[abs]
309 state = repo.dirstate[abs]
310 rel = m.rel(abs)
310 rel = m.rel(abs)
311 exact = m.exact(abs)
311 exact = m.exact(abs)
312 if state in badstates:
312 if state in badstates:
313 if exact and state == '?':
313 if exact and state == '?':
314 ui.warn(_('%s: not copying - file is not managed\n') % rel)
314 ui.warn(_('%s: not copying - file is not managed\n') % rel)
315 if exact and state == 'r':
315 if exact and state == 'r':
316 ui.warn(_('%s: not copying - file has been marked for'
316 ui.warn(_('%s: not copying - file has been marked for'
317 ' remove\n') % rel)
317 ' remove\n') % rel)
318 continue
318 continue
319 # abs: hgsep
319 # abs: hgsep
320 # rel: ossep
320 # rel: ossep
321 srcs.append((abs, rel, exact))
321 srcs.append((abs, rel, exact))
322 return srcs
322 return srcs
323
323
324 # abssrc: hgsep
324 # abssrc: hgsep
325 # relsrc: ossep
325 # relsrc: ossep
326 # otarget: ossep
326 # otarget: ossep
327 def copyfile(abssrc, relsrc, otarget, exact):
327 def copyfile(abssrc, relsrc, otarget, exact):
328 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
328 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
329 if '/' in abstarget:
329 if '/' in abstarget:
330 # We cannot normalize abstarget itself, this would prevent
330 # We cannot normalize abstarget itself, this would prevent
331 # case only renames, like a => A.
331 # case only renames, like a => A.
332 abspath, absname = abstarget.rsplit('/', 1)
332 abspath, absname = abstarget.rsplit('/', 1)
333 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
333 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
334 reltarget = repo.pathto(abstarget, cwd)
334 reltarget = repo.pathto(abstarget, cwd)
335 target = repo.wjoin(abstarget)
335 target = repo.wjoin(abstarget)
336 src = repo.wjoin(abssrc)
336 src = repo.wjoin(abssrc)
337 state = repo.dirstate[abstarget]
337 state = repo.dirstate[abstarget]
338
338
339 scmutil.checkportable(ui, abstarget)
339 scmutil.checkportable(ui, abstarget)
340
340
341 # check for collisions
341 # check for collisions
342 prevsrc = targets.get(abstarget)
342 prevsrc = targets.get(abstarget)
343 if prevsrc is not None:
343 if prevsrc is not None:
344 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
344 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
345 (reltarget, repo.pathto(abssrc, cwd),
345 (reltarget, repo.pathto(abssrc, cwd),
346 repo.pathto(prevsrc, cwd)))
346 repo.pathto(prevsrc, cwd)))
347 return
347 return
348
348
349 # check for overwrites
349 # check for overwrites
350 exists = os.path.lexists(target)
350 exists = os.path.lexists(target)
351 samefile = False
351 samefile = False
352 if exists and abssrc != abstarget:
352 if exists and abssrc != abstarget:
353 if (repo.dirstate.normalize(abssrc) ==
353 if (repo.dirstate.normalize(abssrc) ==
354 repo.dirstate.normalize(abstarget)):
354 repo.dirstate.normalize(abstarget)):
355 if not rename:
355 if not rename:
356 ui.warn(_("%s: can't copy - same file\n") % reltarget)
356 ui.warn(_("%s: can't copy - same file\n") % reltarget)
357 return
357 return
358 exists = False
358 exists = False
359 samefile = True
359 samefile = True
360
360
361 if not after and exists or after and state in 'mn':
361 if not after and exists or after and state in 'mn':
362 if not opts['force']:
362 if not opts['force']:
363 ui.warn(_('%s: not overwriting - file exists\n') %
363 ui.warn(_('%s: not overwriting - file exists\n') %
364 reltarget)
364 reltarget)
365 return
365 return
366
366
367 if after:
367 if after:
368 if not exists:
368 if not exists:
369 if rename:
369 if rename:
370 ui.warn(_('%s: not recording move - %s does not exist\n') %
370 ui.warn(_('%s: not recording move - %s does not exist\n') %
371 (relsrc, reltarget))
371 (relsrc, reltarget))
372 else:
372 else:
373 ui.warn(_('%s: not recording copy - %s does not exist\n') %
373 ui.warn(_('%s: not recording copy - %s does not exist\n') %
374 (relsrc, reltarget))
374 (relsrc, reltarget))
375 return
375 return
376 elif not dryrun:
376 elif not dryrun:
377 try:
377 try:
378 if exists:
378 if exists:
379 os.unlink(target)
379 os.unlink(target)
380 targetdir = os.path.dirname(target) or '.'
380 targetdir = os.path.dirname(target) or '.'
381 if not os.path.isdir(targetdir):
381 if not os.path.isdir(targetdir):
382 os.makedirs(targetdir)
382 os.makedirs(targetdir)
383 if samefile:
383 if samefile:
384 tmp = target + "~hgrename"
384 tmp = target + "~hgrename"
385 os.rename(src, tmp)
385 os.rename(src, tmp)
386 os.rename(tmp, target)
386 os.rename(tmp, target)
387 else:
387 else:
388 util.copyfile(src, target)
388 util.copyfile(src, target)
389 srcexists = True
389 srcexists = True
390 except IOError, inst:
390 except IOError, inst:
391 if inst.errno == errno.ENOENT:
391 if inst.errno == errno.ENOENT:
392 ui.warn(_('%s: deleted in working copy\n') % relsrc)
392 ui.warn(_('%s: deleted in working copy\n') % relsrc)
393 srcexists = False
393 srcexists = False
394 else:
394 else:
395 ui.warn(_('%s: cannot copy - %s\n') %
395 ui.warn(_('%s: cannot copy - %s\n') %
396 (relsrc, inst.strerror))
396 (relsrc, inst.strerror))
397 return True # report a failure
397 return True # report a failure
398
398
399 if ui.verbose or not exact:
399 if ui.verbose or not exact:
400 if rename:
400 if rename:
401 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
401 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
402 else:
402 else:
403 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
403 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
404
404
405 targets[abstarget] = abssrc
405 targets[abstarget] = abssrc
406
406
407 # fix up dirstate
407 # fix up dirstate
408 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
408 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
409 dryrun=dryrun, cwd=cwd)
409 dryrun=dryrun, cwd=cwd)
410 if rename and not dryrun:
410 if rename and not dryrun:
411 if not after and srcexists and not samefile:
411 if not after and srcexists and not samefile:
412 util.unlinkpath(repo.wjoin(abssrc))
412 util.unlinkpath(repo.wjoin(abssrc))
413 wctx.forget([abssrc])
413 wctx.forget([abssrc])
414
414
415 # pat: ossep
415 # pat: ossep
416 # dest ossep
416 # dest ossep
417 # srcs: list of (hgsep, hgsep, ossep, bool)
417 # srcs: list of (hgsep, hgsep, ossep, bool)
418 # return: function that takes hgsep and returns ossep
418 # return: function that takes hgsep and returns ossep
419 def targetpathfn(pat, dest, srcs):
419 def targetpathfn(pat, dest, srcs):
420 if os.path.isdir(pat):
420 if os.path.isdir(pat):
421 abspfx = pathutil.canonpath(repo.root, cwd, pat)
421 abspfx = pathutil.canonpath(repo.root, cwd, pat)
422 abspfx = util.localpath(abspfx)
422 abspfx = util.localpath(abspfx)
423 if destdirexists:
423 if destdirexists:
424 striplen = len(os.path.split(abspfx)[0])
424 striplen = len(os.path.split(abspfx)[0])
425 else:
425 else:
426 striplen = len(abspfx)
426 striplen = len(abspfx)
427 if striplen:
427 if striplen:
428 striplen += len(os.sep)
428 striplen += len(os.sep)
429 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
429 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
430 elif destdirexists:
430 elif destdirexists:
431 res = lambda p: os.path.join(dest,
431 res = lambda p: os.path.join(dest,
432 os.path.basename(util.localpath(p)))
432 os.path.basename(util.localpath(p)))
433 else:
433 else:
434 res = lambda p: dest
434 res = lambda p: dest
435 return res
435 return res
436
436
437 # pat: ossep
437 # pat: ossep
438 # dest ossep
438 # dest ossep
439 # srcs: list of (hgsep, hgsep, ossep, bool)
439 # srcs: list of (hgsep, hgsep, ossep, bool)
440 # return: function that takes hgsep and returns ossep
440 # return: function that takes hgsep and returns ossep
441 def targetpathafterfn(pat, dest, srcs):
441 def targetpathafterfn(pat, dest, srcs):
442 if matchmod.patkind(pat):
442 if matchmod.patkind(pat):
443 # a mercurial pattern
443 # a mercurial pattern
444 res = lambda p: os.path.join(dest,
444 res = lambda p: os.path.join(dest,
445 os.path.basename(util.localpath(p)))
445 os.path.basename(util.localpath(p)))
446 else:
446 else:
447 abspfx = pathutil.canonpath(repo.root, cwd, pat)
447 abspfx = pathutil.canonpath(repo.root, cwd, pat)
448 if len(abspfx) < len(srcs[0][0]):
448 if len(abspfx) < len(srcs[0][0]):
449 # A directory. Either the target path contains the last
449 # A directory. Either the target path contains the last
450 # component of the source path or it does not.
450 # component of the source path or it does not.
451 def evalpath(striplen):
451 def evalpath(striplen):
452 score = 0
452 score = 0
453 for s in srcs:
453 for s in srcs:
454 t = os.path.join(dest, util.localpath(s[0])[striplen:])
454 t = os.path.join(dest, util.localpath(s[0])[striplen:])
455 if os.path.lexists(t):
455 if os.path.lexists(t):
456 score += 1
456 score += 1
457 return score
457 return score
458
458
459 abspfx = util.localpath(abspfx)
459 abspfx = util.localpath(abspfx)
460 striplen = len(abspfx)
460 striplen = len(abspfx)
461 if striplen:
461 if striplen:
462 striplen += len(os.sep)
462 striplen += len(os.sep)
463 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
463 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
464 score = evalpath(striplen)
464 score = evalpath(striplen)
465 striplen1 = len(os.path.split(abspfx)[0])
465 striplen1 = len(os.path.split(abspfx)[0])
466 if striplen1:
466 if striplen1:
467 striplen1 += len(os.sep)
467 striplen1 += len(os.sep)
468 if evalpath(striplen1) > score:
468 if evalpath(striplen1) > score:
469 striplen = striplen1
469 striplen = striplen1
470 res = lambda p: os.path.join(dest,
470 res = lambda p: os.path.join(dest,
471 util.localpath(p)[striplen:])
471 util.localpath(p)[striplen:])
472 else:
472 else:
473 # a file
473 # a file
474 if destdirexists:
474 if destdirexists:
475 res = lambda p: os.path.join(dest,
475 res = lambda p: os.path.join(dest,
476 os.path.basename(util.localpath(p)))
476 os.path.basename(util.localpath(p)))
477 else:
477 else:
478 res = lambda p: dest
478 res = lambda p: dest
479 return res
479 return res
480
480
481
481
482 pats = scmutil.expandpats(pats)
482 pats = scmutil.expandpats(pats)
483 if not pats:
483 if not pats:
484 raise util.Abort(_('no source or destination specified'))
484 raise util.Abort(_('no source or destination specified'))
485 if len(pats) == 1:
485 if len(pats) == 1:
486 raise util.Abort(_('no destination specified'))
486 raise util.Abort(_('no destination specified'))
487 dest = pats.pop()
487 dest = pats.pop()
488 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
488 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
489 if not destdirexists:
489 if not destdirexists:
490 if len(pats) > 1 or matchmod.patkind(pats[0]):
490 if len(pats) > 1 or matchmod.patkind(pats[0]):
491 raise util.Abort(_('with multiple sources, destination must be an '
491 raise util.Abort(_('with multiple sources, destination must be an '
492 'existing directory'))
492 'existing directory'))
493 if util.endswithsep(dest):
493 if util.endswithsep(dest):
494 raise util.Abort(_('destination %s is not a directory') % dest)
494 raise util.Abort(_('destination %s is not a directory') % dest)
495
495
496 tfn = targetpathfn
496 tfn = targetpathfn
497 if after:
497 if after:
498 tfn = targetpathafterfn
498 tfn = targetpathafterfn
499 copylist = []
499 copylist = []
500 for pat in pats:
500 for pat in pats:
501 srcs = walkpat(pat)
501 srcs = walkpat(pat)
502 if not srcs:
502 if not srcs:
503 continue
503 continue
504 copylist.append((tfn(pat, dest, srcs), srcs))
504 copylist.append((tfn(pat, dest, srcs), srcs))
505 if not copylist:
505 if not copylist:
506 raise util.Abort(_('no files to copy'))
506 raise util.Abort(_('no files to copy'))
507
507
508 errors = 0
508 errors = 0
509 for targetpath, srcs in copylist:
509 for targetpath, srcs in copylist:
510 for abssrc, relsrc, exact in srcs:
510 for abssrc, relsrc, exact in srcs:
511 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
511 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
512 errors += 1
512 errors += 1
513
513
514 if errors:
514 if errors:
515 ui.warn(_('(consider using --after)\n'))
515 ui.warn(_('(consider using --after)\n'))
516
516
517 return errors != 0
517 return errors != 0
518
518
519 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
519 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
520 runargs=None, appendpid=False):
520 runargs=None, appendpid=False):
521 '''Run a command as a service.'''
521 '''Run a command as a service.'''
522
522
523 def writepid(pid):
523 def writepid(pid):
524 if opts['pid_file']:
524 if opts['pid_file']:
525 mode = appendpid and 'a' or 'w'
525 mode = appendpid and 'a' or 'w'
526 fp = open(opts['pid_file'], mode)
526 fp = open(opts['pid_file'], mode)
527 fp.write(str(pid) + '\n')
527 fp.write(str(pid) + '\n')
528 fp.close()
528 fp.close()
529
529
530 if opts['daemon'] and not opts['daemon_pipefds']:
530 if opts['daemon'] and not opts['daemon_pipefds']:
531 # Signal child process startup with file removal
531 # Signal child process startup with file removal
532 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
532 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
533 os.close(lockfd)
533 os.close(lockfd)
534 try:
534 try:
535 if not runargs:
535 if not runargs:
536 runargs = util.hgcmd() + sys.argv[1:]
536 runargs = util.hgcmd() + sys.argv[1:]
537 runargs.append('--daemon-pipefds=%s' % lockpath)
537 runargs.append('--daemon-pipefds=%s' % lockpath)
538 # Don't pass --cwd to the child process, because we've already
538 # Don't pass --cwd to the child process, because we've already
539 # changed directory.
539 # changed directory.
540 for i in xrange(1, len(runargs)):
540 for i in xrange(1, len(runargs)):
541 if runargs[i].startswith('--cwd='):
541 if runargs[i].startswith('--cwd='):
542 del runargs[i]
542 del runargs[i]
543 break
543 break
544 elif runargs[i].startswith('--cwd'):
544 elif runargs[i].startswith('--cwd'):
545 del runargs[i:i + 2]
545 del runargs[i:i + 2]
546 break
546 break
547 def condfn():
547 def condfn():
548 return not os.path.exists(lockpath)
548 return not os.path.exists(lockpath)
549 pid = util.rundetached(runargs, condfn)
549 pid = util.rundetached(runargs, condfn)
550 if pid < 0:
550 if pid < 0:
551 raise util.Abort(_('child process failed to start'))
551 raise util.Abort(_('child process failed to start'))
552 writepid(pid)
552 writepid(pid)
553 finally:
553 finally:
554 try:
554 try:
555 os.unlink(lockpath)
555 os.unlink(lockpath)
556 except OSError, e:
556 except OSError, e:
557 if e.errno != errno.ENOENT:
557 if e.errno != errno.ENOENT:
558 raise
558 raise
559 if parentfn:
559 if parentfn:
560 return parentfn(pid)
560 return parentfn(pid)
561 else:
561 else:
562 return
562 return
563
563
564 if initfn:
564 if initfn:
565 initfn()
565 initfn()
566
566
567 if not opts['daemon']:
567 if not opts['daemon']:
568 writepid(os.getpid())
568 writepid(os.getpid())
569
569
570 if opts['daemon_pipefds']:
570 if opts['daemon_pipefds']:
571 lockpath = opts['daemon_pipefds']
571 lockpath = opts['daemon_pipefds']
572 try:
572 try:
573 os.setsid()
573 os.setsid()
574 except AttributeError:
574 except AttributeError:
575 pass
575 pass
576 os.unlink(lockpath)
576 os.unlink(lockpath)
577 util.hidewindow()
577 util.hidewindow()
578 sys.stdout.flush()
578 sys.stdout.flush()
579 sys.stderr.flush()
579 sys.stderr.flush()
580
580
581 nullfd = os.open(os.devnull, os.O_RDWR)
581 nullfd = os.open(os.devnull, os.O_RDWR)
582 logfilefd = nullfd
582 logfilefd = nullfd
583 if logfile:
583 if logfile:
584 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
584 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
585 os.dup2(nullfd, 0)
585 os.dup2(nullfd, 0)
586 os.dup2(logfilefd, 1)
586 os.dup2(logfilefd, 1)
587 os.dup2(logfilefd, 2)
587 os.dup2(logfilefd, 2)
588 if nullfd not in (0, 1, 2):
588 if nullfd not in (0, 1, 2):
589 os.close(nullfd)
589 os.close(nullfd)
590 if logfile and logfilefd not in (0, 1, 2):
590 if logfile and logfilefd not in (0, 1, 2):
591 os.close(logfilefd)
591 os.close(logfilefd)
592
592
593 if runfn:
593 if runfn:
594 return runfn()
594 return runfn()
595
595
596 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
596 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
597 """Utility function used by commands.import to import a single patch
597 """Utility function used by commands.import to import a single patch
598
598
599 This function is explicitly defined here to help the evolve extension to
599 This function is explicitly defined here to help the evolve extension to
600 wrap this part of the import logic.
600 wrap this part of the import logic.
601
601
602 The API is currently a bit ugly because it a simple code translation from
602 The API is currently a bit ugly because it a simple code translation from
603 the import command. Feel free to make it better.
603 the import command. Feel free to make it better.
604
604
605 :hunk: a patch (as a binary string)
605 :hunk: a patch (as a binary string)
606 :parents: nodes that will be parent of the created commit
606 :parents: nodes that will be parent of the created commit
607 :opts: the full dict of option passed to the import command
607 :opts: the full dict of option passed to the import command
608 :msgs: list to save commit message to.
608 :msgs: list to save commit message to.
609 (used in case we need to save it when failing)
609 (used in case we need to save it when failing)
610 :updatefunc: a function that update a repo to a given node
610 :updatefunc: a function that update a repo to a given node
611 updatefunc(<repo>, <node>)
611 updatefunc(<repo>, <node>)
612 """
612 """
613 tmpname, message, user, date, branch, nodeid, p1, p2 = \
613 tmpname, message, user, date, branch, nodeid, p1, p2 = \
614 patch.extract(ui, hunk)
614 patch.extract(ui, hunk)
615
615
616 update = not opts.get('bypass')
616 update = not opts.get('bypass')
617 strip = opts["strip"]
617 strip = opts["strip"]
618 prefix = opts["prefix"]
618 sim = float(opts.get('similarity') or 0)
619 sim = float(opts.get('similarity') or 0)
619 if not tmpname:
620 if not tmpname:
620 return (None, None, False)
621 return (None, None, False)
621 msg = _('applied to working directory')
622 msg = _('applied to working directory')
622
623
623 rejects = False
624 rejects = False
624
625
625 try:
626 try:
626 cmdline_message = logmessage(ui, opts)
627 cmdline_message = logmessage(ui, opts)
627 if cmdline_message:
628 if cmdline_message:
628 # pickup the cmdline msg
629 # pickup the cmdline msg
629 message = cmdline_message
630 message = cmdline_message
630 elif message:
631 elif message:
631 # pickup the patch msg
632 # pickup the patch msg
632 message = message.strip()
633 message = message.strip()
633 else:
634 else:
634 # launch the editor
635 # launch the editor
635 message = None
636 message = None
636 ui.debug('message:\n%s\n' % message)
637 ui.debug('message:\n%s\n' % message)
637
638
638 if len(parents) == 1:
639 if len(parents) == 1:
639 parents.append(repo[nullid])
640 parents.append(repo[nullid])
640 if opts.get('exact'):
641 if opts.get('exact'):
641 if not nodeid or not p1:
642 if not nodeid or not p1:
642 raise util.Abort(_('not a Mercurial patch'))
643 raise util.Abort(_('not a Mercurial patch'))
643 p1 = repo[p1]
644 p1 = repo[p1]
644 p2 = repo[p2 or nullid]
645 p2 = repo[p2 or nullid]
645 elif p2:
646 elif p2:
646 try:
647 try:
647 p1 = repo[p1]
648 p1 = repo[p1]
648 p2 = repo[p2]
649 p2 = repo[p2]
649 # Without any options, consider p2 only if the
650 # Without any options, consider p2 only if the
650 # patch is being applied on top of the recorded
651 # patch is being applied on top of the recorded
651 # first parent.
652 # first parent.
652 if p1 != parents[0]:
653 if p1 != parents[0]:
653 p1 = parents[0]
654 p1 = parents[0]
654 p2 = repo[nullid]
655 p2 = repo[nullid]
655 except error.RepoError:
656 except error.RepoError:
656 p1, p2 = parents
657 p1, p2 = parents
657 if p2.node() == nullid:
658 if p2.node() == nullid:
658 ui.warn(_("warning: import the patch as a normal revision\n"
659 ui.warn(_("warning: import the patch as a normal revision\n"
659 "(use --exact to import the patch as a merge)\n"))
660 "(use --exact to import the patch as a merge)\n"))
660 else:
661 else:
661 p1, p2 = parents
662 p1, p2 = parents
662
663
663 n = None
664 n = None
664 if update:
665 if update:
665 repo.dirstate.beginparentchange()
666 repo.dirstate.beginparentchange()
666 if p1 != parents[0]:
667 if p1 != parents[0]:
667 updatefunc(repo, p1.node())
668 updatefunc(repo, p1.node())
668 if p2 != parents[1]:
669 if p2 != parents[1]:
669 repo.setparents(p1.node(), p2.node())
670 repo.setparents(p1.node(), p2.node())
670
671
671 if opts.get('exact') or opts.get('import_branch'):
672 if opts.get('exact') or opts.get('import_branch'):
672 repo.dirstate.setbranch(branch or 'default')
673 repo.dirstate.setbranch(branch or 'default')
673
674
674 partial = opts.get('partial', False)
675 partial = opts.get('partial', False)
675 files = set()
676 files = set()
676 try:
677 try:
677 patch.patch(ui, repo, tmpname, strip=strip, files=files,
678 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
678 eolmode=None, similarity=sim / 100.0)
679 files=files, eolmode=None, similarity=sim / 100.0)
679 except patch.PatchError, e:
680 except patch.PatchError, e:
680 if not partial:
681 if not partial:
681 raise util.Abort(str(e))
682 raise util.Abort(str(e))
682 if partial:
683 if partial:
683 rejects = True
684 rejects = True
684
685
685 files = list(files)
686 files = list(files)
686 if opts.get('no_commit'):
687 if opts.get('no_commit'):
687 if message:
688 if message:
688 msgs.append(message)
689 msgs.append(message)
689 else:
690 else:
690 if opts.get('exact') or p2:
691 if opts.get('exact') or p2:
691 # If you got here, you either use --force and know what
692 # If you got here, you either use --force and know what
692 # you are doing or used --exact or a merge patch while
693 # you are doing or used --exact or a merge patch while
693 # being updated to its first parent.
694 # being updated to its first parent.
694 m = None
695 m = None
695 else:
696 else:
696 m = scmutil.matchfiles(repo, files or [])
697 m = scmutil.matchfiles(repo, files or [])
697 editform = mergeeditform(repo[None], 'import.normal')
698 editform = mergeeditform(repo[None], 'import.normal')
698 if opts.get('exact'):
699 if opts.get('exact'):
699 editor = None
700 editor = None
700 else:
701 else:
701 editor = getcommiteditor(editform=editform, **opts)
702 editor = getcommiteditor(editform=editform, **opts)
702 n = repo.commit(message, opts.get('user') or user,
703 n = repo.commit(message, opts.get('user') or user,
703 opts.get('date') or date, match=m,
704 opts.get('date') or date, match=m,
704 editor=editor, force=partial)
705 editor=editor, force=partial)
705 repo.dirstate.endparentchange()
706 repo.dirstate.endparentchange()
706 else:
707 else:
707 if opts.get('exact') or opts.get('import_branch'):
708 if opts.get('exact') or opts.get('import_branch'):
708 branch = branch or 'default'
709 branch = branch or 'default'
709 else:
710 else:
710 branch = p1.branch()
711 branch = p1.branch()
711 store = patch.filestore()
712 store = patch.filestore()
712 try:
713 try:
713 files = set()
714 files = set()
714 try:
715 try:
715 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
716 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
716 files, eolmode=None)
717 files, eolmode=None)
717 except patch.PatchError, e:
718 except patch.PatchError, e:
718 raise util.Abort(str(e))
719 raise util.Abort(str(e))
719 if opts.get('exact'):
720 if opts.get('exact'):
720 editor = None
721 editor = None
721 else:
722 else:
722 editor = getcommiteditor(editform='import.bypass')
723 editor = getcommiteditor(editform='import.bypass')
723 memctx = context.makememctx(repo, (p1.node(), p2.node()),
724 memctx = context.makememctx(repo, (p1.node(), p2.node()),
724 message,
725 message,
725 opts.get('user') or user,
726 opts.get('user') or user,
726 opts.get('date') or date,
727 opts.get('date') or date,
727 branch, files, store,
728 branch, files, store,
728 editor=editor)
729 editor=editor)
729 n = memctx.commit()
730 n = memctx.commit()
730 finally:
731 finally:
731 store.close()
732 store.close()
732 if opts.get('exact') and opts.get('no_commit'):
733 if opts.get('exact') and opts.get('no_commit'):
733 # --exact with --no-commit is still useful in that it does merge
734 # --exact with --no-commit is still useful in that it does merge
734 # and branch bits
735 # and branch bits
735 ui.warn(_("warning: can't check exact import with --no-commit\n"))
736 ui.warn(_("warning: can't check exact import with --no-commit\n"))
736 elif opts.get('exact') and hex(n) != nodeid:
737 elif opts.get('exact') and hex(n) != nodeid:
737 raise util.Abort(_('patch is damaged or loses information'))
738 raise util.Abort(_('patch is damaged or loses information'))
738 if n:
739 if n:
739 # i18n: refers to a short changeset id
740 # i18n: refers to a short changeset id
740 msg = _('created %s') % short(n)
741 msg = _('created %s') % short(n)
741 return (msg, n, rejects)
742 return (msg, n, rejects)
742 finally:
743 finally:
743 os.unlink(tmpname)
744 os.unlink(tmpname)
744
745
745 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
746 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
746 opts=None):
747 opts=None):
747 '''export changesets as hg patches.'''
748 '''export changesets as hg patches.'''
748
749
749 total = len(revs)
750 total = len(revs)
750 revwidth = max([len(str(rev)) for rev in revs])
751 revwidth = max([len(str(rev)) for rev in revs])
751 filemode = {}
752 filemode = {}
752
753
753 def single(rev, seqno, fp):
754 def single(rev, seqno, fp):
754 ctx = repo[rev]
755 ctx = repo[rev]
755 node = ctx.node()
756 node = ctx.node()
756 parents = [p.node() for p in ctx.parents() if p]
757 parents = [p.node() for p in ctx.parents() if p]
757 branch = ctx.branch()
758 branch = ctx.branch()
758 if switch_parent:
759 if switch_parent:
759 parents.reverse()
760 parents.reverse()
760 prev = (parents and parents[0]) or nullid
761 prev = (parents and parents[0]) or nullid
761
762
762 shouldclose = False
763 shouldclose = False
763 if not fp and len(template) > 0:
764 if not fp and len(template) > 0:
764 desc_lines = ctx.description().rstrip().split('\n')
765 desc_lines = ctx.description().rstrip().split('\n')
765 desc = desc_lines[0] #Commit always has a first line.
766 desc = desc_lines[0] #Commit always has a first line.
766 fp = makefileobj(repo, template, node, desc=desc, total=total,
767 fp = makefileobj(repo, template, node, desc=desc, total=total,
767 seqno=seqno, revwidth=revwidth, mode='wb',
768 seqno=seqno, revwidth=revwidth, mode='wb',
768 modemap=filemode)
769 modemap=filemode)
769 if fp != template:
770 if fp != template:
770 shouldclose = True
771 shouldclose = True
771 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
772 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
772 repo.ui.note("%s\n" % fp.name)
773 repo.ui.note("%s\n" % fp.name)
773
774
774 if not fp:
775 if not fp:
775 write = repo.ui.write
776 write = repo.ui.write
776 else:
777 else:
777 def write(s, **kw):
778 def write(s, **kw):
778 fp.write(s)
779 fp.write(s)
779
780
780
781
781 write("# HG changeset patch\n")
782 write("# HG changeset patch\n")
782 write("# User %s\n" % ctx.user())
783 write("# User %s\n" % ctx.user())
783 write("# Date %d %d\n" % ctx.date())
784 write("# Date %d %d\n" % ctx.date())
784 write("# %s\n" % util.datestr(ctx.date()))
785 write("# %s\n" % util.datestr(ctx.date()))
785 if branch and branch != 'default':
786 if branch and branch != 'default':
786 write("# Branch %s\n" % branch)
787 write("# Branch %s\n" % branch)
787 write("# Node ID %s\n" % hex(node))
788 write("# Node ID %s\n" % hex(node))
788 write("# Parent %s\n" % hex(prev))
789 write("# Parent %s\n" % hex(prev))
789 if len(parents) > 1:
790 if len(parents) > 1:
790 write("# Parent %s\n" % hex(parents[1]))
791 write("# Parent %s\n" % hex(parents[1]))
791 write(ctx.description().rstrip())
792 write(ctx.description().rstrip())
792 write("\n\n")
793 write("\n\n")
793
794
794 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
795 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
795 write(chunk, label=label)
796 write(chunk, label=label)
796
797
797 if shouldclose:
798 if shouldclose:
798 fp.close()
799 fp.close()
799
800
800 for seqno, rev in enumerate(revs):
801 for seqno, rev in enumerate(revs):
801 single(rev, seqno + 1, fp)
802 single(rev, seqno + 1, fp)
802
803
803 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
804 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
804 changes=None, stat=False, fp=None, prefix='',
805 changes=None, stat=False, fp=None, prefix='',
805 listsubrepos=False):
806 listsubrepos=False):
806 '''show diff or diffstat.'''
807 '''show diff or diffstat.'''
807 if fp is None:
808 if fp is None:
808 write = ui.write
809 write = ui.write
809 else:
810 else:
810 def write(s, **kw):
811 def write(s, **kw):
811 fp.write(s)
812 fp.write(s)
812
813
813 if stat:
814 if stat:
814 diffopts = diffopts.copy(context=0)
815 diffopts = diffopts.copy(context=0)
815 width = 80
816 width = 80
816 if not ui.plain():
817 if not ui.plain():
817 width = ui.termwidth()
818 width = ui.termwidth()
818 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
819 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
819 prefix=prefix)
820 prefix=prefix)
820 for chunk, label in patch.diffstatui(util.iterlines(chunks),
821 for chunk, label in patch.diffstatui(util.iterlines(chunks),
821 width=width,
822 width=width,
822 git=diffopts.git):
823 git=diffopts.git):
823 write(chunk, label=label)
824 write(chunk, label=label)
824 else:
825 else:
825 for chunk, label in patch.diffui(repo, node1, node2, match,
826 for chunk, label in patch.diffui(repo, node1, node2, match,
826 changes, diffopts, prefix=prefix):
827 changes, diffopts, prefix=prefix):
827 write(chunk, label=label)
828 write(chunk, label=label)
828
829
829 if listsubrepos:
830 if listsubrepos:
830 ctx1 = repo[node1]
831 ctx1 = repo[node1]
831 ctx2 = repo[node2]
832 ctx2 = repo[node2]
832 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
833 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
833 tempnode2 = node2
834 tempnode2 = node2
834 try:
835 try:
835 if node2 is not None:
836 if node2 is not None:
836 tempnode2 = ctx2.substate[subpath][1]
837 tempnode2 = ctx2.substate[subpath][1]
837 except KeyError:
838 except KeyError:
838 # A subrepo that existed in node1 was deleted between node1 and
839 # A subrepo that existed in node1 was deleted between node1 and
839 # node2 (inclusive). Thus, ctx2's substate won't contain that
840 # node2 (inclusive). Thus, ctx2's substate won't contain that
840 # subpath. The best we can do is to ignore it.
841 # subpath. The best we can do is to ignore it.
841 tempnode2 = None
842 tempnode2 = None
842 submatch = matchmod.narrowmatcher(subpath, match)
843 submatch = matchmod.narrowmatcher(subpath, match)
843 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
844 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
844 stat=stat, fp=fp, prefix=prefix)
845 stat=stat, fp=fp, prefix=prefix)
845
846
846 class changeset_printer(object):
847 class changeset_printer(object):
847 '''show changeset information when templating not requested.'''
848 '''show changeset information when templating not requested.'''
848
849
849 def __init__(self, ui, repo, matchfn, diffopts, buffered):
850 def __init__(self, ui, repo, matchfn, diffopts, buffered):
850 self.ui = ui
851 self.ui = ui
851 self.repo = repo
852 self.repo = repo
852 self.buffered = buffered
853 self.buffered = buffered
853 self.matchfn = matchfn
854 self.matchfn = matchfn
854 self.diffopts = diffopts
855 self.diffopts = diffopts
855 self.header = {}
856 self.header = {}
856 self.hunk = {}
857 self.hunk = {}
857 self.lastheader = None
858 self.lastheader = None
858 self.footer = None
859 self.footer = None
859
860
860 def flush(self, rev):
861 def flush(self, rev):
861 if rev in self.header:
862 if rev in self.header:
862 h = self.header[rev]
863 h = self.header[rev]
863 if h != self.lastheader:
864 if h != self.lastheader:
864 self.lastheader = h
865 self.lastheader = h
865 self.ui.write(h)
866 self.ui.write(h)
866 del self.header[rev]
867 del self.header[rev]
867 if rev in self.hunk:
868 if rev in self.hunk:
868 self.ui.write(self.hunk[rev])
869 self.ui.write(self.hunk[rev])
869 del self.hunk[rev]
870 del self.hunk[rev]
870 return 1
871 return 1
871 return 0
872 return 0
872
873
873 def close(self):
874 def close(self):
874 if self.footer:
875 if self.footer:
875 self.ui.write(self.footer)
876 self.ui.write(self.footer)
876
877
877 def show(self, ctx, copies=None, matchfn=None, **props):
878 def show(self, ctx, copies=None, matchfn=None, **props):
878 if self.buffered:
879 if self.buffered:
879 self.ui.pushbuffer()
880 self.ui.pushbuffer()
880 self._show(ctx, copies, matchfn, props)
881 self._show(ctx, copies, matchfn, props)
881 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
882 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
882 else:
883 else:
883 self._show(ctx, copies, matchfn, props)
884 self._show(ctx, copies, matchfn, props)
884
885
885 def _show(self, ctx, copies, matchfn, props):
886 def _show(self, ctx, copies, matchfn, props):
886 '''show a single changeset or file revision'''
887 '''show a single changeset or file revision'''
887 changenode = ctx.node()
888 changenode = ctx.node()
888 rev = ctx.rev()
889 rev = ctx.rev()
889
890
890 if self.ui.quiet:
891 if self.ui.quiet:
891 self.ui.write("%d:%s\n" % (rev, short(changenode)),
892 self.ui.write("%d:%s\n" % (rev, short(changenode)),
892 label='log.node')
893 label='log.node')
893 return
894 return
894
895
895 log = self.repo.changelog
896 log = self.repo.changelog
896 date = util.datestr(ctx.date())
897 date = util.datestr(ctx.date())
897
898
898 hexfunc = self.ui.debugflag and hex or short
899 hexfunc = self.ui.debugflag and hex or short
899
900
900 parents = [(p, hexfunc(log.node(p)))
901 parents = [(p, hexfunc(log.node(p)))
901 for p in self._meaningful_parentrevs(log, rev)]
902 for p in self._meaningful_parentrevs(log, rev)]
902
903
903 # i18n: column positioning for "hg log"
904 # i18n: column positioning for "hg log"
904 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
905 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
905 label='log.changeset changeset.%s' % ctx.phasestr())
906 label='log.changeset changeset.%s' % ctx.phasestr())
906
907
907 # branches are shown first before any other names due to backwards
908 # branches are shown first before any other names due to backwards
908 # compatibility
909 # compatibility
909 branch = ctx.branch()
910 branch = ctx.branch()
910 # don't show the default branch name
911 # don't show the default branch name
911 if branch != 'default':
912 if branch != 'default':
912 # i18n: column positioning for "hg log"
913 # i18n: column positioning for "hg log"
913 self.ui.write(_("branch: %s\n") % branch,
914 self.ui.write(_("branch: %s\n") % branch,
914 label='log.branch')
915 label='log.branch')
915
916
916 for name, ns in self.repo.names.iteritems():
917 for name, ns in self.repo.names.iteritems():
917 # branches has special logic already handled above, so here we just
918 # branches has special logic already handled above, so here we just
918 # skip it
919 # skip it
919 if name == 'branches':
920 if name == 'branches':
920 continue
921 continue
921 # we will use the templatename as the color name since those two
922 # we will use the templatename as the color name since those two
922 # should be the same
923 # should be the same
923 for name in ns.names(self.repo, changenode):
924 for name in ns.names(self.repo, changenode):
924 self.ui.write(ns.logfmt % name,
925 self.ui.write(ns.logfmt % name,
925 label='log.%s' % ns.colorname)
926 label='log.%s' % ns.colorname)
926 if self.ui.debugflag:
927 if self.ui.debugflag:
927 # i18n: column positioning for "hg log"
928 # i18n: column positioning for "hg log"
928 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
929 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
929 label='log.phase')
930 label='log.phase')
930 for parent in parents:
931 for parent in parents:
931 label = 'log.parent changeset.%s' % self.repo[parent[0]].phasestr()
932 label = 'log.parent changeset.%s' % self.repo[parent[0]].phasestr()
932 # i18n: column positioning for "hg log"
933 # i18n: column positioning for "hg log"
933 self.ui.write(_("parent: %d:%s\n") % parent,
934 self.ui.write(_("parent: %d:%s\n") % parent,
934 label=label)
935 label=label)
935
936
936 if self.ui.debugflag:
937 if self.ui.debugflag:
937 mnode = ctx.manifestnode()
938 mnode = ctx.manifestnode()
938 # i18n: column positioning for "hg log"
939 # i18n: column positioning for "hg log"
939 self.ui.write(_("manifest: %d:%s\n") %
940 self.ui.write(_("manifest: %d:%s\n") %
940 (self.repo.manifest.rev(mnode), hex(mnode)),
941 (self.repo.manifest.rev(mnode), hex(mnode)),
941 label='ui.debug log.manifest')
942 label='ui.debug log.manifest')
942 # i18n: column positioning for "hg log"
943 # i18n: column positioning for "hg log"
943 self.ui.write(_("user: %s\n") % ctx.user(),
944 self.ui.write(_("user: %s\n") % ctx.user(),
944 label='log.user')
945 label='log.user')
945 # i18n: column positioning for "hg log"
946 # i18n: column positioning for "hg log"
946 self.ui.write(_("date: %s\n") % date,
947 self.ui.write(_("date: %s\n") % date,
947 label='log.date')
948 label='log.date')
948
949
949 if self.ui.debugflag:
950 if self.ui.debugflag:
950 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
951 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
951 for key, value in zip([# i18n: column positioning for "hg log"
952 for key, value in zip([# i18n: column positioning for "hg log"
952 _("files:"),
953 _("files:"),
953 # i18n: column positioning for "hg log"
954 # i18n: column positioning for "hg log"
954 _("files+:"),
955 _("files+:"),
955 # i18n: column positioning for "hg log"
956 # i18n: column positioning for "hg log"
956 _("files-:")], files):
957 _("files-:")], files):
957 if value:
958 if value:
958 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
959 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
959 label='ui.debug log.files')
960 label='ui.debug log.files')
960 elif ctx.files() and self.ui.verbose:
961 elif ctx.files() and self.ui.verbose:
961 # i18n: column positioning for "hg log"
962 # i18n: column positioning for "hg log"
962 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
963 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
963 label='ui.note log.files')
964 label='ui.note log.files')
964 if copies and self.ui.verbose:
965 if copies and self.ui.verbose:
965 copies = ['%s (%s)' % c for c in copies]
966 copies = ['%s (%s)' % c for c in copies]
966 # i18n: column positioning for "hg log"
967 # i18n: column positioning for "hg log"
967 self.ui.write(_("copies: %s\n") % ' '.join(copies),
968 self.ui.write(_("copies: %s\n") % ' '.join(copies),
968 label='ui.note log.copies')
969 label='ui.note log.copies')
969
970
970 extra = ctx.extra()
971 extra = ctx.extra()
971 if extra and self.ui.debugflag:
972 if extra and self.ui.debugflag:
972 for key, value in sorted(extra.items()):
973 for key, value in sorted(extra.items()):
973 # i18n: column positioning for "hg log"
974 # i18n: column positioning for "hg log"
974 self.ui.write(_("extra: %s=%s\n")
975 self.ui.write(_("extra: %s=%s\n")
975 % (key, value.encode('string_escape')),
976 % (key, value.encode('string_escape')),
976 label='ui.debug log.extra')
977 label='ui.debug log.extra')
977
978
978 description = ctx.description().strip()
979 description = ctx.description().strip()
979 if description:
980 if description:
980 if self.ui.verbose:
981 if self.ui.verbose:
981 self.ui.write(_("description:\n"),
982 self.ui.write(_("description:\n"),
982 label='ui.note log.description')
983 label='ui.note log.description')
983 self.ui.write(description,
984 self.ui.write(description,
984 label='ui.note log.description')
985 label='ui.note log.description')
985 self.ui.write("\n\n")
986 self.ui.write("\n\n")
986 else:
987 else:
987 # i18n: column positioning for "hg log"
988 # i18n: column positioning for "hg log"
988 self.ui.write(_("summary: %s\n") %
989 self.ui.write(_("summary: %s\n") %
989 description.splitlines()[0],
990 description.splitlines()[0],
990 label='log.summary')
991 label='log.summary')
991 self.ui.write("\n")
992 self.ui.write("\n")
992
993
993 self.showpatch(changenode, matchfn)
994 self.showpatch(changenode, matchfn)
994
995
995 def showpatch(self, node, matchfn):
996 def showpatch(self, node, matchfn):
996 if not matchfn:
997 if not matchfn:
997 matchfn = self.matchfn
998 matchfn = self.matchfn
998 if matchfn:
999 if matchfn:
999 stat = self.diffopts.get('stat')
1000 stat = self.diffopts.get('stat')
1000 diff = self.diffopts.get('patch')
1001 diff = self.diffopts.get('patch')
1001 diffopts = patch.diffallopts(self.ui, self.diffopts)
1002 diffopts = patch.diffallopts(self.ui, self.diffopts)
1002 prev = self.repo.changelog.parents(node)[0]
1003 prev = self.repo.changelog.parents(node)[0]
1003 if stat:
1004 if stat:
1004 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1005 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1005 match=matchfn, stat=True)
1006 match=matchfn, stat=True)
1006 if diff:
1007 if diff:
1007 if stat:
1008 if stat:
1008 self.ui.write("\n")
1009 self.ui.write("\n")
1009 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1010 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1010 match=matchfn, stat=False)
1011 match=matchfn, stat=False)
1011 self.ui.write("\n")
1012 self.ui.write("\n")
1012
1013
1013 def _meaningful_parentrevs(self, log, rev):
1014 def _meaningful_parentrevs(self, log, rev):
1014 """Return list of meaningful (or all if debug) parentrevs for rev.
1015 """Return list of meaningful (or all if debug) parentrevs for rev.
1015
1016
1016 For merges (two non-nullrev revisions) both parents are meaningful.
1017 For merges (two non-nullrev revisions) both parents are meaningful.
1017 Otherwise the first parent revision is considered meaningful if it
1018 Otherwise the first parent revision is considered meaningful if it
1018 is not the preceding revision.
1019 is not the preceding revision.
1019 """
1020 """
1020 parents = log.parentrevs(rev)
1021 parents = log.parentrevs(rev)
1021 if not self.ui.debugflag and parents[1] == nullrev:
1022 if not self.ui.debugflag and parents[1] == nullrev:
1022 if parents[0] >= rev - 1:
1023 if parents[0] >= rev - 1:
1023 parents = []
1024 parents = []
1024 else:
1025 else:
1025 parents = [parents[0]]
1026 parents = [parents[0]]
1026 return parents
1027 return parents
1027
1028
1028 class jsonchangeset(changeset_printer):
1029 class jsonchangeset(changeset_printer):
1029 '''format changeset information.'''
1030 '''format changeset information.'''
1030
1031
1031 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1032 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1032 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1033 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1033 self.cache = {}
1034 self.cache = {}
1034 self._first = True
1035 self._first = True
1035
1036
1036 def close(self):
1037 def close(self):
1037 if not self._first:
1038 if not self._first:
1038 self.ui.write("\n]\n")
1039 self.ui.write("\n]\n")
1039 else:
1040 else:
1040 self.ui.write("[]\n")
1041 self.ui.write("[]\n")
1041
1042
1042 def _show(self, ctx, copies, matchfn, props):
1043 def _show(self, ctx, copies, matchfn, props):
1043 '''show a single changeset or file revision'''
1044 '''show a single changeset or file revision'''
1044 hexnode = hex(ctx.node())
1045 hexnode = hex(ctx.node())
1045 rev = ctx.rev()
1046 rev = ctx.rev()
1046 j = encoding.jsonescape
1047 j = encoding.jsonescape
1047
1048
1048 if self._first:
1049 if self._first:
1049 self.ui.write("[\n {")
1050 self.ui.write("[\n {")
1050 self._first = False
1051 self._first = False
1051 else:
1052 else:
1052 self.ui.write(",\n {")
1053 self.ui.write(",\n {")
1053
1054
1054 if self.ui.quiet:
1055 if self.ui.quiet:
1055 self.ui.write('\n "rev": %d' % rev)
1056 self.ui.write('\n "rev": %d' % rev)
1056 self.ui.write(',\n "node": "%s"' % hexnode)
1057 self.ui.write(',\n "node": "%s"' % hexnode)
1057 self.ui.write('\n }')
1058 self.ui.write('\n }')
1058 return
1059 return
1059
1060
1060 self.ui.write('\n "rev": %d' % rev)
1061 self.ui.write('\n "rev": %d' % rev)
1061 self.ui.write(',\n "node": "%s"' % hexnode)
1062 self.ui.write(',\n "node": "%s"' % hexnode)
1062 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1063 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1063 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1064 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1064 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1065 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1065 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1066 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1066 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1067 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1067
1068
1068 self.ui.write(',\n "bookmarks": [%s]' %
1069 self.ui.write(',\n "bookmarks": [%s]' %
1069 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1070 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1070 self.ui.write(',\n "tags": [%s]' %
1071 self.ui.write(',\n "tags": [%s]' %
1071 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1072 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1072 self.ui.write(',\n "parents": [%s]' %
1073 self.ui.write(',\n "parents": [%s]' %
1073 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1074 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1074
1075
1075 if self.ui.debugflag:
1076 if self.ui.debugflag:
1076 self.ui.write(',\n "manifest": "%s"' % hex(ctx.manifestnode()))
1077 self.ui.write(',\n "manifest": "%s"' % hex(ctx.manifestnode()))
1077
1078
1078 self.ui.write(',\n "extra": {%s}' %
1079 self.ui.write(',\n "extra": {%s}' %
1079 ", ".join('"%s": "%s"' % (j(k), j(v))
1080 ", ".join('"%s": "%s"' % (j(k), j(v))
1080 for k, v in ctx.extra().items()))
1081 for k, v in ctx.extra().items()))
1081
1082
1082 files = ctx.p1().status(ctx)
1083 files = ctx.p1().status(ctx)
1083 self.ui.write(',\n "modified": [%s]' %
1084 self.ui.write(',\n "modified": [%s]' %
1084 ", ".join('"%s"' % j(f) for f in files[0]))
1085 ", ".join('"%s"' % j(f) for f in files[0]))
1085 self.ui.write(',\n "added": [%s]' %
1086 self.ui.write(',\n "added": [%s]' %
1086 ", ".join('"%s"' % j(f) for f in files[1]))
1087 ", ".join('"%s"' % j(f) for f in files[1]))
1087 self.ui.write(',\n "removed": [%s]' %
1088 self.ui.write(',\n "removed": [%s]' %
1088 ", ".join('"%s"' % j(f) for f in files[2]))
1089 ", ".join('"%s"' % j(f) for f in files[2]))
1089
1090
1090 elif self.ui.verbose:
1091 elif self.ui.verbose:
1091 self.ui.write(',\n "files": [%s]' %
1092 self.ui.write(',\n "files": [%s]' %
1092 ", ".join('"%s"' % j(f) for f in ctx.files()))
1093 ", ".join('"%s"' % j(f) for f in ctx.files()))
1093
1094
1094 if copies:
1095 if copies:
1095 self.ui.write(',\n "copies": {%s}' %
1096 self.ui.write(',\n "copies": {%s}' %
1096 ", ".join('"%s": "%s"' % (j(k), j(v))
1097 ", ".join('"%s": "%s"' % (j(k), j(v))
1097 for k, v in copies))
1098 for k, v in copies))
1098
1099
1099 matchfn = self.matchfn
1100 matchfn = self.matchfn
1100 if matchfn:
1101 if matchfn:
1101 stat = self.diffopts.get('stat')
1102 stat = self.diffopts.get('stat')
1102 diff = self.diffopts.get('patch')
1103 diff = self.diffopts.get('patch')
1103 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1104 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1104 node, prev = ctx.node(), ctx.p1().node()
1105 node, prev = ctx.node(), ctx.p1().node()
1105 if stat:
1106 if stat:
1106 self.ui.pushbuffer()
1107 self.ui.pushbuffer()
1107 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1108 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1108 match=matchfn, stat=True)
1109 match=matchfn, stat=True)
1109 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1110 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1110 if diff:
1111 if diff:
1111 self.ui.pushbuffer()
1112 self.ui.pushbuffer()
1112 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1113 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1113 match=matchfn, stat=False)
1114 match=matchfn, stat=False)
1114 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1115 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1115
1116
1116 self.ui.write("\n }")
1117 self.ui.write("\n }")
1117
1118
1118 class changeset_templater(changeset_printer):
1119 class changeset_templater(changeset_printer):
1119 '''format changeset information.'''
1120 '''format changeset information.'''
1120
1121
1121 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1122 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1122 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1123 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1123 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1124 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1124 defaulttempl = {
1125 defaulttempl = {
1125 'parent': '{rev}:{node|formatnode} ',
1126 'parent': '{rev}:{node|formatnode} ',
1126 'manifest': '{rev}:{node|formatnode}',
1127 'manifest': '{rev}:{node|formatnode}',
1127 'file_copy': '{name} ({source})',
1128 'file_copy': '{name} ({source})',
1128 'extra': '{key}={value|stringescape}'
1129 'extra': '{key}={value|stringescape}'
1129 }
1130 }
1130 # filecopy is preserved for compatibility reasons
1131 # filecopy is preserved for compatibility reasons
1131 defaulttempl['filecopy'] = defaulttempl['file_copy']
1132 defaulttempl['filecopy'] = defaulttempl['file_copy']
1132 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1133 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1133 cache=defaulttempl)
1134 cache=defaulttempl)
1134 if tmpl:
1135 if tmpl:
1135 self.t.cache['changeset'] = tmpl
1136 self.t.cache['changeset'] = tmpl
1136
1137
1137 self.cache = {}
1138 self.cache = {}
1138
1139
1139 def _meaningful_parentrevs(self, ctx):
1140 def _meaningful_parentrevs(self, ctx):
1140 """Return list of meaningful (or all if debug) parentrevs for rev.
1141 """Return list of meaningful (or all if debug) parentrevs for rev.
1141 """
1142 """
1142 parents = ctx.parents()
1143 parents = ctx.parents()
1143 if len(parents) > 1:
1144 if len(parents) > 1:
1144 return parents
1145 return parents
1145 if self.ui.debugflag:
1146 if self.ui.debugflag:
1146 return [parents[0], self.repo['null']]
1147 return [parents[0], self.repo['null']]
1147 if parents[0].rev() >= ctx.rev() - 1:
1148 if parents[0].rev() >= ctx.rev() - 1:
1148 return []
1149 return []
1149 return parents
1150 return parents
1150
1151
1151 def _show(self, ctx, copies, matchfn, props):
1152 def _show(self, ctx, copies, matchfn, props):
1152 '''show a single changeset or file revision'''
1153 '''show a single changeset or file revision'''
1153
1154
1154 showlist = templatekw.showlist
1155 showlist = templatekw.showlist
1155
1156
1156 # showparents() behaviour depends on ui trace level which
1157 # showparents() behaviour depends on ui trace level which
1157 # causes unexpected behaviours at templating level and makes
1158 # causes unexpected behaviours at templating level and makes
1158 # it harder to extract it in a standalone function. Its
1159 # it harder to extract it in a standalone function. Its
1159 # behaviour cannot be changed so leave it here for now.
1160 # behaviour cannot be changed so leave it here for now.
1160 def showparents(**args):
1161 def showparents(**args):
1161 ctx = args['ctx']
1162 ctx = args['ctx']
1162 parents = [[('rev', p.rev()),
1163 parents = [[('rev', p.rev()),
1163 ('node', p.hex()),
1164 ('node', p.hex()),
1164 ('phase', p.phasestr())]
1165 ('phase', p.phasestr())]
1165 for p in self._meaningful_parentrevs(ctx)]
1166 for p in self._meaningful_parentrevs(ctx)]
1166 return showlist('parent', parents, **args)
1167 return showlist('parent', parents, **args)
1167
1168
1168 props = props.copy()
1169 props = props.copy()
1169 props.update(templatekw.keywords)
1170 props.update(templatekw.keywords)
1170 props['parents'] = showparents
1171 props['parents'] = showparents
1171 props['templ'] = self.t
1172 props['templ'] = self.t
1172 props['ctx'] = ctx
1173 props['ctx'] = ctx
1173 props['repo'] = self.repo
1174 props['repo'] = self.repo
1174 props['revcache'] = {'copies': copies}
1175 props['revcache'] = {'copies': copies}
1175 props['cache'] = self.cache
1176 props['cache'] = self.cache
1176
1177
1177 # find correct templates for current mode
1178 # find correct templates for current mode
1178
1179
1179 tmplmodes = [
1180 tmplmodes = [
1180 (True, None),
1181 (True, None),
1181 (self.ui.verbose, 'verbose'),
1182 (self.ui.verbose, 'verbose'),
1182 (self.ui.quiet, 'quiet'),
1183 (self.ui.quiet, 'quiet'),
1183 (self.ui.debugflag, 'debug'),
1184 (self.ui.debugflag, 'debug'),
1184 ]
1185 ]
1185
1186
1186 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1187 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1187 for mode, postfix in tmplmodes:
1188 for mode, postfix in tmplmodes:
1188 for type in types:
1189 for type in types:
1189 cur = postfix and ('%s_%s' % (type, postfix)) or type
1190 cur = postfix and ('%s_%s' % (type, postfix)) or type
1190 if mode and cur in self.t:
1191 if mode and cur in self.t:
1191 types[type] = cur
1192 types[type] = cur
1192
1193
1193 try:
1194 try:
1194
1195
1195 # write header
1196 # write header
1196 if types['header']:
1197 if types['header']:
1197 h = templater.stringify(self.t(types['header'], **props))
1198 h = templater.stringify(self.t(types['header'], **props))
1198 if self.buffered:
1199 if self.buffered:
1199 self.header[ctx.rev()] = h
1200 self.header[ctx.rev()] = h
1200 else:
1201 else:
1201 if self.lastheader != h:
1202 if self.lastheader != h:
1202 self.lastheader = h
1203 self.lastheader = h
1203 self.ui.write(h)
1204 self.ui.write(h)
1204
1205
1205 # write changeset metadata, then patch if requested
1206 # write changeset metadata, then patch if requested
1206 key = types['changeset']
1207 key = types['changeset']
1207 self.ui.write(templater.stringify(self.t(key, **props)))
1208 self.ui.write(templater.stringify(self.t(key, **props)))
1208 self.showpatch(ctx.node(), matchfn)
1209 self.showpatch(ctx.node(), matchfn)
1209
1210
1210 if types['footer']:
1211 if types['footer']:
1211 if not self.footer:
1212 if not self.footer:
1212 self.footer = templater.stringify(self.t(types['footer'],
1213 self.footer = templater.stringify(self.t(types['footer'],
1213 **props))
1214 **props))
1214
1215
1215 except KeyError, inst:
1216 except KeyError, inst:
1216 msg = _("%s: no key named '%s'")
1217 msg = _("%s: no key named '%s'")
1217 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1218 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1218 except SyntaxError, inst:
1219 except SyntaxError, inst:
1219 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1220 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1220
1221
1221 def gettemplate(ui, tmpl, style):
1222 def gettemplate(ui, tmpl, style):
1222 """
1223 """
1223 Find the template matching the given template spec or style.
1224 Find the template matching the given template spec or style.
1224 """
1225 """
1225
1226
1226 # ui settings
1227 # ui settings
1227 if not tmpl and not style: # template are stronger than style
1228 if not tmpl and not style: # template are stronger than style
1228 tmpl = ui.config('ui', 'logtemplate')
1229 tmpl = ui.config('ui', 'logtemplate')
1229 if tmpl:
1230 if tmpl:
1230 try:
1231 try:
1231 tmpl = templater.parsestring(tmpl)
1232 tmpl = templater.parsestring(tmpl)
1232 except SyntaxError:
1233 except SyntaxError:
1233 tmpl = templater.parsestring(tmpl, quoted=False)
1234 tmpl = templater.parsestring(tmpl, quoted=False)
1234 return tmpl, None
1235 return tmpl, None
1235 else:
1236 else:
1236 style = util.expandpath(ui.config('ui', 'style', ''))
1237 style = util.expandpath(ui.config('ui', 'style', ''))
1237
1238
1238 if not tmpl and style:
1239 if not tmpl and style:
1239 mapfile = style
1240 mapfile = style
1240 if not os.path.split(mapfile)[0]:
1241 if not os.path.split(mapfile)[0]:
1241 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1242 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1242 or templater.templatepath(mapfile))
1243 or templater.templatepath(mapfile))
1243 if mapname:
1244 if mapname:
1244 mapfile = mapname
1245 mapfile = mapname
1245 return None, mapfile
1246 return None, mapfile
1246
1247
1247 if not tmpl:
1248 if not tmpl:
1248 return None, None
1249 return None, None
1249
1250
1250 # looks like a literal template?
1251 # looks like a literal template?
1251 if '{' in tmpl:
1252 if '{' in tmpl:
1252 return tmpl, None
1253 return tmpl, None
1253
1254
1254 # perhaps a stock style?
1255 # perhaps a stock style?
1255 if not os.path.split(tmpl)[0]:
1256 if not os.path.split(tmpl)[0]:
1256 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1257 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1257 or templater.templatepath(tmpl))
1258 or templater.templatepath(tmpl))
1258 if mapname and os.path.isfile(mapname):
1259 if mapname and os.path.isfile(mapname):
1259 return None, mapname
1260 return None, mapname
1260
1261
1261 # perhaps it's a reference to [templates]
1262 # perhaps it's a reference to [templates]
1262 t = ui.config('templates', tmpl)
1263 t = ui.config('templates', tmpl)
1263 if t:
1264 if t:
1264 try:
1265 try:
1265 tmpl = templater.parsestring(t)
1266 tmpl = templater.parsestring(t)
1266 except SyntaxError:
1267 except SyntaxError:
1267 tmpl = templater.parsestring(t, quoted=False)
1268 tmpl = templater.parsestring(t, quoted=False)
1268 return tmpl, None
1269 return tmpl, None
1269
1270
1270 if tmpl == 'list':
1271 if tmpl == 'list':
1271 ui.write(_("available styles: %s\n") % templater.stylelist())
1272 ui.write(_("available styles: %s\n") % templater.stylelist())
1272 raise util.Abort(_("specify a template"))
1273 raise util.Abort(_("specify a template"))
1273
1274
1274 # perhaps it's a path to a map or a template
1275 # perhaps it's a path to a map or a template
1275 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1276 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1276 # is it a mapfile for a style?
1277 # is it a mapfile for a style?
1277 if os.path.basename(tmpl).startswith("map-"):
1278 if os.path.basename(tmpl).startswith("map-"):
1278 return None, os.path.realpath(tmpl)
1279 return None, os.path.realpath(tmpl)
1279 tmpl = open(tmpl).read()
1280 tmpl = open(tmpl).read()
1280 return tmpl, None
1281 return tmpl, None
1281
1282
1282 # constant string?
1283 # constant string?
1283 return tmpl, None
1284 return tmpl, None
1284
1285
1285 def show_changeset(ui, repo, opts, buffered=False):
1286 def show_changeset(ui, repo, opts, buffered=False):
1286 """show one changeset using template or regular display.
1287 """show one changeset using template or regular display.
1287
1288
1288 Display format will be the first non-empty hit of:
1289 Display format will be the first non-empty hit of:
1289 1. option 'template'
1290 1. option 'template'
1290 2. option 'style'
1291 2. option 'style'
1291 3. [ui] setting 'logtemplate'
1292 3. [ui] setting 'logtemplate'
1292 4. [ui] setting 'style'
1293 4. [ui] setting 'style'
1293 If all of these values are either the unset or the empty string,
1294 If all of these values are either the unset or the empty string,
1294 regular display via changeset_printer() is done.
1295 regular display via changeset_printer() is done.
1295 """
1296 """
1296 # options
1297 # options
1297 matchfn = None
1298 matchfn = None
1298 if opts.get('patch') or opts.get('stat'):
1299 if opts.get('patch') or opts.get('stat'):
1299 matchfn = scmutil.matchall(repo)
1300 matchfn = scmutil.matchall(repo)
1300
1301
1301 if opts.get('template') == 'json':
1302 if opts.get('template') == 'json':
1302 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1303 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1303
1304
1304 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1305 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1305
1306
1306 if not tmpl and not mapfile:
1307 if not tmpl and not mapfile:
1307 return changeset_printer(ui, repo, matchfn, opts, buffered)
1308 return changeset_printer(ui, repo, matchfn, opts, buffered)
1308
1309
1309 try:
1310 try:
1310 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1311 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1311 buffered)
1312 buffered)
1312 except SyntaxError, inst:
1313 except SyntaxError, inst:
1313 raise util.Abort(inst.args[0])
1314 raise util.Abort(inst.args[0])
1314 return t
1315 return t
1315
1316
1316 def showmarker(ui, marker):
1317 def showmarker(ui, marker):
1317 """utility function to display obsolescence marker in a readable way
1318 """utility function to display obsolescence marker in a readable way
1318
1319
1319 To be used by debug function."""
1320 To be used by debug function."""
1320 ui.write(hex(marker.precnode()))
1321 ui.write(hex(marker.precnode()))
1321 for repl in marker.succnodes():
1322 for repl in marker.succnodes():
1322 ui.write(' ')
1323 ui.write(' ')
1323 ui.write(hex(repl))
1324 ui.write(hex(repl))
1324 ui.write(' %X ' % marker.flags())
1325 ui.write(' %X ' % marker.flags())
1325 parents = marker.parentnodes()
1326 parents = marker.parentnodes()
1326 if parents is not None:
1327 if parents is not None:
1327 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1328 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1328 ui.write('(%s) ' % util.datestr(marker.date()))
1329 ui.write('(%s) ' % util.datestr(marker.date()))
1329 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1330 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1330 sorted(marker.metadata().items())
1331 sorted(marker.metadata().items())
1331 if t[0] != 'date')))
1332 if t[0] != 'date')))
1332 ui.write('\n')
1333 ui.write('\n')
1333
1334
1334 def finddate(ui, repo, date):
1335 def finddate(ui, repo, date):
1335 """Find the tipmost changeset that matches the given date spec"""
1336 """Find the tipmost changeset that matches the given date spec"""
1336
1337
1337 df = util.matchdate(date)
1338 df = util.matchdate(date)
1338 m = scmutil.matchall(repo)
1339 m = scmutil.matchall(repo)
1339 results = {}
1340 results = {}
1340
1341
1341 def prep(ctx, fns):
1342 def prep(ctx, fns):
1342 d = ctx.date()
1343 d = ctx.date()
1343 if df(d[0]):
1344 if df(d[0]):
1344 results[ctx.rev()] = d
1345 results[ctx.rev()] = d
1345
1346
1346 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1347 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1347 rev = ctx.rev()
1348 rev = ctx.rev()
1348 if rev in results:
1349 if rev in results:
1349 ui.status(_("found revision %s from %s\n") %
1350 ui.status(_("found revision %s from %s\n") %
1350 (rev, util.datestr(results[rev])))
1351 (rev, util.datestr(results[rev])))
1351 return str(rev)
1352 return str(rev)
1352
1353
1353 raise util.Abort(_("revision matching date not found"))
1354 raise util.Abort(_("revision matching date not found"))
1354
1355
1355 def increasingwindows(windowsize=8, sizelimit=512):
1356 def increasingwindows(windowsize=8, sizelimit=512):
1356 while True:
1357 while True:
1357 yield windowsize
1358 yield windowsize
1358 if windowsize < sizelimit:
1359 if windowsize < sizelimit:
1359 windowsize *= 2
1360 windowsize *= 2
1360
1361
1361 class FileWalkError(Exception):
1362 class FileWalkError(Exception):
1362 pass
1363 pass
1363
1364
1364 def walkfilerevs(repo, match, follow, revs, fncache):
1365 def walkfilerevs(repo, match, follow, revs, fncache):
1365 '''Walks the file history for the matched files.
1366 '''Walks the file history for the matched files.
1366
1367
1367 Returns the changeset revs that are involved in the file history.
1368 Returns the changeset revs that are involved in the file history.
1368
1369
1369 Throws FileWalkError if the file history can't be walked using
1370 Throws FileWalkError if the file history can't be walked using
1370 filelogs alone.
1371 filelogs alone.
1371 '''
1372 '''
1372 wanted = set()
1373 wanted = set()
1373 copies = []
1374 copies = []
1374 minrev, maxrev = min(revs), max(revs)
1375 minrev, maxrev = min(revs), max(revs)
1375 def filerevgen(filelog, last):
1376 def filerevgen(filelog, last):
1376 """
1377 """
1377 Only files, no patterns. Check the history of each file.
1378 Only files, no patterns. Check the history of each file.
1378
1379
1379 Examines filelog entries within minrev, maxrev linkrev range
1380 Examines filelog entries within minrev, maxrev linkrev range
1380 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1381 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1381 tuples in backwards order
1382 tuples in backwards order
1382 """
1383 """
1383 cl_count = len(repo)
1384 cl_count = len(repo)
1384 revs = []
1385 revs = []
1385 for j in xrange(0, last + 1):
1386 for j in xrange(0, last + 1):
1386 linkrev = filelog.linkrev(j)
1387 linkrev = filelog.linkrev(j)
1387 if linkrev < minrev:
1388 if linkrev < minrev:
1388 continue
1389 continue
1389 # only yield rev for which we have the changelog, it can
1390 # only yield rev for which we have the changelog, it can
1390 # happen while doing "hg log" during a pull or commit
1391 # happen while doing "hg log" during a pull or commit
1391 if linkrev >= cl_count:
1392 if linkrev >= cl_count:
1392 break
1393 break
1393
1394
1394 parentlinkrevs = []
1395 parentlinkrevs = []
1395 for p in filelog.parentrevs(j):
1396 for p in filelog.parentrevs(j):
1396 if p != nullrev:
1397 if p != nullrev:
1397 parentlinkrevs.append(filelog.linkrev(p))
1398 parentlinkrevs.append(filelog.linkrev(p))
1398 n = filelog.node(j)
1399 n = filelog.node(j)
1399 revs.append((linkrev, parentlinkrevs,
1400 revs.append((linkrev, parentlinkrevs,
1400 follow and filelog.renamed(n)))
1401 follow and filelog.renamed(n)))
1401
1402
1402 return reversed(revs)
1403 return reversed(revs)
1403 def iterfiles():
1404 def iterfiles():
1404 pctx = repo['.']
1405 pctx = repo['.']
1405 for filename in match.files():
1406 for filename in match.files():
1406 if follow:
1407 if follow:
1407 if filename not in pctx:
1408 if filename not in pctx:
1408 raise util.Abort(_('cannot follow file not in parent '
1409 raise util.Abort(_('cannot follow file not in parent '
1409 'revision: "%s"') % filename)
1410 'revision: "%s"') % filename)
1410 yield filename, pctx[filename].filenode()
1411 yield filename, pctx[filename].filenode()
1411 else:
1412 else:
1412 yield filename, None
1413 yield filename, None
1413 for filename_node in copies:
1414 for filename_node in copies:
1414 yield filename_node
1415 yield filename_node
1415
1416
1416 for file_, node in iterfiles():
1417 for file_, node in iterfiles():
1417 filelog = repo.file(file_)
1418 filelog = repo.file(file_)
1418 if not len(filelog):
1419 if not len(filelog):
1419 if node is None:
1420 if node is None:
1420 # A zero count may be a directory or deleted file, so
1421 # A zero count may be a directory or deleted file, so
1421 # try to find matching entries on the slow path.
1422 # try to find matching entries on the slow path.
1422 if follow:
1423 if follow:
1423 raise util.Abort(
1424 raise util.Abort(
1424 _('cannot follow nonexistent file: "%s"') % file_)
1425 _('cannot follow nonexistent file: "%s"') % file_)
1425 raise FileWalkError("Cannot walk via filelog")
1426 raise FileWalkError("Cannot walk via filelog")
1426 else:
1427 else:
1427 continue
1428 continue
1428
1429
1429 if node is None:
1430 if node is None:
1430 last = len(filelog) - 1
1431 last = len(filelog) - 1
1431 else:
1432 else:
1432 last = filelog.rev(node)
1433 last = filelog.rev(node)
1433
1434
1434
1435
1435 # keep track of all ancestors of the file
1436 # keep track of all ancestors of the file
1436 ancestors = set([filelog.linkrev(last)])
1437 ancestors = set([filelog.linkrev(last)])
1437
1438
1438 # iterate from latest to oldest revision
1439 # iterate from latest to oldest revision
1439 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1440 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1440 if not follow:
1441 if not follow:
1441 if rev > maxrev:
1442 if rev > maxrev:
1442 continue
1443 continue
1443 else:
1444 else:
1444 # Note that last might not be the first interesting
1445 # Note that last might not be the first interesting
1445 # rev to us:
1446 # rev to us:
1446 # if the file has been changed after maxrev, we'll
1447 # if the file has been changed after maxrev, we'll
1447 # have linkrev(last) > maxrev, and we still need
1448 # have linkrev(last) > maxrev, and we still need
1448 # to explore the file graph
1449 # to explore the file graph
1449 if rev not in ancestors:
1450 if rev not in ancestors:
1450 continue
1451 continue
1451 # XXX insert 1327 fix here
1452 # XXX insert 1327 fix here
1452 if flparentlinkrevs:
1453 if flparentlinkrevs:
1453 ancestors.update(flparentlinkrevs)
1454 ancestors.update(flparentlinkrevs)
1454
1455
1455 fncache.setdefault(rev, []).append(file_)
1456 fncache.setdefault(rev, []).append(file_)
1456 wanted.add(rev)
1457 wanted.add(rev)
1457 if copied:
1458 if copied:
1458 copies.append(copied)
1459 copies.append(copied)
1459
1460
1460 return wanted
1461 return wanted
1461
1462
1462 def walkchangerevs(repo, match, opts, prepare):
1463 def walkchangerevs(repo, match, opts, prepare):
1463 '''Iterate over files and the revs in which they changed.
1464 '''Iterate over files and the revs in which they changed.
1464
1465
1465 Callers most commonly need to iterate backwards over the history
1466 Callers most commonly need to iterate backwards over the history
1466 in which they are interested. Doing so has awful (quadratic-looking)
1467 in which they are interested. Doing so has awful (quadratic-looking)
1467 performance, so we use iterators in a "windowed" way.
1468 performance, so we use iterators in a "windowed" way.
1468
1469
1469 We walk a window of revisions in the desired order. Within the
1470 We walk a window of revisions in the desired order. Within the
1470 window, we first walk forwards to gather data, then in the desired
1471 window, we first walk forwards to gather data, then in the desired
1471 order (usually backwards) to display it.
1472 order (usually backwards) to display it.
1472
1473
1473 This function returns an iterator yielding contexts. Before
1474 This function returns an iterator yielding contexts. Before
1474 yielding each context, the iterator will first call the prepare
1475 yielding each context, the iterator will first call the prepare
1475 function on each context in the window in forward order.'''
1476 function on each context in the window in forward order.'''
1476
1477
1477 follow = opts.get('follow') or opts.get('follow_first')
1478 follow = opts.get('follow') or opts.get('follow_first')
1478 revs = _logrevs(repo, opts)
1479 revs = _logrevs(repo, opts)
1479 if not revs:
1480 if not revs:
1480 return []
1481 return []
1481 wanted = set()
1482 wanted = set()
1482 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1483 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1483 fncache = {}
1484 fncache = {}
1484 change = repo.changectx
1485 change = repo.changectx
1485
1486
1486 # First step is to fill wanted, the set of revisions that we want to yield.
1487 # First step is to fill wanted, the set of revisions that we want to yield.
1487 # When it does not induce extra cost, we also fill fncache for revisions in
1488 # When it does not induce extra cost, we also fill fncache for revisions in
1488 # wanted: a cache of filenames that were changed (ctx.files()) and that
1489 # wanted: a cache of filenames that were changed (ctx.files()) and that
1489 # match the file filtering conditions.
1490 # match the file filtering conditions.
1490
1491
1491 if not slowpath and not match.files():
1492 if not slowpath and not match.files():
1492 # No files, no patterns. Display all revs.
1493 # No files, no patterns. Display all revs.
1493 wanted = revs
1494 wanted = revs
1494
1495
1495 if not slowpath and match.files():
1496 if not slowpath and match.files():
1496 # We only have to read through the filelog to find wanted revisions
1497 # We only have to read through the filelog to find wanted revisions
1497
1498
1498 try:
1499 try:
1499 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1500 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1500 except FileWalkError:
1501 except FileWalkError:
1501 slowpath = True
1502 slowpath = True
1502
1503
1503 # We decided to fall back to the slowpath because at least one
1504 # We decided to fall back to the slowpath because at least one
1504 # of the paths was not a file. Check to see if at least one of them
1505 # of the paths was not a file. Check to see if at least one of them
1505 # existed in history, otherwise simply return
1506 # existed in history, otherwise simply return
1506 for path in match.files():
1507 for path in match.files():
1507 if path == '.' or path in repo.store:
1508 if path == '.' or path in repo.store:
1508 break
1509 break
1509 else:
1510 else:
1510 return []
1511 return []
1511
1512
1512 if slowpath:
1513 if slowpath:
1513 # We have to read the changelog to match filenames against
1514 # We have to read the changelog to match filenames against
1514 # changed files
1515 # changed files
1515
1516
1516 if follow:
1517 if follow:
1517 raise util.Abort(_('can only follow copies/renames for explicit '
1518 raise util.Abort(_('can only follow copies/renames for explicit '
1518 'filenames'))
1519 'filenames'))
1519
1520
1520 # The slow path checks files modified in every changeset.
1521 # The slow path checks files modified in every changeset.
1521 # This is really slow on large repos, so compute the set lazily.
1522 # This is really slow on large repos, so compute the set lazily.
1522 class lazywantedset(object):
1523 class lazywantedset(object):
1523 def __init__(self):
1524 def __init__(self):
1524 self.set = set()
1525 self.set = set()
1525 self.revs = set(revs)
1526 self.revs = set(revs)
1526
1527
1527 # No need to worry about locality here because it will be accessed
1528 # No need to worry about locality here because it will be accessed
1528 # in the same order as the increasing window below.
1529 # in the same order as the increasing window below.
1529 def __contains__(self, value):
1530 def __contains__(self, value):
1530 if value in self.set:
1531 if value in self.set:
1531 return True
1532 return True
1532 elif not value in self.revs:
1533 elif not value in self.revs:
1533 return False
1534 return False
1534 else:
1535 else:
1535 self.revs.discard(value)
1536 self.revs.discard(value)
1536 ctx = change(value)
1537 ctx = change(value)
1537 matches = filter(match, ctx.files())
1538 matches = filter(match, ctx.files())
1538 if matches:
1539 if matches:
1539 fncache[value] = matches
1540 fncache[value] = matches
1540 self.set.add(value)
1541 self.set.add(value)
1541 return True
1542 return True
1542 return False
1543 return False
1543
1544
1544 def discard(self, value):
1545 def discard(self, value):
1545 self.revs.discard(value)
1546 self.revs.discard(value)
1546 self.set.discard(value)
1547 self.set.discard(value)
1547
1548
1548 wanted = lazywantedset()
1549 wanted = lazywantedset()
1549
1550
1550 class followfilter(object):
1551 class followfilter(object):
1551 def __init__(self, onlyfirst=False):
1552 def __init__(self, onlyfirst=False):
1552 self.startrev = nullrev
1553 self.startrev = nullrev
1553 self.roots = set()
1554 self.roots = set()
1554 self.onlyfirst = onlyfirst
1555 self.onlyfirst = onlyfirst
1555
1556
1556 def match(self, rev):
1557 def match(self, rev):
1557 def realparents(rev):
1558 def realparents(rev):
1558 if self.onlyfirst:
1559 if self.onlyfirst:
1559 return repo.changelog.parentrevs(rev)[0:1]
1560 return repo.changelog.parentrevs(rev)[0:1]
1560 else:
1561 else:
1561 return filter(lambda x: x != nullrev,
1562 return filter(lambda x: x != nullrev,
1562 repo.changelog.parentrevs(rev))
1563 repo.changelog.parentrevs(rev))
1563
1564
1564 if self.startrev == nullrev:
1565 if self.startrev == nullrev:
1565 self.startrev = rev
1566 self.startrev = rev
1566 return True
1567 return True
1567
1568
1568 if rev > self.startrev:
1569 if rev > self.startrev:
1569 # forward: all descendants
1570 # forward: all descendants
1570 if not self.roots:
1571 if not self.roots:
1571 self.roots.add(self.startrev)
1572 self.roots.add(self.startrev)
1572 for parent in realparents(rev):
1573 for parent in realparents(rev):
1573 if parent in self.roots:
1574 if parent in self.roots:
1574 self.roots.add(rev)
1575 self.roots.add(rev)
1575 return True
1576 return True
1576 else:
1577 else:
1577 # backwards: all parents
1578 # backwards: all parents
1578 if not self.roots:
1579 if not self.roots:
1579 self.roots.update(realparents(self.startrev))
1580 self.roots.update(realparents(self.startrev))
1580 if rev in self.roots:
1581 if rev in self.roots:
1581 self.roots.remove(rev)
1582 self.roots.remove(rev)
1582 self.roots.update(realparents(rev))
1583 self.roots.update(realparents(rev))
1583 return True
1584 return True
1584
1585
1585 return False
1586 return False
1586
1587
1587 # it might be worthwhile to do this in the iterator if the rev range
1588 # it might be worthwhile to do this in the iterator if the rev range
1588 # is descending and the prune args are all within that range
1589 # is descending and the prune args are all within that range
1589 for rev in opts.get('prune', ()):
1590 for rev in opts.get('prune', ()):
1590 rev = repo[rev].rev()
1591 rev = repo[rev].rev()
1591 ff = followfilter()
1592 ff = followfilter()
1592 stop = min(revs[0], revs[-1])
1593 stop = min(revs[0], revs[-1])
1593 for x in xrange(rev, stop - 1, -1):
1594 for x in xrange(rev, stop - 1, -1):
1594 if ff.match(x):
1595 if ff.match(x):
1595 wanted = wanted - [x]
1596 wanted = wanted - [x]
1596
1597
1597 # Now that wanted is correctly initialized, we can iterate over the
1598 # Now that wanted is correctly initialized, we can iterate over the
1598 # revision range, yielding only revisions in wanted.
1599 # revision range, yielding only revisions in wanted.
1599 def iterate():
1600 def iterate():
1600 if follow and not match.files():
1601 if follow and not match.files():
1601 ff = followfilter(onlyfirst=opts.get('follow_first'))
1602 ff = followfilter(onlyfirst=opts.get('follow_first'))
1602 def want(rev):
1603 def want(rev):
1603 return ff.match(rev) and rev in wanted
1604 return ff.match(rev) and rev in wanted
1604 else:
1605 else:
1605 def want(rev):
1606 def want(rev):
1606 return rev in wanted
1607 return rev in wanted
1607
1608
1608 it = iter(revs)
1609 it = iter(revs)
1609 stopiteration = False
1610 stopiteration = False
1610 for windowsize in increasingwindows():
1611 for windowsize in increasingwindows():
1611 nrevs = []
1612 nrevs = []
1612 for i in xrange(windowsize):
1613 for i in xrange(windowsize):
1613 try:
1614 try:
1614 rev = it.next()
1615 rev = it.next()
1615 if want(rev):
1616 if want(rev):
1616 nrevs.append(rev)
1617 nrevs.append(rev)
1617 except (StopIteration):
1618 except (StopIteration):
1618 stopiteration = True
1619 stopiteration = True
1619 break
1620 break
1620 for rev in sorted(nrevs):
1621 for rev in sorted(nrevs):
1621 fns = fncache.get(rev)
1622 fns = fncache.get(rev)
1622 ctx = change(rev)
1623 ctx = change(rev)
1623 if not fns:
1624 if not fns:
1624 def fns_generator():
1625 def fns_generator():
1625 for f in ctx.files():
1626 for f in ctx.files():
1626 if match(f):
1627 if match(f):
1627 yield f
1628 yield f
1628 fns = fns_generator()
1629 fns = fns_generator()
1629 prepare(ctx, fns)
1630 prepare(ctx, fns)
1630 for rev in nrevs:
1631 for rev in nrevs:
1631 yield change(rev)
1632 yield change(rev)
1632
1633
1633 if stopiteration:
1634 if stopiteration:
1634 break
1635 break
1635
1636
1636 return iterate()
1637 return iterate()
1637
1638
1638 def _makefollowlogfilematcher(repo, files, followfirst):
1639 def _makefollowlogfilematcher(repo, files, followfirst):
1639 # When displaying a revision with --patch --follow FILE, we have
1640 # When displaying a revision with --patch --follow FILE, we have
1640 # to know which file of the revision must be diffed. With
1641 # to know which file of the revision must be diffed. With
1641 # --follow, we want the names of the ancestors of FILE in the
1642 # --follow, we want the names of the ancestors of FILE in the
1642 # revision, stored in "fcache". "fcache" is populated by
1643 # revision, stored in "fcache". "fcache" is populated by
1643 # reproducing the graph traversal already done by --follow revset
1644 # reproducing the graph traversal already done by --follow revset
1644 # and relating linkrevs to file names (which is not "correct" but
1645 # and relating linkrevs to file names (which is not "correct" but
1645 # good enough).
1646 # good enough).
1646 fcache = {}
1647 fcache = {}
1647 fcacheready = [False]
1648 fcacheready = [False]
1648 pctx = repo['.']
1649 pctx = repo['.']
1649
1650
1650 def populate():
1651 def populate():
1651 for fn in files:
1652 for fn in files:
1652 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1653 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1653 for c in i:
1654 for c in i:
1654 fcache.setdefault(c.linkrev(), set()).add(c.path())
1655 fcache.setdefault(c.linkrev(), set()).add(c.path())
1655
1656
1656 def filematcher(rev):
1657 def filematcher(rev):
1657 if not fcacheready[0]:
1658 if not fcacheready[0]:
1658 # Lazy initialization
1659 # Lazy initialization
1659 fcacheready[0] = True
1660 fcacheready[0] = True
1660 populate()
1661 populate()
1661 return scmutil.matchfiles(repo, fcache.get(rev, []))
1662 return scmutil.matchfiles(repo, fcache.get(rev, []))
1662
1663
1663 return filematcher
1664 return filematcher
1664
1665
1665 def _makenofollowlogfilematcher(repo, pats, opts):
1666 def _makenofollowlogfilematcher(repo, pats, opts):
1666 '''hook for extensions to override the filematcher for non-follow cases'''
1667 '''hook for extensions to override the filematcher for non-follow cases'''
1667 return None
1668 return None
1668
1669
1669 def _makelogrevset(repo, pats, opts, revs):
1670 def _makelogrevset(repo, pats, opts, revs):
1670 """Return (expr, filematcher) where expr is a revset string built
1671 """Return (expr, filematcher) where expr is a revset string built
1671 from log options and file patterns or None. If --stat or --patch
1672 from log options and file patterns or None. If --stat or --patch
1672 are not passed filematcher is None. Otherwise it is a callable
1673 are not passed filematcher is None. Otherwise it is a callable
1673 taking a revision number and returning a match objects filtering
1674 taking a revision number and returning a match objects filtering
1674 the files to be detailed when displaying the revision.
1675 the files to be detailed when displaying the revision.
1675 """
1676 """
1676 opt2revset = {
1677 opt2revset = {
1677 'no_merges': ('not merge()', None),
1678 'no_merges': ('not merge()', None),
1678 'only_merges': ('merge()', None),
1679 'only_merges': ('merge()', None),
1679 '_ancestors': ('ancestors(%(val)s)', None),
1680 '_ancestors': ('ancestors(%(val)s)', None),
1680 '_fancestors': ('_firstancestors(%(val)s)', None),
1681 '_fancestors': ('_firstancestors(%(val)s)', None),
1681 '_descendants': ('descendants(%(val)s)', None),
1682 '_descendants': ('descendants(%(val)s)', None),
1682 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1683 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1683 '_matchfiles': ('_matchfiles(%(val)s)', None),
1684 '_matchfiles': ('_matchfiles(%(val)s)', None),
1684 'date': ('date(%(val)r)', None),
1685 'date': ('date(%(val)r)', None),
1685 'branch': ('branch(%(val)r)', ' or '),
1686 'branch': ('branch(%(val)r)', ' or '),
1686 '_patslog': ('filelog(%(val)r)', ' or '),
1687 '_patslog': ('filelog(%(val)r)', ' or '),
1687 '_patsfollow': ('follow(%(val)r)', ' or '),
1688 '_patsfollow': ('follow(%(val)r)', ' or '),
1688 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1689 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1689 'keyword': ('keyword(%(val)r)', ' or '),
1690 'keyword': ('keyword(%(val)r)', ' or '),
1690 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1691 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1691 'user': ('user(%(val)r)', ' or '),
1692 'user': ('user(%(val)r)', ' or '),
1692 }
1693 }
1693
1694
1694 opts = dict(opts)
1695 opts = dict(opts)
1695 # follow or not follow?
1696 # follow or not follow?
1696 follow = opts.get('follow') or opts.get('follow_first')
1697 follow = opts.get('follow') or opts.get('follow_first')
1697 followfirst = opts.get('follow_first') and 1 or 0
1698 followfirst = opts.get('follow_first') and 1 or 0
1698 # --follow with FILE behaviour depends on revs...
1699 # --follow with FILE behaviour depends on revs...
1699 it = iter(revs)
1700 it = iter(revs)
1700 startrev = it.next()
1701 startrev = it.next()
1701 try:
1702 try:
1702 followdescendants = startrev < it.next()
1703 followdescendants = startrev < it.next()
1703 except (StopIteration):
1704 except (StopIteration):
1704 followdescendants = False
1705 followdescendants = False
1705
1706
1706 # branch and only_branch are really aliases and must be handled at
1707 # branch and only_branch are really aliases and must be handled at
1707 # the same time
1708 # the same time
1708 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1709 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1709 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1710 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1710 # pats/include/exclude are passed to match.match() directly in
1711 # pats/include/exclude are passed to match.match() directly in
1711 # _matchfiles() revset but walkchangerevs() builds its matcher with
1712 # _matchfiles() revset but walkchangerevs() builds its matcher with
1712 # scmutil.match(). The difference is input pats are globbed on
1713 # scmutil.match(). The difference is input pats are globbed on
1713 # platforms without shell expansion (windows).
1714 # platforms without shell expansion (windows).
1714 pctx = repo[None]
1715 pctx = repo[None]
1715 match, pats = scmutil.matchandpats(pctx, pats, opts)
1716 match, pats = scmutil.matchandpats(pctx, pats, opts)
1716 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1717 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1717 if not slowpath:
1718 if not slowpath:
1718 for f in match.files():
1719 for f in match.files():
1719 if follow and f not in pctx:
1720 if follow and f not in pctx:
1720 # If the file exists, it may be a directory, so let it
1721 # If the file exists, it may be a directory, so let it
1721 # take the slow path.
1722 # take the slow path.
1722 if os.path.exists(repo.wjoin(f)):
1723 if os.path.exists(repo.wjoin(f)):
1723 slowpath = True
1724 slowpath = True
1724 continue
1725 continue
1725 else:
1726 else:
1726 raise util.Abort(_('cannot follow file not in parent '
1727 raise util.Abort(_('cannot follow file not in parent '
1727 'revision: "%s"') % f)
1728 'revision: "%s"') % f)
1728 filelog = repo.file(f)
1729 filelog = repo.file(f)
1729 if not filelog:
1730 if not filelog:
1730 # A zero count may be a directory or deleted file, so
1731 # A zero count may be a directory or deleted file, so
1731 # try to find matching entries on the slow path.
1732 # try to find matching entries on the slow path.
1732 if follow:
1733 if follow:
1733 raise util.Abort(
1734 raise util.Abort(
1734 _('cannot follow nonexistent file: "%s"') % f)
1735 _('cannot follow nonexistent file: "%s"') % f)
1735 slowpath = True
1736 slowpath = True
1736
1737
1737 # We decided to fall back to the slowpath because at least one
1738 # We decided to fall back to the slowpath because at least one
1738 # of the paths was not a file. Check to see if at least one of them
1739 # of the paths was not a file. Check to see if at least one of them
1739 # existed in history - in that case, we'll continue down the
1740 # existed in history - in that case, we'll continue down the
1740 # slowpath; otherwise, we can turn off the slowpath
1741 # slowpath; otherwise, we can turn off the slowpath
1741 if slowpath:
1742 if slowpath:
1742 for path in match.files():
1743 for path in match.files():
1743 if path == '.' or path in repo.store:
1744 if path == '.' or path in repo.store:
1744 break
1745 break
1745 else:
1746 else:
1746 slowpath = False
1747 slowpath = False
1747
1748
1748 fpats = ('_patsfollow', '_patsfollowfirst')
1749 fpats = ('_patsfollow', '_patsfollowfirst')
1749 fnopats = (('_ancestors', '_fancestors'),
1750 fnopats = (('_ancestors', '_fancestors'),
1750 ('_descendants', '_fdescendants'))
1751 ('_descendants', '_fdescendants'))
1751 if slowpath:
1752 if slowpath:
1752 # See walkchangerevs() slow path.
1753 # See walkchangerevs() slow path.
1753 #
1754 #
1754 # pats/include/exclude cannot be represented as separate
1755 # pats/include/exclude cannot be represented as separate
1755 # revset expressions as their filtering logic applies at file
1756 # revset expressions as their filtering logic applies at file
1756 # level. For instance "-I a -X a" matches a revision touching
1757 # level. For instance "-I a -X a" matches a revision touching
1757 # "a" and "b" while "file(a) and not file(b)" does
1758 # "a" and "b" while "file(a) and not file(b)" does
1758 # not. Besides, filesets are evaluated against the working
1759 # not. Besides, filesets are evaluated against the working
1759 # directory.
1760 # directory.
1760 matchargs = ['r:', 'd:relpath']
1761 matchargs = ['r:', 'd:relpath']
1761 for p in pats:
1762 for p in pats:
1762 matchargs.append('p:' + p)
1763 matchargs.append('p:' + p)
1763 for p in opts.get('include', []):
1764 for p in opts.get('include', []):
1764 matchargs.append('i:' + p)
1765 matchargs.append('i:' + p)
1765 for p in opts.get('exclude', []):
1766 for p in opts.get('exclude', []):
1766 matchargs.append('x:' + p)
1767 matchargs.append('x:' + p)
1767 matchargs = ','.join(('%r' % p) for p in matchargs)
1768 matchargs = ','.join(('%r' % p) for p in matchargs)
1768 opts['_matchfiles'] = matchargs
1769 opts['_matchfiles'] = matchargs
1769 if follow:
1770 if follow:
1770 opts[fnopats[0][followfirst]] = '.'
1771 opts[fnopats[0][followfirst]] = '.'
1771 else:
1772 else:
1772 if follow:
1773 if follow:
1773 if pats:
1774 if pats:
1774 # follow() revset interprets its file argument as a
1775 # follow() revset interprets its file argument as a
1775 # manifest entry, so use match.files(), not pats.
1776 # manifest entry, so use match.files(), not pats.
1776 opts[fpats[followfirst]] = list(match.files())
1777 opts[fpats[followfirst]] = list(match.files())
1777 else:
1778 else:
1778 op = fnopats[followdescendants][followfirst]
1779 op = fnopats[followdescendants][followfirst]
1779 opts[op] = 'rev(%d)' % startrev
1780 opts[op] = 'rev(%d)' % startrev
1780 else:
1781 else:
1781 opts['_patslog'] = list(pats)
1782 opts['_patslog'] = list(pats)
1782
1783
1783 filematcher = None
1784 filematcher = None
1784 if opts.get('patch') or opts.get('stat'):
1785 if opts.get('patch') or opts.get('stat'):
1785 # When following files, track renames via a special matcher.
1786 # When following files, track renames via a special matcher.
1786 # If we're forced to take the slowpath it means we're following
1787 # If we're forced to take the slowpath it means we're following
1787 # at least one pattern/directory, so don't bother with rename tracking.
1788 # at least one pattern/directory, so don't bother with rename tracking.
1788 if follow and not match.always() and not slowpath:
1789 if follow and not match.always() and not slowpath:
1789 # _makefollowlogfilematcher expects its files argument to be
1790 # _makefollowlogfilematcher expects its files argument to be
1790 # relative to the repo root, so use match.files(), not pats.
1791 # relative to the repo root, so use match.files(), not pats.
1791 filematcher = _makefollowlogfilematcher(repo, match.files(),
1792 filematcher = _makefollowlogfilematcher(repo, match.files(),
1792 followfirst)
1793 followfirst)
1793 else:
1794 else:
1794 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1795 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1795 if filematcher is None:
1796 if filematcher is None:
1796 filematcher = lambda rev: match
1797 filematcher = lambda rev: match
1797
1798
1798 expr = []
1799 expr = []
1799 for op, val in sorted(opts.iteritems()):
1800 for op, val in sorted(opts.iteritems()):
1800 if not val:
1801 if not val:
1801 continue
1802 continue
1802 if op not in opt2revset:
1803 if op not in opt2revset:
1803 continue
1804 continue
1804 revop, andor = opt2revset[op]
1805 revop, andor = opt2revset[op]
1805 if '%(val)' not in revop:
1806 if '%(val)' not in revop:
1806 expr.append(revop)
1807 expr.append(revop)
1807 else:
1808 else:
1808 if not isinstance(val, list):
1809 if not isinstance(val, list):
1809 e = revop % {'val': val}
1810 e = revop % {'val': val}
1810 else:
1811 else:
1811 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1812 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1812 expr.append(e)
1813 expr.append(e)
1813
1814
1814 if expr:
1815 if expr:
1815 expr = '(' + ' and '.join(expr) + ')'
1816 expr = '(' + ' and '.join(expr) + ')'
1816 else:
1817 else:
1817 expr = None
1818 expr = None
1818 return expr, filematcher
1819 return expr, filematcher
1819
1820
1820 def _logrevs(repo, opts):
1821 def _logrevs(repo, opts):
1821 # Default --rev value depends on --follow but --follow behaviour
1822 # Default --rev value depends on --follow but --follow behaviour
1822 # depends on revisions resolved from --rev...
1823 # depends on revisions resolved from --rev...
1823 follow = opts.get('follow') or opts.get('follow_first')
1824 follow = opts.get('follow') or opts.get('follow_first')
1824 if opts.get('rev'):
1825 if opts.get('rev'):
1825 revs = scmutil.revrange(repo, opts['rev'])
1826 revs = scmutil.revrange(repo, opts['rev'])
1826 elif follow and repo.dirstate.p1() == nullid:
1827 elif follow and repo.dirstate.p1() == nullid:
1827 revs = revset.baseset()
1828 revs = revset.baseset()
1828 elif follow:
1829 elif follow:
1829 revs = repo.revs('reverse(:.)')
1830 revs = repo.revs('reverse(:.)')
1830 else:
1831 else:
1831 revs = revset.spanset(repo)
1832 revs = revset.spanset(repo)
1832 revs.reverse()
1833 revs.reverse()
1833 return revs
1834 return revs
1834
1835
1835 def getgraphlogrevs(repo, pats, opts):
1836 def getgraphlogrevs(repo, pats, opts):
1836 """Return (revs, expr, filematcher) where revs is an iterable of
1837 """Return (revs, expr, filematcher) where revs is an iterable of
1837 revision numbers, expr is a revset string built from log options
1838 revision numbers, expr is a revset string built from log options
1838 and file patterns or None, and used to filter 'revs'. If --stat or
1839 and file patterns or None, and used to filter 'revs'. If --stat or
1839 --patch are not passed filematcher is None. Otherwise it is a
1840 --patch are not passed filematcher is None. Otherwise it is a
1840 callable taking a revision number and returning a match objects
1841 callable taking a revision number and returning a match objects
1841 filtering the files to be detailed when displaying the revision.
1842 filtering the files to be detailed when displaying the revision.
1842 """
1843 """
1843 limit = loglimit(opts)
1844 limit = loglimit(opts)
1844 revs = _logrevs(repo, opts)
1845 revs = _logrevs(repo, opts)
1845 if not revs:
1846 if not revs:
1846 return revset.baseset(), None, None
1847 return revset.baseset(), None, None
1847 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1848 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1848 if opts.get('rev'):
1849 if opts.get('rev'):
1849 # User-specified revs might be unsorted, but don't sort before
1850 # User-specified revs might be unsorted, but don't sort before
1850 # _makelogrevset because it might depend on the order of revs
1851 # _makelogrevset because it might depend on the order of revs
1851 revs.sort(reverse=True)
1852 revs.sort(reverse=True)
1852 if expr:
1853 if expr:
1853 # Revset matchers often operate faster on revisions in changelog
1854 # Revset matchers often operate faster on revisions in changelog
1854 # order, because most filters deal with the changelog.
1855 # order, because most filters deal with the changelog.
1855 revs.reverse()
1856 revs.reverse()
1856 matcher = revset.match(repo.ui, expr)
1857 matcher = revset.match(repo.ui, expr)
1857 # Revset matches can reorder revisions. "A or B" typically returns
1858 # Revset matches can reorder revisions. "A or B" typically returns
1858 # returns the revision matching A then the revision matching B. Sort
1859 # returns the revision matching A then the revision matching B. Sort
1859 # again to fix that.
1860 # again to fix that.
1860 revs = matcher(repo, revs)
1861 revs = matcher(repo, revs)
1861 revs.sort(reverse=True)
1862 revs.sort(reverse=True)
1862 if limit is not None:
1863 if limit is not None:
1863 limitedrevs = []
1864 limitedrevs = []
1864 for idx, rev in enumerate(revs):
1865 for idx, rev in enumerate(revs):
1865 if idx >= limit:
1866 if idx >= limit:
1866 break
1867 break
1867 limitedrevs.append(rev)
1868 limitedrevs.append(rev)
1868 revs = revset.baseset(limitedrevs)
1869 revs = revset.baseset(limitedrevs)
1869
1870
1870 return revs, expr, filematcher
1871 return revs, expr, filematcher
1871
1872
1872 def getlogrevs(repo, pats, opts):
1873 def getlogrevs(repo, pats, opts):
1873 """Return (revs, expr, filematcher) where revs is an iterable of
1874 """Return (revs, expr, filematcher) where revs is an iterable of
1874 revision numbers, expr is a revset string built from log options
1875 revision numbers, expr is a revset string built from log options
1875 and file patterns or None, and used to filter 'revs'. If --stat or
1876 and file patterns or None, and used to filter 'revs'. If --stat or
1876 --patch are not passed filematcher is None. Otherwise it is a
1877 --patch are not passed filematcher is None. Otherwise it is a
1877 callable taking a revision number and returning a match objects
1878 callable taking a revision number and returning a match objects
1878 filtering the files to be detailed when displaying the revision.
1879 filtering the files to be detailed when displaying the revision.
1879 """
1880 """
1880 limit = loglimit(opts)
1881 limit = loglimit(opts)
1881 revs = _logrevs(repo, opts)
1882 revs = _logrevs(repo, opts)
1882 if not revs:
1883 if not revs:
1883 return revset.baseset([]), None, None
1884 return revset.baseset([]), None, None
1884 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1885 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1885 if expr:
1886 if expr:
1886 # Revset matchers often operate faster on revisions in changelog
1887 # Revset matchers often operate faster on revisions in changelog
1887 # order, because most filters deal with the changelog.
1888 # order, because most filters deal with the changelog.
1888 if not opts.get('rev'):
1889 if not opts.get('rev'):
1889 revs.reverse()
1890 revs.reverse()
1890 matcher = revset.match(repo.ui, expr)
1891 matcher = revset.match(repo.ui, expr)
1891 # Revset matches can reorder revisions. "A or B" typically returns
1892 # Revset matches can reorder revisions. "A or B" typically returns
1892 # returns the revision matching A then the revision matching B. Sort
1893 # returns the revision matching A then the revision matching B. Sort
1893 # again to fix that.
1894 # again to fix that.
1894 revs = matcher(repo, revs)
1895 revs = matcher(repo, revs)
1895 if not opts.get('rev'):
1896 if not opts.get('rev'):
1896 revs.sort(reverse=True)
1897 revs.sort(reverse=True)
1897 if limit is not None:
1898 if limit is not None:
1898 count = 0
1899 count = 0
1899 limitedrevs = []
1900 limitedrevs = []
1900 it = iter(revs)
1901 it = iter(revs)
1901 while count < limit:
1902 while count < limit:
1902 try:
1903 try:
1903 limitedrevs.append(it.next())
1904 limitedrevs.append(it.next())
1904 except (StopIteration):
1905 except (StopIteration):
1905 break
1906 break
1906 count += 1
1907 count += 1
1907 revs = revset.baseset(limitedrevs)
1908 revs = revset.baseset(limitedrevs)
1908
1909
1909 return revs, expr, filematcher
1910 return revs, expr, filematcher
1910
1911
1911 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1912 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1912 filematcher=None):
1913 filematcher=None):
1913 seen, state = [], graphmod.asciistate()
1914 seen, state = [], graphmod.asciistate()
1914 for rev, type, ctx, parents in dag:
1915 for rev, type, ctx, parents in dag:
1915 char = 'o'
1916 char = 'o'
1916 if ctx.node() in showparents:
1917 if ctx.node() in showparents:
1917 char = '@'
1918 char = '@'
1918 elif ctx.obsolete():
1919 elif ctx.obsolete():
1919 char = 'x'
1920 char = 'x'
1920 elif ctx.closesbranch():
1921 elif ctx.closesbranch():
1921 char = '_'
1922 char = '_'
1922 copies = None
1923 copies = None
1923 if getrenamed and ctx.rev():
1924 if getrenamed and ctx.rev():
1924 copies = []
1925 copies = []
1925 for fn in ctx.files():
1926 for fn in ctx.files():
1926 rename = getrenamed(fn, ctx.rev())
1927 rename = getrenamed(fn, ctx.rev())
1927 if rename:
1928 if rename:
1928 copies.append((fn, rename[0]))
1929 copies.append((fn, rename[0]))
1929 revmatchfn = None
1930 revmatchfn = None
1930 if filematcher is not None:
1931 if filematcher is not None:
1931 revmatchfn = filematcher(ctx.rev())
1932 revmatchfn = filematcher(ctx.rev())
1932 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1933 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1933 lines = displayer.hunk.pop(rev).split('\n')
1934 lines = displayer.hunk.pop(rev).split('\n')
1934 if not lines[-1]:
1935 if not lines[-1]:
1935 del lines[-1]
1936 del lines[-1]
1936 displayer.flush(rev)
1937 displayer.flush(rev)
1937 edges = edgefn(type, char, lines, seen, rev, parents)
1938 edges = edgefn(type, char, lines, seen, rev, parents)
1938 for type, char, lines, coldata in edges:
1939 for type, char, lines, coldata in edges:
1939 graphmod.ascii(ui, state, type, char, lines, coldata)
1940 graphmod.ascii(ui, state, type, char, lines, coldata)
1940 displayer.close()
1941 displayer.close()
1941
1942
1942 def graphlog(ui, repo, *pats, **opts):
1943 def graphlog(ui, repo, *pats, **opts):
1943 # Parameters are identical to log command ones
1944 # Parameters are identical to log command ones
1944 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1945 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1945 revdag = graphmod.dagwalker(repo, revs)
1946 revdag = graphmod.dagwalker(repo, revs)
1946
1947
1947 getrenamed = None
1948 getrenamed = None
1948 if opts.get('copies'):
1949 if opts.get('copies'):
1949 endrev = None
1950 endrev = None
1950 if opts.get('rev'):
1951 if opts.get('rev'):
1951 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1952 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1952 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1953 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1953 displayer = show_changeset(ui, repo, opts, buffered=True)
1954 displayer = show_changeset(ui, repo, opts, buffered=True)
1954 showparents = [ctx.node() for ctx in repo[None].parents()]
1955 showparents = [ctx.node() for ctx in repo[None].parents()]
1955 displaygraph(ui, revdag, displayer, showparents,
1956 displaygraph(ui, revdag, displayer, showparents,
1956 graphmod.asciiedges, getrenamed, filematcher)
1957 graphmod.asciiedges, getrenamed, filematcher)
1957
1958
1958 def checkunsupportedgraphflags(pats, opts):
1959 def checkunsupportedgraphflags(pats, opts):
1959 for op in ["newest_first"]:
1960 for op in ["newest_first"]:
1960 if op in opts and opts[op]:
1961 if op in opts and opts[op]:
1961 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1962 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1962 % op.replace("_", "-"))
1963 % op.replace("_", "-"))
1963
1964
1964 def graphrevs(repo, nodes, opts):
1965 def graphrevs(repo, nodes, opts):
1965 limit = loglimit(opts)
1966 limit = loglimit(opts)
1966 nodes.reverse()
1967 nodes.reverse()
1967 if limit is not None:
1968 if limit is not None:
1968 nodes = nodes[:limit]
1969 nodes = nodes[:limit]
1969 return graphmod.nodes(repo, nodes)
1970 return graphmod.nodes(repo, nodes)
1970
1971
1971 def add(ui, repo, match, prefix, explicitonly, **opts):
1972 def add(ui, repo, match, prefix, explicitonly, **opts):
1972 join = lambda f: os.path.join(prefix, f)
1973 join = lambda f: os.path.join(prefix, f)
1973 bad = []
1974 bad = []
1974 oldbad = match.bad
1975 oldbad = match.bad
1975 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1976 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1976 names = []
1977 names = []
1977 wctx = repo[None]
1978 wctx = repo[None]
1978 cca = None
1979 cca = None
1979 abort, warn = scmutil.checkportabilityalert(ui)
1980 abort, warn = scmutil.checkportabilityalert(ui)
1980 if abort or warn:
1981 if abort or warn:
1981 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1982 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1982 for f in wctx.walk(match):
1983 for f in wctx.walk(match):
1983 exact = match.exact(f)
1984 exact = match.exact(f)
1984 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1985 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1985 if cca:
1986 if cca:
1986 cca(f)
1987 cca(f)
1987 names.append(f)
1988 names.append(f)
1988 if ui.verbose or not exact:
1989 if ui.verbose or not exact:
1989 ui.status(_('adding %s\n') % match.rel(f))
1990 ui.status(_('adding %s\n') % match.rel(f))
1990
1991
1991 for subpath in sorted(wctx.substate):
1992 for subpath in sorted(wctx.substate):
1992 sub = wctx.sub(subpath)
1993 sub = wctx.sub(subpath)
1993 try:
1994 try:
1994 submatch = matchmod.narrowmatcher(subpath, match)
1995 submatch = matchmod.narrowmatcher(subpath, match)
1995 if opts.get('subrepos'):
1996 if opts.get('subrepos'):
1996 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
1997 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
1997 else:
1998 else:
1998 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
1999 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
1999 except error.LookupError:
2000 except error.LookupError:
2000 ui.status(_("skipping missing subrepository: %s\n")
2001 ui.status(_("skipping missing subrepository: %s\n")
2001 % join(subpath))
2002 % join(subpath))
2002
2003
2003 if not opts.get('dry_run'):
2004 if not opts.get('dry_run'):
2004 rejected = wctx.add(names, prefix)
2005 rejected = wctx.add(names, prefix)
2005 bad.extend(f for f in rejected if f in match.files())
2006 bad.extend(f for f in rejected if f in match.files())
2006 return bad
2007 return bad
2007
2008
2008 def forget(ui, repo, match, prefix, explicitonly):
2009 def forget(ui, repo, match, prefix, explicitonly):
2009 join = lambda f: os.path.join(prefix, f)
2010 join = lambda f: os.path.join(prefix, f)
2010 bad = []
2011 bad = []
2011 oldbad = match.bad
2012 oldbad = match.bad
2012 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2013 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2013 wctx = repo[None]
2014 wctx = repo[None]
2014 forgot = []
2015 forgot = []
2015 s = repo.status(match=match, clean=True)
2016 s = repo.status(match=match, clean=True)
2016 forget = sorted(s[0] + s[1] + s[3] + s[6])
2017 forget = sorted(s[0] + s[1] + s[3] + s[6])
2017 if explicitonly:
2018 if explicitonly:
2018 forget = [f for f in forget if match.exact(f)]
2019 forget = [f for f in forget if match.exact(f)]
2019
2020
2020 for subpath in sorted(wctx.substate):
2021 for subpath in sorted(wctx.substate):
2021 sub = wctx.sub(subpath)
2022 sub = wctx.sub(subpath)
2022 try:
2023 try:
2023 submatch = matchmod.narrowmatcher(subpath, match)
2024 submatch = matchmod.narrowmatcher(subpath, match)
2024 subbad, subforgot = sub.forget(submatch, prefix)
2025 subbad, subforgot = sub.forget(submatch, prefix)
2025 bad.extend([subpath + '/' + f for f in subbad])
2026 bad.extend([subpath + '/' + f for f in subbad])
2026 forgot.extend([subpath + '/' + f for f in subforgot])
2027 forgot.extend([subpath + '/' + f for f in subforgot])
2027 except error.LookupError:
2028 except error.LookupError:
2028 ui.status(_("skipping missing subrepository: %s\n")
2029 ui.status(_("skipping missing subrepository: %s\n")
2029 % join(subpath))
2030 % join(subpath))
2030
2031
2031 if not explicitonly:
2032 if not explicitonly:
2032 for f in match.files():
2033 for f in match.files():
2033 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2034 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2034 if f not in forgot:
2035 if f not in forgot:
2035 if repo.wvfs.exists(f):
2036 if repo.wvfs.exists(f):
2036 ui.warn(_('not removing %s: '
2037 ui.warn(_('not removing %s: '
2037 'file is already untracked\n')
2038 'file is already untracked\n')
2038 % match.rel(f))
2039 % match.rel(f))
2039 bad.append(f)
2040 bad.append(f)
2040
2041
2041 for f in forget:
2042 for f in forget:
2042 if ui.verbose or not match.exact(f):
2043 if ui.verbose or not match.exact(f):
2043 ui.status(_('removing %s\n') % match.rel(f))
2044 ui.status(_('removing %s\n') % match.rel(f))
2044
2045
2045 rejected = wctx.forget(forget, prefix)
2046 rejected = wctx.forget(forget, prefix)
2046 bad.extend(f for f in rejected if f in match.files())
2047 bad.extend(f for f in rejected if f in match.files())
2047 forgot.extend(f for f in forget if f not in rejected)
2048 forgot.extend(f for f in forget if f not in rejected)
2048 return bad, forgot
2049 return bad, forgot
2049
2050
2050 def remove(ui, repo, m, prefix, after, force, subrepos):
2051 def remove(ui, repo, m, prefix, after, force, subrepos):
2051 join = lambda f: os.path.join(prefix, f)
2052 join = lambda f: os.path.join(prefix, f)
2052 ret = 0
2053 ret = 0
2053 s = repo.status(match=m, clean=True)
2054 s = repo.status(match=m, clean=True)
2054 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2055 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2055
2056
2056 wctx = repo[None]
2057 wctx = repo[None]
2057
2058
2058 for subpath in sorted(wctx.substate):
2059 for subpath in sorted(wctx.substate):
2059 def matchessubrepo(matcher, subpath):
2060 def matchessubrepo(matcher, subpath):
2060 if matcher.exact(subpath):
2061 if matcher.exact(subpath):
2061 return True
2062 return True
2062 for f in matcher.files():
2063 for f in matcher.files():
2063 if f.startswith(subpath):
2064 if f.startswith(subpath):
2064 return True
2065 return True
2065 return False
2066 return False
2066
2067
2067 if subrepos or matchessubrepo(m, subpath):
2068 if subrepos or matchessubrepo(m, subpath):
2068 sub = wctx.sub(subpath)
2069 sub = wctx.sub(subpath)
2069 try:
2070 try:
2070 submatch = matchmod.narrowmatcher(subpath, m)
2071 submatch = matchmod.narrowmatcher(subpath, m)
2071 if sub.removefiles(submatch, prefix, after, force, subrepos):
2072 if sub.removefiles(submatch, prefix, after, force, subrepos):
2072 ret = 1
2073 ret = 1
2073 except error.LookupError:
2074 except error.LookupError:
2074 ui.status(_("skipping missing subrepository: %s\n")
2075 ui.status(_("skipping missing subrepository: %s\n")
2075 % join(subpath))
2076 % join(subpath))
2076
2077
2077 # warn about failure to delete explicit files/dirs
2078 # warn about failure to delete explicit files/dirs
2078 deleteddirs = scmutil.dirs(deleted)
2079 deleteddirs = scmutil.dirs(deleted)
2079 for f in m.files():
2080 for f in m.files():
2080 def insubrepo():
2081 def insubrepo():
2081 for subpath in wctx.substate:
2082 for subpath in wctx.substate:
2082 if f.startswith(subpath):
2083 if f.startswith(subpath):
2083 return True
2084 return True
2084 return False
2085 return False
2085
2086
2086 isdir = f in deleteddirs or f in wctx.dirs()
2087 isdir = f in deleteddirs or f in wctx.dirs()
2087 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2088 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2088 continue
2089 continue
2089
2090
2090 if repo.wvfs.exists(f):
2091 if repo.wvfs.exists(f):
2091 if repo.wvfs.isdir(f):
2092 if repo.wvfs.isdir(f):
2092 ui.warn(_('not removing %s: no tracked files\n')
2093 ui.warn(_('not removing %s: no tracked files\n')
2093 % m.rel(f))
2094 % m.rel(f))
2094 else:
2095 else:
2095 ui.warn(_('not removing %s: file is untracked\n')
2096 ui.warn(_('not removing %s: file is untracked\n')
2096 % m.rel(f))
2097 % m.rel(f))
2097 # missing files will generate a warning elsewhere
2098 # missing files will generate a warning elsewhere
2098 ret = 1
2099 ret = 1
2099
2100
2100 if force:
2101 if force:
2101 list = modified + deleted + clean + added
2102 list = modified + deleted + clean + added
2102 elif after:
2103 elif after:
2103 list = deleted
2104 list = deleted
2104 for f in modified + added + clean:
2105 for f in modified + added + clean:
2105 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2106 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2106 ret = 1
2107 ret = 1
2107 else:
2108 else:
2108 list = deleted + clean
2109 list = deleted + clean
2109 for f in modified:
2110 for f in modified:
2110 ui.warn(_('not removing %s: file is modified (use -f'
2111 ui.warn(_('not removing %s: file is modified (use -f'
2111 ' to force removal)\n') % m.rel(f))
2112 ' to force removal)\n') % m.rel(f))
2112 ret = 1
2113 ret = 1
2113 for f in added:
2114 for f in added:
2114 ui.warn(_('not removing %s: file has been marked for add'
2115 ui.warn(_('not removing %s: file has been marked for add'
2115 ' (use forget to undo)\n') % m.rel(f))
2116 ' (use forget to undo)\n') % m.rel(f))
2116 ret = 1
2117 ret = 1
2117
2118
2118 for f in sorted(list):
2119 for f in sorted(list):
2119 if ui.verbose or not m.exact(f):
2120 if ui.verbose or not m.exact(f):
2120 ui.status(_('removing %s\n') % m.rel(f))
2121 ui.status(_('removing %s\n') % m.rel(f))
2121
2122
2122 wlock = repo.wlock()
2123 wlock = repo.wlock()
2123 try:
2124 try:
2124 if not after:
2125 if not after:
2125 for f in list:
2126 for f in list:
2126 if f in added:
2127 if f in added:
2127 continue # we never unlink added files on remove
2128 continue # we never unlink added files on remove
2128 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2129 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2129 repo[None].forget(list)
2130 repo[None].forget(list)
2130 finally:
2131 finally:
2131 wlock.release()
2132 wlock.release()
2132
2133
2133 return ret
2134 return ret
2134
2135
2135 def cat(ui, repo, ctx, matcher, prefix, **opts):
2136 def cat(ui, repo, ctx, matcher, prefix, **opts):
2136 err = 1
2137 err = 1
2137
2138
2138 def write(path):
2139 def write(path):
2139 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2140 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2140 pathname=os.path.join(prefix, path))
2141 pathname=os.path.join(prefix, path))
2141 data = ctx[path].data()
2142 data = ctx[path].data()
2142 if opts.get('decode'):
2143 if opts.get('decode'):
2143 data = repo.wwritedata(path, data)
2144 data = repo.wwritedata(path, data)
2144 fp.write(data)
2145 fp.write(data)
2145 fp.close()
2146 fp.close()
2146
2147
2147 # Automation often uses hg cat on single files, so special case it
2148 # Automation often uses hg cat on single files, so special case it
2148 # for performance to avoid the cost of parsing the manifest.
2149 # for performance to avoid the cost of parsing the manifest.
2149 if len(matcher.files()) == 1 and not matcher.anypats():
2150 if len(matcher.files()) == 1 and not matcher.anypats():
2150 file = matcher.files()[0]
2151 file = matcher.files()[0]
2151 mf = repo.manifest
2152 mf = repo.manifest
2152 mfnode = ctx._changeset[0]
2153 mfnode = ctx._changeset[0]
2153 if mf.find(mfnode, file)[0]:
2154 if mf.find(mfnode, file)[0]:
2154 write(file)
2155 write(file)
2155 return 0
2156 return 0
2156
2157
2157 # Don't warn about "missing" files that are really in subrepos
2158 # Don't warn about "missing" files that are really in subrepos
2158 bad = matcher.bad
2159 bad = matcher.bad
2159
2160
2160 def badfn(path, msg):
2161 def badfn(path, msg):
2161 for subpath in ctx.substate:
2162 for subpath in ctx.substate:
2162 if path.startswith(subpath):
2163 if path.startswith(subpath):
2163 return
2164 return
2164 bad(path, msg)
2165 bad(path, msg)
2165
2166
2166 matcher.bad = badfn
2167 matcher.bad = badfn
2167
2168
2168 for abs in ctx.walk(matcher):
2169 for abs in ctx.walk(matcher):
2169 write(abs)
2170 write(abs)
2170 err = 0
2171 err = 0
2171
2172
2172 matcher.bad = bad
2173 matcher.bad = bad
2173
2174
2174 for subpath in sorted(ctx.substate):
2175 for subpath in sorted(ctx.substate):
2175 sub = ctx.sub(subpath)
2176 sub = ctx.sub(subpath)
2176 try:
2177 try:
2177 submatch = matchmod.narrowmatcher(subpath, matcher)
2178 submatch = matchmod.narrowmatcher(subpath, matcher)
2178
2179
2179 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2180 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2180 **opts):
2181 **opts):
2181 err = 0
2182 err = 0
2182 except error.RepoLookupError:
2183 except error.RepoLookupError:
2183 ui.status(_("skipping missing subrepository: %s\n")
2184 ui.status(_("skipping missing subrepository: %s\n")
2184 % os.path.join(prefix, subpath))
2185 % os.path.join(prefix, subpath))
2185
2186
2186 return err
2187 return err
2187
2188
2188 def commit(ui, repo, commitfunc, pats, opts):
2189 def commit(ui, repo, commitfunc, pats, opts):
2189 '''commit the specified files or all outstanding changes'''
2190 '''commit the specified files or all outstanding changes'''
2190 date = opts.get('date')
2191 date = opts.get('date')
2191 if date:
2192 if date:
2192 opts['date'] = util.parsedate(date)
2193 opts['date'] = util.parsedate(date)
2193 message = logmessage(ui, opts)
2194 message = logmessage(ui, opts)
2194 matcher = scmutil.match(repo[None], pats, opts)
2195 matcher = scmutil.match(repo[None], pats, opts)
2195
2196
2196 # extract addremove carefully -- this function can be called from a command
2197 # extract addremove carefully -- this function can be called from a command
2197 # that doesn't support addremove
2198 # that doesn't support addremove
2198 if opts.get('addremove'):
2199 if opts.get('addremove'):
2199 if scmutil.addremove(repo, matcher, "", opts) != 0:
2200 if scmutil.addremove(repo, matcher, "", opts) != 0:
2200 raise util.Abort(
2201 raise util.Abort(
2201 _("failed to mark all new/missing files as added/removed"))
2202 _("failed to mark all new/missing files as added/removed"))
2202
2203
2203 return commitfunc(ui, repo, message, matcher, opts)
2204 return commitfunc(ui, repo, message, matcher, opts)
2204
2205
2205 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2206 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2206 # amend will reuse the existing user if not specified, but the obsolete
2207 # amend will reuse the existing user if not specified, but the obsolete
2207 # marker creation requires that the current user's name is specified.
2208 # marker creation requires that the current user's name is specified.
2208 if obsolete._enabled:
2209 if obsolete._enabled:
2209 ui.username() # raise exception if username not set
2210 ui.username() # raise exception if username not set
2210
2211
2211 ui.note(_('amending changeset %s\n') % old)
2212 ui.note(_('amending changeset %s\n') % old)
2212 base = old.p1()
2213 base = old.p1()
2213
2214
2214 wlock = lock = newid = None
2215 wlock = lock = newid = None
2215 try:
2216 try:
2216 wlock = repo.wlock()
2217 wlock = repo.wlock()
2217 lock = repo.lock()
2218 lock = repo.lock()
2218 tr = repo.transaction('amend')
2219 tr = repo.transaction('amend')
2219 try:
2220 try:
2220 # See if we got a message from -m or -l, if not, open the editor
2221 # See if we got a message from -m or -l, if not, open the editor
2221 # with the message of the changeset to amend
2222 # with the message of the changeset to amend
2222 message = logmessage(ui, opts)
2223 message = logmessage(ui, opts)
2223 # ensure logfile does not conflict with later enforcement of the
2224 # ensure logfile does not conflict with later enforcement of the
2224 # message. potential logfile content has been processed by
2225 # message. potential logfile content has been processed by
2225 # `logmessage` anyway.
2226 # `logmessage` anyway.
2226 opts.pop('logfile')
2227 opts.pop('logfile')
2227 # First, do a regular commit to record all changes in the working
2228 # First, do a regular commit to record all changes in the working
2228 # directory (if there are any)
2229 # directory (if there are any)
2229 ui.callhooks = False
2230 ui.callhooks = False
2230 currentbookmark = repo._bookmarkcurrent
2231 currentbookmark = repo._bookmarkcurrent
2231 try:
2232 try:
2232 repo._bookmarkcurrent = None
2233 repo._bookmarkcurrent = None
2233 opts['message'] = 'temporary amend commit for %s' % old
2234 opts['message'] = 'temporary amend commit for %s' % old
2234 node = commit(ui, repo, commitfunc, pats, opts)
2235 node = commit(ui, repo, commitfunc, pats, opts)
2235 finally:
2236 finally:
2236 repo._bookmarkcurrent = currentbookmark
2237 repo._bookmarkcurrent = currentbookmark
2237 ui.callhooks = True
2238 ui.callhooks = True
2238 ctx = repo[node]
2239 ctx = repo[node]
2239
2240
2240 # Participating changesets:
2241 # Participating changesets:
2241 #
2242 #
2242 # node/ctx o - new (intermediate) commit that contains changes
2243 # node/ctx o - new (intermediate) commit that contains changes
2243 # | from working dir to go into amending commit
2244 # | from working dir to go into amending commit
2244 # | (or a workingctx if there were no changes)
2245 # | (or a workingctx if there were no changes)
2245 # |
2246 # |
2246 # old o - changeset to amend
2247 # old o - changeset to amend
2247 # |
2248 # |
2248 # base o - parent of amending changeset
2249 # base o - parent of amending changeset
2249
2250
2250 # Update extra dict from amended commit (e.g. to preserve graft
2251 # Update extra dict from amended commit (e.g. to preserve graft
2251 # source)
2252 # source)
2252 extra.update(old.extra())
2253 extra.update(old.extra())
2253
2254
2254 # Also update it from the intermediate commit or from the wctx
2255 # Also update it from the intermediate commit or from the wctx
2255 extra.update(ctx.extra())
2256 extra.update(ctx.extra())
2256
2257
2257 if len(old.parents()) > 1:
2258 if len(old.parents()) > 1:
2258 # ctx.files() isn't reliable for merges, so fall back to the
2259 # ctx.files() isn't reliable for merges, so fall back to the
2259 # slower repo.status() method
2260 # slower repo.status() method
2260 files = set([fn for st in repo.status(base, old)[:3]
2261 files = set([fn for st in repo.status(base, old)[:3]
2261 for fn in st])
2262 for fn in st])
2262 else:
2263 else:
2263 files = set(old.files())
2264 files = set(old.files())
2264
2265
2265 # Second, we use either the commit we just did, or if there were no
2266 # Second, we use either the commit we just did, or if there were no
2266 # changes the parent of the working directory as the version of the
2267 # changes the parent of the working directory as the version of the
2267 # files in the final amend commit
2268 # files in the final amend commit
2268 if node:
2269 if node:
2269 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2270 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2270
2271
2271 user = ctx.user()
2272 user = ctx.user()
2272 date = ctx.date()
2273 date = ctx.date()
2273 # Recompute copies (avoid recording a -> b -> a)
2274 # Recompute copies (avoid recording a -> b -> a)
2274 copied = copies.pathcopies(base, ctx)
2275 copied = copies.pathcopies(base, ctx)
2275 if old.p2:
2276 if old.p2:
2276 copied.update(copies.pathcopies(old.p2(), ctx))
2277 copied.update(copies.pathcopies(old.p2(), ctx))
2277
2278
2278 # Prune files which were reverted by the updates: if old
2279 # Prune files which were reverted by the updates: if old
2279 # introduced file X and our intermediate commit, node,
2280 # introduced file X and our intermediate commit, node,
2280 # renamed that file, then those two files are the same and
2281 # renamed that file, then those two files are the same and
2281 # we can discard X from our list of files. Likewise if X
2282 # we can discard X from our list of files. Likewise if X
2282 # was deleted, it's no longer relevant
2283 # was deleted, it's no longer relevant
2283 files.update(ctx.files())
2284 files.update(ctx.files())
2284
2285
2285 def samefile(f):
2286 def samefile(f):
2286 if f in ctx.manifest():
2287 if f in ctx.manifest():
2287 a = ctx.filectx(f)
2288 a = ctx.filectx(f)
2288 if f in base.manifest():
2289 if f in base.manifest():
2289 b = base.filectx(f)
2290 b = base.filectx(f)
2290 return (not a.cmp(b)
2291 return (not a.cmp(b)
2291 and a.flags() == b.flags())
2292 and a.flags() == b.flags())
2292 else:
2293 else:
2293 return False
2294 return False
2294 else:
2295 else:
2295 return f not in base.manifest()
2296 return f not in base.manifest()
2296 files = [f for f in files if not samefile(f)]
2297 files = [f for f in files if not samefile(f)]
2297
2298
2298 def filectxfn(repo, ctx_, path):
2299 def filectxfn(repo, ctx_, path):
2299 try:
2300 try:
2300 fctx = ctx[path]
2301 fctx = ctx[path]
2301 flags = fctx.flags()
2302 flags = fctx.flags()
2302 mctx = context.memfilectx(repo,
2303 mctx = context.memfilectx(repo,
2303 fctx.path(), fctx.data(),
2304 fctx.path(), fctx.data(),
2304 islink='l' in flags,
2305 islink='l' in flags,
2305 isexec='x' in flags,
2306 isexec='x' in flags,
2306 copied=copied.get(path))
2307 copied=copied.get(path))
2307 return mctx
2308 return mctx
2308 except KeyError:
2309 except KeyError:
2309 return None
2310 return None
2310 else:
2311 else:
2311 ui.note(_('copying changeset %s to %s\n') % (old, base))
2312 ui.note(_('copying changeset %s to %s\n') % (old, base))
2312
2313
2313 # Use version of files as in the old cset
2314 # Use version of files as in the old cset
2314 def filectxfn(repo, ctx_, path):
2315 def filectxfn(repo, ctx_, path):
2315 try:
2316 try:
2316 return old.filectx(path)
2317 return old.filectx(path)
2317 except KeyError:
2318 except KeyError:
2318 return None
2319 return None
2319
2320
2320 user = opts.get('user') or old.user()
2321 user = opts.get('user') or old.user()
2321 date = opts.get('date') or old.date()
2322 date = opts.get('date') or old.date()
2322 editform = mergeeditform(old, 'commit.amend')
2323 editform = mergeeditform(old, 'commit.amend')
2323 editor = getcommiteditor(editform=editform, **opts)
2324 editor = getcommiteditor(editform=editform, **opts)
2324 if not message:
2325 if not message:
2325 editor = getcommiteditor(edit=True, editform=editform)
2326 editor = getcommiteditor(edit=True, editform=editform)
2326 message = old.description()
2327 message = old.description()
2327
2328
2328 pureextra = extra.copy()
2329 pureextra = extra.copy()
2329 extra['amend_source'] = old.hex()
2330 extra['amend_source'] = old.hex()
2330
2331
2331 new = context.memctx(repo,
2332 new = context.memctx(repo,
2332 parents=[base.node(), old.p2().node()],
2333 parents=[base.node(), old.p2().node()],
2333 text=message,
2334 text=message,
2334 files=files,
2335 files=files,
2335 filectxfn=filectxfn,
2336 filectxfn=filectxfn,
2336 user=user,
2337 user=user,
2337 date=date,
2338 date=date,
2338 extra=extra,
2339 extra=extra,
2339 editor=editor)
2340 editor=editor)
2340
2341
2341 newdesc = changelog.stripdesc(new.description())
2342 newdesc = changelog.stripdesc(new.description())
2342 if ((not node)
2343 if ((not node)
2343 and newdesc == old.description()
2344 and newdesc == old.description()
2344 and user == old.user()
2345 and user == old.user()
2345 and date == old.date()
2346 and date == old.date()
2346 and pureextra == old.extra()):
2347 and pureextra == old.extra()):
2347 # nothing changed. continuing here would create a new node
2348 # nothing changed. continuing here would create a new node
2348 # anyway because of the amend_source noise.
2349 # anyway because of the amend_source noise.
2349 #
2350 #
2350 # This not what we expect from amend.
2351 # This not what we expect from amend.
2351 return old.node()
2352 return old.node()
2352
2353
2353 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2354 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2354 try:
2355 try:
2355 if opts.get('secret'):
2356 if opts.get('secret'):
2356 commitphase = 'secret'
2357 commitphase = 'secret'
2357 else:
2358 else:
2358 commitphase = old.phase()
2359 commitphase = old.phase()
2359 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2360 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2360 newid = repo.commitctx(new)
2361 newid = repo.commitctx(new)
2361 finally:
2362 finally:
2362 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2363 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2363 if newid != old.node():
2364 if newid != old.node():
2364 # Reroute the working copy parent to the new changeset
2365 # Reroute the working copy parent to the new changeset
2365 repo.setparents(newid, nullid)
2366 repo.setparents(newid, nullid)
2366
2367
2367 # Move bookmarks from old parent to amend commit
2368 # Move bookmarks from old parent to amend commit
2368 bms = repo.nodebookmarks(old.node())
2369 bms = repo.nodebookmarks(old.node())
2369 if bms:
2370 if bms:
2370 marks = repo._bookmarks
2371 marks = repo._bookmarks
2371 for bm in bms:
2372 for bm in bms:
2372 marks[bm] = newid
2373 marks[bm] = newid
2373 marks.write()
2374 marks.write()
2374 #commit the whole amend process
2375 #commit the whole amend process
2375 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2376 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2376 if createmarkers and newid != old.node():
2377 if createmarkers and newid != old.node():
2377 # mark the new changeset as successor of the rewritten one
2378 # mark the new changeset as successor of the rewritten one
2378 new = repo[newid]
2379 new = repo[newid]
2379 obs = [(old, (new,))]
2380 obs = [(old, (new,))]
2380 if node:
2381 if node:
2381 obs.append((ctx, ()))
2382 obs.append((ctx, ()))
2382
2383
2383 obsolete.createmarkers(repo, obs)
2384 obsolete.createmarkers(repo, obs)
2384 tr.close()
2385 tr.close()
2385 finally:
2386 finally:
2386 tr.release()
2387 tr.release()
2387 if not createmarkers and newid != old.node():
2388 if not createmarkers and newid != old.node():
2388 # Strip the intermediate commit (if there was one) and the amended
2389 # Strip the intermediate commit (if there was one) and the amended
2389 # commit
2390 # commit
2390 if node:
2391 if node:
2391 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2392 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2392 ui.note(_('stripping amended changeset %s\n') % old)
2393 ui.note(_('stripping amended changeset %s\n') % old)
2393 repair.strip(ui, repo, old.node(), topic='amend-backup')
2394 repair.strip(ui, repo, old.node(), topic='amend-backup')
2394 finally:
2395 finally:
2395 if newid is None:
2396 if newid is None:
2396 repo.dirstate.invalidate()
2397 repo.dirstate.invalidate()
2397 lockmod.release(lock, wlock)
2398 lockmod.release(lock, wlock)
2398 return newid
2399 return newid
2399
2400
2400 def commiteditor(repo, ctx, subs, editform=''):
2401 def commiteditor(repo, ctx, subs, editform=''):
2401 if ctx.description():
2402 if ctx.description():
2402 return ctx.description()
2403 return ctx.description()
2403 return commitforceeditor(repo, ctx, subs, editform=editform)
2404 return commitforceeditor(repo, ctx, subs, editform=editform)
2404
2405
2405 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2406 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2406 editform=''):
2407 editform=''):
2407 if not extramsg:
2408 if not extramsg:
2408 extramsg = _("Leave message empty to abort commit.")
2409 extramsg = _("Leave message empty to abort commit.")
2409
2410
2410 forms = [e for e in editform.split('.') if e]
2411 forms = [e for e in editform.split('.') if e]
2411 forms.insert(0, 'changeset')
2412 forms.insert(0, 'changeset')
2412 while forms:
2413 while forms:
2413 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2414 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2414 if tmpl:
2415 if tmpl:
2415 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2416 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2416 break
2417 break
2417 forms.pop()
2418 forms.pop()
2418 else:
2419 else:
2419 committext = buildcommittext(repo, ctx, subs, extramsg)
2420 committext = buildcommittext(repo, ctx, subs, extramsg)
2420
2421
2421 # run editor in the repository root
2422 # run editor in the repository root
2422 olddir = os.getcwd()
2423 olddir = os.getcwd()
2423 os.chdir(repo.root)
2424 os.chdir(repo.root)
2424 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2425 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2425 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2426 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2426 os.chdir(olddir)
2427 os.chdir(olddir)
2427
2428
2428 if finishdesc:
2429 if finishdesc:
2429 text = finishdesc(text)
2430 text = finishdesc(text)
2430 if not text.strip():
2431 if not text.strip():
2431 raise util.Abort(_("empty commit message"))
2432 raise util.Abort(_("empty commit message"))
2432
2433
2433 return text
2434 return text
2434
2435
2435 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2436 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2436 ui = repo.ui
2437 ui = repo.ui
2437 tmpl, mapfile = gettemplate(ui, tmpl, None)
2438 tmpl, mapfile = gettemplate(ui, tmpl, None)
2438
2439
2439 try:
2440 try:
2440 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2441 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2441 except SyntaxError, inst:
2442 except SyntaxError, inst:
2442 raise util.Abort(inst.args[0])
2443 raise util.Abort(inst.args[0])
2443
2444
2444 for k, v in repo.ui.configitems('committemplate'):
2445 for k, v in repo.ui.configitems('committemplate'):
2445 if k != 'changeset':
2446 if k != 'changeset':
2446 t.t.cache[k] = v
2447 t.t.cache[k] = v
2447
2448
2448 if not extramsg:
2449 if not extramsg:
2449 extramsg = '' # ensure that extramsg is string
2450 extramsg = '' # ensure that extramsg is string
2450
2451
2451 ui.pushbuffer()
2452 ui.pushbuffer()
2452 t.show(ctx, extramsg=extramsg)
2453 t.show(ctx, extramsg=extramsg)
2453 return ui.popbuffer()
2454 return ui.popbuffer()
2454
2455
2455 def buildcommittext(repo, ctx, subs, extramsg):
2456 def buildcommittext(repo, ctx, subs, extramsg):
2456 edittext = []
2457 edittext = []
2457 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2458 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2458 if ctx.description():
2459 if ctx.description():
2459 edittext.append(ctx.description())
2460 edittext.append(ctx.description())
2460 edittext.append("")
2461 edittext.append("")
2461 edittext.append("") # Empty line between message and comments.
2462 edittext.append("") # Empty line between message and comments.
2462 edittext.append(_("HG: Enter commit message."
2463 edittext.append(_("HG: Enter commit message."
2463 " Lines beginning with 'HG:' are removed."))
2464 " Lines beginning with 'HG:' are removed."))
2464 edittext.append("HG: %s" % extramsg)
2465 edittext.append("HG: %s" % extramsg)
2465 edittext.append("HG: --")
2466 edittext.append("HG: --")
2466 edittext.append(_("HG: user: %s") % ctx.user())
2467 edittext.append(_("HG: user: %s") % ctx.user())
2467 if ctx.p2():
2468 if ctx.p2():
2468 edittext.append(_("HG: branch merge"))
2469 edittext.append(_("HG: branch merge"))
2469 if ctx.branch():
2470 if ctx.branch():
2470 edittext.append(_("HG: branch '%s'") % ctx.branch())
2471 edittext.append(_("HG: branch '%s'") % ctx.branch())
2471 if bookmarks.iscurrent(repo):
2472 if bookmarks.iscurrent(repo):
2472 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2473 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2473 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2474 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2474 edittext.extend([_("HG: added %s") % f for f in added])
2475 edittext.extend([_("HG: added %s") % f for f in added])
2475 edittext.extend([_("HG: changed %s") % f for f in modified])
2476 edittext.extend([_("HG: changed %s") % f for f in modified])
2476 edittext.extend([_("HG: removed %s") % f for f in removed])
2477 edittext.extend([_("HG: removed %s") % f for f in removed])
2477 if not added and not modified and not removed:
2478 if not added and not modified and not removed:
2478 edittext.append(_("HG: no files changed"))
2479 edittext.append(_("HG: no files changed"))
2479 edittext.append("")
2480 edittext.append("")
2480
2481
2481 return "\n".join(edittext)
2482 return "\n".join(edittext)
2482
2483
2483 def commitstatus(repo, node, branch, bheads=None, opts={}):
2484 def commitstatus(repo, node, branch, bheads=None, opts={}):
2484 ctx = repo[node]
2485 ctx = repo[node]
2485 parents = ctx.parents()
2486 parents = ctx.parents()
2486
2487
2487 if (not opts.get('amend') and bheads and node not in bheads and not
2488 if (not opts.get('amend') and bheads and node not in bheads and not
2488 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2489 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2489 repo.ui.status(_('created new head\n'))
2490 repo.ui.status(_('created new head\n'))
2490 # The message is not printed for initial roots. For the other
2491 # The message is not printed for initial roots. For the other
2491 # changesets, it is printed in the following situations:
2492 # changesets, it is printed in the following situations:
2492 #
2493 #
2493 # Par column: for the 2 parents with ...
2494 # Par column: for the 2 parents with ...
2494 # N: null or no parent
2495 # N: null or no parent
2495 # B: parent is on another named branch
2496 # B: parent is on another named branch
2496 # C: parent is a regular non head changeset
2497 # C: parent is a regular non head changeset
2497 # H: parent was a branch head of the current branch
2498 # H: parent was a branch head of the current branch
2498 # Msg column: whether we print "created new head" message
2499 # Msg column: whether we print "created new head" message
2499 # In the following, it is assumed that there already exists some
2500 # In the following, it is assumed that there already exists some
2500 # initial branch heads of the current branch, otherwise nothing is
2501 # initial branch heads of the current branch, otherwise nothing is
2501 # printed anyway.
2502 # printed anyway.
2502 #
2503 #
2503 # Par Msg Comment
2504 # Par Msg Comment
2504 # N N y additional topo root
2505 # N N y additional topo root
2505 #
2506 #
2506 # B N y additional branch root
2507 # B N y additional branch root
2507 # C N y additional topo head
2508 # C N y additional topo head
2508 # H N n usual case
2509 # H N n usual case
2509 #
2510 #
2510 # B B y weird additional branch root
2511 # B B y weird additional branch root
2511 # C B y branch merge
2512 # C B y branch merge
2512 # H B n merge with named branch
2513 # H B n merge with named branch
2513 #
2514 #
2514 # C C y additional head from merge
2515 # C C y additional head from merge
2515 # C H n merge with a head
2516 # C H n merge with a head
2516 #
2517 #
2517 # H H n head merge: head count decreases
2518 # H H n head merge: head count decreases
2518
2519
2519 if not opts.get('close_branch'):
2520 if not opts.get('close_branch'):
2520 for r in parents:
2521 for r in parents:
2521 if r.closesbranch() and r.branch() == branch:
2522 if r.closesbranch() and r.branch() == branch:
2522 repo.ui.status(_('reopening closed branch head %d\n') % r)
2523 repo.ui.status(_('reopening closed branch head %d\n') % r)
2523
2524
2524 if repo.ui.debugflag:
2525 if repo.ui.debugflag:
2525 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2526 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2526 elif repo.ui.verbose:
2527 elif repo.ui.verbose:
2527 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2528 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2528
2529
2529 def revert(ui, repo, ctx, parents, *pats, **opts):
2530 def revert(ui, repo, ctx, parents, *pats, **opts):
2530 parent, p2 = parents
2531 parent, p2 = parents
2531 node = ctx.node()
2532 node = ctx.node()
2532
2533
2533 mf = ctx.manifest()
2534 mf = ctx.manifest()
2534 if node == p2:
2535 if node == p2:
2535 parent = p2
2536 parent = p2
2536 if node == parent:
2537 if node == parent:
2537 pmf = mf
2538 pmf = mf
2538 else:
2539 else:
2539 pmf = None
2540 pmf = None
2540
2541
2541 # need all matching names in dirstate and manifest of target rev,
2542 # need all matching names in dirstate and manifest of target rev,
2542 # so have to walk both. do not print errors if files exist in one
2543 # so have to walk both. do not print errors if files exist in one
2543 # but not other.
2544 # but not other.
2544
2545
2545 # `names` is a mapping for all elements in working copy and target revision
2546 # `names` is a mapping for all elements in working copy and target revision
2546 # The mapping is in the form:
2547 # The mapping is in the form:
2547 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2548 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2548 names = {}
2549 names = {}
2549
2550
2550 wlock = repo.wlock()
2551 wlock = repo.wlock()
2551 try:
2552 try:
2552 ## filling of the `names` mapping
2553 ## filling of the `names` mapping
2553 # walk dirstate to fill `names`
2554 # walk dirstate to fill `names`
2554
2555
2555 m = scmutil.match(repo[None], pats, opts)
2556 m = scmutil.match(repo[None], pats, opts)
2556 if not m.always() or node != parent:
2557 if not m.always() or node != parent:
2557 m.bad = lambda x, y: False
2558 m.bad = lambda x, y: False
2558 for abs in repo.walk(m):
2559 for abs in repo.walk(m):
2559 names[abs] = m.rel(abs), m.exact(abs)
2560 names[abs] = m.rel(abs), m.exact(abs)
2560
2561
2561 # walk target manifest to fill `names`
2562 # walk target manifest to fill `names`
2562
2563
2563 def badfn(path, msg):
2564 def badfn(path, msg):
2564 if path in names:
2565 if path in names:
2565 return
2566 return
2566 if path in ctx.substate:
2567 if path in ctx.substate:
2567 return
2568 return
2568 path_ = path + '/'
2569 path_ = path + '/'
2569 for f in names:
2570 for f in names:
2570 if f.startswith(path_):
2571 if f.startswith(path_):
2571 return
2572 return
2572 ui.warn("%s: %s\n" % (m.rel(path), msg))
2573 ui.warn("%s: %s\n" % (m.rel(path), msg))
2573
2574
2574 m = scmutil.match(ctx, pats, opts)
2575 m = scmutil.match(ctx, pats, opts)
2575 m.bad = badfn
2576 m.bad = badfn
2576 for abs in ctx.walk(m):
2577 for abs in ctx.walk(m):
2577 if abs not in names:
2578 if abs not in names:
2578 names[abs] = m.rel(abs), m.exact(abs)
2579 names[abs] = m.rel(abs), m.exact(abs)
2579
2580
2580 # Find status of all file in `names`.
2581 # Find status of all file in `names`.
2581 m = scmutil.matchfiles(repo, names)
2582 m = scmutil.matchfiles(repo, names)
2582
2583
2583 changes = repo.status(node1=node, match=m,
2584 changes = repo.status(node1=node, match=m,
2584 unknown=True, ignored=True, clean=True)
2585 unknown=True, ignored=True, clean=True)
2585 else:
2586 else:
2586 changes = repo.status(match=m)
2587 changes = repo.status(match=m)
2587 for kind in changes:
2588 for kind in changes:
2588 for abs in kind:
2589 for abs in kind:
2589 names[abs] = m.rel(abs), m.exact(abs)
2590 names[abs] = m.rel(abs), m.exact(abs)
2590
2591
2591 m = scmutil.matchfiles(repo, names)
2592 m = scmutil.matchfiles(repo, names)
2592
2593
2593 modified = set(changes.modified)
2594 modified = set(changes.modified)
2594 added = set(changes.added)
2595 added = set(changes.added)
2595 removed = set(changes.removed)
2596 removed = set(changes.removed)
2596 _deleted = set(changes.deleted)
2597 _deleted = set(changes.deleted)
2597 unknown = set(changes.unknown)
2598 unknown = set(changes.unknown)
2598 unknown.update(changes.ignored)
2599 unknown.update(changes.ignored)
2599 clean = set(changes.clean)
2600 clean = set(changes.clean)
2600 modadded = set()
2601 modadded = set()
2601
2602
2602 # split between files known in target manifest and the others
2603 # split between files known in target manifest and the others
2603 smf = set(mf)
2604 smf = set(mf)
2604
2605
2605 # determine the exact nature of the deleted changesets
2606 # determine the exact nature of the deleted changesets
2606 deladded = _deleted - smf
2607 deladded = _deleted - smf
2607 deleted = _deleted - deladded
2608 deleted = _deleted - deladded
2608
2609
2609 # We need to account for the state of the file in the dirstate,
2610 # We need to account for the state of the file in the dirstate,
2610 # even when we revert against something else than parent. This will
2611 # even when we revert against something else than parent. This will
2611 # slightly alter the behavior of revert (doing back up or not, delete
2612 # slightly alter the behavior of revert (doing back up or not, delete
2612 # or just forget etc).
2613 # or just forget etc).
2613 if parent == node:
2614 if parent == node:
2614 dsmodified = modified
2615 dsmodified = modified
2615 dsadded = added
2616 dsadded = added
2616 dsremoved = removed
2617 dsremoved = removed
2617 # store all local modifications, useful later for rename detection
2618 # store all local modifications, useful later for rename detection
2618 localchanges = dsmodified | dsadded
2619 localchanges = dsmodified | dsadded
2619 modified, added, removed = set(), set(), set()
2620 modified, added, removed = set(), set(), set()
2620 else:
2621 else:
2621 changes = repo.status(node1=parent, match=m)
2622 changes = repo.status(node1=parent, match=m)
2622 dsmodified = set(changes.modified)
2623 dsmodified = set(changes.modified)
2623 dsadded = set(changes.added)
2624 dsadded = set(changes.added)
2624 dsremoved = set(changes.removed)
2625 dsremoved = set(changes.removed)
2625 # store all local modifications, useful later for rename detection
2626 # store all local modifications, useful later for rename detection
2626 localchanges = dsmodified | dsadded
2627 localchanges = dsmodified | dsadded
2627
2628
2628 # only take into account for removes between wc and target
2629 # only take into account for removes between wc and target
2629 clean |= dsremoved - removed
2630 clean |= dsremoved - removed
2630 dsremoved &= removed
2631 dsremoved &= removed
2631 # distinct between dirstate remove and other
2632 # distinct between dirstate remove and other
2632 removed -= dsremoved
2633 removed -= dsremoved
2633
2634
2634 modadded = added & dsmodified
2635 modadded = added & dsmodified
2635 added -= modadded
2636 added -= modadded
2636
2637
2637 # tell newly modified apart.
2638 # tell newly modified apart.
2638 dsmodified &= modified
2639 dsmodified &= modified
2639 dsmodified |= modified & dsadded # dirstate added may needs backup
2640 dsmodified |= modified & dsadded # dirstate added may needs backup
2640 modified -= dsmodified
2641 modified -= dsmodified
2641
2642
2642 # We need to wait for some post-processing to update this set
2643 # We need to wait for some post-processing to update this set
2643 # before making the distinction. The dirstate will be used for
2644 # before making the distinction. The dirstate will be used for
2644 # that purpose.
2645 # that purpose.
2645 dsadded = added
2646 dsadded = added
2646
2647
2647 # in case of merge, files that are actually added can be reported as
2648 # in case of merge, files that are actually added can be reported as
2648 # modified, we need to post process the result
2649 # modified, we need to post process the result
2649 if p2 != nullid:
2650 if p2 != nullid:
2650 if pmf is None:
2651 if pmf is None:
2651 # only need parent manifest in the merge case,
2652 # only need parent manifest in the merge case,
2652 # so do not read by default
2653 # so do not read by default
2653 pmf = repo[parent].manifest()
2654 pmf = repo[parent].manifest()
2654 mergeadd = dsmodified - set(pmf)
2655 mergeadd = dsmodified - set(pmf)
2655 dsadded |= mergeadd
2656 dsadded |= mergeadd
2656 dsmodified -= mergeadd
2657 dsmodified -= mergeadd
2657
2658
2658 # if f is a rename, update `names` to also revert the source
2659 # if f is a rename, update `names` to also revert the source
2659 cwd = repo.getcwd()
2660 cwd = repo.getcwd()
2660 for f in localchanges:
2661 for f in localchanges:
2661 src = repo.dirstate.copied(f)
2662 src = repo.dirstate.copied(f)
2662 # XXX should we check for rename down to target node?
2663 # XXX should we check for rename down to target node?
2663 if src and src not in names and repo.dirstate[src] == 'r':
2664 if src and src not in names and repo.dirstate[src] == 'r':
2664 dsremoved.add(src)
2665 dsremoved.add(src)
2665 names[src] = (repo.pathto(src, cwd), True)
2666 names[src] = (repo.pathto(src, cwd), True)
2666
2667
2667 # distinguish between file to forget and the other
2668 # distinguish between file to forget and the other
2668 added = set()
2669 added = set()
2669 for abs in dsadded:
2670 for abs in dsadded:
2670 if repo.dirstate[abs] != 'a':
2671 if repo.dirstate[abs] != 'a':
2671 added.add(abs)
2672 added.add(abs)
2672 dsadded -= added
2673 dsadded -= added
2673
2674
2674 for abs in deladded:
2675 for abs in deladded:
2675 if repo.dirstate[abs] == 'a':
2676 if repo.dirstate[abs] == 'a':
2676 dsadded.add(abs)
2677 dsadded.add(abs)
2677 deladded -= dsadded
2678 deladded -= dsadded
2678
2679
2679 # For files marked as removed, we check if an unknown file is present at
2680 # For files marked as removed, we check if an unknown file is present at
2680 # the same path. If a such file exists it may need to be backed up.
2681 # the same path. If a such file exists it may need to be backed up.
2681 # Making the distinction at this stage helps have simpler backup
2682 # Making the distinction at this stage helps have simpler backup
2682 # logic.
2683 # logic.
2683 removunk = set()
2684 removunk = set()
2684 for abs in removed:
2685 for abs in removed:
2685 target = repo.wjoin(abs)
2686 target = repo.wjoin(abs)
2686 if os.path.lexists(target):
2687 if os.path.lexists(target):
2687 removunk.add(abs)
2688 removunk.add(abs)
2688 removed -= removunk
2689 removed -= removunk
2689
2690
2690 dsremovunk = set()
2691 dsremovunk = set()
2691 for abs in dsremoved:
2692 for abs in dsremoved:
2692 target = repo.wjoin(abs)
2693 target = repo.wjoin(abs)
2693 if os.path.lexists(target):
2694 if os.path.lexists(target):
2694 dsremovunk.add(abs)
2695 dsremovunk.add(abs)
2695 dsremoved -= dsremovunk
2696 dsremoved -= dsremovunk
2696
2697
2697 # action to be actually performed by revert
2698 # action to be actually performed by revert
2698 # (<list of file>, message>) tuple
2699 # (<list of file>, message>) tuple
2699 actions = {'revert': ([], _('reverting %s\n')),
2700 actions = {'revert': ([], _('reverting %s\n')),
2700 'add': ([], _('adding %s\n')),
2701 'add': ([], _('adding %s\n')),
2701 'remove': ([], _('removing %s\n')),
2702 'remove': ([], _('removing %s\n')),
2702 'drop': ([], _('removing %s\n')),
2703 'drop': ([], _('removing %s\n')),
2703 'forget': ([], _('forgetting %s\n')),
2704 'forget': ([], _('forgetting %s\n')),
2704 'undelete': ([], _('undeleting %s\n')),
2705 'undelete': ([], _('undeleting %s\n')),
2705 'noop': (None, _('no changes needed to %s\n')),
2706 'noop': (None, _('no changes needed to %s\n')),
2706 'unknown': (None, _('file not managed: %s\n')),
2707 'unknown': (None, _('file not managed: %s\n')),
2707 }
2708 }
2708
2709
2709 # "constant" that convey the backup strategy.
2710 # "constant" that convey the backup strategy.
2710 # All set to `discard` if `no-backup` is set do avoid checking
2711 # All set to `discard` if `no-backup` is set do avoid checking
2711 # no_backup lower in the code.
2712 # no_backup lower in the code.
2712 # These values are ordered for comparison purposes
2713 # These values are ordered for comparison purposes
2713 backup = 2 # unconditionally do backup
2714 backup = 2 # unconditionally do backup
2714 check = 1 # check if the existing file differs from target
2715 check = 1 # check if the existing file differs from target
2715 discard = 0 # never do backup
2716 discard = 0 # never do backup
2716 if opts.get('no_backup'):
2717 if opts.get('no_backup'):
2717 backup = check = discard
2718 backup = check = discard
2718
2719
2719 backupanddel = actions['remove']
2720 backupanddel = actions['remove']
2720 if not opts.get('no_backup'):
2721 if not opts.get('no_backup'):
2721 backupanddel = actions['drop']
2722 backupanddel = actions['drop']
2722
2723
2723 disptable = (
2724 disptable = (
2724 # dispatch table:
2725 # dispatch table:
2725 # file state
2726 # file state
2726 # action
2727 # action
2727 # make backup
2728 # make backup
2728
2729
2729 ## Sets that results that will change file on disk
2730 ## Sets that results that will change file on disk
2730 # Modified compared to target, no local change
2731 # Modified compared to target, no local change
2731 (modified, actions['revert'], discard),
2732 (modified, actions['revert'], discard),
2732 # Modified compared to target, but local file is deleted
2733 # Modified compared to target, but local file is deleted
2733 (deleted, actions['revert'], discard),
2734 (deleted, actions['revert'], discard),
2734 # Modified compared to target, local change
2735 # Modified compared to target, local change
2735 (dsmodified, actions['revert'], backup),
2736 (dsmodified, actions['revert'], backup),
2736 # Added since target
2737 # Added since target
2737 (added, actions['remove'], discard),
2738 (added, actions['remove'], discard),
2738 # Added in working directory
2739 # Added in working directory
2739 (dsadded, actions['forget'], discard),
2740 (dsadded, actions['forget'], discard),
2740 # Added since target, have local modification
2741 # Added since target, have local modification
2741 (modadded, backupanddel, backup),
2742 (modadded, backupanddel, backup),
2742 # Added since target but file is missing in working directory
2743 # Added since target but file is missing in working directory
2743 (deladded, actions['drop'], discard),
2744 (deladded, actions['drop'], discard),
2744 # Removed since target, before working copy parent
2745 # Removed since target, before working copy parent
2745 (removed, actions['add'], discard),
2746 (removed, actions['add'], discard),
2746 # Same as `removed` but an unknown file exists at the same path
2747 # Same as `removed` but an unknown file exists at the same path
2747 (removunk, actions['add'], check),
2748 (removunk, actions['add'], check),
2748 # Removed since targe, marked as such in working copy parent
2749 # Removed since targe, marked as such in working copy parent
2749 (dsremoved, actions['undelete'], discard),
2750 (dsremoved, actions['undelete'], discard),
2750 # Same as `dsremoved` but an unknown file exists at the same path
2751 # Same as `dsremoved` but an unknown file exists at the same path
2751 (dsremovunk, actions['undelete'], check),
2752 (dsremovunk, actions['undelete'], check),
2752 ## the following sets does not result in any file changes
2753 ## the following sets does not result in any file changes
2753 # File with no modification
2754 # File with no modification
2754 (clean, actions['noop'], discard),
2755 (clean, actions['noop'], discard),
2755 # Existing file, not tracked anywhere
2756 # Existing file, not tracked anywhere
2756 (unknown, actions['unknown'], discard),
2757 (unknown, actions['unknown'], discard),
2757 )
2758 )
2758
2759
2759 wctx = repo[None]
2760 wctx = repo[None]
2760 for abs, (rel, exact) in sorted(names.items()):
2761 for abs, (rel, exact) in sorted(names.items()):
2761 # target file to be touch on disk (relative to cwd)
2762 # target file to be touch on disk (relative to cwd)
2762 target = repo.wjoin(abs)
2763 target = repo.wjoin(abs)
2763 # search the entry in the dispatch table.
2764 # search the entry in the dispatch table.
2764 # if the file is in any of these sets, it was touched in the working
2765 # if the file is in any of these sets, it was touched in the working
2765 # directory parent and we are sure it needs to be reverted.
2766 # directory parent and we are sure it needs to be reverted.
2766 for table, (xlist, msg), dobackup in disptable:
2767 for table, (xlist, msg), dobackup in disptable:
2767 if abs not in table:
2768 if abs not in table:
2768 continue
2769 continue
2769 if xlist is not None:
2770 if xlist is not None:
2770 xlist.append(abs)
2771 xlist.append(abs)
2771 if dobackup and (backup <= dobackup
2772 if dobackup and (backup <= dobackup
2772 or wctx[abs].cmp(ctx[abs])):
2773 or wctx[abs].cmp(ctx[abs])):
2773 bakname = "%s.orig" % rel
2774 bakname = "%s.orig" % rel
2774 ui.note(_('saving current version of %s as %s\n') %
2775 ui.note(_('saving current version of %s as %s\n') %
2775 (rel, bakname))
2776 (rel, bakname))
2776 if not opts.get('dry_run'):
2777 if not opts.get('dry_run'):
2777 util.rename(target, bakname)
2778 util.rename(target, bakname)
2778 if ui.verbose or not exact:
2779 if ui.verbose or not exact:
2779 if not isinstance(msg, basestring):
2780 if not isinstance(msg, basestring):
2780 msg = msg(abs)
2781 msg = msg(abs)
2781 ui.status(msg % rel)
2782 ui.status(msg % rel)
2782 elif exact:
2783 elif exact:
2783 ui.warn(msg % rel)
2784 ui.warn(msg % rel)
2784 break
2785 break
2785
2786
2786
2787
2787 if not opts.get('dry_run'):
2788 if not opts.get('dry_run'):
2788 needdata = ('revert', 'add', 'undelete')
2789 needdata = ('revert', 'add', 'undelete')
2789 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
2790 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
2790
2791
2791 _performrevert(repo, parents, ctx, actions)
2792 _performrevert(repo, parents, ctx, actions)
2792
2793
2793 # get the list of subrepos that must be reverted
2794 # get the list of subrepos that must be reverted
2794 subrepomatch = scmutil.match(ctx, pats, opts)
2795 subrepomatch = scmutil.match(ctx, pats, opts)
2795 targetsubs = sorted(s for s in ctx.substate if subrepomatch(s))
2796 targetsubs = sorted(s for s in ctx.substate if subrepomatch(s))
2796
2797
2797 if targetsubs:
2798 if targetsubs:
2798 # Revert the subrepos on the revert list
2799 # Revert the subrepos on the revert list
2799 for sub in targetsubs:
2800 for sub in targetsubs:
2800 ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
2801 ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
2801 finally:
2802 finally:
2802 wlock.release()
2803 wlock.release()
2803
2804
2804 def _revertprefetch(repo, ctx, *files):
2805 def _revertprefetch(repo, ctx, *files):
2805 """Let extension changing the storage layer prefetch content"""
2806 """Let extension changing the storage layer prefetch content"""
2806 pass
2807 pass
2807
2808
2808 def _performrevert(repo, parents, ctx, actions):
2809 def _performrevert(repo, parents, ctx, actions):
2809 """function that actually perform all the actions computed for revert
2810 """function that actually perform all the actions computed for revert
2810
2811
2811 This is an independent function to let extension to plug in and react to
2812 This is an independent function to let extension to plug in and react to
2812 the imminent revert.
2813 the imminent revert.
2813
2814
2814 Make sure you have the working directory locked when calling this function.
2815 Make sure you have the working directory locked when calling this function.
2815 """
2816 """
2816 parent, p2 = parents
2817 parent, p2 = parents
2817 node = ctx.node()
2818 node = ctx.node()
2818 def checkout(f):
2819 def checkout(f):
2819 fc = ctx[f]
2820 fc = ctx[f]
2820 repo.wwrite(f, fc.data(), fc.flags())
2821 repo.wwrite(f, fc.data(), fc.flags())
2821
2822
2822 audit_path = pathutil.pathauditor(repo.root)
2823 audit_path = pathutil.pathauditor(repo.root)
2823 for f in actions['forget'][0]:
2824 for f in actions['forget'][0]:
2824 repo.dirstate.drop(f)
2825 repo.dirstate.drop(f)
2825 for f in actions['remove'][0]:
2826 for f in actions['remove'][0]:
2826 audit_path(f)
2827 audit_path(f)
2827 util.unlinkpath(repo.wjoin(f))
2828 util.unlinkpath(repo.wjoin(f))
2828 repo.dirstate.remove(f)
2829 repo.dirstate.remove(f)
2829 for f in actions['drop'][0]:
2830 for f in actions['drop'][0]:
2830 audit_path(f)
2831 audit_path(f)
2831 repo.dirstate.remove(f)
2832 repo.dirstate.remove(f)
2832
2833
2833 normal = None
2834 normal = None
2834 if node == parent:
2835 if node == parent:
2835 # We're reverting to our parent. If possible, we'd like status
2836 # We're reverting to our parent. If possible, we'd like status
2836 # to report the file as clean. We have to use normallookup for
2837 # to report the file as clean. We have to use normallookup for
2837 # merges to avoid losing information about merged/dirty files.
2838 # merges to avoid losing information about merged/dirty files.
2838 if p2 != nullid:
2839 if p2 != nullid:
2839 normal = repo.dirstate.normallookup
2840 normal = repo.dirstate.normallookup
2840 else:
2841 else:
2841 normal = repo.dirstate.normal
2842 normal = repo.dirstate.normal
2842 for f in actions['revert'][0]:
2843 for f in actions['revert'][0]:
2843 checkout(f)
2844 checkout(f)
2844 if normal:
2845 if normal:
2845 normal(f)
2846 normal(f)
2846
2847
2847 for f in actions['add'][0]:
2848 for f in actions['add'][0]:
2848 checkout(f)
2849 checkout(f)
2849 repo.dirstate.add(f)
2850 repo.dirstate.add(f)
2850
2851
2851 normal = repo.dirstate.normallookup
2852 normal = repo.dirstate.normallookup
2852 if node == parent and p2 == nullid:
2853 if node == parent and p2 == nullid:
2853 normal = repo.dirstate.normal
2854 normal = repo.dirstate.normal
2854 for f in actions['undelete'][0]:
2855 for f in actions['undelete'][0]:
2855 checkout(f)
2856 checkout(f)
2856 normal(f)
2857 normal(f)
2857
2858
2858 copied = copies.pathcopies(repo[parent], ctx)
2859 copied = copies.pathcopies(repo[parent], ctx)
2859
2860
2860 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2861 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2861 if f in copied:
2862 if f in copied:
2862 repo.dirstate.copy(copied[f], f)
2863 repo.dirstate.copy(copied[f], f)
2863
2864
2864 def command(table):
2865 def command(table):
2865 """Returns a function object to be used as a decorator for making commands.
2866 """Returns a function object to be used as a decorator for making commands.
2866
2867
2867 This function receives a command table as its argument. The table should
2868 This function receives a command table as its argument. The table should
2868 be a dict.
2869 be a dict.
2869
2870
2870 The returned function can be used as a decorator for adding commands
2871 The returned function can be used as a decorator for adding commands
2871 to that command table. This function accepts multiple arguments to define
2872 to that command table. This function accepts multiple arguments to define
2872 a command.
2873 a command.
2873
2874
2874 The first argument is the command name.
2875 The first argument is the command name.
2875
2876
2876 The options argument is an iterable of tuples defining command arguments.
2877 The options argument is an iterable of tuples defining command arguments.
2877 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2878 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2878
2879
2879 The synopsis argument defines a short, one line summary of how to use the
2880 The synopsis argument defines a short, one line summary of how to use the
2880 command. This shows up in the help output.
2881 command. This shows up in the help output.
2881
2882
2882 The norepo argument defines whether the command does not require a
2883 The norepo argument defines whether the command does not require a
2883 local repository. Most commands operate against a repository, thus the
2884 local repository. Most commands operate against a repository, thus the
2884 default is False.
2885 default is False.
2885
2886
2886 The optionalrepo argument defines whether the command optionally requires
2887 The optionalrepo argument defines whether the command optionally requires
2887 a local repository.
2888 a local repository.
2888
2889
2889 The inferrepo argument defines whether to try to find a repository from the
2890 The inferrepo argument defines whether to try to find a repository from the
2890 command line arguments. If True, arguments will be examined for potential
2891 command line arguments. If True, arguments will be examined for potential
2891 repository locations. See ``findrepo()``. If a repository is found, it
2892 repository locations. See ``findrepo()``. If a repository is found, it
2892 will be used.
2893 will be used.
2893 """
2894 """
2894 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2895 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2895 inferrepo=False):
2896 inferrepo=False):
2896 def decorator(func):
2897 def decorator(func):
2897 if synopsis:
2898 if synopsis:
2898 table[name] = func, list(options), synopsis
2899 table[name] = func, list(options), synopsis
2899 else:
2900 else:
2900 table[name] = func, list(options)
2901 table[name] = func, list(options)
2901
2902
2902 if norepo:
2903 if norepo:
2903 # Avoid import cycle.
2904 # Avoid import cycle.
2904 import commands
2905 import commands
2905 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2906 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2906
2907
2907 if optionalrepo:
2908 if optionalrepo:
2908 import commands
2909 import commands
2909 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2910 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2910
2911
2911 if inferrepo:
2912 if inferrepo:
2912 import commands
2913 import commands
2913 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2914 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2914
2915
2915 return func
2916 return func
2916 return decorator
2917 return decorator
2917
2918
2918 return cmd
2919 return cmd
2919
2920
2920 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2921 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2921 # commands.outgoing. "missing" is "missing" of the result of
2922 # commands.outgoing. "missing" is "missing" of the result of
2922 # "findcommonoutgoing()"
2923 # "findcommonoutgoing()"
2923 outgoinghooks = util.hooks()
2924 outgoinghooks = util.hooks()
2924
2925
2925 # a list of (ui, repo) functions called by commands.summary
2926 # a list of (ui, repo) functions called by commands.summary
2926 summaryhooks = util.hooks()
2927 summaryhooks = util.hooks()
2927
2928
2928 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2929 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2929 #
2930 #
2930 # functions should return tuple of booleans below, if 'changes' is None:
2931 # functions should return tuple of booleans below, if 'changes' is None:
2931 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2932 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2932 #
2933 #
2933 # otherwise, 'changes' is a tuple of tuples below:
2934 # otherwise, 'changes' is a tuple of tuples below:
2934 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2935 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2935 # - (desturl, destbranch, destpeer, outgoing)
2936 # - (desturl, destbranch, destpeer, outgoing)
2936 summaryremotehooks = util.hooks()
2937 summaryremotehooks = util.hooks()
2937
2938
2938 # A list of state files kept by multistep operations like graft.
2939 # A list of state files kept by multistep operations like graft.
2939 # Since graft cannot be aborted, it is considered 'clearable' by update.
2940 # Since graft cannot be aborted, it is considered 'clearable' by update.
2940 # note: bisect is intentionally excluded
2941 # note: bisect is intentionally excluded
2941 # (state file, clearable, allowcommit, error, hint)
2942 # (state file, clearable, allowcommit, error, hint)
2942 unfinishedstates = [
2943 unfinishedstates = [
2943 ('graftstate', True, False, _('graft in progress'),
2944 ('graftstate', True, False, _('graft in progress'),
2944 _("use 'hg graft --continue' or 'hg update' to abort")),
2945 _("use 'hg graft --continue' or 'hg update' to abort")),
2945 ('updatestate', True, False, _('last update was interrupted'),
2946 ('updatestate', True, False, _('last update was interrupted'),
2946 _("use 'hg update' to get a consistent checkout"))
2947 _("use 'hg update' to get a consistent checkout"))
2947 ]
2948 ]
2948
2949
2949 def checkunfinished(repo, commit=False):
2950 def checkunfinished(repo, commit=False):
2950 '''Look for an unfinished multistep operation, like graft, and abort
2951 '''Look for an unfinished multistep operation, like graft, and abort
2951 if found. It's probably good to check this right before
2952 if found. It's probably good to check this right before
2952 bailifchanged().
2953 bailifchanged().
2953 '''
2954 '''
2954 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2955 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2955 if commit and allowcommit:
2956 if commit and allowcommit:
2956 continue
2957 continue
2957 if repo.vfs.exists(f):
2958 if repo.vfs.exists(f):
2958 raise util.Abort(msg, hint=hint)
2959 raise util.Abort(msg, hint=hint)
2959
2960
2960 def clearunfinished(repo):
2961 def clearunfinished(repo):
2961 '''Check for unfinished operations (as above), and clear the ones
2962 '''Check for unfinished operations (as above), and clear the ones
2962 that are clearable.
2963 that are clearable.
2963 '''
2964 '''
2964 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2965 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2965 if not clearable and repo.vfs.exists(f):
2966 if not clearable and repo.vfs.exists(f):
2966 raise util.Abort(msg, hint=hint)
2967 raise util.Abort(msg, hint=hint)
2967 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2968 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2968 if clearable and repo.vfs.exists(f):
2969 if clearable and repo.vfs.exists(f):
2969 util.unlink(repo.join(f))
2970 util.unlink(repo.join(f))
@@ -1,1990 +1,1990 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import cStringIO, email, os, errno, re, posixpath
9 import cStringIO, email, os, errno, re, posixpath
10 import tempfile, zlib, shutil
10 import tempfile, zlib, shutil
11 # On python2.4 you have to import these by name or they fail to
11 # On python2.4 you have to import these by name or they fail to
12 # load. This was not a problem on Python 2.7.
12 # load. This was not a problem on Python 2.7.
13 import email.Generator
13 import email.Generator
14 import email.Parser
14 import email.Parser
15
15
16 from i18n import _
16 from i18n import _
17 from node import hex, short
17 from node import hex, short
18 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
18 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
19
19
20 gitre = re.compile('diff --git a/(.*) b/(.*)')
20 gitre = re.compile('diff --git a/(.*) b/(.*)')
21 tabsplitter = re.compile(r'(\t+|[^\t]+)')
21 tabsplitter = re.compile(r'(\t+|[^\t]+)')
22
22
23 class PatchError(Exception):
23 class PatchError(Exception):
24 pass
24 pass
25
25
26
26
27 # public functions
27 # public functions
28
28
29 def split(stream):
29 def split(stream):
30 '''return an iterator of individual patches from a stream'''
30 '''return an iterator of individual patches from a stream'''
31 def isheader(line, inheader):
31 def isheader(line, inheader):
32 if inheader and line[0] in (' ', '\t'):
32 if inheader and line[0] in (' ', '\t'):
33 # continuation
33 # continuation
34 return True
34 return True
35 if line[0] in (' ', '-', '+'):
35 if line[0] in (' ', '-', '+'):
36 # diff line - don't check for header pattern in there
36 # diff line - don't check for header pattern in there
37 return False
37 return False
38 l = line.split(': ', 1)
38 l = line.split(': ', 1)
39 return len(l) == 2 and ' ' not in l[0]
39 return len(l) == 2 and ' ' not in l[0]
40
40
41 def chunk(lines):
41 def chunk(lines):
42 return cStringIO.StringIO(''.join(lines))
42 return cStringIO.StringIO(''.join(lines))
43
43
44 def hgsplit(stream, cur):
44 def hgsplit(stream, cur):
45 inheader = True
45 inheader = True
46
46
47 for line in stream:
47 for line in stream:
48 if not line.strip():
48 if not line.strip():
49 inheader = False
49 inheader = False
50 if not inheader and line.startswith('# HG changeset patch'):
50 if not inheader and line.startswith('# HG changeset patch'):
51 yield chunk(cur)
51 yield chunk(cur)
52 cur = []
52 cur = []
53 inheader = True
53 inheader = True
54
54
55 cur.append(line)
55 cur.append(line)
56
56
57 if cur:
57 if cur:
58 yield chunk(cur)
58 yield chunk(cur)
59
59
60 def mboxsplit(stream, cur):
60 def mboxsplit(stream, cur):
61 for line in stream:
61 for line in stream:
62 if line.startswith('From '):
62 if line.startswith('From '):
63 for c in split(chunk(cur[1:])):
63 for c in split(chunk(cur[1:])):
64 yield c
64 yield c
65 cur = []
65 cur = []
66
66
67 cur.append(line)
67 cur.append(line)
68
68
69 if cur:
69 if cur:
70 for c in split(chunk(cur[1:])):
70 for c in split(chunk(cur[1:])):
71 yield c
71 yield c
72
72
73 def mimesplit(stream, cur):
73 def mimesplit(stream, cur):
74 def msgfp(m):
74 def msgfp(m):
75 fp = cStringIO.StringIO()
75 fp = cStringIO.StringIO()
76 g = email.Generator.Generator(fp, mangle_from_=False)
76 g = email.Generator.Generator(fp, mangle_from_=False)
77 g.flatten(m)
77 g.flatten(m)
78 fp.seek(0)
78 fp.seek(0)
79 return fp
79 return fp
80
80
81 for line in stream:
81 for line in stream:
82 cur.append(line)
82 cur.append(line)
83 c = chunk(cur)
83 c = chunk(cur)
84
84
85 m = email.Parser.Parser().parse(c)
85 m = email.Parser.Parser().parse(c)
86 if not m.is_multipart():
86 if not m.is_multipart():
87 yield msgfp(m)
87 yield msgfp(m)
88 else:
88 else:
89 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
89 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
90 for part in m.walk():
90 for part in m.walk():
91 ct = part.get_content_type()
91 ct = part.get_content_type()
92 if ct not in ok_types:
92 if ct not in ok_types:
93 continue
93 continue
94 yield msgfp(part)
94 yield msgfp(part)
95
95
96 def headersplit(stream, cur):
96 def headersplit(stream, cur):
97 inheader = False
97 inheader = False
98
98
99 for line in stream:
99 for line in stream:
100 if not inheader and isheader(line, inheader):
100 if not inheader and isheader(line, inheader):
101 yield chunk(cur)
101 yield chunk(cur)
102 cur = []
102 cur = []
103 inheader = True
103 inheader = True
104 if inheader and not isheader(line, inheader):
104 if inheader and not isheader(line, inheader):
105 inheader = False
105 inheader = False
106
106
107 cur.append(line)
107 cur.append(line)
108
108
109 if cur:
109 if cur:
110 yield chunk(cur)
110 yield chunk(cur)
111
111
112 def remainder(cur):
112 def remainder(cur):
113 yield chunk(cur)
113 yield chunk(cur)
114
114
115 class fiter(object):
115 class fiter(object):
116 def __init__(self, fp):
116 def __init__(self, fp):
117 self.fp = fp
117 self.fp = fp
118
118
119 def __iter__(self):
119 def __iter__(self):
120 return self
120 return self
121
121
122 def next(self):
122 def next(self):
123 l = self.fp.readline()
123 l = self.fp.readline()
124 if not l:
124 if not l:
125 raise StopIteration
125 raise StopIteration
126 return l
126 return l
127
127
128 inheader = False
128 inheader = False
129 cur = []
129 cur = []
130
130
131 mimeheaders = ['content-type']
131 mimeheaders = ['content-type']
132
132
133 if not util.safehasattr(stream, 'next'):
133 if not util.safehasattr(stream, 'next'):
134 # http responses, for example, have readline but not next
134 # http responses, for example, have readline but not next
135 stream = fiter(stream)
135 stream = fiter(stream)
136
136
137 for line in stream:
137 for line in stream:
138 cur.append(line)
138 cur.append(line)
139 if line.startswith('# HG changeset patch'):
139 if line.startswith('# HG changeset patch'):
140 return hgsplit(stream, cur)
140 return hgsplit(stream, cur)
141 elif line.startswith('From '):
141 elif line.startswith('From '):
142 return mboxsplit(stream, cur)
142 return mboxsplit(stream, cur)
143 elif isheader(line, inheader):
143 elif isheader(line, inheader):
144 inheader = True
144 inheader = True
145 if line.split(':', 1)[0].lower() in mimeheaders:
145 if line.split(':', 1)[0].lower() in mimeheaders:
146 # let email parser handle this
146 # let email parser handle this
147 return mimesplit(stream, cur)
147 return mimesplit(stream, cur)
148 elif line.startswith('--- ') and inheader:
148 elif line.startswith('--- ') and inheader:
149 # No evil headers seen by diff start, split by hand
149 # No evil headers seen by diff start, split by hand
150 return headersplit(stream, cur)
150 return headersplit(stream, cur)
151 # Not enough info, keep reading
151 # Not enough info, keep reading
152
152
153 # if we are here, we have a very plain patch
153 # if we are here, we have a very plain patch
154 return remainder(cur)
154 return remainder(cur)
155
155
156 def extract(ui, fileobj):
156 def extract(ui, fileobj):
157 '''extract patch from data read from fileobj.
157 '''extract patch from data read from fileobj.
158
158
159 patch can be a normal patch or contained in an email message.
159 patch can be a normal patch or contained in an email message.
160
160
161 return tuple (filename, message, user, date, branch, node, p1, p2).
161 return tuple (filename, message, user, date, branch, node, p1, p2).
162 Any item in the returned tuple can be None. If filename is None,
162 Any item in the returned tuple can be None. If filename is None,
163 fileobj did not contain a patch. Caller must unlink filename when done.'''
163 fileobj did not contain a patch. Caller must unlink filename when done.'''
164
164
165 # attempt to detect the start of a patch
165 # attempt to detect the start of a patch
166 # (this heuristic is borrowed from quilt)
166 # (this heuristic is borrowed from quilt)
167 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
167 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
168 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
168 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
169 r'---[ \t].*?^\+\+\+[ \t]|'
169 r'---[ \t].*?^\+\+\+[ \t]|'
170 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
170 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
171
171
172 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
172 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
173 tmpfp = os.fdopen(fd, 'w')
173 tmpfp = os.fdopen(fd, 'w')
174 try:
174 try:
175 msg = email.Parser.Parser().parse(fileobj)
175 msg = email.Parser.Parser().parse(fileobj)
176
176
177 subject = msg['Subject']
177 subject = msg['Subject']
178 user = msg['From']
178 user = msg['From']
179 if not subject and not user:
179 if not subject and not user:
180 # Not an email, restore parsed headers if any
180 # Not an email, restore parsed headers if any
181 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
181 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
182
182
183 # should try to parse msg['Date']
183 # should try to parse msg['Date']
184 date = None
184 date = None
185 nodeid = None
185 nodeid = None
186 branch = None
186 branch = None
187 parents = []
187 parents = []
188
188
189 if subject:
189 if subject:
190 if subject.startswith('[PATCH'):
190 if subject.startswith('[PATCH'):
191 pend = subject.find(']')
191 pend = subject.find(']')
192 if pend >= 0:
192 if pend >= 0:
193 subject = subject[pend + 1:].lstrip()
193 subject = subject[pend + 1:].lstrip()
194 subject = re.sub(r'\n[ \t]+', ' ', subject)
194 subject = re.sub(r'\n[ \t]+', ' ', subject)
195 ui.debug('Subject: %s\n' % subject)
195 ui.debug('Subject: %s\n' % subject)
196 if user:
196 if user:
197 ui.debug('From: %s\n' % user)
197 ui.debug('From: %s\n' % user)
198 diffs_seen = 0
198 diffs_seen = 0
199 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
199 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
200 message = ''
200 message = ''
201 for part in msg.walk():
201 for part in msg.walk():
202 content_type = part.get_content_type()
202 content_type = part.get_content_type()
203 ui.debug('Content-Type: %s\n' % content_type)
203 ui.debug('Content-Type: %s\n' % content_type)
204 if content_type not in ok_types:
204 if content_type not in ok_types:
205 continue
205 continue
206 payload = part.get_payload(decode=True)
206 payload = part.get_payload(decode=True)
207 m = diffre.search(payload)
207 m = diffre.search(payload)
208 if m:
208 if m:
209 hgpatch = False
209 hgpatch = False
210 hgpatchheader = False
210 hgpatchheader = False
211 ignoretext = False
211 ignoretext = False
212
212
213 ui.debug('found patch at byte %d\n' % m.start(0))
213 ui.debug('found patch at byte %d\n' % m.start(0))
214 diffs_seen += 1
214 diffs_seen += 1
215 cfp = cStringIO.StringIO()
215 cfp = cStringIO.StringIO()
216 for line in payload[:m.start(0)].splitlines():
216 for line in payload[:m.start(0)].splitlines():
217 if line.startswith('# HG changeset patch') and not hgpatch:
217 if line.startswith('# HG changeset patch') and not hgpatch:
218 ui.debug('patch generated by hg export\n')
218 ui.debug('patch generated by hg export\n')
219 hgpatch = True
219 hgpatch = True
220 hgpatchheader = True
220 hgpatchheader = True
221 # drop earlier commit message content
221 # drop earlier commit message content
222 cfp.seek(0)
222 cfp.seek(0)
223 cfp.truncate()
223 cfp.truncate()
224 subject = None
224 subject = None
225 elif hgpatchheader:
225 elif hgpatchheader:
226 if line.startswith('# User '):
226 if line.startswith('# User '):
227 user = line[7:]
227 user = line[7:]
228 ui.debug('From: %s\n' % user)
228 ui.debug('From: %s\n' % user)
229 elif line.startswith("# Date "):
229 elif line.startswith("# Date "):
230 date = line[7:]
230 date = line[7:]
231 elif line.startswith("# Branch "):
231 elif line.startswith("# Branch "):
232 branch = line[9:]
232 branch = line[9:]
233 elif line.startswith("# Node ID "):
233 elif line.startswith("# Node ID "):
234 nodeid = line[10:]
234 nodeid = line[10:]
235 elif line.startswith("# Parent "):
235 elif line.startswith("# Parent "):
236 parents.append(line[9:].lstrip())
236 parents.append(line[9:].lstrip())
237 elif not line.startswith("# "):
237 elif not line.startswith("# "):
238 hgpatchheader = False
238 hgpatchheader = False
239 elif line == '---':
239 elif line == '---':
240 ignoretext = True
240 ignoretext = True
241 if not hgpatchheader and not ignoretext:
241 if not hgpatchheader and not ignoretext:
242 cfp.write(line)
242 cfp.write(line)
243 cfp.write('\n')
243 cfp.write('\n')
244 message = cfp.getvalue()
244 message = cfp.getvalue()
245 if tmpfp:
245 if tmpfp:
246 tmpfp.write(payload)
246 tmpfp.write(payload)
247 if not payload.endswith('\n'):
247 if not payload.endswith('\n'):
248 tmpfp.write('\n')
248 tmpfp.write('\n')
249 elif not diffs_seen and message and content_type == 'text/plain':
249 elif not diffs_seen and message and content_type == 'text/plain':
250 message += '\n' + payload
250 message += '\n' + payload
251 except: # re-raises
251 except: # re-raises
252 tmpfp.close()
252 tmpfp.close()
253 os.unlink(tmpname)
253 os.unlink(tmpname)
254 raise
254 raise
255
255
256 if subject and not message.startswith(subject):
256 if subject and not message.startswith(subject):
257 message = '%s\n%s' % (subject, message)
257 message = '%s\n%s' % (subject, message)
258 tmpfp.close()
258 tmpfp.close()
259 if not diffs_seen:
259 if not diffs_seen:
260 os.unlink(tmpname)
260 os.unlink(tmpname)
261 return None, message, user, date, branch, None, None, None
261 return None, message, user, date, branch, None, None, None
262 p1 = parents and parents.pop(0) or None
262 p1 = parents and parents.pop(0) or None
263 p2 = parents and parents.pop(0) or None
263 p2 = parents and parents.pop(0) or None
264 return tmpname, message, user, date, branch, nodeid, p1, p2
264 return tmpname, message, user, date, branch, nodeid, p1, p2
265
265
266 class patchmeta(object):
266 class patchmeta(object):
267 """Patched file metadata
267 """Patched file metadata
268
268
269 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
269 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
270 or COPY. 'path' is patched file path. 'oldpath' is set to the
270 or COPY. 'path' is patched file path. 'oldpath' is set to the
271 origin file when 'op' is either COPY or RENAME, None otherwise. If
271 origin file when 'op' is either COPY or RENAME, None otherwise. If
272 file mode is changed, 'mode' is a tuple (islink, isexec) where
272 file mode is changed, 'mode' is a tuple (islink, isexec) where
273 'islink' is True if the file is a symlink and 'isexec' is True if
273 'islink' is True if the file is a symlink and 'isexec' is True if
274 the file is executable. Otherwise, 'mode' is None.
274 the file is executable. Otherwise, 'mode' is None.
275 """
275 """
276 def __init__(self, path):
276 def __init__(self, path):
277 self.path = path
277 self.path = path
278 self.oldpath = None
278 self.oldpath = None
279 self.mode = None
279 self.mode = None
280 self.op = 'MODIFY'
280 self.op = 'MODIFY'
281 self.binary = False
281 self.binary = False
282
282
283 def setmode(self, mode):
283 def setmode(self, mode):
284 islink = mode & 020000
284 islink = mode & 020000
285 isexec = mode & 0100
285 isexec = mode & 0100
286 self.mode = (islink, isexec)
286 self.mode = (islink, isexec)
287
287
288 def copy(self):
288 def copy(self):
289 other = patchmeta(self.path)
289 other = patchmeta(self.path)
290 other.oldpath = self.oldpath
290 other.oldpath = self.oldpath
291 other.mode = self.mode
291 other.mode = self.mode
292 other.op = self.op
292 other.op = self.op
293 other.binary = self.binary
293 other.binary = self.binary
294 return other
294 return other
295
295
296 def _ispatchinga(self, afile):
296 def _ispatchinga(self, afile):
297 if afile == '/dev/null':
297 if afile == '/dev/null':
298 return self.op == 'ADD'
298 return self.op == 'ADD'
299 return afile == 'a/' + (self.oldpath or self.path)
299 return afile == 'a/' + (self.oldpath or self.path)
300
300
301 def _ispatchingb(self, bfile):
301 def _ispatchingb(self, bfile):
302 if bfile == '/dev/null':
302 if bfile == '/dev/null':
303 return self.op == 'DELETE'
303 return self.op == 'DELETE'
304 return bfile == 'b/' + self.path
304 return bfile == 'b/' + self.path
305
305
306 def ispatching(self, afile, bfile):
306 def ispatching(self, afile, bfile):
307 return self._ispatchinga(afile) and self._ispatchingb(bfile)
307 return self._ispatchinga(afile) and self._ispatchingb(bfile)
308
308
309 def __repr__(self):
309 def __repr__(self):
310 return "<patchmeta %s %r>" % (self.op, self.path)
310 return "<patchmeta %s %r>" % (self.op, self.path)
311
311
312 def readgitpatch(lr):
312 def readgitpatch(lr):
313 """extract git-style metadata about patches from <patchname>"""
313 """extract git-style metadata about patches from <patchname>"""
314
314
315 # Filter patch for git information
315 # Filter patch for git information
316 gp = None
316 gp = None
317 gitpatches = []
317 gitpatches = []
318 for line in lr:
318 for line in lr:
319 line = line.rstrip(' \r\n')
319 line = line.rstrip(' \r\n')
320 if line.startswith('diff --git a/'):
320 if line.startswith('diff --git a/'):
321 m = gitre.match(line)
321 m = gitre.match(line)
322 if m:
322 if m:
323 if gp:
323 if gp:
324 gitpatches.append(gp)
324 gitpatches.append(gp)
325 dst = m.group(2)
325 dst = m.group(2)
326 gp = patchmeta(dst)
326 gp = patchmeta(dst)
327 elif gp:
327 elif gp:
328 if line.startswith('--- '):
328 if line.startswith('--- '):
329 gitpatches.append(gp)
329 gitpatches.append(gp)
330 gp = None
330 gp = None
331 continue
331 continue
332 if line.startswith('rename from '):
332 if line.startswith('rename from '):
333 gp.op = 'RENAME'
333 gp.op = 'RENAME'
334 gp.oldpath = line[12:]
334 gp.oldpath = line[12:]
335 elif line.startswith('rename to '):
335 elif line.startswith('rename to '):
336 gp.path = line[10:]
336 gp.path = line[10:]
337 elif line.startswith('copy from '):
337 elif line.startswith('copy from '):
338 gp.op = 'COPY'
338 gp.op = 'COPY'
339 gp.oldpath = line[10:]
339 gp.oldpath = line[10:]
340 elif line.startswith('copy to '):
340 elif line.startswith('copy to '):
341 gp.path = line[8:]
341 gp.path = line[8:]
342 elif line.startswith('deleted file'):
342 elif line.startswith('deleted file'):
343 gp.op = 'DELETE'
343 gp.op = 'DELETE'
344 elif line.startswith('new file mode '):
344 elif line.startswith('new file mode '):
345 gp.op = 'ADD'
345 gp.op = 'ADD'
346 gp.setmode(int(line[-6:], 8))
346 gp.setmode(int(line[-6:], 8))
347 elif line.startswith('new mode '):
347 elif line.startswith('new mode '):
348 gp.setmode(int(line[-6:], 8))
348 gp.setmode(int(line[-6:], 8))
349 elif line.startswith('GIT binary patch'):
349 elif line.startswith('GIT binary patch'):
350 gp.binary = True
350 gp.binary = True
351 if gp:
351 if gp:
352 gitpatches.append(gp)
352 gitpatches.append(gp)
353
353
354 return gitpatches
354 return gitpatches
355
355
356 class linereader(object):
356 class linereader(object):
357 # simple class to allow pushing lines back into the input stream
357 # simple class to allow pushing lines back into the input stream
358 def __init__(self, fp):
358 def __init__(self, fp):
359 self.fp = fp
359 self.fp = fp
360 self.buf = []
360 self.buf = []
361
361
362 def push(self, line):
362 def push(self, line):
363 if line is not None:
363 if line is not None:
364 self.buf.append(line)
364 self.buf.append(line)
365
365
366 def readline(self):
366 def readline(self):
367 if self.buf:
367 if self.buf:
368 l = self.buf[0]
368 l = self.buf[0]
369 del self.buf[0]
369 del self.buf[0]
370 return l
370 return l
371 return self.fp.readline()
371 return self.fp.readline()
372
372
373 def __iter__(self):
373 def __iter__(self):
374 while True:
374 while True:
375 l = self.readline()
375 l = self.readline()
376 if not l:
376 if not l:
377 break
377 break
378 yield l
378 yield l
379
379
380 class abstractbackend(object):
380 class abstractbackend(object):
381 def __init__(self, ui):
381 def __init__(self, ui):
382 self.ui = ui
382 self.ui = ui
383
383
384 def getfile(self, fname):
384 def getfile(self, fname):
385 """Return target file data and flags as a (data, (islink,
385 """Return target file data and flags as a (data, (islink,
386 isexec)) tuple. Data is None if file is missing/deleted.
386 isexec)) tuple. Data is None if file is missing/deleted.
387 """
387 """
388 raise NotImplementedError
388 raise NotImplementedError
389
389
390 def setfile(self, fname, data, mode, copysource):
390 def setfile(self, fname, data, mode, copysource):
391 """Write data to target file fname and set its mode. mode is a
391 """Write data to target file fname and set its mode. mode is a
392 (islink, isexec) tuple. If data is None, the file content should
392 (islink, isexec) tuple. If data is None, the file content should
393 be left unchanged. If the file is modified after being copied,
393 be left unchanged. If the file is modified after being copied,
394 copysource is set to the original file name.
394 copysource is set to the original file name.
395 """
395 """
396 raise NotImplementedError
396 raise NotImplementedError
397
397
398 def unlink(self, fname):
398 def unlink(self, fname):
399 """Unlink target file."""
399 """Unlink target file."""
400 raise NotImplementedError
400 raise NotImplementedError
401
401
402 def writerej(self, fname, failed, total, lines):
402 def writerej(self, fname, failed, total, lines):
403 """Write rejected lines for fname. total is the number of hunks
403 """Write rejected lines for fname. total is the number of hunks
404 which failed to apply and total the total number of hunks for this
404 which failed to apply and total the total number of hunks for this
405 files.
405 files.
406 """
406 """
407 pass
407 pass
408
408
409 def exists(self, fname):
409 def exists(self, fname):
410 raise NotImplementedError
410 raise NotImplementedError
411
411
412 class fsbackend(abstractbackend):
412 class fsbackend(abstractbackend):
413 def __init__(self, ui, basedir):
413 def __init__(self, ui, basedir):
414 super(fsbackend, self).__init__(ui)
414 super(fsbackend, self).__init__(ui)
415 self.opener = scmutil.opener(basedir)
415 self.opener = scmutil.opener(basedir)
416
416
417 def _join(self, f):
417 def _join(self, f):
418 return os.path.join(self.opener.base, f)
418 return os.path.join(self.opener.base, f)
419
419
420 def getfile(self, fname):
420 def getfile(self, fname):
421 if self.opener.islink(fname):
421 if self.opener.islink(fname):
422 return (self.opener.readlink(fname), (True, False))
422 return (self.opener.readlink(fname), (True, False))
423
423
424 isexec = False
424 isexec = False
425 try:
425 try:
426 isexec = self.opener.lstat(fname).st_mode & 0100 != 0
426 isexec = self.opener.lstat(fname).st_mode & 0100 != 0
427 except OSError, e:
427 except OSError, e:
428 if e.errno != errno.ENOENT:
428 if e.errno != errno.ENOENT:
429 raise
429 raise
430 try:
430 try:
431 return (self.opener.read(fname), (False, isexec))
431 return (self.opener.read(fname), (False, isexec))
432 except IOError, e:
432 except IOError, e:
433 if e.errno != errno.ENOENT:
433 if e.errno != errno.ENOENT:
434 raise
434 raise
435 return None, None
435 return None, None
436
436
437 def setfile(self, fname, data, mode, copysource):
437 def setfile(self, fname, data, mode, copysource):
438 islink, isexec = mode
438 islink, isexec = mode
439 if data is None:
439 if data is None:
440 self.opener.setflags(fname, islink, isexec)
440 self.opener.setflags(fname, islink, isexec)
441 return
441 return
442 if islink:
442 if islink:
443 self.opener.symlink(data, fname)
443 self.opener.symlink(data, fname)
444 else:
444 else:
445 self.opener.write(fname, data)
445 self.opener.write(fname, data)
446 if isexec:
446 if isexec:
447 self.opener.setflags(fname, False, True)
447 self.opener.setflags(fname, False, True)
448
448
449 def unlink(self, fname):
449 def unlink(self, fname):
450 self.opener.unlinkpath(fname, ignoremissing=True)
450 self.opener.unlinkpath(fname, ignoremissing=True)
451
451
452 def writerej(self, fname, failed, total, lines):
452 def writerej(self, fname, failed, total, lines):
453 fname = fname + ".rej"
453 fname = fname + ".rej"
454 self.ui.warn(
454 self.ui.warn(
455 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
455 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
456 (failed, total, fname))
456 (failed, total, fname))
457 fp = self.opener(fname, 'w')
457 fp = self.opener(fname, 'w')
458 fp.writelines(lines)
458 fp.writelines(lines)
459 fp.close()
459 fp.close()
460
460
461 def exists(self, fname):
461 def exists(self, fname):
462 return self.opener.lexists(fname)
462 return self.opener.lexists(fname)
463
463
464 class workingbackend(fsbackend):
464 class workingbackend(fsbackend):
465 def __init__(self, ui, repo, similarity):
465 def __init__(self, ui, repo, similarity):
466 super(workingbackend, self).__init__(ui, repo.root)
466 super(workingbackend, self).__init__(ui, repo.root)
467 self.repo = repo
467 self.repo = repo
468 self.similarity = similarity
468 self.similarity = similarity
469 self.removed = set()
469 self.removed = set()
470 self.changed = set()
470 self.changed = set()
471 self.copied = []
471 self.copied = []
472
472
473 def _checkknown(self, fname):
473 def _checkknown(self, fname):
474 if self.repo.dirstate[fname] == '?' and self.exists(fname):
474 if self.repo.dirstate[fname] == '?' and self.exists(fname):
475 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
475 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
476
476
477 def setfile(self, fname, data, mode, copysource):
477 def setfile(self, fname, data, mode, copysource):
478 self._checkknown(fname)
478 self._checkknown(fname)
479 super(workingbackend, self).setfile(fname, data, mode, copysource)
479 super(workingbackend, self).setfile(fname, data, mode, copysource)
480 if copysource is not None:
480 if copysource is not None:
481 self.copied.append((copysource, fname))
481 self.copied.append((copysource, fname))
482 self.changed.add(fname)
482 self.changed.add(fname)
483
483
484 def unlink(self, fname):
484 def unlink(self, fname):
485 self._checkknown(fname)
485 self._checkknown(fname)
486 super(workingbackend, self).unlink(fname)
486 super(workingbackend, self).unlink(fname)
487 self.removed.add(fname)
487 self.removed.add(fname)
488 self.changed.add(fname)
488 self.changed.add(fname)
489
489
490 def close(self):
490 def close(self):
491 wctx = self.repo[None]
491 wctx = self.repo[None]
492 changed = set(self.changed)
492 changed = set(self.changed)
493 for src, dst in self.copied:
493 for src, dst in self.copied:
494 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
494 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
495 if self.removed:
495 if self.removed:
496 wctx.forget(sorted(self.removed))
496 wctx.forget(sorted(self.removed))
497 for f in self.removed:
497 for f in self.removed:
498 if f not in self.repo.dirstate:
498 if f not in self.repo.dirstate:
499 # File was deleted and no longer belongs to the
499 # File was deleted and no longer belongs to the
500 # dirstate, it was probably marked added then
500 # dirstate, it was probably marked added then
501 # deleted, and should not be considered by
501 # deleted, and should not be considered by
502 # marktouched().
502 # marktouched().
503 changed.discard(f)
503 changed.discard(f)
504 if changed:
504 if changed:
505 scmutil.marktouched(self.repo, changed, self.similarity)
505 scmutil.marktouched(self.repo, changed, self.similarity)
506 return sorted(self.changed)
506 return sorted(self.changed)
507
507
508 class filestore(object):
508 class filestore(object):
509 def __init__(self, maxsize=None):
509 def __init__(self, maxsize=None):
510 self.opener = None
510 self.opener = None
511 self.files = {}
511 self.files = {}
512 self.created = 0
512 self.created = 0
513 self.maxsize = maxsize
513 self.maxsize = maxsize
514 if self.maxsize is None:
514 if self.maxsize is None:
515 self.maxsize = 4*(2**20)
515 self.maxsize = 4*(2**20)
516 self.size = 0
516 self.size = 0
517 self.data = {}
517 self.data = {}
518
518
519 def setfile(self, fname, data, mode, copied=None):
519 def setfile(self, fname, data, mode, copied=None):
520 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
520 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
521 self.data[fname] = (data, mode, copied)
521 self.data[fname] = (data, mode, copied)
522 self.size += len(data)
522 self.size += len(data)
523 else:
523 else:
524 if self.opener is None:
524 if self.opener is None:
525 root = tempfile.mkdtemp(prefix='hg-patch-')
525 root = tempfile.mkdtemp(prefix='hg-patch-')
526 self.opener = scmutil.opener(root)
526 self.opener = scmutil.opener(root)
527 # Avoid filename issues with these simple names
527 # Avoid filename issues with these simple names
528 fn = str(self.created)
528 fn = str(self.created)
529 self.opener.write(fn, data)
529 self.opener.write(fn, data)
530 self.created += 1
530 self.created += 1
531 self.files[fname] = (fn, mode, copied)
531 self.files[fname] = (fn, mode, copied)
532
532
533 def getfile(self, fname):
533 def getfile(self, fname):
534 if fname in self.data:
534 if fname in self.data:
535 return self.data[fname]
535 return self.data[fname]
536 if not self.opener or fname not in self.files:
536 if not self.opener or fname not in self.files:
537 return None, None, None
537 return None, None, None
538 fn, mode, copied = self.files[fname]
538 fn, mode, copied = self.files[fname]
539 return self.opener.read(fn), mode, copied
539 return self.opener.read(fn), mode, copied
540
540
541 def close(self):
541 def close(self):
542 if self.opener:
542 if self.opener:
543 shutil.rmtree(self.opener.base)
543 shutil.rmtree(self.opener.base)
544
544
545 class repobackend(abstractbackend):
545 class repobackend(abstractbackend):
546 def __init__(self, ui, repo, ctx, store):
546 def __init__(self, ui, repo, ctx, store):
547 super(repobackend, self).__init__(ui)
547 super(repobackend, self).__init__(ui)
548 self.repo = repo
548 self.repo = repo
549 self.ctx = ctx
549 self.ctx = ctx
550 self.store = store
550 self.store = store
551 self.changed = set()
551 self.changed = set()
552 self.removed = set()
552 self.removed = set()
553 self.copied = {}
553 self.copied = {}
554
554
555 def _checkknown(self, fname):
555 def _checkknown(self, fname):
556 if fname not in self.ctx:
556 if fname not in self.ctx:
557 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
557 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
558
558
559 def getfile(self, fname):
559 def getfile(self, fname):
560 try:
560 try:
561 fctx = self.ctx[fname]
561 fctx = self.ctx[fname]
562 except error.LookupError:
562 except error.LookupError:
563 return None, None
563 return None, None
564 flags = fctx.flags()
564 flags = fctx.flags()
565 return fctx.data(), ('l' in flags, 'x' in flags)
565 return fctx.data(), ('l' in flags, 'x' in flags)
566
566
567 def setfile(self, fname, data, mode, copysource):
567 def setfile(self, fname, data, mode, copysource):
568 if copysource:
568 if copysource:
569 self._checkknown(copysource)
569 self._checkknown(copysource)
570 if data is None:
570 if data is None:
571 data = self.ctx[fname].data()
571 data = self.ctx[fname].data()
572 self.store.setfile(fname, data, mode, copysource)
572 self.store.setfile(fname, data, mode, copysource)
573 self.changed.add(fname)
573 self.changed.add(fname)
574 if copysource:
574 if copysource:
575 self.copied[fname] = copysource
575 self.copied[fname] = copysource
576
576
577 def unlink(self, fname):
577 def unlink(self, fname):
578 self._checkknown(fname)
578 self._checkknown(fname)
579 self.removed.add(fname)
579 self.removed.add(fname)
580
580
581 def exists(self, fname):
581 def exists(self, fname):
582 return fname in self.ctx
582 return fname in self.ctx
583
583
584 def close(self):
584 def close(self):
585 return self.changed | self.removed
585 return self.changed | self.removed
586
586
587 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
587 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
588 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
588 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
589 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
589 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
590 eolmodes = ['strict', 'crlf', 'lf', 'auto']
590 eolmodes = ['strict', 'crlf', 'lf', 'auto']
591
591
592 class patchfile(object):
592 class patchfile(object):
593 def __init__(self, ui, gp, backend, store, eolmode='strict'):
593 def __init__(self, ui, gp, backend, store, eolmode='strict'):
594 self.fname = gp.path
594 self.fname = gp.path
595 self.eolmode = eolmode
595 self.eolmode = eolmode
596 self.eol = None
596 self.eol = None
597 self.backend = backend
597 self.backend = backend
598 self.ui = ui
598 self.ui = ui
599 self.lines = []
599 self.lines = []
600 self.exists = False
600 self.exists = False
601 self.missing = True
601 self.missing = True
602 self.mode = gp.mode
602 self.mode = gp.mode
603 self.copysource = gp.oldpath
603 self.copysource = gp.oldpath
604 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
604 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
605 self.remove = gp.op == 'DELETE'
605 self.remove = gp.op == 'DELETE'
606 if self.copysource is None:
606 if self.copysource is None:
607 data, mode = backend.getfile(self.fname)
607 data, mode = backend.getfile(self.fname)
608 else:
608 else:
609 data, mode = store.getfile(self.copysource)[:2]
609 data, mode = store.getfile(self.copysource)[:2]
610 if data is not None:
610 if data is not None:
611 self.exists = self.copysource is None or backend.exists(self.fname)
611 self.exists = self.copysource is None or backend.exists(self.fname)
612 self.missing = False
612 self.missing = False
613 if data:
613 if data:
614 self.lines = mdiff.splitnewlines(data)
614 self.lines = mdiff.splitnewlines(data)
615 if self.mode is None:
615 if self.mode is None:
616 self.mode = mode
616 self.mode = mode
617 if self.lines:
617 if self.lines:
618 # Normalize line endings
618 # Normalize line endings
619 if self.lines[0].endswith('\r\n'):
619 if self.lines[0].endswith('\r\n'):
620 self.eol = '\r\n'
620 self.eol = '\r\n'
621 elif self.lines[0].endswith('\n'):
621 elif self.lines[0].endswith('\n'):
622 self.eol = '\n'
622 self.eol = '\n'
623 if eolmode != 'strict':
623 if eolmode != 'strict':
624 nlines = []
624 nlines = []
625 for l in self.lines:
625 for l in self.lines:
626 if l.endswith('\r\n'):
626 if l.endswith('\r\n'):
627 l = l[:-2] + '\n'
627 l = l[:-2] + '\n'
628 nlines.append(l)
628 nlines.append(l)
629 self.lines = nlines
629 self.lines = nlines
630 else:
630 else:
631 if self.create:
631 if self.create:
632 self.missing = False
632 self.missing = False
633 if self.mode is None:
633 if self.mode is None:
634 self.mode = (False, False)
634 self.mode = (False, False)
635 if self.missing:
635 if self.missing:
636 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
636 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
637
637
638 self.hash = {}
638 self.hash = {}
639 self.dirty = 0
639 self.dirty = 0
640 self.offset = 0
640 self.offset = 0
641 self.skew = 0
641 self.skew = 0
642 self.rej = []
642 self.rej = []
643 self.fileprinted = False
643 self.fileprinted = False
644 self.printfile(False)
644 self.printfile(False)
645 self.hunks = 0
645 self.hunks = 0
646
646
647 def writelines(self, fname, lines, mode):
647 def writelines(self, fname, lines, mode):
648 if self.eolmode == 'auto':
648 if self.eolmode == 'auto':
649 eol = self.eol
649 eol = self.eol
650 elif self.eolmode == 'crlf':
650 elif self.eolmode == 'crlf':
651 eol = '\r\n'
651 eol = '\r\n'
652 else:
652 else:
653 eol = '\n'
653 eol = '\n'
654
654
655 if self.eolmode != 'strict' and eol and eol != '\n':
655 if self.eolmode != 'strict' and eol and eol != '\n':
656 rawlines = []
656 rawlines = []
657 for l in lines:
657 for l in lines:
658 if l and l[-1] == '\n':
658 if l and l[-1] == '\n':
659 l = l[:-1] + eol
659 l = l[:-1] + eol
660 rawlines.append(l)
660 rawlines.append(l)
661 lines = rawlines
661 lines = rawlines
662
662
663 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
663 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
664
664
665 def printfile(self, warn):
665 def printfile(self, warn):
666 if self.fileprinted:
666 if self.fileprinted:
667 return
667 return
668 if warn or self.ui.verbose:
668 if warn or self.ui.verbose:
669 self.fileprinted = True
669 self.fileprinted = True
670 s = _("patching file %s\n") % self.fname
670 s = _("patching file %s\n") % self.fname
671 if warn:
671 if warn:
672 self.ui.warn(s)
672 self.ui.warn(s)
673 else:
673 else:
674 self.ui.note(s)
674 self.ui.note(s)
675
675
676
676
677 def findlines(self, l, linenum):
677 def findlines(self, l, linenum):
678 # looks through the hash and finds candidate lines. The
678 # looks through the hash and finds candidate lines. The
679 # result is a list of line numbers sorted based on distance
679 # result is a list of line numbers sorted based on distance
680 # from linenum
680 # from linenum
681
681
682 cand = self.hash.get(l, [])
682 cand = self.hash.get(l, [])
683 if len(cand) > 1:
683 if len(cand) > 1:
684 # resort our list of potentials forward then back.
684 # resort our list of potentials forward then back.
685 cand.sort(key=lambda x: abs(x - linenum))
685 cand.sort(key=lambda x: abs(x - linenum))
686 return cand
686 return cand
687
687
688 def write_rej(self):
688 def write_rej(self):
689 # our rejects are a little different from patch(1). This always
689 # our rejects are a little different from patch(1). This always
690 # creates rejects in the same form as the original patch. A file
690 # creates rejects in the same form as the original patch. A file
691 # header is inserted so that you can run the reject through patch again
691 # header is inserted so that you can run the reject through patch again
692 # without having to type the filename.
692 # without having to type the filename.
693 if not self.rej:
693 if not self.rej:
694 return
694 return
695 base = os.path.basename(self.fname)
695 base = os.path.basename(self.fname)
696 lines = ["--- %s\n+++ %s\n" % (base, base)]
696 lines = ["--- %s\n+++ %s\n" % (base, base)]
697 for x in self.rej:
697 for x in self.rej:
698 for l in x.hunk:
698 for l in x.hunk:
699 lines.append(l)
699 lines.append(l)
700 if l[-1] != '\n':
700 if l[-1] != '\n':
701 lines.append("\n\ No newline at end of file\n")
701 lines.append("\n\ No newline at end of file\n")
702 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
702 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
703
703
704 def apply(self, h):
704 def apply(self, h):
705 if not h.complete():
705 if not h.complete():
706 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
706 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
707 (h.number, h.desc, len(h.a), h.lena, len(h.b),
707 (h.number, h.desc, len(h.a), h.lena, len(h.b),
708 h.lenb))
708 h.lenb))
709
709
710 self.hunks += 1
710 self.hunks += 1
711
711
712 if self.missing:
712 if self.missing:
713 self.rej.append(h)
713 self.rej.append(h)
714 return -1
714 return -1
715
715
716 if self.exists and self.create:
716 if self.exists and self.create:
717 if self.copysource:
717 if self.copysource:
718 self.ui.warn(_("cannot create %s: destination already "
718 self.ui.warn(_("cannot create %s: destination already "
719 "exists\n") % self.fname)
719 "exists\n") % self.fname)
720 else:
720 else:
721 self.ui.warn(_("file %s already exists\n") % self.fname)
721 self.ui.warn(_("file %s already exists\n") % self.fname)
722 self.rej.append(h)
722 self.rej.append(h)
723 return -1
723 return -1
724
724
725 if isinstance(h, binhunk):
725 if isinstance(h, binhunk):
726 if self.remove:
726 if self.remove:
727 self.backend.unlink(self.fname)
727 self.backend.unlink(self.fname)
728 else:
728 else:
729 l = h.new(self.lines)
729 l = h.new(self.lines)
730 self.lines[:] = l
730 self.lines[:] = l
731 self.offset += len(l)
731 self.offset += len(l)
732 self.dirty = True
732 self.dirty = True
733 return 0
733 return 0
734
734
735 horig = h
735 horig = h
736 if (self.eolmode in ('crlf', 'lf')
736 if (self.eolmode in ('crlf', 'lf')
737 or self.eolmode == 'auto' and self.eol):
737 or self.eolmode == 'auto' and self.eol):
738 # If new eols are going to be normalized, then normalize
738 # If new eols are going to be normalized, then normalize
739 # hunk data before patching. Otherwise, preserve input
739 # hunk data before patching. Otherwise, preserve input
740 # line-endings.
740 # line-endings.
741 h = h.getnormalized()
741 h = h.getnormalized()
742
742
743 # fast case first, no offsets, no fuzz
743 # fast case first, no offsets, no fuzz
744 old, oldstart, new, newstart = h.fuzzit(0, False)
744 old, oldstart, new, newstart = h.fuzzit(0, False)
745 oldstart += self.offset
745 oldstart += self.offset
746 orig_start = oldstart
746 orig_start = oldstart
747 # if there's skew we want to emit the "(offset %d lines)" even
747 # if there's skew we want to emit the "(offset %d lines)" even
748 # when the hunk cleanly applies at start + skew, so skip the
748 # when the hunk cleanly applies at start + skew, so skip the
749 # fast case code
749 # fast case code
750 if (self.skew == 0 and
750 if (self.skew == 0 and
751 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
751 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
752 if self.remove:
752 if self.remove:
753 self.backend.unlink(self.fname)
753 self.backend.unlink(self.fname)
754 else:
754 else:
755 self.lines[oldstart:oldstart + len(old)] = new
755 self.lines[oldstart:oldstart + len(old)] = new
756 self.offset += len(new) - len(old)
756 self.offset += len(new) - len(old)
757 self.dirty = True
757 self.dirty = True
758 return 0
758 return 0
759
759
760 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
760 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
761 self.hash = {}
761 self.hash = {}
762 for x, s in enumerate(self.lines):
762 for x, s in enumerate(self.lines):
763 self.hash.setdefault(s, []).append(x)
763 self.hash.setdefault(s, []).append(x)
764
764
765 for fuzzlen in xrange(3):
765 for fuzzlen in xrange(3):
766 for toponly in [True, False]:
766 for toponly in [True, False]:
767 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
767 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
768 oldstart = oldstart + self.offset + self.skew
768 oldstart = oldstart + self.offset + self.skew
769 oldstart = min(oldstart, len(self.lines))
769 oldstart = min(oldstart, len(self.lines))
770 if old:
770 if old:
771 cand = self.findlines(old[0][1:], oldstart)
771 cand = self.findlines(old[0][1:], oldstart)
772 else:
772 else:
773 # Only adding lines with no or fuzzed context, just
773 # Only adding lines with no or fuzzed context, just
774 # take the skew in account
774 # take the skew in account
775 cand = [oldstart]
775 cand = [oldstart]
776
776
777 for l in cand:
777 for l in cand:
778 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
778 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
779 self.lines[l : l + len(old)] = new
779 self.lines[l : l + len(old)] = new
780 self.offset += len(new) - len(old)
780 self.offset += len(new) - len(old)
781 self.skew = l - orig_start
781 self.skew = l - orig_start
782 self.dirty = True
782 self.dirty = True
783 offset = l - orig_start - fuzzlen
783 offset = l - orig_start - fuzzlen
784 if fuzzlen:
784 if fuzzlen:
785 msg = _("Hunk #%d succeeded at %d "
785 msg = _("Hunk #%d succeeded at %d "
786 "with fuzz %d "
786 "with fuzz %d "
787 "(offset %d lines).\n")
787 "(offset %d lines).\n")
788 self.printfile(True)
788 self.printfile(True)
789 self.ui.warn(msg %
789 self.ui.warn(msg %
790 (h.number, l + 1, fuzzlen, offset))
790 (h.number, l + 1, fuzzlen, offset))
791 else:
791 else:
792 msg = _("Hunk #%d succeeded at %d "
792 msg = _("Hunk #%d succeeded at %d "
793 "(offset %d lines).\n")
793 "(offset %d lines).\n")
794 self.ui.note(msg % (h.number, l + 1, offset))
794 self.ui.note(msg % (h.number, l + 1, offset))
795 return fuzzlen
795 return fuzzlen
796 self.printfile(True)
796 self.printfile(True)
797 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
797 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
798 self.rej.append(horig)
798 self.rej.append(horig)
799 return -1
799 return -1
800
800
801 def close(self):
801 def close(self):
802 if self.dirty:
802 if self.dirty:
803 self.writelines(self.fname, self.lines, self.mode)
803 self.writelines(self.fname, self.lines, self.mode)
804 self.write_rej()
804 self.write_rej()
805 return len(self.rej)
805 return len(self.rej)
806
806
807 class hunk(object):
807 class hunk(object):
808 def __init__(self, desc, num, lr, context):
808 def __init__(self, desc, num, lr, context):
809 self.number = num
809 self.number = num
810 self.desc = desc
810 self.desc = desc
811 self.hunk = [desc]
811 self.hunk = [desc]
812 self.a = []
812 self.a = []
813 self.b = []
813 self.b = []
814 self.starta = self.lena = None
814 self.starta = self.lena = None
815 self.startb = self.lenb = None
815 self.startb = self.lenb = None
816 if lr is not None:
816 if lr is not None:
817 if context:
817 if context:
818 self.read_context_hunk(lr)
818 self.read_context_hunk(lr)
819 else:
819 else:
820 self.read_unified_hunk(lr)
820 self.read_unified_hunk(lr)
821
821
822 def getnormalized(self):
822 def getnormalized(self):
823 """Return a copy with line endings normalized to LF."""
823 """Return a copy with line endings normalized to LF."""
824
824
825 def normalize(lines):
825 def normalize(lines):
826 nlines = []
826 nlines = []
827 for line in lines:
827 for line in lines:
828 if line.endswith('\r\n'):
828 if line.endswith('\r\n'):
829 line = line[:-2] + '\n'
829 line = line[:-2] + '\n'
830 nlines.append(line)
830 nlines.append(line)
831 return nlines
831 return nlines
832
832
833 # Dummy object, it is rebuilt manually
833 # Dummy object, it is rebuilt manually
834 nh = hunk(self.desc, self.number, None, None)
834 nh = hunk(self.desc, self.number, None, None)
835 nh.number = self.number
835 nh.number = self.number
836 nh.desc = self.desc
836 nh.desc = self.desc
837 nh.hunk = self.hunk
837 nh.hunk = self.hunk
838 nh.a = normalize(self.a)
838 nh.a = normalize(self.a)
839 nh.b = normalize(self.b)
839 nh.b = normalize(self.b)
840 nh.starta = self.starta
840 nh.starta = self.starta
841 nh.startb = self.startb
841 nh.startb = self.startb
842 nh.lena = self.lena
842 nh.lena = self.lena
843 nh.lenb = self.lenb
843 nh.lenb = self.lenb
844 return nh
844 return nh
845
845
846 def read_unified_hunk(self, lr):
846 def read_unified_hunk(self, lr):
847 m = unidesc.match(self.desc)
847 m = unidesc.match(self.desc)
848 if not m:
848 if not m:
849 raise PatchError(_("bad hunk #%d") % self.number)
849 raise PatchError(_("bad hunk #%d") % self.number)
850 self.starta, self.lena, self.startb, self.lenb = m.groups()
850 self.starta, self.lena, self.startb, self.lenb = m.groups()
851 if self.lena is None:
851 if self.lena is None:
852 self.lena = 1
852 self.lena = 1
853 else:
853 else:
854 self.lena = int(self.lena)
854 self.lena = int(self.lena)
855 if self.lenb is None:
855 if self.lenb is None:
856 self.lenb = 1
856 self.lenb = 1
857 else:
857 else:
858 self.lenb = int(self.lenb)
858 self.lenb = int(self.lenb)
859 self.starta = int(self.starta)
859 self.starta = int(self.starta)
860 self.startb = int(self.startb)
860 self.startb = int(self.startb)
861 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
861 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
862 self.b)
862 self.b)
863 # if we hit eof before finishing out the hunk, the last line will
863 # if we hit eof before finishing out the hunk, the last line will
864 # be zero length. Lets try to fix it up.
864 # be zero length. Lets try to fix it up.
865 while len(self.hunk[-1]) == 0:
865 while len(self.hunk[-1]) == 0:
866 del self.hunk[-1]
866 del self.hunk[-1]
867 del self.a[-1]
867 del self.a[-1]
868 del self.b[-1]
868 del self.b[-1]
869 self.lena -= 1
869 self.lena -= 1
870 self.lenb -= 1
870 self.lenb -= 1
871 self._fixnewline(lr)
871 self._fixnewline(lr)
872
872
873 def read_context_hunk(self, lr):
873 def read_context_hunk(self, lr):
874 self.desc = lr.readline()
874 self.desc = lr.readline()
875 m = contextdesc.match(self.desc)
875 m = contextdesc.match(self.desc)
876 if not m:
876 if not m:
877 raise PatchError(_("bad hunk #%d") % self.number)
877 raise PatchError(_("bad hunk #%d") % self.number)
878 self.starta, aend = m.groups()
878 self.starta, aend = m.groups()
879 self.starta = int(self.starta)
879 self.starta = int(self.starta)
880 if aend is None:
880 if aend is None:
881 aend = self.starta
881 aend = self.starta
882 self.lena = int(aend) - self.starta
882 self.lena = int(aend) - self.starta
883 if self.starta:
883 if self.starta:
884 self.lena += 1
884 self.lena += 1
885 for x in xrange(self.lena):
885 for x in xrange(self.lena):
886 l = lr.readline()
886 l = lr.readline()
887 if l.startswith('---'):
887 if l.startswith('---'):
888 # lines addition, old block is empty
888 # lines addition, old block is empty
889 lr.push(l)
889 lr.push(l)
890 break
890 break
891 s = l[2:]
891 s = l[2:]
892 if l.startswith('- ') or l.startswith('! '):
892 if l.startswith('- ') or l.startswith('! '):
893 u = '-' + s
893 u = '-' + s
894 elif l.startswith(' '):
894 elif l.startswith(' '):
895 u = ' ' + s
895 u = ' ' + s
896 else:
896 else:
897 raise PatchError(_("bad hunk #%d old text line %d") %
897 raise PatchError(_("bad hunk #%d old text line %d") %
898 (self.number, x))
898 (self.number, x))
899 self.a.append(u)
899 self.a.append(u)
900 self.hunk.append(u)
900 self.hunk.append(u)
901
901
902 l = lr.readline()
902 l = lr.readline()
903 if l.startswith('\ '):
903 if l.startswith('\ '):
904 s = self.a[-1][:-1]
904 s = self.a[-1][:-1]
905 self.a[-1] = s
905 self.a[-1] = s
906 self.hunk[-1] = s
906 self.hunk[-1] = s
907 l = lr.readline()
907 l = lr.readline()
908 m = contextdesc.match(l)
908 m = contextdesc.match(l)
909 if not m:
909 if not m:
910 raise PatchError(_("bad hunk #%d") % self.number)
910 raise PatchError(_("bad hunk #%d") % self.number)
911 self.startb, bend = m.groups()
911 self.startb, bend = m.groups()
912 self.startb = int(self.startb)
912 self.startb = int(self.startb)
913 if bend is None:
913 if bend is None:
914 bend = self.startb
914 bend = self.startb
915 self.lenb = int(bend) - self.startb
915 self.lenb = int(bend) - self.startb
916 if self.startb:
916 if self.startb:
917 self.lenb += 1
917 self.lenb += 1
918 hunki = 1
918 hunki = 1
919 for x in xrange(self.lenb):
919 for x in xrange(self.lenb):
920 l = lr.readline()
920 l = lr.readline()
921 if l.startswith('\ '):
921 if l.startswith('\ '):
922 # XXX: the only way to hit this is with an invalid line range.
922 # XXX: the only way to hit this is with an invalid line range.
923 # The no-eol marker is not counted in the line range, but I
923 # The no-eol marker is not counted in the line range, but I
924 # guess there are diff(1) out there which behave differently.
924 # guess there are diff(1) out there which behave differently.
925 s = self.b[-1][:-1]
925 s = self.b[-1][:-1]
926 self.b[-1] = s
926 self.b[-1] = s
927 self.hunk[hunki - 1] = s
927 self.hunk[hunki - 1] = s
928 continue
928 continue
929 if not l:
929 if not l:
930 # line deletions, new block is empty and we hit EOF
930 # line deletions, new block is empty and we hit EOF
931 lr.push(l)
931 lr.push(l)
932 break
932 break
933 s = l[2:]
933 s = l[2:]
934 if l.startswith('+ ') or l.startswith('! '):
934 if l.startswith('+ ') or l.startswith('! '):
935 u = '+' + s
935 u = '+' + s
936 elif l.startswith(' '):
936 elif l.startswith(' '):
937 u = ' ' + s
937 u = ' ' + s
938 elif len(self.b) == 0:
938 elif len(self.b) == 0:
939 # line deletions, new block is empty
939 # line deletions, new block is empty
940 lr.push(l)
940 lr.push(l)
941 break
941 break
942 else:
942 else:
943 raise PatchError(_("bad hunk #%d old text line %d") %
943 raise PatchError(_("bad hunk #%d old text line %d") %
944 (self.number, x))
944 (self.number, x))
945 self.b.append(s)
945 self.b.append(s)
946 while True:
946 while True:
947 if hunki >= len(self.hunk):
947 if hunki >= len(self.hunk):
948 h = ""
948 h = ""
949 else:
949 else:
950 h = self.hunk[hunki]
950 h = self.hunk[hunki]
951 hunki += 1
951 hunki += 1
952 if h == u:
952 if h == u:
953 break
953 break
954 elif h.startswith('-'):
954 elif h.startswith('-'):
955 continue
955 continue
956 else:
956 else:
957 self.hunk.insert(hunki - 1, u)
957 self.hunk.insert(hunki - 1, u)
958 break
958 break
959
959
960 if not self.a:
960 if not self.a:
961 # this happens when lines were only added to the hunk
961 # this happens when lines were only added to the hunk
962 for x in self.hunk:
962 for x in self.hunk:
963 if x.startswith('-') or x.startswith(' '):
963 if x.startswith('-') or x.startswith(' '):
964 self.a.append(x)
964 self.a.append(x)
965 if not self.b:
965 if not self.b:
966 # this happens when lines were only deleted from the hunk
966 # this happens when lines were only deleted from the hunk
967 for x in self.hunk:
967 for x in self.hunk:
968 if x.startswith('+') or x.startswith(' '):
968 if x.startswith('+') or x.startswith(' '):
969 self.b.append(x[1:])
969 self.b.append(x[1:])
970 # @@ -start,len +start,len @@
970 # @@ -start,len +start,len @@
971 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
971 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
972 self.startb, self.lenb)
972 self.startb, self.lenb)
973 self.hunk[0] = self.desc
973 self.hunk[0] = self.desc
974 self._fixnewline(lr)
974 self._fixnewline(lr)
975
975
976 def _fixnewline(self, lr):
976 def _fixnewline(self, lr):
977 l = lr.readline()
977 l = lr.readline()
978 if l.startswith('\ '):
978 if l.startswith('\ '):
979 diffhelpers.fix_newline(self.hunk, self.a, self.b)
979 diffhelpers.fix_newline(self.hunk, self.a, self.b)
980 else:
980 else:
981 lr.push(l)
981 lr.push(l)
982
982
983 def complete(self):
983 def complete(self):
984 return len(self.a) == self.lena and len(self.b) == self.lenb
984 return len(self.a) == self.lena and len(self.b) == self.lenb
985
985
986 def _fuzzit(self, old, new, fuzz, toponly):
986 def _fuzzit(self, old, new, fuzz, toponly):
987 # this removes context lines from the top and bottom of list 'l'. It
987 # this removes context lines from the top and bottom of list 'l'. It
988 # checks the hunk to make sure only context lines are removed, and then
988 # checks the hunk to make sure only context lines are removed, and then
989 # returns a new shortened list of lines.
989 # returns a new shortened list of lines.
990 fuzz = min(fuzz, len(old))
990 fuzz = min(fuzz, len(old))
991 if fuzz:
991 if fuzz:
992 top = 0
992 top = 0
993 bot = 0
993 bot = 0
994 hlen = len(self.hunk)
994 hlen = len(self.hunk)
995 for x in xrange(hlen - 1):
995 for x in xrange(hlen - 1):
996 # the hunk starts with the @@ line, so use x+1
996 # the hunk starts with the @@ line, so use x+1
997 if self.hunk[x + 1][0] == ' ':
997 if self.hunk[x + 1][0] == ' ':
998 top += 1
998 top += 1
999 else:
999 else:
1000 break
1000 break
1001 if not toponly:
1001 if not toponly:
1002 for x in xrange(hlen - 1):
1002 for x in xrange(hlen - 1):
1003 if self.hunk[hlen - bot - 1][0] == ' ':
1003 if self.hunk[hlen - bot - 1][0] == ' ':
1004 bot += 1
1004 bot += 1
1005 else:
1005 else:
1006 break
1006 break
1007
1007
1008 bot = min(fuzz, bot)
1008 bot = min(fuzz, bot)
1009 top = min(fuzz, top)
1009 top = min(fuzz, top)
1010 return old[top:len(old) - bot], new[top:len(new) - bot], top
1010 return old[top:len(old) - bot], new[top:len(new) - bot], top
1011 return old, new, 0
1011 return old, new, 0
1012
1012
1013 def fuzzit(self, fuzz, toponly):
1013 def fuzzit(self, fuzz, toponly):
1014 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1014 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1015 oldstart = self.starta + top
1015 oldstart = self.starta + top
1016 newstart = self.startb + top
1016 newstart = self.startb + top
1017 # zero length hunk ranges already have their start decremented
1017 # zero length hunk ranges already have their start decremented
1018 if self.lena and oldstart > 0:
1018 if self.lena and oldstart > 0:
1019 oldstart -= 1
1019 oldstart -= 1
1020 if self.lenb and newstart > 0:
1020 if self.lenb and newstart > 0:
1021 newstart -= 1
1021 newstart -= 1
1022 return old, oldstart, new, newstart
1022 return old, oldstart, new, newstart
1023
1023
1024 class binhunk(object):
1024 class binhunk(object):
1025 'A binary patch file.'
1025 'A binary patch file.'
1026 def __init__(self, lr, fname):
1026 def __init__(self, lr, fname):
1027 self.text = None
1027 self.text = None
1028 self.delta = False
1028 self.delta = False
1029 self.hunk = ['GIT binary patch\n']
1029 self.hunk = ['GIT binary patch\n']
1030 self._fname = fname
1030 self._fname = fname
1031 self._read(lr)
1031 self._read(lr)
1032
1032
1033 def complete(self):
1033 def complete(self):
1034 return self.text is not None
1034 return self.text is not None
1035
1035
1036 def new(self, lines):
1036 def new(self, lines):
1037 if self.delta:
1037 if self.delta:
1038 return [applybindelta(self.text, ''.join(lines))]
1038 return [applybindelta(self.text, ''.join(lines))]
1039 return [self.text]
1039 return [self.text]
1040
1040
1041 def _read(self, lr):
1041 def _read(self, lr):
1042 def getline(lr, hunk):
1042 def getline(lr, hunk):
1043 l = lr.readline()
1043 l = lr.readline()
1044 hunk.append(l)
1044 hunk.append(l)
1045 return l.rstrip('\r\n')
1045 return l.rstrip('\r\n')
1046
1046
1047 size = 0
1047 size = 0
1048 while True:
1048 while True:
1049 line = getline(lr, self.hunk)
1049 line = getline(lr, self.hunk)
1050 if not line:
1050 if not line:
1051 raise PatchError(_('could not extract "%s" binary data')
1051 raise PatchError(_('could not extract "%s" binary data')
1052 % self._fname)
1052 % self._fname)
1053 if line.startswith('literal '):
1053 if line.startswith('literal '):
1054 size = int(line[8:].rstrip())
1054 size = int(line[8:].rstrip())
1055 break
1055 break
1056 if line.startswith('delta '):
1056 if line.startswith('delta '):
1057 size = int(line[6:].rstrip())
1057 size = int(line[6:].rstrip())
1058 self.delta = True
1058 self.delta = True
1059 break
1059 break
1060 dec = []
1060 dec = []
1061 line = getline(lr, self.hunk)
1061 line = getline(lr, self.hunk)
1062 while len(line) > 1:
1062 while len(line) > 1:
1063 l = line[0]
1063 l = line[0]
1064 if l <= 'Z' and l >= 'A':
1064 if l <= 'Z' and l >= 'A':
1065 l = ord(l) - ord('A') + 1
1065 l = ord(l) - ord('A') + 1
1066 else:
1066 else:
1067 l = ord(l) - ord('a') + 27
1067 l = ord(l) - ord('a') + 27
1068 try:
1068 try:
1069 dec.append(base85.b85decode(line[1:])[:l])
1069 dec.append(base85.b85decode(line[1:])[:l])
1070 except ValueError, e:
1070 except ValueError, e:
1071 raise PatchError(_('could not decode "%s" binary patch: %s')
1071 raise PatchError(_('could not decode "%s" binary patch: %s')
1072 % (self._fname, str(e)))
1072 % (self._fname, str(e)))
1073 line = getline(lr, self.hunk)
1073 line = getline(lr, self.hunk)
1074 text = zlib.decompress(''.join(dec))
1074 text = zlib.decompress(''.join(dec))
1075 if len(text) != size:
1075 if len(text) != size:
1076 raise PatchError(_('"%s" length is %d bytes, should be %d')
1076 raise PatchError(_('"%s" length is %d bytes, should be %d')
1077 % (self._fname, len(text), size))
1077 % (self._fname, len(text), size))
1078 self.text = text
1078 self.text = text
1079
1079
1080 def parsefilename(str):
1080 def parsefilename(str):
1081 # --- filename \t|space stuff
1081 # --- filename \t|space stuff
1082 s = str[4:].rstrip('\r\n')
1082 s = str[4:].rstrip('\r\n')
1083 i = s.find('\t')
1083 i = s.find('\t')
1084 if i < 0:
1084 if i < 0:
1085 i = s.find(' ')
1085 i = s.find(' ')
1086 if i < 0:
1086 if i < 0:
1087 return s
1087 return s
1088 return s[:i]
1088 return s[:i]
1089
1089
1090 def pathtransform(path, strip, prefix):
1090 def pathtransform(path, strip, prefix):
1091 '''turn a path from a patch into a path suitable for the repository
1091 '''turn a path from a patch into a path suitable for the repository
1092
1092
1093 prefix, if not empty, is expected to be normalized with a / at the end.
1093 prefix, if not empty, is expected to be normalized with a / at the end.
1094
1094
1095 Returns (stripped components, path in repository).
1095 Returns (stripped components, path in repository).
1096
1096
1097 >>> pathtransform('a/b/c', 0, '')
1097 >>> pathtransform('a/b/c', 0, '')
1098 ('', 'a/b/c')
1098 ('', 'a/b/c')
1099 >>> pathtransform(' a/b/c ', 0, '')
1099 >>> pathtransform(' a/b/c ', 0, '')
1100 ('', ' a/b/c')
1100 ('', ' a/b/c')
1101 >>> pathtransform(' a/b/c ', 2, '')
1101 >>> pathtransform(' a/b/c ', 2, '')
1102 ('a/b/', 'c')
1102 ('a/b/', 'c')
1103 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1103 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1104 ('a//b/', 'd/e/c')
1104 ('a//b/', 'd/e/c')
1105 >>> pathtransform('a/b/c', 3, '')
1105 >>> pathtransform('a/b/c', 3, '')
1106 Traceback (most recent call last):
1106 Traceback (most recent call last):
1107 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1107 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1108 '''
1108 '''
1109 pathlen = len(path)
1109 pathlen = len(path)
1110 i = 0
1110 i = 0
1111 if strip == 0:
1111 if strip == 0:
1112 return '', path.rstrip()
1112 return '', path.rstrip()
1113 count = strip
1113 count = strip
1114 while count > 0:
1114 while count > 0:
1115 i = path.find('/', i)
1115 i = path.find('/', i)
1116 if i == -1:
1116 if i == -1:
1117 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1117 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1118 (count, strip, path))
1118 (count, strip, path))
1119 i += 1
1119 i += 1
1120 # consume '//' in the path
1120 # consume '//' in the path
1121 while i < pathlen - 1 and path[i] == '/':
1121 while i < pathlen - 1 and path[i] == '/':
1122 i += 1
1122 i += 1
1123 count -= 1
1123 count -= 1
1124 return path[:i].lstrip(), prefix + path[i:].rstrip()
1124 return path[:i].lstrip(), prefix + path[i:].rstrip()
1125
1125
1126 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1126 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1127 nulla = afile_orig == "/dev/null"
1127 nulla = afile_orig == "/dev/null"
1128 nullb = bfile_orig == "/dev/null"
1128 nullb = bfile_orig == "/dev/null"
1129 create = nulla and hunk.starta == 0 and hunk.lena == 0
1129 create = nulla and hunk.starta == 0 and hunk.lena == 0
1130 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1130 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1131 abase, afile = pathtransform(afile_orig, strip, prefix)
1131 abase, afile = pathtransform(afile_orig, strip, prefix)
1132 gooda = not nulla and backend.exists(afile)
1132 gooda = not nulla and backend.exists(afile)
1133 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1133 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1134 if afile == bfile:
1134 if afile == bfile:
1135 goodb = gooda
1135 goodb = gooda
1136 else:
1136 else:
1137 goodb = not nullb and backend.exists(bfile)
1137 goodb = not nullb and backend.exists(bfile)
1138 missing = not goodb and not gooda and not create
1138 missing = not goodb and not gooda and not create
1139
1139
1140 # some diff programs apparently produce patches where the afile is
1140 # some diff programs apparently produce patches where the afile is
1141 # not /dev/null, but afile starts with bfile
1141 # not /dev/null, but afile starts with bfile
1142 abasedir = afile[:afile.rfind('/') + 1]
1142 abasedir = afile[:afile.rfind('/') + 1]
1143 bbasedir = bfile[:bfile.rfind('/') + 1]
1143 bbasedir = bfile[:bfile.rfind('/') + 1]
1144 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1144 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1145 and hunk.starta == 0 and hunk.lena == 0):
1145 and hunk.starta == 0 and hunk.lena == 0):
1146 create = True
1146 create = True
1147 missing = False
1147 missing = False
1148
1148
1149 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1149 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1150 # diff is between a file and its backup. In this case, the original
1150 # diff is between a file and its backup. In this case, the original
1151 # file should be patched (see original mpatch code).
1151 # file should be patched (see original mpatch code).
1152 isbackup = (abase == bbase and bfile.startswith(afile))
1152 isbackup = (abase == bbase and bfile.startswith(afile))
1153 fname = None
1153 fname = None
1154 if not missing:
1154 if not missing:
1155 if gooda and goodb:
1155 if gooda and goodb:
1156 fname = isbackup and afile or bfile
1156 fname = isbackup and afile or bfile
1157 elif gooda:
1157 elif gooda:
1158 fname = afile
1158 fname = afile
1159
1159
1160 if not fname:
1160 if not fname:
1161 if not nullb:
1161 if not nullb:
1162 fname = isbackup and afile or bfile
1162 fname = isbackup and afile or bfile
1163 elif not nulla:
1163 elif not nulla:
1164 fname = afile
1164 fname = afile
1165 else:
1165 else:
1166 raise PatchError(_("undefined source and destination files"))
1166 raise PatchError(_("undefined source and destination files"))
1167
1167
1168 gp = patchmeta(fname)
1168 gp = patchmeta(fname)
1169 if create:
1169 if create:
1170 gp.op = 'ADD'
1170 gp.op = 'ADD'
1171 elif remove:
1171 elif remove:
1172 gp.op = 'DELETE'
1172 gp.op = 'DELETE'
1173 return gp
1173 return gp
1174
1174
1175 def scangitpatch(lr, firstline):
1175 def scangitpatch(lr, firstline):
1176 """
1176 """
1177 Git patches can emit:
1177 Git patches can emit:
1178 - rename a to b
1178 - rename a to b
1179 - change b
1179 - change b
1180 - copy a to c
1180 - copy a to c
1181 - change c
1181 - change c
1182
1182
1183 We cannot apply this sequence as-is, the renamed 'a' could not be
1183 We cannot apply this sequence as-is, the renamed 'a' could not be
1184 found for it would have been renamed already. And we cannot copy
1184 found for it would have been renamed already. And we cannot copy
1185 from 'b' instead because 'b' would have been changed already. So
1185 from 'b' instead because 'b' would have been changed already. So
1186 we scan the git patch for copy and rename commands so we can
1186 we scan the git patch for copy and rename commands so we can
1187 perform the copies ahead of time.
1187 perform the copies ahead of time.
1188 """
1188 """
1189 pos = 0
1189 pos = 0
1190 try:
1190 try:
1191 pos = lr.fp.tell()
1191 pos = lr.fp.tell()
1192 fp = lr.fp
1192 fp = lr.fp
1193 except IOError:
1193 except IOError:
1194 fp = cStringIO.StringIO(lr.fp.read())
1194 fp = cStringIO.StringIO(lr.fp.read())
1195 gitlr = linereader(fp)
1195 gitlr = linereader(fp)
1196 gitlr.push(firstline)
1196 gitlr.push(firstline)
1197 gitpatches = readgitpatch(gitlr)
1197 gitpatches = readgitpatch(gitlr)
1198 fp.seek(pos)
1198 fp.seek(pos)
1199 return gitpatches
1199 return gitpatches
1200
1200
1201 def iterhunks(fp):
1201 def iterhunks(fp):
1202 """Read a patch and yield the following events:
1202 """Read a patch and yield the following events:
1203 - ("file", afile, bfile, firsthunk): select a new target file.
1203 - ("file", afile, bfile, firsthunk): select a new target file.
1204 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1204 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1205 "file" event.
1205 "file" event.
1206 - ("git", gitchanges): current diff is in git format, gitchanges
1206 - ("git", gitchanges): current diff is in git format, gitchanges
1207 maps filenames to gitpatch records. Unique event.
1207 maps filenames to gitpatch records. Unique event.
1208 """
1208 """
1209 afile = ""
1209 afile = ""
1210 bfile = ""
1210 bfile = ""
1211 state = None
1211 state = None
1212 hunknum = 0
1212 hunknum = 0
1213 emitfile = newfile = False
1213 emitfile = newfile = False
1214 gitpatches = None
1214 gitpatches = None
1215
1215
1216 # our states
1216 # our states
1217 BFILE = 1
1217 BFILE = 1
1218 context = None
1218 context = None
1219 lr = linereader(fp)
1219 lr = linereader(fp)
1220
1220
1221 while True:
1221 while True:
1222 x = lr.readline()
1222 x = lr.readline()
1223 if not x:
1223 if not x:
1224 break
1224 break
1225 if state == BFILE and (
1225 if state == BFILE and (
1226 (not context and x[0] == '@')
1226 (not context and x[0] == '@')
1227 or (context is not False and x.startswith('***************'))
1227 or (context is not False and x.startswith('***************'))
1228 or x.startswith('GIT binary patch')):
1228 or x.startswith('GIT binary patch')):
1229 gp = None
1229 gp = None
1230 if (gitpatches and
1230 if (gitpatches and
1231 gitpatches[-1].ispatching(afile, bfile)):
1231 gitpatches[-1].ispatching(afile, bfile)):
1232 gp = gitpatches.pop()
1232 gp = gitpatches.pop()
1233 if x.startswith('GIT binary patch'):
1233 if x.startswith('GIT binary patch'):
1234 h = binhunk(lr, gp.path)
1234 h = binhunk(lr, gp.path)
1235 else:
1235 else:
1236 if context is None and x.startswith('***************'):
1236 if context is None and x.startswith('***************'):
1237 context = True
1237 context = True
1238 h = hunk(x, hunknum + 1, lr, context)
1238 h = hunk(x, hunknum + 1, lr, context)
1239 hunknum += 1
1239 hunknum += 1
1240 if emitfile:
1240 if emitfile:
1241 emitfile = False
1241 emitfile = False
1242 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1242 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1243 yield 'hunk', h
1243 yield 'hunk', h
1244 elif x.startswith('diff --git a/'):
1244 elif x.startswith('diff --git a/'):
1245 m = gitre.match(x.rstrip(' \r\n'))
1245 m = gitre.match(x.rstrip(' \r\n'))
1246 if not m:
1246 if not m:
1247 continue
1247 continue
1248 if gitpatches is None:
1248 if gitpatches is None:
1249 # scan whole input for git metadata
1249 # scan whole input for git metadata
1250 gitpatches = scangitpatch(lr, x)
1250 gitpatches = scangitpatch(lr, x)
1251 yield 'git', [g.copy() for g in gitpatches
1251 yield 'git', [g.copy() for g in gitpatches
1252 if g.op in ('COPY', 'RENAME')]
1252 if g.op in ('COPY', 'RENAME')]
1253 gitpatches.reverse()
1253 gitpatches.reverse()
1254 afile = 'a/' + m.group(1)
1254 afile = 'a/' + m.group(1)
1255 bfile = 'b/' + m.group(2)
1255 bfile = 'b/' + m.group(2)
1256 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1256 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1257 gp = gitpatches.pop()
1257 gp = gitpatches.pop()
1258 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1258 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1259 if not gitpatches:
1259 if not gitpatches:
1260 raise PatchError(_('failed to synchronize metadata for "%s"')
1260 raise PatchError(_('failed to synchronize metadata for "%s"')
1261 % afile[2:])
1261 % afile[2:])
1262 gp = gitpatches[-1]
1262 gp = gitpatches[-1]
1263 newfile = True
1263 newfile = True
1264 elif x.startswith('---'):
1264 elif x.startswith('---'):
1265 # check for a unified diff
1265 # check for a unified diff
1266 l2 = lr.readline()
1266 l2 = lr.readline()
1267 if not l2.startswith('+++'):
1267 if not l2.startswith('+++'):
1268 lr.push(l2)
1268 lr.push(l2)
1269 continue
1269 continue
1270 newfile = True
1270 newfile = True
1271 context = False
1271 context = False
1272 afile = parsefilename(x)
1272 afile = parsefilename(x)
1273 bfile = parsefilename(l2)
1273 bfile = parsefilename(l2)
1274 elif x.startswith('***'):
1274 elif x.startswith('***'):
1275 # check for a context diff
1275 # check for a context diff
1276 l2 = lr.readline()
1276 l2 = lr.readline()
1277 if not l2.startswith('---'):
1277 if not l2.startswith('---'):
1278 lr.push(l2)
1278 lr.push(l2)
1279 continue
1279 continue
1280 l3 = lr.readline()
1280 l3 = lr.readline()
1281 lr.push(l3)
1281 lr.push(l3)
1282 if not l3.startswith("***************"):
1282 if not l3.startswith("***************"):
1283 lr.push(l2)
1283 lr.push(l2)
1284 continue
1284 continue
1285 newfile = True
1285 newfile = True
1286 context = True
1286 context = True
1287 afile = parsefilename(x)
1287 afile = parsefilename(x)
1288 bfile = parsefilename(l2)
1288 bfile = parsefilename(l2)
1289
1289
1290 if newfile:
1290 if newfile:
1291 newfile = False
1291 newfile = False
1292 emitfile = True
1292 emitfile = True
1293 state = BFILE
1293 state = BFILE
1294 hunknum = 0
1294 hunknum = 0
1295
1295
1296 while gitpatches:
1296 while gitpatches:
1297 gp = gitpatches.pop()
1297 gp = gitpatches.pop()
1298 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1298 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1299
1299
1300 def applybindelta(binchunk, data):
1300 def applybindelta(binchunk, data):
1301 """Apply a binary delta hunk
1301 """Apply a binary delta hunk
1302 The algorithm used is the algorithm from git's patch-delta.c
1302 The algorithm used is the algorithm from git's patch-delta.c
1303 """
1303 """
1304 def deltahead(binchunk):
1304 def deltahead(binchunk):
1305 i = 0
1305 i = 0
1306 for c in binchunk:
1306 for c in binchunk:
1307 i += 1
1307 i += 1
1308 if not (ord(c) & 0x80):
1308 if not (ord(c) & 0x80):
1309 return i
1309 return i
1310 return i
1310 return i
1311 out = ""
1311 out = ""
1312 s = deltahead(binchunk)
1312 s = deltahead(binchunk)
1313 binchunk = binchunk[s:]
1313 binchunk = binchunk[s:]
1314 s = deltahead(binchunk)
1314 s = deltahead(binchunk)
1315 binchunk = binchunk[s:]
1315 binchunk = binchunk[s:]
1316 i = 0
1316 i = 0
1317 while i < len(binchunk):
1317 while i < len(binchunk):
1318 cmd = ord(binchunk[i])
1318 cmd = ord(binchunk[i])
1319 i += 1
1319 i += 1
1320 if (cmd & 0x80):
1320 if (cmd & 0x80):
1321 offset = 0
1321 offset = 0
1322 size = 0
1322 size = 0
1323 if (cmd & 0x01):
1323 if (cmd & 0x01):
1324 offset = ord(binchunk[i])
1324 offset = ord(binchunk[i])
1325 i += 1
1325 i += 1
1326 if (cmd & 0x02):
1326 if (cmd & 0x02):
1327 offset |= ord(binchunk[i]) << 8
1327 offset |= ord(binchunk[i]) << 8
1328 i += 1
1328 i += 1
1329 if (cmd & 0x04):
1329 if (cmd & 0x04):
1330 offset |= ord(binchunk[i]) << 16
1330 offset |= ord(binchunk[i]) << 16
1331 i += 1
1331 i += 1
1332 if (cmd & 0x08):
1332 if (cmd & 0x08):
1333 offset |= ord(binchunk[i]) << 24
1333 offset |= ord(binchunk[i]) << 24
1334 i += 1
1334 i += 1
1335 if (cmd & 0x10):
1335 if (cmd & 0x10):
1336 size = ord(binchunk[i])
1336 size = ord(binchunk[i])
1337 i += 1
1337 i += 1
1338 if (cmd & 0x20):
1338 if (cmd & 0x20):
1339 size |= ord(binchunk[i]) << 8
1339 size |= ord(binchunk[i]) << 8
1340 i += 1
1340 i += 1
1341 if (cmd & 0x40):
1341 if (cmd & 0x40):
1342 size |= ord(binchunk[i]) << 16
1342 size |= ord(binchunk[i]) << 16
1343 i += 1
1343 i += 1
1344 if size == 0:
1344 if size == 0:
1345 size = 0x10000
1345 size = 0x10000
1346 offset_end = offset + size
1346 offset_end = offset + size
1347 out += data[offset:offset_end]
1347 out += data[offset:offset_end]
1348 elif cmd != 0:
1348 elif cmd != 0:
1349 offset_end = i + cmd
1349 offset_end = i + cmd
1350 out += binchunk[i:offset_end]
1350 out += binchunk[i:offset_end]
1351 i += cmd
1351 i += cmd
1352 else:
1352 else:
1353 raise PatchError(_('unexpected delta opcode 0'))
1353 raise PatchError(_('unexpected delta opcode 0'))
1354 return out
1354 return out
1355
1355
1356 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1356 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1357 """Reads a patch from fp and tries to apply it.
1357 """Reads a patch from fp and tries to apply it.
1358
1358
1359 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1359 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1360 there was any fuzz.
1360 there was any fuzz.
1361
1361
1362 If 'eolmode' is 'strict', the patch content and patched file are
1362 If 'eolmode' is 'strict', the patch content and patched file are
1363 read in binary mode. Otherwise, line endings are ignored when
1363 read in binary mode. Otherwise, line endings are ignored when
1364 patching then normalized according to 'eolmode'.
1364 patching then normalized according to 'eolmode'.
1365 """
1365 """
1366 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1366 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1367 prefix=prefix, eolmode=eolmode)
1367 prefix=prefix, eolmode=eolmode)
1368
1368
1369 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1369 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1370 eolmode='strict'):
1370 eolmode='strict'):
1371
1371
1372 if prefix:
1372 if prefix:
1373 # clean up double slashes, lack of trailing slashes, etc
1373 # clean up double slashes, lack of trailing slashes, etc
1374 prefix = util.normpath(prefix) + '/'
1374 prefix = util.normpath(prefix) + '/'
1375 def pstrip(p):
1375 def pstrip(p):
1376 return pathtransform(p, strip - 1, prefix)[1]
1376 return pathtransform(p, strip - 1, prefix)[1]
1377
1377
1378 rejects = 0
1378 rejects = 0
1379 err = 0
1379 err = 0
1380 current_file = None
1380 current_file = None
1381
1381
1382 for state, values in iterhunks(fp):
1382 for state, values in iterhunks(fp):
1383 if state == 'hunk':
1383 if state == 'hunk':
1384 if not current_file:
1384 if not current_file:
1385 continue
1385 continue
1386 ret = current_file.apply(values)
1386 ret = current_file.apply(values)
1387 if ret > 0:
1387 if ret > 0:
1388 err = 1
1388 err = 1
1389 elif state == 'file':
1389 elif state == 'file':
1390 if current_file:
1390 if current_file:
1391 rejects += current_file.close()
1391 rejects += current_file.close()
1392 current_file = None
1392 current_file = None
1393 afile, bfile, first_hunk, gp = values
1393 afile, bfile, first_hunk, gp = values
1394 if gp:
1394 if gp:
1395 gp.path = pstrip(gp.path)
1395 gp.path = pstrip(gp.path)
1396 if gp.oldpath:
1396 if gp.oldpath:
1397 gp.oldpath = pstrip(gp.oldpath)
1397 gp.oldpath = pstrip(gp.oldpath)
1398 else:
1398 else:
1399 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1399 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1400 prefix)
1400 prefix)
1401 if gp.op == 'RENAME':
1401 if gp.op == 'RENAME':
1402 backend.unlink(gp.oldpath)
1402 backend.unlink(gp.oldpath)
1403 if not first_hunk:
1403 if not first_hunk:
1404 if gp.op == 'DELETE':
1404 if gp.op == 'DELETE':
1405 backend.unlink(gp.path)
1405 backend.unlink(gp.path)
1406 continue
1406 continue
1407 data, mode = None, None
1407 data, mode = None, None
1408 if gp.op in ('RENAME', 'COPY'):
1408 if gp.op in ('RENAME', 'COPY'):
1409 data, mode = store.getfile(gp.oldpath)[:2]
1409 data, mode = store.getfile(gp.oldpath)[:2]
1410 # FIXME: failing getfile has never been handled here
1410 # FIXME: failing getfile has never been handled here
1411 assert data is not None
1411 assert data is not None
1412 if gp.mode:
1412 if gp.mode:
1413 mode = gp.mode
1413 mode = gp.mode
1414 if gp.op == 'ADD':
1414 if gp.op == 'ADD':
1415 # Added files without content have no hunk and
1415 # Added files without content have no hunk and
1416 # must be created
1416 # must be created
1417 data = ''
1417 data = ''
1418 if data or mode:
1418 if data or mode:
1419 if (gp.op in ('ADD', 'RENAME', 'COPY')
1419 if (gp.op in ('ADD', 'RENAME', 'COPY')
1420 and backend.exists(gp.path)):
1420 and backend.exists(gp.path)):
1421 raise PatchError(_("cannot create %s: destination "
1421 raise PatchError(_("cannot create %s: destination "
1422 "already exists") % gp.path)
1422 "already exists") % gp.path)
1423 backend.setfile(gp.path, data, mode, gp.oldpath)
1423 backend.setfile(gp.path, data, mode, gp.oldpath)
1424 continue
1424 continue
1425 try:
1425 try:
1426 current_file = patcher(ui, gp, backend, store,
1426 current_file = patcher(ui, gp, backend, store,
1427 eolmode=eolmode)
1427 eolmode=eolmode)
1428 except PatchError, inst:
1428 except PatchError, inst:
1429 ui.warn(str(inst) + '\n')
1429 ui.warn(str(inst) + '\n')
1430 current_file = None
1430 current_file = None
1431 rejects += 1
1431 rejects += 1
1432 continue
1432 continue
1433 elif state == 'git':
1433 elif state == 'git':
1434 for gp in values:
1434 for gp in values:
1435 path = pstrip(gp.oldpath)
1435 path = pstrip(gp.oldpath)
1436 data, mode = backend.getfile(path)
1436 data, mode = backend.getfile(path)
1437 if data is None:
1437 if data is None:
1438 # The error ignored here will trigger a getfile()
1438 # The error ignored here will trigger a getfile()
1439 # error in a place more appropriate for error
1439 # error in a place more appropriate for error
1440 # handling, and will not interrupt the patching
1440 # handling, and will not interrupt the patching
1441 # process.
1441 # process.
1442 pass
1442 pass
1443 else:
1443 else:
1444 store.setfile(path, data, mode)
1444 store.setfile(path, data, mode)
1445 else:
1445 else:
1446 raise util.Abort(_('unsupported parser state: %s') % state)
1446 raise util.Abort(_('unsupported parser state: %s') % state)
1447
1447
1448 if current_file:
1448 if current_file:
1449 rejects += current_file.close()
1449 rejects += current_file.close()
1450
1450
1451 if rejects:
1451 if rejects:
1452 return -1
1452 return -1
1453 return err
1453 return err
1454
1454
1455 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1455 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1456 similarity):
1456 similarity):
1457 """use <patcher> to apply <patchname> to the working directory.
1457 """use <patcher> to apply <patchname> to the working directory.
1458 returns whether patch was applied with fuzz factor."""
1458 returns whether patch was applied with fuzz factor."""
1459
1459
1460 fuzz = False
1460 fuzz = False
1461 args = []
1461 args = []
1462 cwd = repo.root
1462 cwd = repo.root
1463 if cwd:
1463 if cwd:
1464 args.append('-d %s' % util.shellquote(cwd))
1464 args.append('-d %s' % util.shellquote(cwd))
1465 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1465 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1466 util.shellquote(patchname)))
1466 util.shellquote(patchname)))
1467 try:
1467 try:
1468 for line in fp:
1468 for line in fp:
1469 line = line.rstrip()
1469 line = line.rstrip()
1470 ui.note(line + '\n')
1470 ui.note(line + '\n')
1471 if line.startswith('patching file '):
1471 if line.startswith('patching file '):
1472 pf = util.parsepatchoutput(line)
1472 pf = util.parsepatchoutput(line)
1473 printed_file = False
1473 printed_file = False
1474 files.add(pf)
1474 files.add(pf)
1475 elif line.find('with fuzz') >= 0:
1475 elif line.find('with fuzz') >= 0:
1476 fuzz = True
1476 fuzz = True
1477 if not printed_file:
1477 if not printed_file:
1478 ui.warn(pf + '\n')
1478 ui.warn(pf + '\n')
1479 printed_file = True
1479 printed_file = True
1480 ui.warn(line + '\n')
1480 ui.warn(line + '\n')
1481 elif line.find('saving rejects to file') >= 0:
1481 elif line.find('saving rejects to file') >= 0:
1482 ui.warn(line + '\n')
1482 ui.warn(line + '\n')
1483 elif line.find('FAILED') >= 0:
1483 elif line.find('FAILED') >= 0:
1484 if not printed_file:
1484 if not printed_file:
1485 ui.warn(pf + '\n')
1485 ui.warn(pf + '\n')
1486 printed_file = True
1486 printed_file = True
1487 ui.warn(line + '\n')
1487 ui.warn(line + '\n')
1488 finally:
1488 finally:
1489 if files:
1489 if files:
1490 scmutil.marktouched(repo, files, similarity)
1490 scmutil.marktouched(repo, files, similarity)
1491 code = fp.close()
1491 code = fp.close()
1492 if code:
1492 if code:
1493 raise PatchError(_("patch command failed: %s") %
1493 raise PatchError(_("patch command failed: %s") %
1494 util.explainexit(code)[0])
1494 util.explainexit(code)[0])
1495 return fuzz
1495 return fuzz
1496
1496
1497 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
1497 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
1498 eolmode='strict'):
1498 eolmode='strict'):
1499 if files is None:
1499 if files is None:
1500 files = set()
1500 files = set()
1501 if eolmode is None:
1501 if eolmode is None:
1502 eolmode = ui.config('patch', 'eol', 'strict')
1502 eolmode = ui.config('patch', 'eol', 'strict')
1503 if eolmode.lower() not in eolmodes:
1503 if eolmode.lower() not in eolmodes:
1504 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1504 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1505 eolmode = eolmode.lower()
1505 eolmode = eolmode.lower()
1506
1506
1507 store = filestore()
1507 store = filestore()
1508 try:
1508 try:
1509 fp = open(patchobj, 'rb')
1509 fp = open(patchobj, 'rb')
1510 except TypeError:
1510 except TypeError:
1511 fp = patchobj
1511 fp = patchobj
1512 try:
1512 try:
1513 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
1513 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
1514 eolmode=eolmode)
1514 eolmode=eolmode)
1515 finally:
1515 finally:
1516 if fp != patchobj:
1516 if fp != patchobj:
1517 fp.close()
1517 fp.close()
1518 files.update(backend.close())
1518 files.update(backend.close())
1519 store.close()
1519 store.close()
1520 if ret < 0:
1520 if ret < 0:
1521 raise PatchError(_('patch failed to apply'))
1521 raise PatchError(_('patch failed to apply'))
1522 return ret > 0
1522 return ret > 0
1523
1523
1524 def internalpatch(ui, repo, patchobj, strip, prefix, files=None,
1524 def internalpatch(ui, repo, patchobj, strip, prefix, files=None,
1525 eolmode='strict', similarity=0):
1525 eolmode='strict', similarity=0):
1526 """use builtin patch to apply <patchobj> to the working directory.
1526 """use builtin patch to apply <patchobj> to the working directory.
1527 returns whether patch was applied with fuzz factor."""
1527 returns whether patch was applied with fuzz factor."""
1528 backend = workingbackend(ui, repo, similarity)
1528 backend = workingbackend(ui, repo, similarity)
1529 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1529 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1530
1530
1531 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1531 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1532 eolmode='strict'):
1532 eolmode='strict'):
1533 backend = repobackend(ui, repo, ctx, store)
1533 backend = repobackend(ui, repo, ctx, store)
1534 return patchbackend(ui, backend, patchobj, strip, '', files, eolmode)
1534 return patchbackend(ui, backend, patchobj, strip, '', files, eolmode)
1535
1535
1536 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1536 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
1537 similarity=0):
1537 similarity=0):
1538 """Apply <patchname> to the working directory.
1538 """Apply <patchname> to the working directory.
1539
1539
1540 'eolmode' specifies how end of lines should be handled. It can be:
1540 'eolmode' specifies how end of lines should be handled. It can be:
1541 - 'strict': inputs are read in binary mode, EOLs are preserved
1541 - 'strict': inputs are read in binary mode, EOLs are preserved
1542 - 'crlf': EOLs are ignored when patching and reset to CRLF
1542 - 'crlf': EOLs are ignored when patching and reset to CRLF
1543 - 'lf': EOLs are ignored when patching and reset to LF
1543 - 'lf': EOLs are ignored when patching and reset to LF
1544 - None: get it from user settings, default to 'strict'
1544 - None: get it from user settings, default to 'strict'
1545 'eolmode' is ignored when using an external patcher program.
1545 'eolmode' is ignored when using an external patcher program.
1546
1546
1547 Returns whether patch was applied with fuzz factor.
1547 Returns whether patch was applied with fuzz factor.
1548 """
1548 """
1549 patcher = ui.config('ui', 'patch')
1549 patcher = ui.config('ui', 'patch')
1550 if files is None:
1550 if files is None:
1551 files = set()
1551 files = set()
1552 if patcher:
1552 if patcher:
1553 return _externalpatch(ui, repo, patcher, patchname, strip,
1553 return _externalpatch(ui, repo, patcher, patchname, strip,
1554 files, similarity)
1554 files, similarity)
1555 return internalpatch(ui, repo, patchname, strip, '', files, eolmode,
1555 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
1556 similarity)
1556 similarity)
1557
1557
1558 def changedfiles(ui, repo, patchpath, strip=1):
1558 def changedfiles(ui, repo, patchpath, strip=1):
1559 backend = fsbackend(ui, repo.root)
1559 backend = fsbackend(ui, repo.root)
1560 fp = open(patchpath, 'rb')
1560 fp = open(patchpath, 'rb')
1561 try:
1561 try:
1562 changed = set()
1562 changed = set()
1563 for state, values in iterhunks(fp):
1563 for state, values in iterhunks(fp):
1564 if state == 'file':
1564 if state == 'file':
1565 afile, bfile, first_hunk, gp = values
1565 afile, bfile, first_hunk, gp = values
1566 if gp:
1566 if gp:
1567 gp.path = pathtransform(gp.path, strip - 1, '')[1]
1567 gp.path = pathtransform(gp.path, strip - 1, '')[1]
1568 if gp.oldpath:
1568 if gp.oldpath:
1569 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
1569 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
1570 else:
1570 else:
1571 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1571 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1572 '')
1572 '')
1573 changed.add(gp.path)
1573 changed.add(gp.path)
1574 if gp.op == 'RENAME':
1574 if gp.op == 'RENAME':
1575 changed.add(gp.oldpath)
1575 changed.add(gp.oldpath)
1576 elif state not in ('hunk', 'git'):
1576 elif state not in ('hunk', 'git'):
1577 raise util.Abort(_('unsupported parser state: %s') % state)
1577 raise util.Abort(_('unsupported parser state: %s') % state)
1578 return changed
1578 return changed
1579 finally:
1579 finally:
1580 fp.close()
1580 fp.close()
1581
1581
1582 class GitDiffRequired(Exception):
1582 class GitDiffRequired(Exception):
1583 pass
1583 pass
1584
1584
1585 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
1585 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
1586 '''return diffopts with all features supported and parsed'''
1586 '''return diffopts with all features supported and parsed'''
1587 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
1587 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
1588 git=True, whitespace=True, formatchanging=True)
1588 git=True, whitespace=True, formatchanging=True)
1589
1589
1590 diffopts = diffallopts
1590 diffopts = diffallopts
1591
1591
1592 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
1592 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
1593 whitespace=False, formatchanging=False):
1593 whitespace=False, formatchanging=False):
1594 '''return diffopts with only opted-in features parsed
1594 '''return diffopts with only opted-in features parsed
1595
1595
1596 Features:
1596 Features:
1597 - git: git-style diffs
1597 - git: git-style diffs
1598 - whitespace: whitespace options like ignoreblanklines and ignorews
1598 - whitespace: whitespace options like ignoreblanklines and ignorews
1599 - formatchanging: options that will likely break or cause correctness issues
1599 - formatchanging: options that will likely break or cause correctness issues
1600 with most diff parsers
1600 with most diff parsers
1601 '''
1601 '''
1602 def get(key, name=None, getter=ui.configbool, forceplain=None):
1602 def get(key, name=None, getter=ui.configbool, forceplain=None):
1603 if opts:
1603 if opts:
1604 v = opts.get(key)
1604 v = opts.get(key)
1605 if v:
1605 if v:
1606 return v
1606 return v
1607 if forceplain is not None and ui.plain():
1607 if forceplain is not None and ui.plain():
1608 return forceplain
1608 return forceplain
1609 return getter(section, name or key, None, untrusted=untrusted)
1609 return getter(section, name or key, None, untrusted=untrusted)
1610
1610
1611 # core options, expected to be understood by every diff parser
1611 # core options, expected to be understood by every diff parser
1612 buildopts = {
1612 buildopts = {
1613 'nodates': get('nodates'),
1613 'nodates': get('nodates'),
1614 'showfunc': get('show_function', 'showfunc'),
1614 'showfunc': get('show_function', 'showfunc'),
1615 'context': get('unified', getter=ui.config),
1615 'context': get('unified', getter=ui.config),
1616 }
1616 }
1617
1617
1618 if git:
1618 if git:
1619 buildopts['git'] = get('git')
1619 buildopts['git'] = get('git')
1620 if whitespace:
1620 if whitespace:
1621 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
1621 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
1622 buildopts['ignorewsamount'] = get('ignore_space_change',
1622 buildopts['ignorewsamount'] = get('ignore_space_change',
1623 'ignorewsamount')
1623 'ignorewsamount')
1624 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
1624 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
1625 'ignoreblanklines')
1625 'ignoreblanklines')
1626 if formatchanging:
1626 if formatchanging:
1627 buildopts['text'] = opts and opts.get('text')
1627 buildopts['text'] = opts and opts.get('text')
1628 buildopts['nobinary'] = get('nobinary')
1628 buildopts['nobinary'] = get('nobinary')
1629 buildopts['noprefix'] = get('noprefix', forceplain=False)
1629 buildopts['noprefix'] = get('noprefix', forceplain=False)
1630
1630
1631 return mdiff.diffopts(**buildopts)
1631 return mdiff.diffopts(**buildopts)
1632
1632
1633 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1633 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1634 losedatafn=None, prefix=''):
1634 losedatafn=None, prefix=''):
1635 '''yields diff of changes to files between two nodes, or node and
1635 '''yields diff of changes to files between two nodes, or node and
1636 working directory.
1636 working directory.
1637
1637
1638 if node1 is None, use first dirstate parent instead.
1638 if node1 is None, use first dirstate parent instead.
1639 if node2 is None, compare node1 with working directory.
1639 if node2 is None, compare node1 with working directory.
1640
1640
1641 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1641 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1642 every time some change cannot be represented with the current
1642 every time some change cannot be represented with the current
1643 patch format. Return False to upgrade to git patch format, True to
1643 patch format. Return False to upgrade to git patch format, True to
1644 accept the loss or raise an exception to abort the diff. It is
1644 accept the loss or raise an exception to abort the diff. It is
1645 called with the name of current file being diffed as 'fn'. If set
1645 called with the name of current file being diffed as 'fn'. If set
1646 to None, patches will always be upgraded to git format when
1646 to None, patches will always be upgraded to git format when
1647 necessary.
1647 necessary.
1648
1648
1649 prefix is a filename prefix that is prepended to all filenames on
1649 prefix is a filename prefix that is prepended to all filenames on
1650 display (used for subrepos).
1650 display (used for subrepos).
1651 '''
1651 '''
1652
1652
1653 if opts is None:
1653 if opts is None:
1654 opts = mdiff.defaultopts
1654 opts = mdiff.defaultopts
1655
1655
1656 if not node1 and not node2:
1656 if not node1 and not node2:
1657 node1 = repo.dirstate.p1()
1657 node1 = repo.dirstate.p1()
1658
1658
1659 def lrugetfilectx():
1659 def lrugetfilectx():
1660 cache = {}
1660 cache = {}
1661 order = util.deque()
1661 order = util.deque()
1662 def getfilectx(f, ctx):
1662 def getfilectx(f, ctx):
1663 fctx = ctx.filectx(f, filelog=cache.get(f))
1663 fctx = ctx.filectx(f, filelog=cache.get(f))
1664 if f not in cache:
1664 if f not in cache:
1665 if len(cache) > 20:
1665 if len(cache) > 20:
1666 del cache[order.popleft()]
1666 del cache[order.popleft()]
1667 cache[f] = fctx.filelog()
1667 cache[f] = fctx.filelog()
1668 else:
1668 else:
1669 order.remove(f)
1669 order.remove(f)
1670 order.append(f)
1670 order.append(f)
1671 return fctx
1671 return fctx
1672 return getfilectx
1672 return getfilectx
1673 getfilectx = lrugetfilectx()
1673 getfilectx = lrugetfilectx()
1674
1674
1675 ctx1 = repo[node1]
1675 ctx1 = repo[node1]
1676 ctx2 = repo[node2]
1676 ctx2 = repo[node2]
1677
1677
1678 if not changes:
1678 if not changes:
1679 changes = repo.status(ctx1, ctx2, match=match)
1679 changes = repo.status(ctx1, ctx2, match=match)
1680 modified, added, removed = changes[:3]
1680 modified, added, removed = changes[:3]
1681
1681
1682 if not modified and not added and not removed:
1682 if not modified and not added and not removed:
1683 return []
1683 return []
1684
1684
1685 hexfunc = repo.ui.debugflag and hex or short
1685 hexfunc = repo.ui.debugflag and hex or short
1686 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
1686 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
1687
1687
1688 copy = {}
1688 copy = {}
1689 if opts.git or opts.upgrade:
1689 if opts.git or opts.upgrade:
1690 copy = copies.pathcopies(ctx1, ctx2)
1690 copy = copies.pathcopies(ctx1, ctx2)
1691
1691
1692 def difffn(opts, losedata):
1692 def difffn(opts, losedata):
1693 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1693 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1694 copy, getfilectx, opts, losedata, prefix)
1694 copy, getfilectx, opts, losedata, prefix)
1695 if opts.upgrade and not opts.git:
1695 if opts.upgrade and not opts.git:
1696 try:
1696 try:
1697 def losedata(fn):
1697 def losedata(fn):
1698 if not losedatafn or not losedatafn(fn=fn):
1698 if not losedatafn or not losedatafn(fn=fn):
1699 raise GitDiffRequired
1699 raise GitDiffRequired
1700 # Buffer the whole output until we are sure it can be generated
1700 # Buffer the whole output until we are sure it can be generated
1701 return list(difffn(opts.copy(git=False), losedata))
1701 return list(difffn(opts.copy(git=False), losedata))
1702 except GitDiffRequired:
1702 except GitDiffRequired:
1703 return difffn(opts.copy(git=True), None)
1703 return difffn(opts.copy(git=True), None)
1704 else:
1704 else:
1705 return difffn(opts, None)
1705 return difffn(opts, None)
1706
1706
1707 def difflabel(func, *args, **kw):
1707 def difflabel(func, *args, **kw):
1708 '''yields 2-tuples of (output, label) based on the output of func()'''
1708 '''yields 2-tuples of (output, label) based on the output of func()'''
1709 headprefixes = [('diff', 'diff.diffline'),
1709 headprefixes = [('diff', 'diff.diffline'),
1710 ('copy', 'diff.extended'),
1710 ('copy', 'diff.extended'),
1711 ('rename', 'diff.extended'),
1711 ('rename', 'diff.extended'),
1712 ('old', 'diff.extended'),
1712 ('old', 'diff.extended'),
1713 ('new', 'diff.extended'),
1713 ('new', 'diff.extended'),
1714 ('deleted', 'diff.extended'),
1714 ('deleted', 'diff.extended'),
1715 ('---', 'diff.file_a'),
1715 ('---', 'diff.file_a'),
1716 ('+++', 'diff.file_b')]
1716 ('+++', 'diff.file_b')]
1717 textprefixes = [('@', 'diff.hunk'),
1717 textprefixes = [('@', 'diff.hunk'),
1718 ('-', 'diff.deleted'),
1718 ('-', 'diff.deleted'),
1719 ('+', 'diff.inserted')]
1719 ('+', 'diff.inserted')]
1720 head = False
1720 head = False
1721 for chunk in func(*args, **kw):
1721 for chunk in func(*args, **kw):
1722 lines = chunk.split('\n')
1722 lines = chunk.split('\n')
1723 for i, line in enumerate(lines):
1723 for i, line in enumerate(lines):
1724 if i != 0:
1724 if i != 0:
1725 yield ('\n', '')
1725 yield ('\n', '')
1726 if head:
1726 if head:
1727 if line.startswith('@'):
1727 if line.startswith('@'):
1728 head = False
1728 head = False
1729 else:
1729 else:
1730 if line and line[0] not in ' +-@\\':
1730 if line and line[0] not in ' +-@\\':
1731 head = True
1731 head = True
1732 stripline = line
1732 stripline = line
1733 diffline = False
1733 diffline = False
1734 if not head and line and line[0] in '+-':
1734 if not head and line and line[0] in '+-':
1735 # highlight tabs and trailing whitespace, but only in
1735 # highlight tabs and trailing whitespace, but only in
1736 # changed lines
1736 # changed lines
1737 stripline = line.rstrip()
1737 stripline = line.rstrip()
1738 diffline = True
1738 diffline = True
1739
1739
1740 prefixes = textprefixes
1740 prefixes = textprefixes
1741 if head:
1741 if head:
1742 prefixes = headprefixes
1742 prefixes = headprefixes
1743 for prefix, label in prefixes:
1743 for prefix, label in prefixes:
1744 if stripline.startswith(prefix):
1744 if stripline.startswith(prefix):
1745 if diffline:
1745 if diffline:
1746 for token in tabsplitter.findall(stripline):
1746 for token in tabsplitter.findall(stripline):
1747 if '\t' == token[0]:
1747 if '\t' == token[0]:
1748 yield (token, 'diff.tab')
1748 yield (token, 'diff.tab')
1749 else:
1749 else:
1750 yield (token, label)
1750 yield (token, label)
1751 else:
1751 else:
1752 yield (stripline, label)
1752 yield (stripline, label)
1753 break
1753 break
1754 else:
1754 else:
1755 yield (line, '')
1755 yield (line, '')
1756 if line != stripline:
1756 if line != stripline:
1757 yield (line[len(stripline):], 'diff.trailingwhitespace')
1757 yield (line[len(stripline):], 'diff.trailingwhitespace')
1758
1758
1759 def diffui(*args, **kw):
1759 def diffui(*args, **kw):
1760 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1760 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1761 return difflabel(diff, *args, **kw)
1761 return difflabel(diff, *args, **kw)
1762
1762
1763 def _filepairs(ctx1, modified, added, removed, copy, opts):
1763 def _filepairs(ctx1, modified, added, removed, copy, opts):
1764 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
1764 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
1765 before and f2 is the the name after. For added files, f1 will be None,
1765 before and f2 is the the name after. For added files, f1 will be None,
1766 and for removed files, f2 will be None. copyop may be set to None, 'copy'
1766 and for removed files, f2 will be None. copyop may be set to None, 'copy'
1767 or 'rename' (the latter two only if opts.git is set).'''
1767 or 'rename' (the latter two only if opts.git is set).'''
1768 gone = set()
1768 gone = set()
1769
1769
1770 copyto = dict([(v, k) for k, v in copy.items()])
1770 copyto = dict([(v, k) for k, v in copy.items()])
1771
1771
1772 addedset, removedset = set(added), set(removed)
1772 addedset, removedset = set(added), set(removed)
1773 # Fix up added, since merged-in additions appear as
1773 # Fix up added, since merged-in additions appear as
1774 # modifications during merges
1774 # modifications during merges
1775 for f in modified:
1775 for f in modified:
1776 if f not in ctx1:
1776 if f not in ctx1:
1777 addedset.add(f)
1777 addedset.add(f)
1778
1778
1779 for f in sorted(modified + added + removed):
1779 for f in sorted(modified + added + removed):
1780 copyop = None
1780 copyop = None
1781 f1, f2 = f, f
1781 f1, f2 = f, f
1782 if f in addedset:
1782 if f in addedset:
1783 f1 = None
1783 f1 = None
1784 if f in copy:
1784 if f in copy:
1785 if opts.git:
1785 if opts.git:
1786 f1 = copy[f]
1786 f1 = copy[f]
1787 if f1 in removedset and f1 not in gone:
1787 if f1 in removedset and f1 not in gone:
1788 copyop = 'rename'
1788 copyop = 'rename'
1789 gone.add(f1)
1789 gone.add(f1)
1790 else:
1790 else:
1791 copyop = 'copy'
1791 copyop = 'copy'
1792 elif f in removedset:
1792 elif f in removedset:
1793 f2 = None
1793 f2 = None
1794 if opts.git:
1794 if opts.git:
1795 # have we already reported a copy above?
1795 # have we already reported a copy above?
1796 if (f in copyto and copyto[f] in addedset
1796 if (f in copyto and copyto[f] in addedset
1797 and copy[copyto[f]] == f):
1797 and copy[copyto[f]] == f):
1798 continue
1798 continue
1799 yield f1, f2, copyop
1799 yield f1, f2, copyop
1800
1800
1801 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1801 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1802 copy, getfilectx, opts, losedatafn, prefix):
1802 copy, getfilectx, opts, losedatafn, prefix):
1803
1803
1804 def gitindex(text):
1804 def gitindex(text):
1805 if not text:
1805 if not text:
1806 text = ""
1806 text = ""
1807 l = len(text)
1807 l = len(text)
1808 s = util.sha1('blob %d\0' % l)
1808 s = util.sha1('blob %d\0' % l)
1809 s.update(text)
1809 s.update(text)
1810 return s.hexdigest()
1810 return s.hexdigest()
1811
1811
1812 if opts.noprefix:
1812 if opts.noprefix:
1813 aprefix = bprefix = ''
1813 aprefix = bprefix = ''
1814 else:
1814 else:
1815 aprefix = 'a/'
1815 aprefix = 'a/'
1816 bprefix = 'b/'
1816 bprefix = 'b/'
1817
1817
1818 def diffline(f, revs):
1818 def diffline(f, revs):
1819 revinfo = ' '.join(["-r %s" % rev for rev in revs])
1819 revinfo = ' '.join(["-r %s" % rev for rev in revs])
1820 return 'diff %s %s' % (revinfo, f)
1820 return 'diff %s %s' % (revinfo, f)
1821
1821
1822 date1 = util.datestr(ctx1.date())
1822 date1 = util.datestr(ctx1.date())
1823 date2 = util.datestr(ctx2.date())
1823 date2 = util.datestr(ctx2.date())
1824
1824
1825 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1825 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1826
1826
1827 for f1, f2, copyop in _filepairs(
1827 for f1, f2, copyop in _filepairs(
1828 ctx1, modified, added, removed, copy, opts):
1828 ctx1, modified, added, removed, copy, opts):
1829 content1 = None
1829 content1 = None
1830 content2 = None
1830 content2 = None
1831 flag1 = None
1831 flag1 = None
1832 flag2 = None
1832 flag2 = None
1833 if f1:
1833 if f1:
1834 content1 = getfilectx(f1, ctx1).data()
1834 content1 = getfilectx(f1, ctx1).data()
1835 if opts.git or losedatafn:
1835 if opts.git or losedatafn:
1836 flag1 = ctx1.flags(f1)
1836 flag1 = ctx1.flags(f1)
1837 if f2:
1837 if f2:
1838 content2 = getfilectx(f2, ctx2).data()
1838 content2 = getfilectx(f2, ctx2).data()
1839 if opts.git or losedatafn:
1839 if opts.git or losedatafn:
1840 flag2 = ctx2.flags(f2)
1840 flag2 = ctx2.flags(f2)
1841 binary = False
1841 binary = False
1842 if opts.git or losedatafn:
1842 if opts.git or losedatafn:
1843 binary = util.binary(content1) or util.binary(content2)
1843 binary = util.binary(content1) or util.binary(content2)
1844
1844
1845 if losedatafn and not opts.git:
1845 if losedatafn and not opts.git:
1846 if (binary or
1846 if (binary or
1847 # copy/rename
1847 # copy/rename
1848 f2 in copy or
1848 f2 in copy or
1849 # empty file creation
1849 # empty file creation
1850 (not f1 and not content2) or
1850 (not f1 and not content2) or
1851 # empty file deletion
1851 # empty file deletion
1852 (not content1 and not f2) or
1852 (not content1 and not f2) or
1853 # create with flags
1853 # create with flags
1854 (not f1 and flag2) or
1854 (not f1 and flag2) or
1855 # change flags
1855 # change flags
1856 (f1 and f2 and flag1 != flag2)):
1856 (f1 and f2 and flag1 != flag2)):
1857 losedatafn(f2 or f1)
1857 losedatafn(f2 or f1)
1858
1858
1859 path1 = posixpath.join(prefix, f1 or f2)
1859 path1 = posixpath.join(prefix, f1 or f2)
1860 path2 = posixpath.join(prefix, f2 or f1)
1860 path2 = posixpath.join(prefix, f2 or f1)
1861 header = []
1861 header = []
1862 if opts.git:
1862 if opts.git:
1863 header.append('diff --git %s%s %s%s' %
1863 header.append('diff --git %s%s %s%s' %
1864 (aprefix, path1, bprefix, path2))
1864 (aprefix, path1, bprefix, path2))
1865 if not f1: # added
1865 if not f1: # added
1866 header.append('new file mode %s' % gitmode[flag2])
1866 header.append('new file mode %s' % gitmode[flag2])
1867 elif not f2: # removed
1867 elif not f2: # removed
1868 header.append('deleted file mode %s' % gitmode[flag1])
1868 header.append('deleted file mode %s' % gitmode[flag1])
1869 else: # modified/copied/renamed
1869 else: # modified/copied/renamed
1870 mode1, mode2 = gitmode[flag1], gitmode[flag2]
1870 mode1, mode2 = gitmode[flag1], gitmode[flag2]
1871 if mode1 != mode2:
1871 if mode1 != mode2:
1872 header.append('old mode %s' % mode1)
1872 header.append('old mode %s' % mode1)
1873 header.append('new mode %s' % mode2)
1873 header.append('new mode %s' % mode2)
1874 if copyop is not None:
1874 if copyop is not None:
1875 header.append('%s from %s' % (copyop, path1))
1875 header.append('%s from %s' % (copyop, path1))
1876 header.append('%s to %s' % (copyop, path2))
1876 header.append('%s to %s' % (copyop, path2))
1877 elif revs and not repo.ui.quiet:
1877 elif revs and not repo.ui.quiet:
1878 header.append(diffline(path1, revs))
1878 header.append(diffline(path1, revs))
1879
1879
1880 if binary and opts.git and not opts.nobinary:
1880 if binary and opts.git and not opts.nobinary:
1881 text = mdiff.b85diff(content1, content2)
1881 text = mdiff.b85diff(content1, content2)
1882 if text:
1882 if text:
1883 header.append('index %s..%s' %
1883 header.append('index %s..%s' %
1884 (gitindex(content1), gitindex(content2)))
1884 (gitindex(content1), gitindex(content2)))
1885 else:
1885 else:
1886 text = mdiff.unidiff(content1, date1,
1886 text = mdiff.unidiff(content1, date1,
1887 content2, date2,
1887 content2, date2,
1888 path1, path2, opts=opts)
1888 path1, path2, opts=opts)
1889 if header and (text or len(header) > 1):
1889 if header and (text or len(header) > 1):
1890 yield '\n'.join(header) + '\n'
1890 yield '\n'.join(header) + '\n'
1891 if text:
1891 if text:
1892 yield text
1892 yield text
1893
1893
1894 def diffstatsum(stats):
1894 def diffstatsum(stats):
1895 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1895 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1896 for f, a, r, b in stats:
1896 for f, a, r, b in stats:
1897 maxfile = max(maxfile, encoding.colwidth(f))
1897 maxfile = max(maxfile, encoding.colwidth(f))
1898 maxtotal = max(maxtotal, a + r)
1898 maxtotal = max(maxtotal, a + r)
1899 addtotal += a
1899 addtotal += a
1900 removetotal += r
1900 removetotal += r
1901 binary = binary or b
1901 binary = binary or b
1902
1902
1903 return maxfile, maxtotal, addtotal, removetotal, binary
1903 return maxfile, maxtotal, addtotal, removetotal, binary
1904
1904
1905 def diffstatdata(lines):
1905 def diffstatdata(lines):
1906 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1906 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1907
1907
1908 results = []
1908 results = []
1909 filename, adds, removes, isbinary = None, 0, 0, False
1909 filename, adds, removes, isbinary = None, 0, 0, False
1910
1910
1911 def addresult():
1911 def addresult():
1912 if filename:
1912 if filename:
1913 results.append((filename, adds, removes, isbinary))
1913 results.append((filename, adds, removes, isbinary))
1914
1914
1915 for line in lines:
1915 for line in lines:
1916 if line.startswith('diff'):
1916 if line.startswith('diff'):
1917 addresult()
1917 addresult()
1918 # set numbers to 0 anyway when starting new file
1918 # set numbers to 0 anyway when starting new file
1919 adds, removes, isbinary = 0, 0, False
1919 adds, removes, isbinary = 0, 0, False
1920 if line.startswith('diff --git a/'):
1920 if line.startswith('diff --git a/'):
1921 filename = gitre.search(line).group(2)
1921 filename = gitre.search(line).group(2)
1922 elif line.startswith('diff -r'):
1922 elif line.startswith('diff -r'):
1923 # format: "diff -r ... -r ... filename"
1923 # format: "diff -r ... -r ... filename"
1924 filename = diffre.search(line).group(1)
1924 filename = diffre.search(line).group(1)
1925 elif line.startswith('+') and not line.startswith('+++ '):
1925 elif line.startswith('+') and not line.startswith('+++ '):
1926 adds += 1
1926 adds += 1
1927 elif line.startswith('-') and not line.startswith('--- '):
1927 elif line.startswith('-') and not line.startswith('--- '):
1928 removes += 1
1928 removes += 1
1929 elif (line.startswith('GIT binary patch') or
1929 elif (line.startswith('GIT binary patch') or
1930 line.startswith('Binary file')):
1930 line.startswith('Binary file')):
1931 isbinary = True
1931 isbinary = True
1932 addresult()
1932 addresult()
1933 return results
1933 return results
1934
1934
1935 def diffstat(lines, width=80, git=False):
1935 def diffstat(lines, width=80, git=False):
1936 output = []
1936 output = []
1937 stats = diffstatdata(lines)
1937 stats = diffstatdata(lines)
1938 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1938 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1939
1939
1940 countwidth = len(str(maxtotal))
1940 countwidth = len(str(maxtotal))
1941 if hasbinary and countwidth < 3:
1941 if hasbinary and countwidth < 3:
1942 countwidth = 3
1942 countwidth = 3
1943 graphwidth = width - countwidth - maxname - 6
1943 graphwidth = width - countwidth - maxname - 6
1944 if graphwidth < 10:
1944 if graphwidth < 10:
1945 graphwidth = 10
1945 graphwidth = 10
1946
1946
1947 def scale(i):
1947 def scale(i):
1948 if maxtotal <= graphwidth:
1948 if maxtotal <= graphwidth:
1949 return i
1949 return i
1950 # If diffstat runs out of room it doesn't print anything,
1950 # If diffstat runs out of room it doesn't print anything,
1951 # which isn't very useful, so always print at least one + or -
1951 # which isn't very useful, so always print at least one + or -
1952 # if there were at least some changes.
1952 # if there were at least some changes.
1953 return max(i * graphwidth // maxtotal, int(bool(i)))
1953 return max(i * graphwidth // maxtotal, int(bool(i)))
1954
1954
1955 for filename, adds, removes, isbinary in stats:
1955 for filename, adds, removes, isbinary in stats:
1956 if isbinary:
1956 if isbinary:
1957 count = 'Bin'
1957 count = 'Bin'
1958 else:
1958 else:
1959 count = adds + removes
1959 count = adds + removes
1960 pluses = '+' * scale(adds)
1960 pluses = '+' * scale(adds)
1961 minuses = '-' * scale(removes)
1961 minuses = '-' * scale(removes)
1962 output.append(' %s%s | %*s %s%s\n' %
1962 output.append(' %s%s | %*s %s%s\n' %
1963 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1963 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1964 countwidth, count, pluses, minuses))
1964 countwidth, count, pluses, minuses))
1965
1965
1966 if stats:
1966 if stats:
1967 output.append(_(' %d files changed, %d insertions(+), '
1967 output.append(_(' %d files changed, %d insertions(+), '
1968 '%d deletions(-)\n')
1968 '%d deletions(-)\n')
1969 % (len(stats), totaladds, totalremoves))
1969 % (len(stats), totaladds, totalremoves))
1970
1970
1971 return ''.join(output)
1971 return ''.join(output)
1972
1972
1973 def diffstatui(*args, **kw):
1973 def diffstatui(*args, **kw):
1974 '''like diffstat(), but yields 2-tuples of (output, label) for
1974 '''like diffstat(), but yields 2-tuples of (output, label) for
1975 ui.write()
1975 ui.write()
1976 '''
1976 '''
1977
1977
1978 for line in diffstat(*args, **kw).splitlines():
1978 for line in diffstat(*args, **kw).splitlines():
1979 if line and line[-1] in '+-':
1979 if line and line[-1] in '+-':
1980 name, graph = line.rsplit(' ', 1)
1980 name, graph = line.rsplit(' ', 1)
1981 yield (name + ' ', '')
1981 yield (name + ' ', '')
1982 m = re.search(r'\++', graph)
1982 m = re.search(r'\++', graph)
1983 if m:
1983 if m:
1984 yield (m.group(0), 'diffstat.inserted')
1984 yield (m.group(0), 'diffstat.inserted')
1985 m = re.search(r'-+', graph)
1985 m = re.search(r'-+', graph)
1986 if m:
1986 if m:
1987 yield (m.group(0), 'diffstat.deleted')
1987 yield (m.group(0), 'diffstat.deleted')
1988 else:
1988 else:
1989 yield (line, '')
1989 yield (line, '')
1990 yield ('\n', '')
1990 yield ('\n', '')
@@ -1,723 +1,775 b''
1 $ hg init repo
1 $ hg init repo
2 $ cd repo
2 $ cd repo
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 #if execbit
43 #if execbit
44 $ hg tip -q
44 $ hg tip -q
45 2:3a34410f282e
45 2:3a34410f282e
46 $ test -x new
46 $ test -x new
47 $ hg rollback -q
47 $ hg rollback -q
48 #else
48 #else
49 $ hg tip -q
49 $ hg tip -q
50 1:ab199dc869b5
50 1:ab199dc869b5
51 #endif
51 #endif
52
52
53 Copy and removing x bit:
53 Copy and removing x bit:
54
54
55 $ hg import -f -d "1000000 0" -mcopy - <<EOF
55 $ hg import -f -d "1000000 0" -mcopy - <<EOF
56 > diff --git a/new b/copy
56 > diff --git a/new b/copy
57 > old mode 100755
57 > old mode 100755
58 > new mode 100644
58 > new mode 100644
59 > similarity index 100%
59 > similarity index 100%
60 > copy from new
60 > copy from new
61 > copy to copy
61 > copy to copy
62 > diff --git a/new b/copyx
62 > diff --git a/new b/copyx
63 > similarity index 100%
63 > similarity index 100%
64 > copy from new
64 > copy from new
65 > copy to copyx
65 > copy to copyx
66 > EOF
66 > EOF
67 applying patch from stdin
67 applying patch from stdin
68
68
69 $ test -f copy
69 $ test -f copy
70 #if execbit
70 #if execbit
71 $ test ! -x copy
71 $ test ! -x copy
72 $ test -x copyx
72 $ test -x copyx
73 $ hg tip -q
73 $ hg tip -q
74 2:21dfaae65c71
74 2:21dfaae65c71
75 #else
75 #else
76 $ hg tip -q
76 $ hg tip -q
77 2:0efdaa8e3bf3
77 2:0efdaa8e3bf3
78 #endif
78 #endif
79
79
80 $ hg up -qCr1
80 $ hg up -qCr1
81 $ hg rollback -q
81 $ hg rollback -q
82
82
83 Copy (like above but independent of execbit):
83 Copy (like above but independent of execbit):
84
84
85 $ hg import -d "1000000 0" -mcopy - <<EOF
85 $ hg import -d "1000000 0" -mcopy - <<EOF
86 > diff --git a/new b/copy
86 > diff --git a/new b/copy
87 > similarity index 100%
87 > similarity index 100%
88 > copy from new
88 > copy from new
89 > copy to copy
89 > copy to copy
90 > diff --git a/new b/copyx
90 > diff --git a/new b/copyx
91 > similarity index 100%
91 > similarity index 100%
92 > copy from new
92 > copy from new
93 > copy to copyx
93 > copy to copyx
94 > EOF
94 > EOF
95 applying patch from stdin
95 applying patch from stdin
96
96
97 $ hg tip -q
97 $ hg tip -q
98 2:0efdaa8e3bf3
98 2:0efdaa8e3bf3
99 $ test -f copy
99 $ test -f copy
100
100
101 $ cat copy
101 $ cat copy
102 a
102 a
103
103
104 $ hg cat copy
104 $ hg cat copy
105 a
105 a
106
106
107 Rename:
107 Rename:
108
108
109 $ hg import -d "1000000 0" -mrename - <<EOF
109 $ hg import -d "1000000 0" -mrename - <<EOF
110 > diff --git a/copy b/rename
110 > diff --git a/copy b/rename
111 > similarity index 100%
111 > similarity index 100%
112 > rename from copy
112 > rename from copy
113 > rename to rename
113 > rename to rename
114 > EOF
114 > EOF
115 applying patch from stdin
115 applying patch from stdin
116
116
117 $ hg tip -q
117 $ hg tip -q
118 3:b1f57753fad2
118 3:b1f57753fad2
119
119
120 $ hg locate
120 $ hg locate
121 copyx
121 copyx
122 empty
122 empty
123 new
123 new
124 rename
124 rename
125
125
126 Delete:
126 Delete:
127
127
128 $ hg import -d "1000000 0" -mdelete - <<EOF
128 $ hg import -d "1000000 0" -mdelete - <<EOF
129 > diff --git a/copyx b/copyx
129 > diff --git a/copyx b/copyx
130 > deleted file mode 100755
130 > deleted file mode 100755
131 > index 7898192..0000000
131 > index 7898192..0000000
132 > --- a/copyx
132 > --- a/copyx
133 > +++ /dev/null
133 > +++ /dev/null
134 > @@ -1 +0,0 @@
134 > @@ -1 +0,0 @@
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 4:1bd1da94b9b2
140 4:1bd1da94b9b2
141
141
142 $ hg locate
142 $ hg locate
143 empty
143 empty
144 new
144 new
145 rename
145 rename
146
146
147 $ test -f copyx
147 $ test -f copyx
148 [1]
148 [1]
149
149
150 Regular diff:
150 Regular diff:
151
151
152 $ hg import -d "1000000 0" -mregular - <<EOF
152 $ hg import -d "1000000 0" -mregular - <<EOF
153 > diff --git a/rename b/rename
153 > diff --git a/rename b/rename
154 > index 7898192..72e1fe3 100644
154 > index 7898192..72e1fe3 100644
155 > --- a/rename
155 > --- a/rename
156 > +++ b/rename
156 > +++ b/rename
157 > @@ -1 +1,5 @@
157 > @@ -1 +1,5 @@
158 > a
158 > a
159 > +a
159 > +a
160 > +a
160 > +a
161 > +a
161 > +a
162 > +a
162 > +a
163 > EOF
163 > EOF
164 applying patch from stdin
164 applying patch from stdin
165
165
166 $ hg tip -q
166 $ hg tip -q
167 5:46fe99cb3035
167 5:46fe99cb3035
168
168
169 Copy and modify:
169 Copy and modify:
170
170
171 $ hg import -d "1000000 0" -mcopymod - <<EOF
171 $ hg import -d "1000000 0" -mcopymod - <<EOF
172 > diff --git a/rename b/copy2
172 > diff --git a/rename b/copy2
173 > similarity index 80%
173 > similarity index 80%
174 > copy from rename
174 > copy from rename
175 > copy to copy2
175 > copy to copy2
176 > index 72e1fe3..b53c148 100644
176 > index 72e1fe3..b53c148 100644
177 > --- a/rename
177 > --- a/rename
178 > +++ b/copy2
178 > +++ b/copy2
179 > @@ -1,5 +1,5 @@
179 > @@ -1,5 +1,5 @@
180 > a
180 > a
181 > a
181 > a
182 > -a
182 > -a
183 > +b
183 > +b
184 > a
184 > a
185 > a
185 > a
186 > EOF
186 > EOF
187 applying patch from stdin
187 applying patch from stdin
188
188
189 $ hg tip -q
189 $ hg tip -q
190 6:ffeb3197c12d
190 6:ffeb3197c12d
191
191
192 $ hg cat copy2
192 $ hg cat copy2
193 a
193 a
194 a
194 a
195 b
195 b
196 a
196 a
197 a
197 a
198
198
199 Rename and modify:
199 Rename and modify:
200
200
201 $ hg import -d "1000000 0" -mrenamemod - <<EOF
201 $ hg import -d "1000000 0" -mrenamemod - <<EOF
202 > diff --git a/copy2 b/rename2
202 > diff --git a/copy2 b/rename2
203 > similarity index 80%
203 > similarity index 80%
204 > rename from copy2
204 > rename from copy2
205 > rename to rename2
205 > rename to rename2
206 > index b53c148..8f81e29 100644
206 > index b53c148..8f81e29 100644
207 > --- a/copy2
207 > --- a/copy2
208 > +++ b/rename2
208 > +++ b/rename2
209 > @@ -1,5 +1,5 @@
209 > @@ -1,5 +1,5 @@
210 > a
210 > a
211 > a
211 > a
212 > b
212 > b
213 > -a
213 > -a
214 > +c
214 > +c
215 > a
215 > a
216 > EOF
216 > EOF
217 applying patch from stdin
217 applying patch from stdin
218
218
219 $ hg tip -q
219 $ hg tip -q
220 7:401aede9e6bb
220 7:401aede9e6bb
221
221
222 $ hg locate copy2
222 $ hg locate copy2
223 [1]
223 [1]
224 $ hg cat rename2
224 $ hg cat rename2
225 a
225 a
226 a
226 a
227 b
227 b
228 c
228 c
229 a
229 a
230
230
231 One file renamed multiple times:
231 One file renamed multiple times:
232
232
233 $ hg import -d "1000000 0" -mmultirenames - <<EOF
233 $ hg import -d "1000000 0" -mmultirenames - <<EOF
234 > diff --git a/rename2 b/rename3
234 > diff --git a/rename2 b/rename3
235 > rename from rename2
235 > rename from rename2
236 > rename to rename3
236 > rename to rename3
237 > diff --git a/rename2 b/rename3-2
237 > diff --git a/rename2 b/rename3-2
238 > rename from rename2
238 > rename from rename2
239 > rename to rename3-2
239 > rename to rename3-2
240 > EOF
240 > EOF
241 applying patch from stdin
241 applying patch from stdin
242
242
243 $ hg tip -q
243 $ hg tip -q
244 8:2ef727e684e8
244 8:2ef727e684e8
245
245
246 $ hg log -vr. --template '{rev} {files} / {file_copies}\n'
246 $ hg log -vr. --template '{rev} {files} / {file_copies}\n'
247 8 rename2 rename3 rename3-2 / rename3 (rename2)rename3-2 (rename2)
247 8 rename2 rename3 rename3-2 / rename3 (rename2)rename3-2 (rename2)
248
248
249 $ hg locate rename2 rename3 rename3-2
249 $ hg locate rename2 rename3 rename3-2
250 rename3
250 rename3
251 rename3-2
251 rename3-2
252
252
253 $ hg cat rename3
253 $ hg cat rename3
254 a
254 a
255 a
255 a
256 b
256 b
257 c
257 c
258 a
258 a
259
259
260 $ hg cat rename3-2
260 $ hg cat rename3-2
261 a
261 a
262 a
262 a
263 b
263 b
264 c
264 c
265 a
265 a
266
266
267 $ echo foo > foo
267 $ echo foo > foo
268 $ hg add foo
268 $ hg add foo
269 $ hg ci -m 'add foo'
269 $ hg ci -m 'add foo'
270
270
271 Binary files and regular patch hunks:
271 Binary files and regular patch hunks:
272
272
273 $ hg import -d "1000000 0" -m binaryregular - <<EOF
273 $ hg import -d "1000000 0" -m binaryregular - <<EOF
274 > diff --git a/binary b/binary
274 > diff --git a/binary b/binary
275 > new file mode 100644
275 > new file mode 100644
276 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
276 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
277 > GIT binary patch
277 > GIT binary patch
278 > literal 4
278 > literal 4
279 > Lc\${NkU|;|M00aO5
279 > Lc\${NkU|;|M00aO5
280 >
280 >
281 > diff --git a/foo b/foo2
281 > diff --git a/foo b/foo2
282 > rename from foo
282 > rename from foo
283 > rename to foo2
283 > rename to foo2
284 > EOF
284 > EOF
285 applying patch from stdin
285 applying patch from stdin
286
286
287 $ hg tip -q
287 $ hg tip -q
288 10:27377172366e
288 10:27377172366e
289
289
290 $ cat foo2
290 $ cat foo2
291 foo
291 foo
292
292
293 $ hg manifest --debug | grep binary
293 $ hg manifest --debug | grep binary
294 045c85ba38952325e126c70962cc0f9d9077bc67 644 binary
294 045c85ba38952325e126c70962cc0f9d9077bc67 644 binary
295
295
296 Multiple binary files:
296 Multiple binary files:
297
297
298 $ hg import -d "1000000 0" -m multibinary - <<EOF
298 $ hg import -d "1000000 0" -m multibinary - <<EOF
299 > diff --git a/mbinary1 b/mbinary1
299 > diff --git a/mbinary1 b/mbinary1
300 > new file mode 100644
300 > new file mode 100644
301 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
301 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
302 > GIT binary patch
302 > GIT binary patch
303 > literal 4
303 > literal 4
304 > Lc\${NkU|;|M00aO5
304 > Lc\${NkU|;|M00aO5
305 >
305 >
306 > diff --git a/mbinary2 b/mbinary2
306 > diff --git a/mbinary2 b/mbinary2
307 > new file mode 100644
307 > new file mode 100644
308 > index 0000000000000000000000000000000000000000..112363ac1917b417ffbd7f376ca786a1e5fa7490
308 > index 0000000000000000000000000000000000000000..112363ac1917b417ffbd7f376ca786a1e5fa7490
309 > GIT binary patch
309 > GIT binary patch
310 > literal 5
310 > literal 5
311 > Mc\${NkU|\`?^000jF3jhEB
311 > Mc\${NkU|\`?^000jF3jhEB
312 >
312 >
313 > EOF
313 > EOF
314 applying patch from stdin
314 applying patch from stdin
315
315
316 $ hg tip -q
316 $ hg tip -q
317 11:18b73a84b4ab
317 11:18b73a84b4ab
318
318
319 $ hg manifest --debug | grep mbinary
319 $ hg manifest --debug | grep mbinary
320 045c85ba38952325e126c70962cc0f9d9077bc67 644 mbinary1
320 045c85ba38952325e126c70962cc0f9d9077bc67 644 mbinary1
321 a874b471193996e7cb034bb301cac7bdaf3e3f46 644 mbinary2
321 a874b471193996e7cb034bb301cac7bdaf3e3f46 644 mbinary2
322
322
323 Binary file and delta hunk (we build the patch using this sed hack to
323 Binary file and delta hunk (we build the patch using this sed hack to
324 avoid an unquoted ^, which check-code says breaks sh on Solaris):
324 avoid an unquoted ^, which check-code says breaks sh on Solaris):
325
325
326 $ sed 's/ caret /^/g;s/ dollarparen /$(/g' > quote-hack.patch <<'EOF'
326 $ sed 's/ caret /^/g;s/ dollarparen /$(/g' > quote-hack.patch <<'EOF'
327 > diff --git a/delta b/delta
327 > diff --git a/delta b/delta
328 > new file mode 100644
328 > new file mode 100644
329 > index 0000000000000000000000000000000000000000..8c9b7831b231c2600843e303e66b521353a200b3
329 > index 0000000000000000000000000000000000000000..8c9b7831b231c2600843e303e66b521353a200b3
330 > GIT binary patch
330 > GIT binary patch
331 > literal 3749
331 > literal 3749
332 > zcmV;W4qEYvP)<h;3K|Lk000e1NJLTq006iE002D*0ssI2kt{U(0000PbVXQnQ*UN;
332 > zcmV;W4qEYvP)<h;3K|Lk000e1NJLTq006iE002D*0ssI2kt{U(0000PbVXQnQ*UN;
333 > zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU=M@d9MRCwC#oC!>o#}>x{(W-y~UN*tK
333 > zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU=M@d9MRCwC#oC!>o#}>x{(W-y~UN*tK
334 > z%A%sxiUy2Ys)0Vm#ueArYKoYqX;GuiqZpgirM6nCVoYk?YNAz3G~z;BZ~@~&OQEe4
334 > z%A%sxiUy2Ys)0Vm#ueArYKoYqX;GuiqZpgirM6nCVoYk?YNAz3G~z;BZ~@~&OQEe4
335 > zmGvS5isFJI;Pd_7J+EKxyHZeu` caret t4r2>F;h-+VK3{_{WoGv8dSpFDYDrA%3UX03pt
335 > zmGvS5isFJI;Pd_7J+EKxyHZeu` caret t4r2>F;h-+VK3{_{WoGv8dSpFDYDrA%3UX03pt
336 > zOaVoi0*W#P6lDr1$`nwPDWE7*rhuYM0Y#YtiZTThWeO<D6i}2YpqR<%$s>bRRaI42
336 > zOaVoi0*W#P6lDr1$`nwPDWE7*rhuYM0Y#YtiZTThWeO<D6i}2YpqR<%$s>bRRaI42
337 > zS3iFIxJ8Q=EnBv1Z7?pBw_bLjJb3V+tgP(Tty_2R-mR#p04x78n2n7MSOFyt4i1iv
337 > zS3iFIxJ8Q=EnBv1Z7?pBw_bLjJb3V+tgP(Tty_2R-mR#p04x78n2n7MSOFyt4i1iv
338 > zjxH`PPEJmgD7U?IK&h;(EGQ@_DJc<@01=4fiNXHcKZ8LhZQ8T}E3U4tUS3}OrcgQW
338 > zjxH`PPEJmgD7U?IK&h;(EGQ@_DJc<@01=4fiNXHcKZ8LhZQ8T}E3U4tUS3}OrcgQW
339 > zWdX{K8#l7Ev&#$ysR)G#0*rC+<WGZ3?CtG4bm-ve>Dj$|_qJ`@D*stNP_AFUe&x!Q
339 > zWdX{K8#l7Ev&#$ysR)G#0*rC+<WGZ3?CtG4bm-ve>Dj$|_qJ`@D*stNP_AFUe&x!Q
340 > zJ9q9B7Z=ym)MyZ?Tg1ROunUYr81nV?B@!tYS~5_|%gfW#(_s<4UN1!Q?Dv8d>g#m6
340 > zJ9q9B7Z=ym)MyZ?Tg1ROunUYr81nV?B@!tYS~5_|%gfW#(_s<4UN1!Q?Dv8d>g#m6
341 > z%*@R2@bI2JdnzxQ!EDU`$eQY!tgI~Zn$prz;gaXNod5*5p(1Bz=P$qfvZ$y?dC@X~
341 > z%*@R2@bI2JdnzxQ!EDU`$eQY!tgI~Zn$prz;gaXNod5*5p(1Bz=P$qfvZ$y?dC@X~
342 > zlAD+NAKhB{=;6bMwzjqn>9mavvKOGd`s%A+fBiL>Q;xJWpa72C+}u{JTHUX>{~}Qj
342 > zlAD+NAKhB{=;6bMwzjqn>9mavvKOGd`s%A+fBiL>Q;xJWpa72C+}u{JTHUX>{~}Qj
343 > zUb%hyHgN~c?cBLjInvUALMD9g-aXt54ZL8AOCvXL-V6!~ijR*kEG$&Mv?!pE61OlI
343 > zUb%hyHgN~c?cBLjInvUALMD9g-aXt54ZL8AOCvXL-V6!~ijR*kEG$&Mv?!pE61OlI
344 > z8nzMSPE8F7bH|Py*RNl1VUCggq<V)>@_6gkEeiz7{rmTeuNTW6+KVS#0FG%IHf-3L
344 > z8nzMSPE8F7bH|Py*RNl1VUCggq<V)>@_6gkEeiz7{rmTeuNTW6+KVS#0FG%IHf-3L
345 > zGiS21vn>WCCr+GLx caret !uNetzB6u3o(w6&1C2?_LW8ij$+$sZ*zZ`|US3H@8N~%&V%Z
345 > zGiS21vn>WCCr+GLx caret !uNetzB6u3o(w6&1C2?_LW8ij$+$sZ*zZ`|US3H@8N~%&V%Z
346 > zAeA0HdhFS=$6|nzn3%YH`SN<>DQRO;Qc caret )dfdvA caret 5u`Xf;Zzu<ZQHgG?28V-#s<;T
346 > zAeA0HdhFS=$6|nzn3%YH`SN<>DQRO;Qc caret )dfdvA caret 5u`Xf;Zzu<ZQHgG?28V-#s<;T
347 > zzkh#LA)v7gpoE5ou3o*GoUUF%b#iht&kl9d0)><$FE1}ACr68;uCA`6DrGmz_U+rp
347 > zzkh#LA)v7gpoE5ou3o*GoUUF%b#iht&kl9d0)><$FE1}ACr68;uCA`6DrGmz_U+rp
348 > zL>Rx;X_yhk$fP_yJrTCQ|NgsW0A<985g&c@k-NKly<>mgU8n||ZPPV<`SN8#%$+-T
348 > zL>Rx;X_yhk$fP_yJrTCQ|NgsW0A<985g&c@k-NKly<>mgU8n||ZPPV<`SN8#%$+-T
349 > zfP$T!ou8jypFVwnzqhxyUvIxXd-wF~*U!ht=hCH1wzjqn9x#)IrhDa;S0JbK caret z_$W
349 > zfP$T!ou8jypFVwnzqhxyUvIxXd-wF~*U!ht=hCH1wzjqn9x#)IrhDa;S0JbK caret z_$W
350 > zd(8rX@;7|t*;GJ5h$SZ{v(}+UBEs$4w~?{@9%`_Z<P<kox5bMWuUWH(sF9hONgd$Q
350 > zd(8rX@;7|t*;GJ5h$SZ{v(}+UBEs$4w~?{@9%`_Z<P<kox5bMWuUWH(sF9hONgd$Q
351 > zunCgwT@1|CU9+;X caret 4z&|M~@yw23Ay50NFWn=FqF%yLZEUty;AT2??1oV@B)Nt))J7
351 > zunCgwT@1|CU9+;X caret 4z&|M~@yw23Ay50NFWn=FqF%yLZEUty;AT2??1oV@B)Nt))J7
352 > zh>{5j2@f7T=-an%L_`E)h;mZ4D_5>?7tjQtVPRo2XU-&;mX(!l-MSTJP4XWY82JAC
352 > zh>{5j2@f7T=-an%L_`E)h;mZ4D_5>?7tjQtVPRo2XU-&;mX(!l-MSTJP4XWY82JAC
353 > z@57+y&!1=P{Mn{W8)-HzEsgAtd63}Cazc>O6vGb>51%@9DzbyI3?4j~$ijmT95_IS
353 > z@57+y&!1=P{Mn{W8)-HzEsgAtd63}Cazc>O6vGb>51%@9DzbyI3?4j~$ijmT95_IS
354 > zS#r!LCDW%*4-O7CGnkr$xXR1RQ&UrA<CQt} caret 73NL%zk`)Jk!yxUAt-1r}ggLn-Zq}
354 > zS#r!LCDW%*4-O7CGnkr$xXR1RQ&UrA<CQt} caret 73NL%zk`)Jk!yxUAt-1r}ggLn-Zq}
355 > z*s){8pw68;i+kiG%CpBKYSJLLFyq&*U8}qDp+kpe&6<Vp(Z58%l#~>ZK?&s7y?b}i
355 > z*s){8pw68;i+kiG%CpBKYSJLLFyq&*U8}qDp+kpe&6<Vp(Z58%l#~>ZK?&s7y?b}i
356 > zuwcOgO%x-27A;y785zknl_{sU;E6v$8{pWmVS{KaJPpu`i;HP$#flY@u~Ua~K3%tN
356 > zuwcOgO%x-27A;y785zknl_{sU;E6v$8{pWmVS{KaJPpu`i;HP$#flY@u~Ua~K3%tN
357 > z-LhrNh{9SoHgDd%WXTc$$~Dq{?AWou3!H&?V8K{ caret {P9Ot5vecD?%1&-E-ntBFj87(
357 > z-LhrNh{9SoHgDd%WXTc$$~Dq{?AWou3!H&?V8K{ caret {P9Ot5vecD?%1&-E-ntBFj87(
358 > zy5`QE%QRX7qcHC%1{Ua}M~}L6=`wQUNEQ=I;qc+ZMMXtK2T+0os;jEco;}OV9z1w3
358 > zy5`QE%QRX7qcHC%1{Ua}M~}L6=`wQUNEQ=I;qc+ZMMXtK2T+0os;jEco;}OV9z1w3
359 > zARqv caret bm-85xnRCng3OT|MyVSmR3ND7 caret ?KaQGG! caret (aTbo1N;Nz;X3Q9FJbwK6`0?Yp
359 > zARqv caret bm-85xnRCng3OT|MyVSmR3ND7 caret ?KaQGG! caret (aTbo1N;Nz;X3Q9FJbwK6`0?Yp
360 > zj*X2ac;Pw3!I2|JShDaF>-gJmzm1NLj){rk&o|$E caret WAsfrK=x&@B!`w7Hik81sPz4
360 > zj*X2ac;Pw3!I2|JShDaF>-gJmzm1NLj){rk&o|$E caret WAsfrK=x&@B!`w7Hik81sPz4
361 > zuJTaiCppM>-+c!wPzcUw)5@?J4U-u|pJ~xbWUe-C+60k caret 7>9!)56DbjmA~`OJJ40v
361 > zuJTaiCppM>-+c!wPzcUw)5@?J4U-u|pJ~xbWUe-C+60k caret 7>9!)56DbjmA~`OJJ40v
362 > zu3hCA7eJXZWeN|1iJLu87$;+fS8+Kq6O`aT)*_x@sY#t7LxwoEcVw*)cWhhQW@l%!
362 > zu3hCA7eJXZWeN|1iJLu87$;+fS8+Kq6O`aT)*_x@sY#t7LxwoEcVw*)cWhhQW@l%!
363 > z{#Z=y+qcK@%z{p*D=8_Fcg278AnH3fI5;~yGu?9TscxXaaP*4$f<LIv! caret 5Lfr%vKg
363 > z{#Z=y+qcK@%z{p*D=8_Fcg278AnH3fI5;~yGu?9TscxXaaP*4$f<LIv! caret 5Lfr%vKg
364 > zpxmunH#%=+ICMvZA~wyNH%~eMl!-g caret R!cYJ#WmLq5N8viz#J%%LPtkO?V)tZ81cp>
364 > zpxmunH#%=+ICMvZA~wyNH%~eMl!-g caret R!cYJ#WmLq5N8viz#J%%LPtkO?V)tZ81cp>
365 > z{ALK?fNPePmd;289&M8Q3>YwgZX5GcGY&n>K1<x)!`;Qjg&}bb!Lrnl@xH#kS~VYE
365 > z{ALK?fNPePmd;289&M8Q3>YwgZX5GcGY&n>K1<x)!`;Qjg&}bb!Lrnl@xH#kS~VYE
366 > zpJmIJO`A3iy+Y3X`k>cY-@}Iw2Onq`=!ba3eATgs3yg3Wej=+P-Z8WF#w=RXvS@J3
366 > zpJmIJO`A3iy+Y3X`k>cY-@}Iw2Onq`=!ba3eATgs3yg3Wej=+P-Z8WF#w=RXvS@J3
367 > zEyhVTj-gO?kfDu1g9afo<RkPrYzG#_yF41IFxF%Ylg>9lx6<clPweR-b7Hn+r)e1l
367 > zEyhVTj-gO?kfDu1g9afo<RkPrYzG#_yF41IFxF%Ylg>9lx6<clPweR-b7Hn+r)e1l
368 > zO6c6FbNt@;;*w$z;N|H>h{czme)_4V6UC4hv**kX2@L caret Bgds dollarparen &P7M4dhfmWe)!=B
368 > zO6c6FbNt@;;*w$z;N|H>h{czme)_4V6UC4hv**kX2@L caret Bgds dollarparen &P7M4dhfmWe)!=B
369 > zR3X=Y{P9N}p@-##@1ZNW1YbVaiP~D@8m&<dzEP&cO|87Ju#j*=;wH~Exr>i*Hpp&@
369 > zR3X=Y{P9N}p@-##@1ZNW1YbVaiP~D@8m&<dzEP&cO|87Ju#j*=;wH~Exr>i*Hpp&@
370 > z`9!Sj+O;byD~s8qZ>6QB8uv7Bpn&&?xe;;e<M4F8KEID&pT7QmqoSgq&06adp5T=U
370 > z`9!Sj+O;byD~s8qZ>6QB8uv7Bpn&&?xe;;e<M4F8KEID&pT7QmqoSgq&06adp5T=U
371 > z6DH*4=AB7C1D9Amu?ia-wtxSAlmTEO96XHx)-+rKP;ip$pukuSJGW3P1aUmc2yo%)
371 > z6DH*4=AB7C1D9Amu?ia-wtxSAlmTEO96XHx)-+rKP;ip$pukuSJGW3P1aUmc2yo%)
372 > z&<t3F>d1X+1qzaag-%x+eKHx{?Afz3GBQSw9u0lw<mB+I#v11TKRpKWQS+lvVL7=u
372 > z&<t3F>d1X+1qzaag-%x+eKHx{?Afz3GBQSw9u0lw<mB+I#v11TKRpKWQS+lvVL7=u
373 > zHr6)1ynEF<i3kO6A8&ppPMo-F=PnWfXkSj@i*7J6C<F}wR?s(O0niC?t+6;+k}pPq
373 > zHr6)1ynEF<i3kO6A8&ppPMo-F=PnWfXkSj@i*7J6C<F}wR?s(O0niC?t+6;+k}pPq
374 > zrok&TPU40rL0ZYDwenNrrmPZ`gjo@DEF`7 caret cKP||pUr;+r)hyn9O37=xA`3%Bj-ih
374 > zrok&TPU40rL0ZYDwenNrrmPZ`gjo@DEF`7 caret cKP||pUr;+r)hyn9O37=xA`3%Bj-ih
375 > z+1usk<%5G-y+R?tA`qY=)6&vNjL{P?QzHg%P%>`ZxP=QB%DHY6L26?36V_p caret {}n$q
375 > z+1usk<%5G-y+R?tA`qY=)6&vNjL{P?QzHg%P%>`ZxP=QB%DHY6L26?36V_p caret {}n$q
376 > z3@9W=KmGI*Ng_Q#AzA%-z|Z caret |#oW(hkfgpuS$RKRhlrarX%efMMCs}GLChec5+y{6
376 > z3@9W=KmGI*Ng_Q#AzA%-z|Z caret |#oW(hkfgpuS$RKRhlrarX%efMMCs}GLChec5+y{6
377 > z1Qnxim_C-fmQuaAK_NUHUBV&;1c0V)wji<RcdZ*aAWTwyt>hVnlt caret asFCe0&a@tqp
377 > z1Qnxim_C-fmQuaAK_NUHUBV&;1c0V)wji<RcdZ*aAWTwyt>hVnlt caret asFCe0&a@tqp
378 > zEEy;$L}D$X6)wfQNl8gu6Z>oB3_RrP=gTyK2@@w#LbQfLNHj>Q&z(C5wUFhK+}0aV
378 > zEEy;$L}D$X6)wfQNl8gu6Z>oB3_RrP=gTyK2@@w#LbQfLNHj>Q&z(C5wUFhK+}0aV
379 > zSohlc=7K+spN<ctf}5KgKqNyJDNP9;LZd)nTE=9|6Xdr9%Hzk63-tL2c9FD*rsyYY
379 > zSohlc=7K+spN<ctf}5KgKqNyJDNP9;LZd)nTE=9|6Xdr9%Hzk63-tL2c9FD*rsyYY
380 > z!}t+Yljq7-p$X;4_YL?6d;mdY3R##o1e%rlPxrsMh8|;sKTr~ caret QD#sw3&vS$FwlTk
380 > z!}t+Yljq7-p$X;4_YL?6d;mdY3R##o1e%rlPxrsMh8|;sKTr~ caret QD#sw3&vS$FwlTk
381 > zp1#Gw!Qo-$LtvpXt#ApV0g) caret F=qFB`VB!W297x=$mr<$>rco3v$QKih_xN!k6;M=@
381 > zp1#Gw!Qo-$LtvpXt#ApV0g) caret F=qFB`VB!W297x=$mr<$>rco3v$QKih_xN!k6;M=@
382 > zCr?gDNQj7tm@;JwD;Ty&NlBSCYZk(b3dZeN8D4h2{r20dSFc7;(>E&r`s=TVtzpB4
382 > zCr?gDNQj7tm@;JwD;Ty&NlBSCYZk(b3dZeN8D4h2{r20dSFc7;(>E&r`s=TVtzpB4
383 > zk+ caret N&zCAiRns(?p6iBlk9v&h{1ve(FNtc)td51M>)TkXhc6{>5C)`fS$&)A1*CP1%
383 > zk+ caret N&zCAiRns(?p6iBlk9v&h{1ve(FNtc)td51M>)TkXhc6{>5C)`fS$&)A1*CP1%
384 > zld+peue4aYbg3C0!+4mu+}vE caret j_feX+ZijvffBI7Ofh#RZ*U3<3J5(+nfRCzexqQ5
384 > zld+peue4aYbg3C0!+4mu+}vE caret j_feX+ZijvffBI7Ofh#RZ*U3<3J5(+nfRCzexqQ5
385 > zgM&##Y4Dd{e%ZKjqrbm@|Ni}l4jo!AqtFynj3Xsd$o caret ?yV4$|UQ(j&UWCH>M=o_&N
385 > zgM&##Y4Dd{e%ZKjqrbm@|Ni}l4jo!AqtFynj3Xsd$o caret ?yV4$|UQ(j&UWCH>M=o_&N
386 > zmclXc3i|Q#<;#EoG>~V}4unTHbUK}u=y4;rA3S&vzC3 caret aJP!&D4RvvGfoyo(>C>la
386 > zmclXc3i|Q#<;#EoG>~V}4unTHbUK}u=y4;rA3S&vzC3 caret aJP!&D4RvvGfoyo(>C>la
387 > zijP<=v>X{3Ne&2BXo}DV8l0V-jdv`$am0ubG{Wuh%CTd|l9Q7m;G&|U@#Dvbhlj(d
387 > zijP<=v>X{3Ne&2BXo}DV8l0V-jdv`$am0ubG{Wuh%CTd|l9Q7m;G&|U@#Dvbhlj(d
388 > zg6W{3ATxYt#T?)3;SmIgOP4M|Dki~I_TX7SxP0x}wI~DQI7Lhm2BI7gph(aPIFAd;
388 > zg6W{3ATxYt#T?)3;SmIgOP4M|Dki~I_TX7SxP0x}wI~DQI7Lhm2BI7gph(aPIFAd;
389 > zQ&UsF`Q{rOz+z=87c5v%@5u~d6dWV5OlX`oH3cAH&UlvsZUEo(Q(P|lKs17rXvaiU
389 > zQ&UsF`Q{rOz+z=87c5v%@5u~d6dWV5OlX`oH3cAH&UlvsZUEo(Q(P|lKs17rXvaiU
390 > zQcj}IEufi1+Bnh6&(EhF{7O3vLHp`jjlp0J<M1kh$+$2xGm~Zk7OY7(q=&Rdhq*RG
390 > zQcj}IEufi1+Bnh6&(EhF{7O3vLHp`jjlp0J<M1kh$+$2xGm~Zk7OY7(q=&Rdhq*RG
391 > zwrmcd5MnP}xByB_)P@{J>DR9x6;`cUwPM8z){yooNiXPOc9_{W-gtwxE5TUg0vJk6
391 > zwrmcd5MnP}xByB_)P@{J>DR9x6;`cUwPM8z){yooNiXPOc9_{W-gtwxE5TUg0vJk6
392 > zO#JGruV&1cL6VGK2?+_YQr4`+EY8;Sm$9U$uuGRN=uj3k7?O9b+R~J7t_y*K64ZnI
392 > zO#JGruV&1cL6VGK2?+_YQr4`+EY8;Sm$9U$uuGRN=uj3k7?O9b+R~J7t_y*K64ZnI
393 > zM+{aE<b(v?vSmw;9zFP!aE266zHIhlmdI@ caret xa6o2jwdRk54a$>pcRbC29ZyG!Cfdp
393 > zM+{aE<b(v?vSmw;9zFP!aE266zHIhlmdI@ caret xa6o2jwdRk54a$>pcRbC29ZyG!Cfdp
394 > zutFf`Q`vljgo!(wHf=)F#m2_MIuj;L(2ja2YsQRX+rswV{d<H`Ar;(@%aNa9VPU8Z
394 > zutFf`Q`vljgo!(wHf=)F#m2_MIuj;L(2ja2YsQRX+rswV{d<H`Ar;(@%aNa9VPU8Z
395 > z;tq*`y}dm#NDJHKlV}uTIm!_vAq5E7!X-p{P=Z=Sh668>PuVS1*6e}OwOiMc;u3OQ
395 > z;tq*`y}dm#NDJHKlV}uTIm!_vAq5E7!X-p{P=Z=Sh668>PuVS1*6e}OwOiMc;u3OQ
396 > z@Bs)w3=lzfKoufH$SFuPG@uZ4NOnM#+=8LnQ2Q4zUd+nM+OT26;lqbN{P07dhH{jH
396 > z@Bs)w3=lzfKoufH$SFuPG@uZ4NOnM#+=8LnQ2Q4zUd+nM+OT26;lqbN{P07dhH{jH
397 > zManE8 caret dLms-Q2;1kB<*Q1a3f8kZr;xX=!Qro@`~@xN*Qj>gx;i;0Z24!~i2uLb`}v
397 > zManE8 caret dLms-Q2;1kB<*Q1a3f8kZr;xX=!Qro@`~@xN*Qj>gx;i;0Z24!~i2uLb`}v
398 > zA?R$|wvC+m caret Ups=*(4lDh*=UN8{5h(A?p#D caret 2N$8u4Z55!q?ZAh(iEEng9_Zi>IgO
398 > zA?R$|wvC+m caret Ups=*(4lDh*=UN8{5h(A?p#D caret 2N$8u4Z55!q?ZAh(iEEng9_Zi>IgO
399 > z#~**JC8hE4@n{hO&8btT5F*?nC_%LhA3i)PDhh-pB_&1wGrDIl caret *=8x3n&;akBf caret -
399 > z#~**JC8hE4@n{hO&8btT5F*?nC_%LhA3i)PDhh-pB_&1wGrDIl caret *=8x3n&;akBf caret -
400 > zJd&86kq$%%907v caret tgWoQdwI`|oNK%VvU~S#C<o caret F?6c48?Cjj#-4P<>HFD%&|Ni~t
400 > zJd&86kq$%%907v caret tgWoQdwI`|oNK%VvU~S#C<o caret F?6c48?Cjj#-4P<>HFD%&|Ni~t
401 > zKJ(|#H`$<5W+6ZkBb213rXonKZLB+X> caret L}J@W6osP3piLD_5?R!`S}*{xLBzFiL4@
401 > zKJ(|#H`$<5W+6ZkBb213rXonKZLB+X> caret L}J@W6osP3piLD_5?R!`S}*{xLBzFiL4@
402 > zX+}l{`A%?f@T5tT%ztu60p;)be`fWC`tP@WpO=?cpf8Xuf1OSj6d3f@Ki(ovDYq%0
402 > zX+}l{`A%?f@T5tT%ztu60p;)be`fWC`tP@WpO=?cpf8Xuf1OSj6d3f@Ki(ovDYq%0
403 > z{4ZSe`kOay5@=lAT!}vFzxyemC{sXDrhuYM0Y#ZI1r%ipD9W11{w=@&xgJ}t2x;ep
403 > z{4ZSe`kOay5@=lAT!}vFzxyemC{sXDrhuYM0Y#ZI1r%ipD9W11{w=@&xgJ}t2x;ep
404 > P00000NkvXXu0mjfZ5|Er
404 > P00000NkvXXu0mjfZ5|Er
405 >
405 >
406 > literal 0
406 > literal 0
407 > HcmV?d00001
407 > HcmV?d00001
408 >
408 >
409 > EOF
409 > EOF
410 $ hg import -d "1000000 0" -m delta quote-hack.patch
410 $ hg import -d "1000000 0" -m delta quote-hack.patch
411 applying quote-hack.patch
411 applying quote-hack.patch
412 $ rm quote-hack.patch
412 $ rm quote-hack.patch
413
413
414 $ hg manifest --debug | grep delta
414 $ hg manifest --debug | grep delta
415 9600f98bb60ce732634d126aaa4ac1ec959c573e 644 delta
415 9600f98bb60ce732634d126aaa4ac1ec959c573e 644 delta
416
416
417 $ hg import -d "1000000 0" -m delta - <<'EOF'
417 $ hg import -d "1000000 0" -m delta - <<'EOF'
418 > diff --git a/delta b/delta
418 > diff --git a/delta b/delta
419 > index 8c9b7831b231c2600843e303e66b521353a200b3..0021dd95bc0dba53c39ce81377126d43731d68df 100644
419 > index 8c9b7831b231c2600843e303e66b521353a200b3..0021dd95bc0dba53c39ce81377126d43731d68df 100644
420 > GIT binary patch
420 > GIT binary patch
421 > delta 49
421 > delta 49
422 > zcmZ1~yHs|=21Z8J$r~9bFdA-lVv=EEw4WT$qRf2QSa5SIOAHI6(&k4T8H|kLo4vWB
422 > zcmZ1~yHs|=21Z8J$r~9bFdA-lVv=EEw4WT$qRf2QSa5SIOAHI6(&k4T8H|kLo4vWB
423 > FSO9ZT4bA`n
423 > FSO9ZT4bA`n
424 >
424 >
425 > delta 49
425 > delta 49
426 > zcmV-10M7rV9i<(xumJ(}ld%Di0Xefm0vrMXpOaq%BLm9I%d>?9Tm%6Vv*HM70RcC&
426 > zcmV-10M7rV9i<(xumJ(}ld%Di0Xefm0vrMXpOaq%BLm9I%d>?9Tm%6Vv*HM70RcC&
427 > HOA1;9yU-AD
427 > HOA1;9yU-AD
428 >
428 >
429 > EOF
429 > EOF
430 applying patch from stdin
430 applying patch from stdin
431
431
432 $ hg manifest --debug | grep delta
432 $ hg manifest --debug | grep delta
433 56094bbea136dcf8dbd4088f6af469bde1a98b75 644 delta
433 56094bbea136dcf8dbd4088f6af469bde1a98b75 644 delta
434
434
435 Filenames with spaces:
435 Filenames with spaces:
436
436
437 $ sed 's,EOL$,,g' <<EOF | hg import -d "1000000 0" -m spaces -
437 $ sed 's,EOL$,,g' <<EOF | hg import -d "1000000 0" -m spaces -
438 > diff --git a/foo bar b/foo bar
438 > diff --git a/foo bar b/foo bar
439 > new file mode 100644
439 > new file mode 100644
440 > index 0000000..257cc56
440 > index 0000000..257cc56
441 > --- /dev/null
441 > --- /dev/null
442 > +++ b/foo bar EOL
442 > +++ b/foo bar EOL
443 > @@ -0,0 +1 @@
443 > @@ -0,0 +1 @@
444 > +foo
444 > +foo
445 > EOF
445 > EOF
446 applying patch from stdin
446 applying patch from stdin
447
447
448 $ hg tip -q
448 $ hg tip -q
449 14:4b79479c9a6d
449 14:4b79479c9a6d
450
450
451 $ cat "foo bar"
451 $ cat "foo bar"
452 foo
452 foo
453
453
454 Copy then modify the original file:
454 Copy then modify the original file:
455
455
456 $ hg import -d "1000000 0" -m copy-mod-orig - <<EOF
456 $ hg import -d "1000000 0" -m copy-mod-orig - <<EOF
457 > diff --git a/foo2 b/foo2
457 > diff --git a/foo2 b/foo2
458 > index 257cc56..fe08ec6 100644
458 > index 257cc56..fe08ec6 100644
459 > --- a/foo2
459 > --- a/foo2
460 > +++ b/foo2
460 > +++ b/foo2
461 > @@ -1 +1,2 @@
461 > @@ -1 +1,2 @@
462 > foo
462 > foo
463 > +new line
463 > +new line
464 > diff --git a/foo2 b/foo3
464 > diff --git a/foo2 b/foo3
465 > similarity index 100%
465 > similarity index 100%
466 > copy from foo2
466 > copy from foo2
467 > copy to foo3
467 > copy to foo3
468 > EOF
468 > EOF
469 applying patch from stdin
469 applying patch from stdin
470
470
471 $ hg tip -q
471 $ hg tip -q
472 15:9cbe44af4ae9
472 15:9cbe44af4ae9
473
473
474 $ cat foo3
474 $ cat foo3
475 foo
475 foo
476
476
477 Move text file and patch as binary
477 Move text file and patch as binary
478
478
479 $ echo a > text2
479 $ echo a > text2
480 $ hg ci -Am0
480 $ hg ci -Am0
481 adding text2
481 adding text2
482 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
482 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
483 > diff --git a/text2 b/binary2
483 > diff --git a/text2 b/binary2
484 > rename from text2
484 > rename from text2
485 > rename to binary2
485 > rename to binary2
486 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
486 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
487 > GIT binary patch
487 > GIT binary patch
488 > literal 5
488 > literal 5
489 > Mc$`b*O5$Pw00T?_*Z=?k
489 > Mc$`b*O5$Pw00T?_*Z=?k
490 >
490 >
491 > EOF
491 > EOF
492 applying patch from stdin
492 applying patch from stdin
493
493
494 $ cat binary2
494 $ cat binary2
495 a
495 a
496 b
496 b
497 \x00 (no-eol) (esc)
497 \x00 (no-eol) (esc)
498
498
499 $ hg st --copies --change .
499 $ hg st --copies --change .
500 A binary2
500 A binary2
501 text2
501 text2
502 R text2
502 R text2
503
503
504 Invalid base85 content
504 Invalid base85 content
505
505
506 $ hg rollback
506 $ hg rollback
507 repository tip rolled back to revision 16 (undo import)
507 repository tip rolled back to revision 16 (undo import)
508 working directory now based on revision 16
508 working directory now based on revision 16
509 $ hg revert -aq
509 $ hg revert -aq
510 $ hg import -d "1000000 0" -m invalid-binary - <<"EOF"
510 $ hg import -d "1000000 0" -m invalid-binary - <<"EOF"
511 > diff --git a/text2 b/binary2
511 > diff --git a/text2 b/binary2
512 > rename from text2
512 > rename from text2
513 > rename to binary2
513 > rename to binary2
514 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
514 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
515 > GIT binary patch
515 > GIT binary patch
516 > literal 5
516 > literal 5
517 > Mc$`b*O.$Pw00T?_*Z=?k
517 > Mc$`b*O.$Pw00T?_*Z=?k
518 >
518 >
519 > EOF
519 > EOF
520 applying patch from stdin
520 applying patch from stdin
521 abort: could not decode "binary2" binary patch: bad base85 character at position 6
521 abort: could not decode "binary2" binary patch: bad base85 character at position 6
522 [255]
522 [255]
523
523
524 $ hg revert -aq
524 $ hg revert -aq
525 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
525 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
526 > diff --git a/text2 b/binary2
526 > diff --git a/text2 b/binary2
527 > rename from text2
527 > rename from text2
528 > rename to binary2
528 > rename to binary2
529 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
529 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
530 > GIT binary patch
530 > GIT binary patch
531 > literal 6
531 > literal 6
532 > Mc$`b*O5$Pw00T?_*Z=?k
532 > Mc$`b*O5$Pw00T?_*Z=?k
533 >
533 >
534 > EOF
534 > EOF
535 applying patch from stdin
535 applying patch from stdin
536 abort: "binary2" length is 5 bytes, should be 6
536 abort: "binary2" length is 5 bytes, should be 6
537 [255]
537 [255]
538
538
539 $ hg revert -aq
539 $ hg revert -aq
540 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
540 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
541 > diff --git a/text2 b/binary2
541 > diff --git a/text2 b/binary2
542 > rename from text2
542 > rename from text2
543 > rename to binary2
543 > rename to binary2
544 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
544 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
545 > GIT binary patch
545 > GIT binary patch
546 > Mc$`b*O5$Pw00T?_*Z=?k
546 > Mc$`b*O5$Pw00T?_*Z=?k
547 >
547 >
548 > EOF
548 > EOF
549 applying patch from stdin
549 applying patch from stdin
550 abort: could not extract "binary2" binary data
550 abort: could not extract "binary2" binary data
551 [255]
551 [255]
552
552
553 Simulate a copy/paste turning LF into CRLF (issue2870)
553 Simulate a copy/paste turning LF into CRLF (issue2870)
554
554
555 $ hg revert -aq
555 $ hg revert -aq
556 $ cat > binary.diff <<"EOF"
556 $ cat > binary.diff <<"EOF"
557 > diff --git a/text2 b/binary2
557 > diff --git a/text2 b/binary2
558 > rename from text2
558 > rename from text2
559 > rename to binary2
559 > rename to binary2
560 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
560 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
561 > GIT binary patch
561 > GIT binary patch
562 > literal 5
562 > literal 5
563 > Mc$`b*O5$Pw00T?_*Z=?k
563 > Mc$`b*O5$Pw00T?_*Z=?k
564 >
564 >
565 > EOF
565 > EOF
566 >>> fp = file('binary.diff', 'rb')
566 >>> fp = file('binary.diff', 'rb')
567 >>> data = fp.read()
567 >>> data = fp.read()
568 >>> fp.close()
568 >>> fp.close()
569 >>> file('binary.diff', 'wb').write(data.replace('\n', '\r\n'))
569 >>> file('binary.diff', 'wb').write(data.replace('\n', '\r\n'))
570 $ rm binary2
570 $ rm binary2
571 $ hg import --no-commit binary.diff
571 $ hg import --no-commit binary.diff
572 applying binary.diff
572 applying binary.diff
573
573
574 $ cd ..
574 $ cd ..
575
575
576 Consecutive import with renames (issue2459)
576 Consecutive import with renames (issue2459)
577
577
578 $ hg init issue2459
578 $ hg init issue2459
579 $ cd issue2459
579 $ cd issue2459
580 $ hg import --no-commit --force - <<EOF
580 $ hg import --no-commit --force - <<EOF
581 > diff --git a/a b/a
581 > diff --git a/a b/a
582 > new file mode 100644
582 > new file mode 100644
583 > EOF
583 > EOF
584 applying patch from stdin
584 applying patch from stdin
585 $ hg import --no-commit --force - <<EOF
585 $ hg import --no-commit --force - <<EOF
586 > diff --git a/a b/b
586 > diff --git a/a b/b
587 > rename from a
587 > rename from a
588 > rename to b
588 > rename to b
589 > EOF
589 > EOF
590 applying patch from stdin
590 applying patch from stdin
591 a has not been committed yet, so no copy data will be stored for b.
591 a has not been committed yet, so no copy data will be stored for b.
592 $ hg debugstate
592 $ hg debugstate
593 a 0 -1 unset b
593 a 0 -1 unset b
594 $ hg ci -m done
594 $ hg ci -m done
595 $ cd ..
595 $ cd ..
596
596
597 Renames and strip
597 Renames and strip
598
598
599 $ hg init renameandstrip
599 $ hg init renameandstrip
600 $ cd renameandstrip
600 $ cd renameandstrip
601 $ echo a > a
601 $ echo a > a
602 $ hg ci -Am adda
602 $ hg ci -Am adda
603 adding a
603 adding a
604 $ hg import --no-commit -p2 - <<EOF
604 $ hg import --no-commit -p2 - <<EOF
605 > diff --git a/foo/a b/foo/b
605 > diff --git a/foo/a b/foo/b
606 > rename from foo/a
606 > rename from foo/a
607 > rename to foo/b
607 > rename to foo/b
608 > EOF
608 > EOF
609 applying patch from stdin
609 applying patch from stdin
610 $ hg st --copies
610 $ hg st --copies
611 A b
611 A b
612 a
612 a
613 R a
613 R a
614
614
615 Renames, similarity and git diff
615 Prefix with strip, renames, creates etc
616
616
617 $ hg revert -aC
617 $ hg revert -aC
618 undeleting a
618 undeleting a
619 forgetting b
619 forgetting b
620 $ rm b
620 $ rm b
621 $ mkdir -p dir/dir2
622 $ echo b > dir/dir2/b
623 $ echo c > dir/dir2/c
624 $ echo d > dir/d
625 $ hg ci -Am addbcd
626 adding dir/d
627 adding dir/dir2/b
628 adding dir/dir2/c
629 (test that prefixes are relative to the root)
630 $ mkdir tmpdir
631 $ cd tmpdir
632 $ hg import --no-commit -p2 --prefix dir/ - <<EOF
633 > diff --git a/foo/a b/foo/a
634 > new file mode 100644
635 > --- /dev/null
636 > +++ b/foo/a
637 > @@ -0,0 +1 @@
638 > +a
639 > diff --git a/foo/dir2/b b/foo/dir2/b2
640 > rename from foo/dir2/b
641 > rename to foo/dir2/b2
642 > diff --git a/foo/dir2/c b/foo/dir2/c
643 > --- a/foo/dir2/c
644 > +++ b/foo/dir2/c
645 > @@ -0,0 +1 @@
646 > +cc
647 > diff --git a/foo/d b/foo/d
648 > deleted file mode 100644
649 > --- a/foo/d
650 > +++ /dev/null
651 > @@ -1,1 +0,0 @@
652 > -d
653 > EOF
654 applying patch from stdin
655 $ hg st --copies
656 M dir/dir2/c
657 A dir/a
658 A dir/dir2/b2
659 dir/dir2/b
660 R dir/d
661 R dir/dir2/b
662 $ cd ..
663
664 Renames, similarity and git diff
665
666 $ hg revert -aC
667 forgetting dir/a (glob)
668 undeleting dir/d (glob)
669 undeleting dir/dir2/b (glob)
670 forgetting dir/dir2/b2 (glob)
671 reverting dir/dir2/c (glob)
672 $ rm dir/a dir/dir2/b2
621 $ hg import --similarity 90 --no-commit - <<EOF
673 $ hg import --similarity 90 --no-commit - <<EOF
622 > diff --git a/a b/b
674 > diff --git a/a b/b
623 > rename from a
675 > rename from a
624 > rename to b
676 > rename to b
625 > EOF
677 > EOF
626 applying patch from stdin
678 applying patch from stdin
627 $ hg st --copies
679 $ hg st --copies
628 A b
680 A b
629 a
681 a
630 R a
682 R a
631 $ cd ..
683 $ cd ..
632
684
633 Pure copy with existing destination
685 Pure copy with existing destination
634
686
635 $ hg init copytoexisting
687 $ hg init copytoexisting
636 $ cd copytoexisting
688 $ cd copytoexisting
637 $ echo a > a
689 $ echo a > a
638 $ echo b > b
690 $ echo b > b
639 $ hg ci -Am add
691 $ hg ci -Am add
640 adding a
692 adding a
641 adding b
693 adding b
642 $ hg import --no-commit - <<EOF
694 $ hg import --no-commit - <<EOF
643 > diff --git a/a b/b
695 > diff --git a/a b/b
644 > copy from a
696 > copy from a
645 > copy to b
697 > copy to b
646 > EOF
698 > EOF
647 applying patch from stdin
699 applying patch from stdin
648 abort: cannot create b: destination already exists
700 abort: cannot create b: destination already exists
649 [255]
701 [255]
650 $ cat b
702 $ cat b
651 b
703 b
652
704
653 Copy and changes with existing destination
705 Copy and changes with existing destination
654
706
655 $ hg import --no-commit - <<EOF
707 $ hg import --no-commit - <<EOF
656 > diff --git a/a b/b
708 > diff --git a/a b/b
657 > copy from a
709 > copy from a
658 > copy to b
710 > copy to b
659 > --- a/a
711 > --- a/a
660 > +++ b/b
712 > +++ b/b
661 > @@ -1,1 +1,2 @@
713 > @@ -1,1 +1,2 @@
662 > a
714 > a
663 > +b
715 > +b
664 > EOF
716 > EOF
665 applying patch from stdin
717 applying patch from stdin
666 cannot create b: destination already exists
718 cannot create b: destination already exists
667 1 out of 1 hunks FAILED -- saving rejects to file b.rej
719 1 out of 1 hunks FAILED -- saving rejects to file b.rej
668 abort: patch failed to apply
720 abort: patch failed to apply
669 [255]
721 [255]
670 $ cat b
722 $ cat b
671 b
723 b
672
724
673 #if symlink
725 #if symlink
674
726
675 $ ln -s b linkb
727 $ ln -s b linkb
676 $ hg add linkb
728 $ hg add linkb
677 $ hg ci -m addlinkb
729 $ hg ci -m addlinkb
678 $ hg import --no-commit - <<EOF
730 $ hg import --no-commit - <<EOF
679 > diff --git a/linkb b/linkb
731 > diff --git a/linkb b/linkb
680 > deleted file mode 120000
732 > deleted file mode 120000
681 > --- a/linkb
733 > --- a/linkb
682 > +++ /dev/null
734 > +++ /dev/null
683 > @@ -1,1 +0,0 @@
735 > @@ -1,1 +0,0 @@
684 > -badhunk
736 > -badhunk
685 > \ No newline at end of file
737 > \ No newline at end of file
686 > EOF
738 > EOF
687 applying patch from stdin
739 applying patch from stdin
688 patching file linkb
740 patching file linkb
689 Hunk #1 FAILED at 0
741 Hunk #1 FAILED at 0
690 1 out of 1 hunks FAILED -- saving rejects to file linkb.rej
742 1 out of 1 hunks FAILED -- saving rejects to file linkb.rej
691 abort: patch failed to apply
743 abort: patch failed to apply
692 [255]
744 [255]
693 $ hg st
745 $ hg st
694 ? b.rej
746 ? b.rej
695 ? linkb.rej
747 ? linkb.rej
696
748
697 #endif
749 #endif
698
750
699 Test corner case involving copies and multiple hunks (issue3384)
751 Test corner case involving copies and multiple hunks (issue3384)
700
752
701 $ hg revert -qa
753 $ hg revert -qa
702 $ hg import --no-commit - <<EOF
754 $ hg import --no-commit - <<EOF
703 > diff --git a/a b/c
755 > diff --git a/a b/c
704 > copy from a
756 > copy from a
705 > copy to c
757 > copy to c
706 > --- a/a
758 > --- a/a
707 > +++ b/c
759 > +++ b/c
708 > @@ -1,1 +1,2 @@
760 > @@ -1,1 +1,2 @@
709 > a
761 > a
710 > +a
762 > +a
711 > @@ -2,1 +2,2 @@
763 > @@ -2,1 +2,2 @@
712 > a
764 > a
713 > +a
765 > +a
714 > diff --git a/a b/a
766 > diff --git a/a b/a
715 > --- a/a
767 > --- a/a
716 > +++ b/a
768 > +++ b/a
717 > @@ -1,1 +1,2 @@
769 > @@ -1,1 +1,2 @@
718 > a
770 > a
719 > +b
771 > +b
720 > EOF
772 > EOF
721 applying patch from stdin
773 applying patch from stdin
722
774
723 $ cd ..
775 $ cd ..
@@ -1,1479 +1,1498 b''
1 $ hg init a
1 $ hg init a
2 $ mkdir a/d1
2 $ mkdir a/d1
3 $ mkdir a/d1/d2
3 $ mkdir a/d1/d2
4 $ echo line 1 > a/a
4 $ echo line 1 > a/a
5 $ echo line 1 > a/d1/d2/a
5 $ echo line 1 > a/d1/d2/a
6 $ hg --cwd a ci -Ama
6 $ hg --cwd a ci -Ama
7 adding a
7 adding a
8 adding d1/d2/a
8 adding d1/d2/a
9
9
10 $ echo line 2 >> a/a
10 $ echo line 2 >> a/a
11 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
11 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
12
12
13 import with no args:
13 import with no args:
14
14
15 $ hg --cwd a import
15 $ hg --cwd a import
16 abort: need at least one patch to import
16 abort: need at least one patch to import
17 [255]
17 [255]
18
18
19 generate patches for the test
19 generate patches for the test
20
20
21 $ hg --cwd a export tip > exported-tip.patch
21 $ hg --cwd a export tip > exported-tip.patch
22 $ hg --cwd a diff -r0:1 > diffed-tip.patch
22 $ hg --cwd a diff -r0:1 > diffed-tip.patch
23
23
24
24
25 import exported patch
25 import exported patch
26 (this also tests that editor is not invoked, if the patch contains the
26 (this also tests that editor is not invoked, if the patch contains the
27 commit message and '--edit' is not specified)
27 commit message and '--edit' is not specified)
28
28
29 $ hg clone -r0 a b
29 $ hg clone -r0 a b
30 adding changesets
30 adding changesets
31 adding manifests
31 adding manifests
32 adding file changes
32 adding file changes
33 added 1 changesets with 2 changes to 2 files
33 added 1 changesets with 2 changes to 2 files
34 updating to branch default
34 updating to branch default
35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
36 $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
37 applying ../exported-tip.patch
37 applying ../exported-tip.patch
38
38
39 message and committer and date should be same
39 message and committer and date should be same
40
40
41 $ hg --cwd b tip
41 $ hg --cwd b tip
42 changeset: 1:1d4bd90af0e4
42 changeset: 1:1d4bd90af0e4
43 tag: tip
43 tag: tip
44 user: someone
44 user: someone
45 date: Thu Jan 01 00:00:01 1970 +0000
45 date: Thu Jan 01 00:00:01 1970 +0000
46 summary: second change
46 summary: second change
47
47
48 $ rm -r b
48 $ rm -r b
49
49
50
50
51 import exported patch with external patcher
51 import exported patch with external patcher
52 (this also tests that editor is invoked, if the '--edit' is specified,
52 (this also tests that editor is invoked, if the '--edit' is specified,
53 regardless of the commit message in the patch)
53 regardless of the commit message in the patch)
54
54
55 $ cat > dummypatch.py <<EOF
55 $ cat > dummypatch.py <<EOF
56 > print 'patching file a'
56 > print 'patching file a'
57 > file('a', 'wb').write('line2\n')
57 > file('a', 'wb').write('line2\n')
58 > EOF
58 > EOF
59 $ hg clone -r0 a b
59 $ hg clone -r0 a b
60 adding changesets
60 adding changesets
61 adding manifests
61 adding manifests
62 adding file changes
62 adding file changes
63 added 1 changesets with 2 changes to 2 files
63 added 1 changesets with 2 changes to 2 files
64 updating to branch default
64 updating to branch default
65 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
65 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 $ HGEDITOR=cat hg --config ui.patch='python ../dummypatch.py' --cwd b import --edit ../exported-tip.patch
66 $ HGEDITOR=cat hg --config ui.patch='python ../dummypatch.py' --cwd b import --edit ../exported-tip.patch
67 applying ../exported-tip.patch
67 applying ../exported-tip.patch
68 second change
68 second change
69
69
70
70
71 HG: Enter commit message. Lines beginning with 'HG:' are removed.
71 HG: Enter commit message. Lines beginning with 'HG:' are removed.
72 HG: Leave message empty to abort commit.
72 HG: Leave message empty to abort commit.
73 HG: --
73 HG: --
74 HG: user: someone
74 HG: user: someone
75 HG: branch 'default'
75 HG: branch 'default'
76 HG: changed a
76 HG: changed a
77 $ cat b/a
77 $ cat b/a
78 line2
78 line2
79 $ rm -r b
79 $ rm -r b
80
80
81
81
82 import of plain diff should fail without message
82 import of plain diff should fail without message
83 (this also tests that editor is invoked, if the patch doesn't contain
83 (this also tests that editor is invoked, if the patch doesn't contain
84 the commit message, regardless of '--edit')
84 the commit message, regardless of '--edit')
85
85
86 $ hg clone -r0 a b
86 $ hg clone -r0 a b
87 adding changesets
87 adding changesets
88 adding manifests
88 adding manifests
89 adding file changes
89 adding file changes
90 added 1 changesets with 2 changes to 2 files
90 added 1 changesets with 2 changes to 2 files
91 updating to branch default
91 updating to branch default
92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 $ cat > $TESTTMP/editor.sh <<EOF
93 $ cat > $TESTTMP/editor.sh <<EOF
94 > env | grep HGEDITFORM
94 > env | grep HGEDITFORM
95 > cat \$1
95 > cat \$1
96 > EOF
96 > EOF
97 $ HGEDITOR="sh $TESTTMP/editor.sh" hg --cwd b import ../diffed-tip.patch
97 $ HGEDITOR="sh $TESTTMP/editor.sh" hg --cwd b import ../diffed-tip.patch
98 applying ../diffed-tip.patch
98 applying ../diffed-tip.patch
99 HGEDITFORM=import.normal.normal
99 HGEDITFORM=import.normal.normal
100
100
101
101
102 HG: Enter commit message. Lines beginning with 'HG:' are removed.
102 HG: Enter commit message. Lines beginning with 'HG:' are removed.
103 HG: Leave message empty to abort commit.
103 HG: Leave message empty to abort commit.
104 HG: --
104 HG: --
105 HG: user: test
105 HG: user: test
106 HG: branch 'default'
106 HG: branch 'default'
107 HG: changed a
107 HG: changed a
108 abort: empty commit message
108 abort: empty commit message
109 [255]
109 [255]
110
110
111 Test avoiding editor invocation at applying the patch with --exact,
111 Test avoiding editor invocation at applying the patch with --exact,
112 even if commit message is empty
112 even if commit message is empty
113
113
114 $ echo a >> b/a
114 $ echo a >> b/a
115 $ hg --cwd b commit -m ' '
115 $ hg --cwd b commit -m ' '
116 $ hg --cwd b tip -T "{node}\n"
116 $ hg --cwd b tip -T "{node}\n"
117 d8804f3f5396d800812f579c8452796a5993bdb2
117 d8804f3f5396d800812f579c8452796a5993bdb2
118 $ hg --cwd b export -o ../empty-log.diff .
118 $ hg --cwd b export -o ../empty-log.diff .
119 $ hg --cwd b update -q -C ".^1"
119 $ hg --cwd b update -q -C ".^1"
120 $ hg --cwd b --config extensions.strip= strip -q tip
120 $ hg --cwd b --config extensions.strip= strip -q tip
121 $ HGEDITOR=cat hg --cwd b import --exact ../empty-log.diff
121 $ HGEDITOR=cat hg --cwd b import --exact ../empty-log.diff
122 applying ../empty-log.diff
122 applying ../empty-log.diff
123 $ hg --cwd b tip -T "{node}\n"
123 $ hg --cwd b tip -T "{node}\n"
124 d8804f3f5396d800812f579c8452796a5993bdb2
124 d8804f3f5396d800812f579c8452796a5993bdb2
125
125
126 $ rm -r b
126 $ rm -r b
127
127
128
128
129 import of plain diff should be ok with message
129 import of plain diff should be ok with message
130
130
131 $ hg clone -r0 a b
131 $ hg clone -r0 a b
132 adding changesets
132 adding changesets
133 adding manifests
133 adding manifests
134 adding file changes
134 adding file changes
135 added 1 changesets with 2 changes to 2 files
135 added 1 changesets with 2 changes to 2 files
136 updating to branch default
136 updating to branch default
137 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
137 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
138 $ hg --cwd b import -mpatch ../diffed-tip.patch
138 $ hg --cwd b import -mpatch ../diffed-tip.patch
139 applying ../diffed-tip.patch
139 applying ../diffed-tip.patch
140 $ rm -r b
140 $ rm -r b
141
141
142
142
143 import of plain diff with specific date and user
143 import of plain diff with specific date and user
144 (this also tests that editor is not invoked, if
144 (this also tests that editor is not invoked, if
145 '--message'/'--logfile' is specified and '--edit' is not)
145 '--message'/'--logfile' is specified and '--edit' is not)
146
146
147 $ hg clone -r0 a b
147 $ hg clone -r0 a b
148 adding changesets
148 adding changesets
149 adding manifests
149 adding manifests
150 adding file changes
150 adding file changes
151 added 1 changesets with 2 changes to 2 files
151 added 1 changesets with 2 changes to 2 files
152 updating to branch default
152 updating to branch default
153 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
153 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
154 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
155 applying ../diffed-tip.patch
155 applying ../diffed-tip.patch
156 $ hg -R b tip -pv
156 $ hg -R b tip -pv
157 changeset: 1:ca68f19f3a40
157 changeset: 1:ca68f19f3a40
158 tag: tip
158 tag: tip
159 user: user@nowhere.net
159 user: user@nowhere.net
160 date: Thu Jan 01 00:00:01 1970 +0000
160 date: Thu Jan 01 00:00:01 1970 +0000
161 files: a
161 files: a
162 description:
162 description:
163 patch
163 patch
164
164
165
165
166 diff -r 80971e65b431 -r ca68f19f3a40 a
166 diff -r 80971e65b431 -r ca68f19f3a40 a
167 --- a/a Thu Jan 01 00:00:00 1970 +0000
167 --- a/a Thu Jan 01 00:00:00 1970 +0000
168 +++ b/a Thu Jan 01 00:00:01 1970 +0000
168 +++ b/a Thu Jan 01 00:00:01 1970 +0000
169 @@ -1,1 +1,2 @@
169 @@ -1,1 +1,2 @@
170 line 1
170 line 1
171 +line 2
171 +line 2
172
172
173 $ rm -r b
173 $ rm -r b
174
174
175
175
176 import of plain diff should be ok with --no-commit
176 import of plain diff should be ok with --no-commit
177 (this also tests that editor is not invoked, if '--no-commit' is
177 (this also tests that editor is not invoked, if '--no-commit' is
178 specified, regardless of '--edit')
178 specified, regardless of '--edit')
179
179
180 $ hg clone -r0 a b
180 $ hg clone -r0 a b
181 adding changesets
181 adding changesets
182 adding manifests
182 adding manifests
183 adding file changes
183 adding file changes
184 added 1 changesets with 2 changes to 2 files
184 added 1 changesets with 2 changes to 2 files
185 updating to branch default
185 updating to branch default
186 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
186 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
187 $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
187 $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
188 applying ../diffed-tip.patch
188 applying ../diffed-tip.patch
189 $ hg --cwd b diff --nodates
189 $ hg --cwd b diff --nodates
190 diff -r 80971e65b431 a
190 diff -r 80971e65b431 a
191 --- a/a
191 --- a/a
192 +++ b/a
192 +++ b/a
193 @@ -1,1 +1,2 @@
193 @@ -1,1 +1,2 @@
194 line 1
194 line 1
195 +line 2
195 +line 2
196 $ rm -r b
196 $ rm -r b
197
197
198
198
199 import of malformed plain diff should fail
199 import of malformed plain diff should fail
200
200
201 $ hg clone -r0 a b
201 $ hg clone -r0 a b
202 adding changesets
202 adding changesets
203 adding manifests
203 adding manifests
204 adding file changes
204 adding file changes
205 added 1 changesets with 2 changes to 2 files
205 added 1 changesets with 2 changes to 2 files
206 updating to branch default
206 updating to branch default
207 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
207 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
208 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
208 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
209 $ hg --cwd b import -mpatch ../broken.patch
209 $ hg --cwd b import -mpatch ../broken.patch
210 applying ../broken.patch
210 applying ../broken.patch
211 abort: bad hunk #1
211 abort: bad hunk #1
212 [255]
212 [255]
213 $ rm -r b
213 $ rm -r b
214
214
215
215
216 hg -R repo import
216 hg -R repo import
217 put the clone in a subdir - having a directory named "a"
217 put the clone in a subdir - having a directory named "a"
218 used to hide a bug.
218 used to hide a bug.
219
219
220 $ mkdir dir
220 $ mkdir dir
221 $ hg clone -r0 a dir/b
221 $ hg clone -r0 a dir/b
222 adding changesets
222 adding changesets
223 adding manifests
223 adding manifests
224 adding file changes
224 adding file changes
225 added 1 changesets with 2 changes to 2 files
225 added 1 changesets with 2 changes to 2 files
226 updating to branch default
226 updating to branch default
227 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
227 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
228 $ cd dir
228 $ cd dir
229 $ hg -R b import ../exported-tip.patch
229 $ hg -R b import ../exported-tip.patch
230 applying ../exported-tip.patch
230 applying ../exported-tip.patch
231 $ cd ..
231 $ cd ..
232 $ rm -r dir
232 $ rm -r dir
233
233
234
234
235 import from stdin
235 import from stdin
236
236
237 $ hg clone -r0 a b
237 $ hg clone -r0 a b
238 adding changesets
238 adding changesets
239 adding manifests
239 adding manifests
240 adding file changes
240 adding file changes
241 added 1 changesets with 2 changes to 2 files
241 added 1 changesets with 2 changes to 2 files
242 updating to branch default
242 updating to branch default
243 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
243 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 $ hg --cwd b import - < exported-tip.patch
244 $ hg --cwd b import - < exported-tip.patch
245 applying patch from stdin
245 applying patch from stdin
246 $ rm -r b
246 $ rm -r b
247
247
248
248
249 import two patches in one stream
249 import two patches in one stream
250
250
251 $ hg init b
251 $ hg init b
252 $ hg --cwd a export 0:tip | hg --cwd b import -
252 $ hg --cwd a export 0:tip | hg --cwd b import -
253 applying patch from stdin
253 applying patch from stdin
254 $ hg --cwd a id
254 $ hg --cwd a id
255 1d4bd90af0e4 tip
255 1d4bd90af0e4 tip
256 $ hg --cwd b id
256 $ hg --cwd b id
257 1d4bd90af0e4 tip
257 1d4bd90af0e4 tip
258 $ rm -r b
258 $ rm -r b
259
259
260
260
261 override commit message
261 override commit message
262
262
263 $ hg clone -r0 a b
263 $ hg clone -r0 a b
264 adding changesets
264 adding changesets
265 adding manifests
265 adding manifests
266 adding file changes
266 adding file changes
267 added 1 changesets with 2 changes to 2 files
267 added 1 changesets with 2 changes to 2 files
268 updating to branch default
268 updating to branch default
269 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
269 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
270 $ hg --cwd b import -m 'override' - < exported-tip.patch
270 $ hg --cwd b import -m 'override' - < exported-tip.patch
271 applying patch from stdin
271 applying patch from stdin
272 $ hg --cwd b tip | grep override
272 $ hg --cwd b tip | grep override
273 summary: override
273 summary: override
274 $ rm -r b
274 $ rm -r b
275
275
276 $ cat > mkmsg.py <<EOF
276 $ cat > mkmsg.py <<EOF
277 > import email.Message, sys
277 > import email.Message, sys
278 > msg = email.Message.Message()
278 > msg = email.Message.Message()
279 > patch = open(sys.argv[1], 'rb').read()
279 > patch = open(sys.argv[1], 'rb').read()
280 > msg.set_payload('email commit message\n' + patch)
280 > msg.set_payload('email commit message\n' + patch)
281 > msg['Subject'] = 'email patch'
281 > msg['Subject'] = 'email patch'
282 > msg['From'] = 'email patcher'
282 > msg['From'] = 'email patcher'
283 > file(sys.argv[2], 'wb').write(msg.as_string())
283 > file(sys.argv[2], 'wb').write(msg.as_string())
284 > EOF
284 > EOF
285
285
286
286
287 plain diff in email, subject, message body
287 plain diff in email, subject, message body
288
288
289 $ hg clone -r0 a b
289 $ hg clone -r0 a b
290 adding changesets
290 adding changesets
291 adding manifests
291 adding manifests
292 adding file changes
292 adding file changes
293 added 1 changesets with 2 changes to 2 files
293 added 1 changesets with 2 changes to 2 files
294 updating to branch default
294 updating to branch default
295 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
295 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 $ python mkmsg.py diffed-tip.patch msg.patch
296 $ python mkmsg.py diffed-tip.patch msg.patch
297 $ hg --cwd b import ../msg.patch
297 $ hg --cwd b import ../msg.patch
298 applying ../msg.patch
298 applying ../msg.patch
299 $ hg --cwd b tip | grep email
299 $ hg --cwd b tip | grep email
300 user: email patcher
300 user: email patcher
301 summary: email patch
301 summary: email patch
302 $ rm -r b
302 $ rm -r b
303
303
304
304
305 plain diff in email, no subject, message body
305 plain diff in email, no subject, message body
306
306
307 $ hg clone -r0 a b
307 $ hg clone -r0 a b
308 adding changesets
308 adding changesets
309 adding manifests
309 adding manifests
310 adding file changes
310 adding file changes
311 added 1 changesets with 2 changes to 2 files
311 added 1 changesets with 2 changes to 2 files
312 updating to branch default
312 updating to branch default
313 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
313 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
314 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
314 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
315 applying patch from stdin
315 applying patch from stdin
316 $ rm -r b
316 $ rm -r b
317
317
318
318
319 plain diff in email, subject, no message body
319 plain diff in email, subject, no message body
320
320
321 $ hg clone -r0 a b
321 $ hg clone -r0 a b
322 adding changesets
322 adding changesets
323 adding manifests
323 adding manifests
324 adding file changes
324 adding file changes
325 added 1 changesets with 2 changes to 2 files
325 added 1 changesets with 2 changes to 2 files
326 updating to branch default
326 updating to branch default
327 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
327 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
328 $ grep -v '^email ' msg.patch | hg --cwd b import -
328 $ grep -v '^email ' msg.patch | hg --cwd b import -
329 applying patch from stdin
329 applying patch from stdin
330 $ rm -r b
330 $ rm -r b
331
331
332
332
333 plain diff in email, no subject, no message body, should fail
333 plain diff in email, no subject, no message body, should fail
334
334
335 $ hg clone -r0 a b
335 $ hg clone -r0 a b
336 adding changesets
336 adding changesets
337 adding manifests
337 adding manifests
338 adding file changes
338 adding file changes
339 added 1 changesets with 2 changes to 2 files
339 added 1 changesets with 2 changes to 2 files
340 updating to branch default
340 updating to branch default
341 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
341 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
342 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
342 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
343 applying patch from stdin
343 applying patch from stdin
344 abort: empty commit message
344 abort: empty commit message
345 [255]
345 [255]
346 $ rm -r b
346 $ rm -r b
347
347
348
348
349 hg export in email, should use patch header
349 hg export in email, should use patch header
350
350
351 $ hg clone -r0 a b
351 $ hg clone -r0 a b
352 adding changesets
352 adding changesets
353 adding manifests
353 adding manifests
354 adding file changes
354 adding file changes
355 added 1 changesets with 2 changes to 2 files
355 added 1 changesets with 2 changes to 2 files
356 updating to branch default
356 updating to branch default
357 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
357 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
358 $ python mkmsg.py exported-tip.patch msg.patch
358 $ python mkmsg.py exported-tip.patch msg.patch
359 $ cat msg.patch | hg --cwd b import -
359 $ cat msg.patch | hg --cwd b import -
360 applying patch from stdin
360 applying patch from stdin
361 $ hg --cwd b tip | grep second
361 $ hg --cwd b tip | grep second
362 summary: second change
362 summary: second change
363 $ rm -r b
363 $ rm -r b
364
364
365
365
366 subject: duplicate detection, removal of [PATCH]
366 subject: duplicate detection, removal of [PATCH]
367 The '---' tests the gitsendmail handling without proper mail headers
367 The '---' tests the gitsendmail handling without proper mail headers
368
368
369 $ cat > mkmsg2.py <<EOF
369 $ cat > mkmsg2.py <<EOF
370 > import email.Message, sys
370 > import email.Message, sys
371 > msg = email.Message.Message()
371 > msg = email.Message.Message()
372 > patch = open(sys.argv[1], 'rb').read()
372 > patch = open(sys.argv[1], 'rb').read()
373 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
373 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
374 > msg['Subject'] = '[PATCH] email patch'
374 > msg['Subject'] = '[PATCH] email patch'
375 > msg['From'] = 'email patcher'
375 > msg['From'] = 'email patcher'
376 > file(sys.argv[2], 'wb').write(msg.as_string())
376 > file(sys.argv[2], 'wb').write(msg.as_string())
377 > EOF
377 > EOF
378
378
379
379
380 plain diff in email, [PATCH] subject, message body with subject
380 plain diff in email, [PATCH] subject, message body with subject
381
381
382 $ hg clone -r0 a b
382 $ hg clone -r0 a b
383 adding changesets
383 adding changesets
384 adding manifests
384 adding manifests
385 adding file changes
385 adding file changes
386 added 1 changesets with 2 changes to 2 files
386 added 1 changesets with 2 changes to 2 files
387 updating to branch default
387 updating to branch default
388 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
388 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
389 $ python mkmsg2.py diffed-tip.patch msg.patch
389 $ python mkmsg2.py diffed-tip.patch msg.patch
390 $ cat msg.patch | hg --cwd b import -
390 $ cat msg.patch | hg --cwd b import -
391 applying patch from stdin
391 applying patch from stdin
392 $ hg --cwd b tip --template '{desc}\n'
392 $ hg --cwd b tip --template '{desc}\n'
393 email patch
393 email patch
394
394
395 next line
395 next line
396 $ rm -r b
396 $ rm -r b
397
397
398
398
399 Issue963: Parent of working dir incorrect after import of multiple
399 Issue963: Parent of working dir incorrect after import of multiple
400 patches and rollback
400 patches and rollback
401
401
402 We weren't backing up the correct dirstate file when importing many
402 We weren't backing up the correct dirstate file when importing many
403 patches: import patch1 patch2; rollback
403 patches: import patch1 patch2; rollback
404
404
405 $ echo line 3 >> a/a
405 $ echo line 3 >> a/a
406 $ hg --cwd a ci -m'third change'
406 $ hg --cwd a ci -m'third change'
407 $ hg --cwd a export -o '../patch%R' 1 2
407 $ hg --cwd a export -o '../patch%R' 1 2
408 $ hg clone -qr0 a b
408 $ hg clone -qr0 a b
409 $ hg --cwd b parents --template 'parent: {rev}\n'
409 $ hg --cwd b parents --template 'parent: {rev}\n'
410 parent: 0
410 parent: 0
411 $ hg --cwd b import -v ../patch1 ../patch2
411 $ hg --cwd b import -v ../patch1 ../patch2
412 applying ../patch1
412 applying ../patch1
413 patching file a
413 patching file a
414 committing files:
414 committing files:
415 a
415 a
416 committing manifest
416 committing manifest
417 committing changelog
417 committing changelog
418 created 1d4bd90af0e4
418 created 1d4bd90af0e4
419 applying ../patch2
419 applying ../patch2
420 patching file a
420 patching file a
421 committing files:
421 committing files:
422 a
422 a
423 committing manifest
423 committing manifest
424 committing changelog
424 committing changelog
425 created 6d019af21222
425 created 6d019af21222
426 $ hg --cwd b rollback
426 $ hg --cwd b rollback
427 repository tip rolled back to revision 0 (undo import)
427 repository tip rolled back to revision 0 (undo import)
428 working directory now based on revision 0
428 working directory now based on revision 0
429 $ hg --cwd b parents --template 'parent: {rev}\n'
429 $ hg --cwd b parents --template 'parent: {rev}\n'
430 parent: 0
430 parent: 0
431 $ rm -r b
431 $ rm -r b
432
432
433
433
434 importing a patch in a subdirectory failed at the commit stage
434 importing a patch in a subdirectory failed at the commit stage
435
435
436 $ echo line 2 >> a/d1/d2/a
436 $ echo line 2 >> a/d1/d2/a
437 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
437 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
438
438
439 hg import in a subdirectory
439 hg import in a subdirectory
440
440
441 $ hg clone -r0 a b
441 $ hg clone -r0 a b
442 adding changesets
442 adding changesets
443 adding manifests
443 adding manifests
444 adding file changes
444 adding file changes
445 added 1 changesets with 2 changes to 2 files
445 added 1 changesets with 2 changes to 2 files
446 updating to branch default
446 updating to branch default
447 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
447 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
448 $ hg --cwd a export tip > tmp
448 $ hg --cwd a export tip > tmp
449 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
449 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
450 $ dir=`pwd`
450 $ dir=`pwd`
451 $ cd b/d1/d2 2>&1 > /dev/null
451 $ cd b/d1/d2 2>&1 > /dev/null
452 $ hg import ../../../subdir-tip.patch
452 $ hg import ../../../subdir-tip.patch
453 applying ../../../subdir-tip.patch
453 applying ../../../subdir-tip.patch
454 $ cd "$dir"
454 $ cd "$dir"
455
455
456 message should be 'subdir change'
456 message should be 'subdir change'
457 committer should be 'someoneelse'
457 committer should be 'someoneelse'
458
458
459 $ hg --cwd b tip
459 $ hg --cwd b tip
460 changeset: 1:3577f5aea227
460 changeset: 1:3577f5aea227
461 tag: tip
461 tag: tip
462 user: someoneelse
462 user: someoneelse
463 date: Thu Jan 01 00:00:01 1970 +0000
463 date: Thu Jan 01 00:00:01 1970 +0000
464 summary: subdir change
464 summary: subdir change
465
465
466
466
467 should be empty
467 should be empty
468
468
469 $ hg --cwd b status
469 $ hg --cwd b status
470
470
471
471
472 Test fuzziness (ambiguous patch location, fuzz=2)
472 Test fuzziness (ambiguous patch location, fuzz=2)
473
473
474 $ hg init fuzzy
474 $ hg init fuzzy
475 $ cd fuzzy
475 $ cd fuzzy
476 $ echo line1 > a
476 $ echo line1 > a
477 $ echo line0 >> a
477 $ echo line0 >> a
478 $ echo line3 >> a
478 $ echo line3 >> a
479 $ hg ci -Am adda
479 $ hg ci -Am adda
480 adding a
480 adding a
481 $ echo line1 > a
481 $ echo line1 > a
482 $ echo line2 >> a
482 $ echo line2 >> a
483 $ echo line0 >> a
483 $ echo line0 >> a
484 $ echo line3 >> a
484 $ echo line3 >> a
485 $ hg ci -m change a
485 $ hg ci -m change a
486 $ hg export tip > fuzzy-tip.patch
486 $ hg export tip > fuzzy-tip.patch
487 $ hg up -C 0
487 $ hg up -C 0
488 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
488 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
489 $ echo line1 > a
489 $ echo line1 > a
490 $ echo line0 >> a
490 $ echo line0 >> a
491 $ echo line1 >> a
491 $ echo line1 >> a
492 $ echo line0 >> a
492 $ echo line0 >> a
493 $ hg ci -m brancha
493 $ hg ci -m brancha
494 created new head
494 created new head
495 $ hg import --no-commit -v fuzzy-tip.patch
495 $ hg import --no-commit -v fuzzy-tip.patch
496 applying fuzzy-tip.patch
496 applying fuzzy-tip.patch
497 patching file a
497 patching file a
498 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
498 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
499 applied to working directory
499 applied to working directory
500 $ hg revert -a
500 $ hg revert -a
501 reverting a
501 reverting a
502
502
503
503
504 import with --no-commit should have written .hg/last-message.txt
504 import with --no-commit should have written .hg/last-message.txt
505
505
506 $ cat .hg/last-message.txt
506 $ cat .hg/last-message.txt
507 change (no-eol)
507 change (no-eol)
508
508
509
509
510 test fuzziness with eol=auto
510 test fuzziness with eol=auto
511
511
512 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
512 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
513 applying fuzzy-tip.patch
513 applying fuzzy-tip.patch
514 patching file a
514 patching file a
515 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
515 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
516 applied to working directory
516 applied to working directory
517 $ cd ..
517 $ cd ..
518
518
519
519
520 Test hunk touching empty files (issue906)
520 Test hunk touching empty files (issue906)
521
521
522 $ hg init empty
522 $ hg init empty
523 $ cd empty
523 $ cd empty
524 $ touch a
524 $ touch a
525 $ touch b1
525 $ touch b1
526 $ touch c1
526 $ touch c1
527 $ echo d > d
527 $ echo d > d
528 $ hg ci -Am init
528 $ hg ci -Am init
529 adding a
529 adding a
530 adding b1
530 adding b1
531 adding c1
531 adding c1
532 adding d
532 adding d
533 $ echo a > a
533 $ echo a > a
534 $ echo b > b1
534 $ echo b > b1
535 $ hg mv b1 b2
535 $ hg mv b1 b2
536 $ echo c > c1
536 $ echo c > c1
537 $ hg copy c1 c2
537 $ hg copy c1 c2
538 $ rm d
538 $ rm d
539 $ touch d
539 $ touch d
540 $ hg diff --git
540 $ hg diff --git
541 diff --git a/a b/a
541 diff --git a/a b/a
542 --- a/a
542 --- a/a
543 +++ b/a
543 +++ b/a
544 @@ -0,0 +1,1 @@
544 @@ -0,0 +1,1 @@
545 +a
545 +a
546 diff --git a/b1 b/b2
546 diff --git a/b1 b/b2
547 rename from b1
547 rename from b1
548 rename to b2
548 rename to b2
549 --- a/b1
549 --- a/b1
550 +++ b/b2
550 +++ b/b2
551 @@ -0,0 +1,1 @@
551 @@ -0,0 +1,1 @@
552 +b
552 +b
553 diff --git a/c1 b/c1
553 diff --git a/c1 b/c1
554 --- a/c1
554 --- a/c1
555 +++ b/c1
555 +++ b/c1
556 @@ -0,0 +1,1 @@
556 @@ -0,0 +1,1 @@
557 +c
557 +c
558 diff --git a/c1 b/c2
558 diff --git a/c1 b/c2
559 copy from c1
559 copy from c1
560 copy to c2
560 copy to c2
561 --- a/c1
561 --- a/c1
562 +++ b/c2
562 +++ b/c2
563 @@ -0,0 +1,1 @@
563 @@ -0,0 +1,1 @@
564 +c
564 +c
565 diff --git a/d b/d
565 diff --git a/d b/d
566 --- a/d
566 --- a/d
567 +++ b/d
567 +++ b/d
568 @@ -1,1 +0,0 @@
568 @@ -1,1 +0,0 @@
569 -d
569 -d
570 $ hg ci -m empty
570 $ hg ci -m empty
571 $ hg export --git tip > empty.diff
571 $ hg export --git tip > empty.diff
572 $ hg up -C 0
572 $ hg up -C 0
573 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
573 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
574 $ hg import empty.diff
574 $ hg import empty.diff
575 applying empty.diff
575 applying empty.diff
576 $ for name in a b1 b2 c1 c2 d; do
576 $ for name in a b1 b2 c1 c2 d; do
577 > echo % $name file
577 > echo % $name file
578 > test -f $name && cat $name
578 > test -f $name && cat $name
579 > done
579 > done
580 % a file
580 % a file
581 a
581 a
582 % b1 file
582 % b1 file
583 % b2 file
583 % b2 file
584 b
584 b
585 % c1 file
585 % c1 file
586 c
586 c
587 % c2 file
587 % c2 file
588 c
588 c
589 % d file
589 % d file
590 $ cd ..
590 $ cd ..
591
591
592
592
593 Test importing a patch ending with a binary file removal
593 Test importing a patch ending with a binary file removal
594
594
595 $ hg init binaryremoval
595 $ hg init binaryremoval
596 $ cd binaryremoval
596 $ cd binaryremoval
597 $ echo a > a
597 $ echo a > a
598 $ $PYTHON -c "file('b', 'wb').write('a\x00b')"
598 $ $PYTHON -c "file('b', 'wb').write('a\x00b')"
599 $ hg ci -Am addall
599 $ hg ci -Am addall
600 adding a
600 adding a
601 adding b
601 adding b
602 $ hg rm a
602 $ hg rm a
603 $ hg rm b
603 $ hg rm b
604 $ hg st
604 $ hg st
605 R a
605 R a
606 R b
606 R b
607 $ hg ci -m remove
607 $ hg ci -m remove
608 $ hg export --git . > remove.diff
608 $ hg export --git . > remove.diff
609 $ cat remove.diff | grep git
609 $ cat remove.diff | grep git
610 diff --git a/a b/a
610 diff --git a/a b/a
611 diff --git a/b b/b
611 diff --git a/b b/b
612 $ hg up -C 0
612 $ hg up -C 0
613 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
613 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
614 $ hg import remove.diff
614 $ hg import remove.diff
615 applying remove.diff
615 applying remove.diff
616 $ hg manifest
616 $ hg manifest
617 $ cd ..
617 $ cd ..
618
618
619
619
620 Issue927: test update+rename with common name
620 Issue927: test update+rename with common name
621
621
622 $ hg init t
622 $ hg init t
623 $ cd t
623 $ cd t
624 $ touch a
624 $ touch a
625 $ hg ci -Am t
625 $ hg ci -Am t
626 adding a
626 adding a
627 $ echo a > a
627 $ echo a > a
628
628
629 Here, bfile.startswith(afile)
629 Here, bfile.startswith(afile)
630
630
631 $ hg copy a a2
631 $ hg copy a a2
632 $ hg ci -m copya
632 $ hg ci -m copya
633 $ hg export --git tip > copy.diff
633 $ hg export --git tip > copy.diff
634 $ hg up -C 0
634 $ hg up -C 0
635 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
635 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
636 $ hg import copy.diff
636 $ hg import copy.diff
637 applying copy.diff
637 applying copy.diff
638
638
639 a should contain an 'a'
639 a should contain an 'a'
640
640
641 $ cat a
641 $ cat a
642 a
642 a
643
643
644 and a2 should have duplicated it
644 and a2 should have duplicated it
645
645
646 $ cat a2
646 $ cat a2
647 a
647 a
648 $ cd ..
648 $ cd ..
649
649
650
650
651 test -p0
651 test -p0
652
652
653 $ hg init p0
653 $ hg init p0
654 $ cd p0
654 $ cd p0
655 $ echo a > a
655 $ echo a > a
656 $ hg ci -Am t
656 $ hg ci -Am t
657 adding a
657 adding a
658 $ hg import -p foo
658 $ hg import -p foo
659 abort: invalid value 'foo' for option -p, expected int
659 abort: invalid value 'foo' for option -p, expected int
660 [255]
660 [255]
661 $ hg import -p0 - << EOF
661 $ hg import -p0 - << EOF
662 > foobar
662 > foobar
663 > --- a Sat Apr 12 22:43:58 2008 -0400
663 > --- a Sat Apr 12 22:43:58 2008 -0400
664 > +++ a Sat Apr 12 22:44:05 2008 -0400
664 > +++ a Sat Apr 12 22:44:05 2008 -0400
665 > @@ -1,1 +1,1 @@
665 > @@ -1,1 +1,1 @@
666 > -a
666 > -a
667 > +bb
667 > +bb
668 > EOF
668 > EOF
669 applying patch from stdin
669 applying patch from stdin
670 $ hg status
670 $ hg status
671 $ cat a
671 $ cat a
672 bb
672 bb
673
674 test --prefix
675
676 $ mkdir -p dir/dir2
677 $ echo b > dir/dir2/b
678 $ hg ci -Am b
679 adding dir/dir2/b
680 $ hg import -p2 --prefix dir - << EOF
681 > foobar
682 > --- drop1/drop2/dir2/b
683 > +++ drop1/drop2/dir2/b
684 > @@ -1,1 +1,1 @@
685 > -b
686 > +cc
687 > EOF
688 applying patch from stdin
689 $ hg status
690 $ cat dir/dir2/b
691 cc
673 $ cd ..
692 $ cd ..
674
693
675
694
676 test paths outside repo root
695 test paths outside repo root
677
696
678 $ mkdir outside
697 $ mkdir outside
679 $ touch outside/foo
698 $ touch outside/foo
680 $ hg init inside
699 $ hg init inside
681 $ cd inside
700 $ cd inside
682 $ hg import - <<EOF
701 $ hg import - <<EOF
683 > diff --git a/a b/b
702 > diff --git a/a b/b
684 > rename from ../outside/foo
703 > rename from ../outside/foo
685 > rename to bar
704 > rename to bar
686 > EOF
705 > EOF
687 applying patch from stdin
706 applying patch from stdin
688 abort: path contains illegal component: ../outside/foo (glob)
707 abort: path contains illegal component: ../outside/foo (glob)
689 [255]
708 [255]
690 $ cd ..
709 $ cd ..
691
710
692
711
693 test import with similarity and git and strip (issue295 et al.)
712 test import with similarity and git and strip (issue295 et al.)
694
713
695 $ hg init sim
714 $ hg init sim
696 $ cd sim
715 $ cd sim
697 $ echo 'this is a test' > a
716 $ echo 'this is a test' > a
698 $ hg ci -Ama
717 $ hg ci -Ama
699 adding a
718 adding a
700 $ cat > ../rename.diff <<EOF
719 $ cat > ../rename.diff <<EOF
701 > diff --git a/foo/a b/foo/a
720 > diff --git a/foo/a b/foo/a
702 > deleted file mode 100644
721 > deleted file mode 100644
703 > --- a/foo/a
722 > --- a/foo/a
704 > +++ /dev/null
723 > +++ /dev/null
705 > @@ -1,1 +0,0 @@
724 > @@ -1,1 +0,0 @@
706 > -this is a test
725 > -this is a test
707 > diff --git a/foo/b b/foo/b
726 > diff --git a/foo/b b/foo/b
708 > new file mode 100644
727 > new file mode 100644
709 > --- /dev/null
728 > --- /dev/null
710 > +++ b/foo/b
729 > +++ b/foo/b
711 > @@ -0,0 +1,2 @@
730 > @@ -0,0 +1,2 @@
712 > +this is a test
731 > +this is a test
713 > +foo
732 > +foo
714 > EOF
733 > EOF
715 $ hg import --no-commit -v -s 1 ../rename.diff -p2
734 $ hg import --no-commit -v -s 1 ../rename.diff -p2
716 applying ../rename.diff
735 applying ../rename.diff
717 patching file a
736 patching file a
718 patching file b
737 patching file b
719 adding b
738 adding b
720 recording removal of a as rename to b (88% similar)
739 recording removal of a as rename to b (88% similar)
721 applied to working directory
740 applied to working directory
722 $ hg st -C
741 $ hg st -C
723 A b
742 A b
724 a
743 a
725 R a
744 R a
726 $ hg revert -a
745 $ hg revert -a
727 undeleting a
746 undeleting a
728 forgetting b
747 forgetting b
729 $ rm b
748 $ rm b
730 $ hg import --no-commit -v -s 100 ../rename.diff -p2
749 $ hg import --no-commit -v -s 100 ../rename.diff -p2
731 applying ../rename.diff
750 applying ../rename.diff
732 patching file a
751 patching file a
733 patching file b
752 patching file b
734 adding b
753 adding b
735 applied to working directory
754 applied to working directory
736 $ hg st -C
755 $ hg st -C
737 A b
756 A b
738 R a
757 R a
739 $ cd ..
758 $ cd ..
740
759
741
760
742 Issue1495: add empty file from the end of patch
761 Issue1495: add empty file from the end of patch
743
762
744 $ hg init addemptyend
763 $ hg init addemptyend
745 $ cd addemptyend
764 $ cd addemptyend
746 $ touch a
765 $ touch a
747 $ hg addremove
766 $ hg addremove
748 adding a
767 adding a
749 $ hg ci -m "commit"
768 $ hg ci -m "commit"
750 $ cat > a.patch <<EOF
769 $ cat > a.patch <<EOF
751 > add a, b
770 > add a, b
752 > diff --git a/a b/a
771 > diff --git a/a b/a
753 > --- a/a
772 > --- a/a
754 > +++ b/a
773 > +++ b/a
755 > @@ -0,0 +1,1 @@
774 > @@ -0,0 +1,1 @@
756 > +a
775 > +a
757 > diff --git a/b b/b
776 > diff --git a/b b/b
758 > new file mode 100644
777 > new file mode 100644
759 > EOF
778 > EOF
760 $ hg import --no-commit a.patch
779 $ hg import --no-commit a.patch
761 applying a.patch
780 applying a.patch
762
781
763 apply a good patch followed by an empty patch (mainly to ensure
782 apply a good patch followed by an empty patch (mainly to ensure
764 that dirstate is *not* updated when import crashes)
783 that dirstate is *not* updated when import crashes)
765 $ hg update -q -C .
784 $ hg update -q -C .
766 $ rm b
785 $ rm b
767 $ touch empty.patch
786 $ touch empty.patch
768 $ hg import a.patch empty.patch
787 $ hg import a.patch empty.patch
769 applying a.patch
788 applying a.patch
770 applying empty.patch
789 applying empty.patch
771 transaction abort!
790 transaction abort!
772 rollback completed
791 rollback completed
773 abort: empty.patch: no diffs found
792 abort: empty.patch: no diffs found
774 [255]
793 [255]
775 $ hg tip --template '{rev} {desc|firstline}\n'
794 $ hg tip --template '{rev} {desc|firstline}\n'
776 0 commit
795 0 commit
777 $ hg -q status
796 $ hg -q status
778 M a
797 M a
779 $ cd ..
798 $ cd ..
780
799
781 create file when source is not /dev/null
800 create file when source is not /dev/null
782
801
783 $ cat > create.patch <<EOF
802 $ cat > create.patch <<EOF
784 > diff -Naur proj-orig/foo proj-new/foo
803 > diff -Naur proj-orig/foo proj-new/foo
785 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
804 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
786 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
805 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
787 > @@ -0,0 +1,1 @@
806 > @@ -0,0 +1,1 @@
788 > +a
807 > +a
789 > EOF
808 > EOF
790
809
791 some people have patches like the following too
810 some people have patches like the following too
792
811
793 $ cat > create2.patch <<EOF
812 $ cat > create2.patch <<EOF
794 > diff -Naur proj-orig/foo proj-new/foo
813 > diff -Naur proj-orig/foo proj-new/foo
795 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
814 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
796 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
815 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
797 > @@ -0,0 +1,1 @@
816 > @@ -0,0 +1,1 @@
798 > +a
817 > +a
799 > EOF
818 > EOF
800 $ hg init oddcreate
819 $ hg init oddcreate
801 $ cd oddcreate
820 $ cd oddcreate
802 $ hg import --no-commit ../create.patch
821 $ hg import --no-commit ../create.patch
803 applying ../create.patch
822 applying ../create.patch
804 $ cat foo
823 $ cat foo
805 a
824 a
806 $ rm foo
825 $ rm foo
807 $ hg revert foo
826 $ hg revert foo
808 $ hg import --no-commit ../create2.patch
827 $ hg import --no-commit ../create2.patch
809 applying ../create2.patch
828 applying ../create2.patch
810 $ cat foo
829 $ cat foo
811 a
830 a
812
831
813 $ cd ..
832 $ cd ..
814
833
815 Issue1859: first line mistaken for email headers
834 Issue1859: first line mistaken for email headers
816
835
817 $ hg init emailconfusion
836 $ hg init emailconfusion
818 $ cd emailconfusion
837 $ cd emailconfusion
819 $ cat > a.patch <<EOF
838 $ cat > a.patch <<EOF
820 > module: summary
839 > module: summary
821 >
840 >
822 > description
841 > description
823 >
842 >
824 >
843 >
825 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
844 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
826 > --- /dev/null
845 > --- /dev/null
827 > +++ b/a
846 > +++ b/a
828 > @@ -0,0 +1,1 @@
847 > @@ -0,0 +1,1 @@
829 > +a
848 > +a
830 > EOF
849 > EOF
831 $ hg import -d '0 0' a.patch
850 $ hg import -d '0 0' a.patch
832 applying a.patch
851 applying a.patch
833 $ hg parents -v
852 $ hg parents -v
834 changeset: 0:5a681217c0ad
853 changeset: 0:5a681217c0ad
835 tag: tip
854 tag: tip
836 user: test
855 user: test
837 date: Thu Jan 01 00:00:00 1970 +0000
856 date: Thu Jan 01 00:00:00 1970 +0000
838 files: a
857 files: a
839 description:
858 description:
840 module: summary
859 module: summary
841
860
842 description
861 description
843
862
844
863
845 $ cd ..
864 $ cd ..
846
865
847
866
848 in commit message
867 in commit message
849
868
850 $ hg init commitconfusion
869 $ hg init commitconfusion
851 $ cd commitconfusion
870 $ cd commitconfusion
852 $ cat > a.patch <<EOF
871 $ cat > a.patch <<EOF
853 > module: summary
872 > module: summary
854 >
873 >
855 > --- description
874 > --- description
856 >
875 >
857 > diff --git a/a b/a
876 > diff --git a/a b/a
858 > new file mode 100644
877 > new file mode 100644
859 > --- /dev/null
878 > --- /dev/null
860 > +++ b/a
879 > +++ b/a
861 > @@ -0,0 +1,1 @@
880 > @@ -0,0 +1,1 @@
862 > +a
881 > +a
863 > EOF
882 > EOF
864 > hg import -d '0 0' a.patch
883 > hg import -d '0 0' a.patch
865 > hg parents -v
884 > hg parents -v
866 > cd ..
885 > cd ..
867 >
886 >
868 > echo '% tricky header splitting'
887 > echo '% tricky header splitting'
869 > cat > trickyheaders.patch <<EOF
888 > cat > trickyheaders.patch <<EOF
870 > From: User A <user@a>
889 > From: User A <user@a>
871 > Subject: [PATCH] from: tricky!
890 > Subject: [PATCH] from: tricky!
872 >
891 >
873 > # HG changeset patch
892 > # HG changeset patch
874 > # User User B
893 > # User User B
875 > # Date 1266264441 18000
894 > # Date 1266264441 18000
876 > # Branch stable
895 > # Branch stable
877 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
896 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
878 > # Parent 0000000000000000000000000000000000000000
897 > # Parent 0000000000000000000000000000000000000000
879 > from: tricky!
898 > from: tricky!
880 >
899 >
881 > That is not a header.
900 > That is not a header.
882 >
901 >
883 > diff -r 000000000000 -r f2be6a1170ac foo
902 > diff -r 000000000000 -r f2be6a1170ac foo
884 > --- /dev/null
903 > --- /dev/null
885 > +++ b/foo
904 > +++ b/foo
886 > @@ -0,0 +1,1 @@
905 > @@ -0,0 +1,1 @@
887 > +foo
906 > +foo
888 > EOF
907 > EOF
889 applying a.patch
908 applying a.patch
890 changeset: 0:f34d9187897d
909 changeset: 0:f34d9187897d
891 tag: tip
910 tag: tip
892 user: test
911 user: test
893 date: Thu Jan 01 00:00:00 1970 +0000
912 date: Thu Jan 01 00:00:00 1970 +0000
894 files: a
913 files: a
895 description:
914 description:
896 module: summary
915 module: summary
897
916
898
917
899 % tricky header splitting
918 % tricky header splitting
900
919
901 $ hg init trickyheaders
920 $ hg init trickyheaders
902 $ cd trickyheaders
921 $ cd trickyheaders
903 $ hg import -d '0 0' ../trickyheaders.patch
922 $ hg import -d '0 0' ../trickyheaders.patch
904 applying ../trickyheaders.patch
923 applying ../trickyheaders.patch
905 $ hg export --git tip
924 $ hg export --git tip
906 # HG changeset patch
925 # HG changeset patch
907 # User User B
926 # User User B
908 # Date 0 0
927 # Date 0 0
909 # Thu Jan 01 00:00:00 1970 +0000
928 # Thu Jan 01 00:00:00 1970 +0000
910 # Node ID eb56ab91903632294ac504838508cb370c0901d2
929 # Node ID eb56ab91903632294ac504838508cb370c0901d2
911 # Parent 0000000000000000000000000000000000000000
930 # Parent 0000000000000000000000000000000000000000
912 from: tricky!
931 from: tricky!
913
932
914 That is not a header.
933 That is not a header.
915
934
916 diff --git a/foo b/foo
935 diff --git a/foo b/foo
917 new file mode 100644
936 new file mode 100644
918 --- /dev/null
937 --- /dev/null
919 +++ b/foo
938 +++ b/foo
920 @@ -0,0 +1,1 @@
939 @@ -0,0 +1,1 @@
921 +foo
940 +foo
922 $ cd ..
941 $ cd ..
923
942
924
943
925 Issue2102: hg export and hg import speak different languages
944 Issue2102: hg export and hg import speak different languages
926
945
927 $ hg init issue2102
946 $ hg init issue2102
928 $ cd issue2102
947 $ cd issue2102
929 $ mkdir -p src/cmd/gc
948 $ mkdir -p src/cmd/gc
930 $ touch src/cmd/gc/mksys.bash
949 $ touch src/cmd/gc/mksys.bash
931 $ hg ci -Am init
950 $ hg ci -Am init
932 adding src/cmd/gc/mksys.bash
951 adding src/cmd/gc/mksys.bash
933 $ hg import - <<EOF
952 $ hg import - <<EOF
934 > # HG changeset patch
953 > # HG changeset patch
935 > # User Rob Pike
954 > # User Rob Pike
936 > # Date 1216685449 25200
955 > # Date 1216685449 25200
937 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
956 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
938 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
957 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
939 > help management of empty pkg and lib directories in perforce
958 > help management of empty pkg and lib directories in perforce
940 >
959 >
941 > R=gri
960 > R=gri
942 > DELTA=4 (4 added, 0 deleted, 0 changed)
961 > DELTA=4 (4 added, 0 deleted, 0 changed)
943 > OCL=13328
962 > OCL=13328
944 > CL=13328
963 > CL=13328
945 >
964 >
946 > diff --git a/lib/place-holder b/lib/place-holder
965 > diff --git a/lib/place-holder b/lib/place-holder
947 > new file mode 100644
966 > new file mode 100644
948 > --- /dev/null
967 > --- /dev/null
949 > +++ b/lib/place-holder
968 > +++ b/lib/place-holder
950 > @@ -0,0 +1,2 @@
969 > @@ -0,0 +1,2 @@
951 > +perforce does not maintain empty directories.
970 > +perforce does not maintain empty directories.
952 > +this file helps.
971 > +this file helps.
953 > diff --git a/pkg/place-holder b/pkg/place-holder
972 > diff --git a/pkg/place-holder b/pkg/place-holder
954 > new file mode 100644
973 > new file mode 100644
955 > --- /dev/null
974 > --- /dev/null
956 > +++ b/pkg/place-holder
975 > +++ b/pkg/place-holder
957 > @@ -0,0 +1,2 @@
976 > @@ -0,0 +1,2 @@
958 > +perforce does not maintain empty directories.
977 > +perforce does not maintain empty directories.
959 > +this file helps.
978 > +this file helps.
960 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
979 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
961 > old mode 100644
980 > old mode 100644
962 > new mode 100755
981 > new mode 100755
963 > EOF
982 > EOF
964 applying patch from stdin
983 applying patch from stdin
965
984
966 #if execbit
985 #if execbit
967
986
968 $ hg sum
987 $ hg sum
969 parent: 1:d59915696727 tip
988 parent: 1:d59915696727 tip
970 help management of empty pkg and lib directories in perforce
989 help management of empty pkg and lib directories in perforce
971 branch: default
990 branch: default
972 commit: (clean)
991 commit: (clean)
973 update: (current)
992 update: (current)
974
993
975 $ hg diff --git -c tip
994 $ hg diff --git -c tip
976 diff --git a/lib/place-holder b/lib/place-holder
995 diff --git a/lib/place-holder b/lib/place-holder
977 new file mode 100644
996 new file mode 100644
978 --- /dev/null
997 --- /dev/null
979 +++ b/lib/place-holder
998 +++ b/lib/place-holder
980 @@ -0,0 +1,2 @@
999 @@ -0,0 +1,2 @@
981 +perforce does not maintain empty directories.
1000 +perforce does not maintain empty directories.
982 +this file helps.
1001 +this file helps.
983 diff --git a/pkg/place-holder b/pkg/place-holder
1002 diff --git a/pkg/place-holder b/pkg/place-holder
984 new file mode 100644
1003 new file mode 100644
985 --- /dev/null
1004 --- /dev/null
986 +++ b/pkg/place-holder
1005 +++ b/pkg/place-holder
987 @@ -0,0 +1,2 @@
1006 @@ -0,0 +1,2 @@
988 +perforce does not maintain empty directories.
1007 +perforce does not maintain empty directories.
989 +this file helps.
1008 +this file helps.
990 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1009 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
991 old mode 100644
1010 old mode 100644
992 new mode 100755
1011 new mode 100755
993
1012
994 #else
1013 #else
995
1014
996 $ hg sum
1015 $ hg sum
997 parent: 1:28f089cc9ccc tip
1016 parent: 1:28f089cc9ccc tip
998 help management of empty pkg and lib directories in perforce
1017 help management of empty pkg and lib directories in perforce
999 branch: default
1018 branch: default
1000 commit: (clean)
1019 commit: (clean)
1001 update: (current)
1020 update: (current)
1002
1021
1003 $ hg diff --git -c tip
1022 $ hg diff --git -c tip
1004 diff --git a/lib/place-holder b/lib/place-holder
1023 diff --git a/lib/place-holder b/lib/place-holder
1005 new file mode 100644
1024 new file mode 100644
1006 --- /dev/null
1025 --- /dev/null
1007 +++ b/lib/place-holder
1026 +++ b/lib/place-holder
1008 @@ -0,0 +1,2 @@
1027 @@ -0,0 +1,2 @@
1009 +perforce does not maintain empty directories.
1028 +perforce does not maintain empty directories.
1010 +this file helps.
1029 +this file helps.
1011 diff --git a/pkg/place-holder b/pkg/place-holder
1030 diff --git a/pkg/place-holder b/pkg/place-holder
1012 new file mode 100644
1031 new file mode 100644
1013 --- /dev/null
1032 --- /dev/null
1014 +++ b/pkg/place-holder
1033 +++ b/pkg/place-holder
1015 @@ -0,0 +1,2 @@
1034 @@ -0,0 +1,2 @@
1016 +perforce does not maintain empty directories.
1035 +perforce does not maintain empty directories.
1017 +this file helps.
1036 +this file helps.
1018
1037
1019 /* The mode change for mksys.bash is missing here, because on platforms */
1038 /* The mode change for mksys.bash is missing here, because on platforms */
1020 /* that don't support execbits, mode changes in patches are ignored when */
1039 /* that don't support execbits, mode changes in patches are ignored when */
1021 /* they are imported. This is obviously also the reason for why the hash */
1040 /* they are imported. This is obviously also the reason for why the hash */
1022 /* in the created changeset is different to the one you see above the */
1041 /* in the created changeset is different to the one you see above the */
1023 /* #else clause */
1042 /* #else clause */
1024
1043
1025 #endif
1044 #endif
1026 $ cd ..
1045 $ cd ..
1027
1046
1028
1047
1029 diff lines looking like headers
1048 diff lines looking like headers
1030
1049
1031 $ hg init difflineslikeheaders
1050 $ hg init difflineslikeheaders
1032 $ cd difflineslikeheaders
1051 $ cd difflineslikeheaders
1033 $ echo a >a
1052 $ echo a >a
1034 $ echo b >b
1053 $ echo b >b
1035 $ echo c >c
1054 $ echo c >c
1036 $ hg ci -Am1
1055 $ hg ci -Am1
1037 adding a
1056 adding a
1038 adding b
1057 adding b
1039 adding c
1058 adding c
1040
1059
1041 $ echo "key: value" >>a
1060 $ echo "key: value" >>a
1042 $ echo "key: value" >>b
1061 $ echo "key: value" >>b
1043 $ echo "foo" >>c
1062 $ echo "foo" >>c
1044 $ hg ci -m2
1063 $ hg ci -m2
1045
1064
1046 $ hg up -C 0
1065 $ hg up -C 0
1047 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1066 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1048 $ hg diff --git -c1 >want
1067 $ hg diff --git -c1 >want
1049 $ hg diff -c1 | hg import --no-commit -
1068 $ hg diff -c1 | hg import --no-commit -
1050 applying patch from stdin
1069 applying patch from stdin
1051 $ hg diff --git >have
1070 $ hg diff --git >have
1052 $ diff want have
1071 $ diff want have
1053 $ cd ..
1072 $ cd ..
1054
1073
1055 import a unified diff with no lines of context (diff -U0)
1074 import a unified diff with no lines of context (diff -U0)
1056
1075
1057 $ hg init diffzero
1076 $ hg init diffzero
1058 $ cd diffzero
1077 $ cd diffzero
1059 $ cat > f << EOF
1078 $ cat > f << EOF
1060 > c2
1079 > c2
1061 > c4
1080 > c4
1062 > c5
1081 > c5
1063 > EOF
1082 > EOF
1064 $ hg commit -Am0
1083 $ hg commit -Am0
1065 adding f
1084 adding f
1066
1085
1067 $ hg import --no-commit - << EOF
1086 $ hg import --no-commit - << EOF
1068 > # HG changeset patch
1087 > # HG changeset patch
1069 > # User test
1088 > # User test
1070 > # Date 0 0
1089 > # Date 0 0
1071 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
1090 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
1072 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
1091 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
1073 > 1
1092 > 1
1074 > diff -r 8679a12a975b -r f4974ab632f3 f
1093 > diff -r 8679a12a975b -r f4974ab632f3 f
1075 > --- a/f Thu Jan 01 00:00:00 1970 +0000
1094 > --- a/f Thu Jan 01 00:00:00 1970 +0000
1076 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
1095 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
1077 > @@ -0,0 +1,1 @@
1096 > @@ -0,0 +1,1 @@
1078 > +c1
1097 > +c1
1079 > @@ -1,0 +3,1 @@
1098 > @@ -1,0 +3,1 @@
1080 > +c3
1099 > +c3
1081 > @@ -3,1 +4,0 @@
1100 > @@ -3,1 +4,0 @@
1082 > -c5
1101 > -c5
1083 > EOF
1102 > EOF
1084 applying patch from stdin
1103 applying patch from stdin
1085
1104
1086 $ cat f
1105 $ cat f
1087 c1
1106 c1
1088 c2
1107 c2
1089 c3
1108 c3
1090 c4
1109 c4
1091
1110
1092 $ cd ..
1111 $ cd ..
1093
1112
1094 no segfault while importing a unified diff which start line is zero but chunk
1113 no segfault while importing a unified diff which start line is zero but chunk
1095 size is non-zero
1114 size is non-zero
1096
1115
1097 $ hg init startlinezero
1116 $ hg init startlinezero
1098 $ cd startlinezero
1117 $ cd startlinezero
1099 $ echo foo > foo
1118 $ echo foo > foo
1100 $ hg commit -Amfoo
1119 $ hg commit -Amfoo
1101 adding foo
1120 adding foo
1102
1121
1103 $ hg import --no-commit - << EOF
1122 $ hg import --no-commit - << EOF
1104 > diff a/foo b/foo
1123 > diff a/foo b/foo
1105 > --- a/foo
1124 > --- a/foo
1106 > +++ b/foo
1125 > +++ b/foo
1107 > @@ -0,1 +0,1 @@
1126 > @@ -0,1 +0,1 @@
1108 > foo
1127 > foo
1109 > EOF
1128 > EOF
1110 applying patch from stdin
1129 applying patch from stdin
1111
1130
1112 $ cd ..
1131 $ cd ..
1113
1132
1114 Test corner case involving fuzz and skew
1133 Test corner case involving fuzz and skew
1115
1134
1116 $ hg init morecornercases
1135 $ hg init morecornercases
1117 $ cd morecornercases
1136 $ cd morecornercases
1118
1137
1119 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1138 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1120 > diff --git a/a b/a
1139 > diff --git a/a b/a
1121 > --- a/a
1140 > --- a/a
1122 > +++ b/a
1141 > +++ b/a
1123 > @@ -1,0 +1,1 @@
1142 > @@ -1,0 +1,1 @@
1124 > +line
1143 > +line
1125 > EOF
1144 > EOF
1126
1145
1127 $ cat > 02-no-context-middle-of-file.diff <<EOF
1146 $ cat > 02-no-context-middle-of-file.diff <<EOF
1128 > diff --git a/a b/a
1147 > diff --git a/a b/a
1129 > --- a/a
1148 > --- a/a
1130 > +++ b/a
1149 > +++ b/a
1131 > @@ -1,1 +1,1 @@
1150 > @@ -1,1 +1,1 @@
1132 > -2
1151 > -2
1133 > +add some skew
1152 > +add some skew
1134 > @@ -2,0 +2,1 @@
1153 > @@ -2,0 +2,1 @@
1135 > +line
1154 > +line
1136 > EOF
1155 > EOF
1137
1156
1138 $ cat > 03-no-context-end-of-file.diff <<EOF
1157 $ cat > 03-no-context-end-of-file.diff <<EOF
1139 > diff --git a/a b/a
1158 > diff --git a/a b/a
1140 > --- a/a
1159 > --- a/a
1141 > +++ b/a
1160 > +++ b/a
1142 > @@ -10,0 +10,1 @@
1161 > @@ -10,0 +10,1 @@
1143 > +line
1162 > +line
1144 > EOF
1163 > EOF
1145
1164
1146 $ cat > 04-middle-of-file-completely-fuzzed.diff <<EOF
1165 $ cat > 04-middle-of-file-completely-fuzzed.diff <<EOF
1147 > diff --git a/a b/a
1166 > diff --git a/a b/a
1148 > --- a/a
1167 > --- a/a
1149 > +++ b/a
1168 > +++ b/a
1150 > @@ -1,1 +1,1 @@
1169 > @@ -1,1 +1,1 @@
1151 > -2
1170 > -2
1152 > +add some skew
1171 > +add some skew
1153 > @@ -2,2 +2,3 @@
1172 > @@ -2,2 +2,3 @@
1154 > not matching, should fuzz
1173 > not matching, should fuzz
1155 > ... a bit
1174 > ... a bit
1156 > +line
1175 > +line
1157 > EOF
1176 > EOF
1158
1177
1159 $ cat > a <<EOF
1178 $ cat > a <<EOF
1160 > 1
1179 > 1
1161 > 2
1180 > 2
1162 > 3
1181 > 3
1163 > 4
1182 > 4
1164 > EOF
1183 > EOF
1165 $ hg ci -Am adda a
1184 $ hg ci -Am adda a
1166 $ for p in *.diff; do
1185 $ for p in *.diff; do
1167 > hg import -v --no-commit $p
1186 > hg import -v --no-commit $p
1168 > cat a
1187 > cat a
1169 > hg revert -aqC a
1188 > hg revert -aqC a
1170 > # patch -p1 < $p
1189 > # patch -p1 < $p
1171 > # cat a
1190 > # cat a
1172 > # hg revert -aC a
1191 > # hg revert -aC a
1173 > done
1192 > done
1174 applying 01-no-context-beginning-of-file.diff
1193 applying 01-no-context-beginning-of-file.diff
1175 patching file a
1194 patching file a
1176 applied to working directory
1195 applied to working directory
1177 1
1196 1
1178 line
1197 line
1179 2
1198 2
1180 3
1199 3
1181 4
1200 4
1182 applying 02-no-context-middle-of-file.diff
1201 applying 02-no-context-middle-of-file.diff
1183 patching file a
1202 patching file a
1184 Hunk #1 succeeded at 2 (offset 1 lines).
1203 Hunk #1 succeeded at 2 (offset 1 lines).
1185 Hunk #2 succeeded at 4 (offset 1 lines).
1204 Hunk #2 succeeded at 4 (offset 1 lines).
1186 applied to working directory
1205 applied to working directory
1187 1
1206 1
1188 add some skew
1207 add some skew
1189 3
1208 3
1190 line
1209 line
1191 4
1210 4
1192 applying 03-no-context-end-of-file.diff
1211 applying 03-no-context-end-of-file.diff
1193 patching file a
1212 patching file a
1194 Hunk #1 succeeded at 5 (offset -6 lines).
1213 Hunk #1 succeeded at 5 (offset -6 lines).
1195 applied to working directory
1214 applied to working directory
1196 1
1215 1
1197 2
1216 2
1198 3
1217 3
1199 4
1218 4
1200 line
1219 line
1201 applying 04-middle-of-file-completely-fuzzed.diff
1220 applying 04-middle-of-file-completely-fuzzed.diff
1202 patching file a
1221 patching file a
1203 Hunk #1 succeeded at 2 (offset 1 lines).
1222 Hunk #1 succeeded at 2 (offset 1 lines).
1204 Hunk #2 succeeded at 5 with fuzz 2 (offset 1 lines).
1223 Hunk #2 succeeded at 5 with fuzz 2 (offset 1 lines).
1205 applied to working directory
1224 applied to working directory
1206 1
1225 1
1207 add some skew
1226 add some skew
1208 3
1227 3
1209 4
1228 4
1210 line
1229 line
1211 $ cd ..
1230 $ cd ..
1212
1231
1213 Test partial application
1232 Test partial application
1214 ------------------------
1233 ------------------------
1215
1234
1216 prepare a stack of patches depending on each other
1235 prepare a stack of patches depending on each other
1217
1236
1218 $ hg init partial
1237 $ hg init partial
1219 $ cd partial
1238 $ cd partial
1220 $ cat << EOF > a
1239 $ cat << EOF > a
1221 > one
1240 > one
1222 > two
1241 > two
1223 > three
1242 > three
1224 > four
1243 > four
1225 > five
1244 > five
1226 > six
1245 > six
1227 > seven
1246 > seven
1228 > EOF
1247 > EOF
1229 $ hg add a
1248 $ hg add a
1230 $ echo 'b' > b
1249 $ echo 'b' > b
1231 $ hg add b
1250 $ hg add b
1232 $ hg commit -m 'initial' -u Babar
1251 $ hg commit -m 'initial' -u Babar
1233 $ cat << EOF > a
1252 $ cat << EOF > a
1234 > one
1253 > one
1235 > two
1254 > two
1236 > 3
1255 > 3
1237 > four
1256 > four
1238 > five
1257 > five
1239 > six
1258 > six
1240 > seven
1259 > seven
1241 > EOF
1260 > EOF
1242 $ hg commit -m 'three' -u Celeste
1261 $ hg commit -m 'three' -u Celeste
1243 $ cat << EOF > a
1262 $ cat << EOF > a
1244 > one
1263 > one
1245 > two
1264 > two
1246 > 3
1265 > 3
1247 > 4
1266 > 4
1248 > five
1267 > five
1249 > six
1268 > six
1250 > seven
1269 > seven
1251 > EOF
1270 > EOF
1252 $ hg commit -m 'four' -u Rataxes
1271 $ hg commit -m 'four' -u Rataxes
1253 $ cat << EOF > a
1272 $ cat << EOF > a
1254 > one
1273 > one
1255 > two
1274 > two
1256 > 3
1275 > 3
1257 > 4
1276 > 4
1258 > 5
1277 > 5
1259 > six
1278 > six
1260 > seven
1279 > seven
1261 > EOF
1280 > EOF
1262 $ echo bb >> b
1281 $ echo bb >> b
1263 $ hg commit -m 'five' -u Arthur
1282 $ hg commit -m 'five' -u Arthur
1264 $ echo 'Babar' > jungle
1283 $ echo 'Babar' > jungle
1265 $ hg add jungle
1284 $ hg add jungle
1266 $ hg ci -m 'jungle' -u Zephir
1285 $ hg ci -m 'jungle' -u Zephir
1267 $ echo 'Celeste' >> jungle
1286 $ echo 'Celeste' >> jungle
1268 $ hg ci -m 'extended jungle' -u Cornelius
1287 $ hg ci -m 'extended jungle' -u Cornelius
1269 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1288 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1270 @ extended jungle [Cornelius] 1: +1/-0
1289 @ extended jungle [Cornelius] 1: +1/-0
1271 |
1290 |
1272 o jungle [Zephir] 1: +1/-0
1291 o jungle [Zephir] 1: +1/-0
1273 |
1292 |
1274 o five [Arthur] 2: +2/-1
1293 o five [Arthur] 2: +2/-1
1275 |
1294 |
1276 o four [Rataxes] 1: +1/-1
1295 o four [Rataxes] 1: +1/-1
1277 |
1296 |
1278 o three [Celeste] 1: +1/-1
1297 o three [Celeste] 1: +1/-1
1279 |
1298 |
1280 o initial [Babar] 2: +8/-0
1299 o initial [Babar] 2: +8/-0
1281
1300
1282
1301
1283 Importing with some success and some errors:
1302 Importing with some success and some errors:
1284
1303
1285 $ hg update --rev 'desc(initial)'
1304 $ hg update --rev 'desc(initial)'
1286 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1305 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1287 $ hg export --rev 'desc(five)' | hg import --partial -
1306 $ hg export --rev 'desc(five)' | hg import --partial -
1288 applying patch from stdin
1307 applying patch from stdin
1289 patching file a
1308 patching file a
1290 Hunk #1 FAILED at 1
1309 Hunk #1 FAILED at 1
1291 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1310 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1292 patch applied partially
1311 patch applied partially
1293 (fix the .rej files and run `hg commit --amend`)
1312 (fix the .rej files and run `hg commit --amend`)
1294 [1]
1313 [1]
1295
1314
1296 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1315 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1297 @ five [Arthur] 1: +1/-0
1316 @ five [Arthur] 1: +1/-0
1298 |
1317 |
1299 | o extended jungle [Cornelius] 1: +1/-0
1318 | o extended jungle [Cornelius] 1: +1/-0
1300 | |
1319 | |
1301 | o jungle [Zephir] 1: +1/-0
1320 | o jungle [Zephir] 1: +1/-0
1302 | |
1321 | |
1303 | o five [Arthur] 2: +2/-1
1322 | o five [Arthur] 2: +2/-1
1304 | |
1323 | |
1305 | o four [Rataxes] 1: +1/-1
1324 | o four [Rataxes] 1: +1/-1
1306 | |
1325 | |
1307 | o three [Celeste] 1: +1/-1
1326 | o three [Celeste] 1: +1/-1
1308 |/
1327 |/
1309 o initial [Babar] 2: +8/-0
1328 o initial [Babar] 2: +8/-0
1310
1329
1311 $ hg export
1330 $ hg export
1312 # HG changeset patch
1331 # HG changeset patch
1313 # User Arthur
1332 # User Arthur
1314 # Date 0 0
1333 # Date 0 0
1315 # Thu Jan 01 00:00:00 1970 +0000
1334 # Thu Jan 01 00:00:00 1970 +0000
1316 # Node ID 26e6446bb2526e2be1037935f5fca2b2706f1509
1335 # Node ID 26e6446bb2526e2be1037935f5fca2b2706f1509
1317 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1336 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1318 five
1337 five
1319
1338
1320 diff -r 8e4f0351909e -r 26e6446bb252 b
1339 diff -r 8e4f0351909e -r 26e6446bb252 b
1321 --- a/b Thu Jan 01 00:00:00 1970 +0000
1340 --- a/b Thu Jan 01 00:00:00 1970 +0000
1322 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1341 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1323 @@ -1,1 +1,2 @@
1342 @@ -1,1 +1,2 @@
1324 b
1343 b
1325 +bb
1344 +bb
1326 $ hg status -c .
1345 $ hg status -c .
1327 C a
1346 C a
1328 C b
1347 C b
1329 $ ls
1348 $ ls
1330 a
1349 a
1331 a.rej
1350 a.rej
1332 b
1351 b
1333
1352
1334 Importing with zero success:
1353 Importing with zero success:
1335
1354
1336 $ hg update --rev 'desc(initial)'
1355 $ hg update --rev 'desc(initial)'
1337 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1356 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1338 $ hg export --rev 'desc(four)' | hg import --partial -
1357 $ hg export --rev 'desc(four)' | hg import --partial -
1339 applying patch from stdin
1358 applying patch from stdin
1340 patching file a
1359 patching file a
1341 Hunk #1 FAILED at 0
1360 Hunk #1 FAILED at 0
1342 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1361 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1343 patch applied partially
1362 patch applied partially
1344 (fix the .rej files and run `hg commit --amend`)
1363 (fix the .rej files and run `hg commit --amend`)
1345 [1]
1364 [1]
1346
1365
1347 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1366 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1348 @ four [Rataxes] 0: +0/-0
1367 @ four [Rataxes] 0: +0/-0
1349 |
1368 |
1350 | o five [Arthur] 1: +1/-0
1369 | o five [Arthur] 1: +1/-0
1351 |/
1370 |/
1352 | o extended jungle [Cornelius] 1: +1/-0
1371 | o extended jungle [Cornelius] 1: +1/-0
1353 | |
1372 | |
1354 | o jungle [Zephir] 1: +1/-0
1373 | o jungle [Zephir] 1: +1/-0
1355 | |
1374 | |
1356 | o five [Arthur] 2: +2/-1
1375 | o five [Arthur] 2: +2/-1
1357 | |
1376 | |
1358 | o four [Rataxes] 1: +1/-1
1377 | o four [Rataxes] 1: +1/-1
1359 | |
1378 | |
1360 | o three [Celeste] 1: +1/-1
1379 | o three [Celeste] 1: +1/-1
1361 |/
1380 |/
1362 o initial [Babar] 2: +8/-0
1381 o initial [Babar] 2: +8/-0
1363
1382
1364 $ hg export
1383 $ hg export
1365 # HG changeset patch
1384 # HG changeset patch
1366 # User Rataxes
1385 # User Rataxes
1367 # Date 0 0
1386 # Date 0 0
1368 # Thu Jan 01 00:00:00 1970 +0000
1387 # Thu Jan 01 00:00:00 1970 +0000
1369 # Node ID cb9b1847a74d9ad52e93becaf14b98dbcc274e1e
1388 # Node ID cb9b1847a74d9ad52e93becaf14b98dbcc274e1e
1370 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1389 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1371 four
1390 four
1372
1391
1373 $ hg status -c .
1392 $ hg status -c .
1374 C a
1393 C a
1375 C b
1394 C b
1376 $ ls
1395 $ ls
1377 a
1396 a
1378 a.rej
1397 a.rej
1379 b
1398 b
1380
1399
1381 Importing with unknown file:
1400 Importing with unknown file:
1382
1401
1383 $ hg update --rev 'desc(initial)'
1402 $ hg update --rev 'desc(initial)'
1384 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1403 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1385 $ hg export --rev 'desc("extended jungle")' | hg import --partial -
1404 $ hg export --rev 'desc("extended jungle")' | hg import --partial -
1386 applying patch from stdin
1405 applying patch from stdin
1387 unable to find 'jungle' for patching
1406 unable to find 'jungle' for patching
1388 1 out of 1 hunks FAILED -- saving rejects to file jungle.rej
1407 1 out of 1 hunks FAILED -- saving rejects to file jungle.rej
1389 patch applied partially
1408 patch applied partially
1390 (fix the .rej files and run `hg commit --amend`)
1409 (fix the .rej files and run `hg commit --amend`)
1391 [1]
1410 [1]
1392
1411
1393 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1412 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1394 @ extended jungle [Cornelius] 0: +0/-0
1413 @ extended jungle [Cornelius] 0: +0/-0
1395 |
1414 |
1396 | o four [Rataxes] 0: +0/-0
1415 | o four [Rataxes] 0: +0/-0
1397 |/
1416 |/
1398 | o five [Arthur] 1: +1/-0
1417 | o five [Arthur] 1: +1/-0
1399 |/
1418 |/
1400 | o extended jungle [Cornelius] 1: +1/-0
1419 | o extended jungle [Cornelius] 1: +1/-0
1401 | |
1420 | |
1402 | o jungle [Zephir] 1: +1/-0
1421 | o jungle [Zephir] 1: +1/-0
1403 | |
1422 | |
1404 | o five [Arthur] 2: +2/-1
1423 | o five [Arthur] 2: +2/-1
1405 | |
1424 | |
1406 | o four [Rataxes] 1: +1/-1
1425 | o four [Rataxes] 1: +1/-1
1407 | |
1426 | |
1408 | o three [Celeste] 1: +1/-1
1427 | o three [Celeste] 1: +1/-1
1409 |/
1428 |/
1410 o initial [Babar] 2: +8/-0
1429 o initial [Babar] 2: +8/-0
1411
1430
1412 $ hg export
1431 $ hg export
1413 # HG changeset patch
1432 # HG changeset patch
1414 # User Cornelius
1433 # User Cornelius
1415 # Date 0 0
1434 # Date 0 0
1416 # Thu Jan 01 00:00:00 1970 +0000
1435 # Thu Jan 01 00:00:00 1970 +0000
1417 # Node ID 1fb1f86bef43c5a75918178f8d23c29fb0a7398d
1436 # Node ID 1fb1f86bef43c5a75918178f8d23c29fb0a7398d
1418 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1437 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1419 extended jungle
1438 extended jungle
1420
1439
1421 $ hg status -c .
1440 $ hg status -c .
1422 C a
1441 C a
1423 C b
1442 C b
1424 $ ls
1443 $ ls
1425 a
1444 a
1426 a.rej
1445 a.rej
1427 b
1446 b
1428 jungle.rej
1447 jungle.rej
1429
1448
1430 Importing multiple failing patches:
1449 Importing multiple failing patches:
1431
1450
1432 $ hg update --rev 'desc(initial)'
1451 $ hg update --rev 'desc(initial)'
1433 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1452 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1434 $ echo 'B' > b # just to make another commit
1453 $ echo 'B' > b # just to make another commit
1435 $ hg commit -m "a new base"
1454 $ hg commit -m "a new base"
1436 created new head
1455 created new head
1437 $ hg export --rev 'desc("four") + desc("extended jungle")' | hg import --partial -
1456 $ hg export --rev 'desc("four") + desc("extended jungle")' | hg import --partial -
1438 applying patch from stdin
1457 applying patch from stdin
1439 patching file a
1458 patching file a
1440 Hunk #1 FAILED at 0
1459 Hunk #1 FAILED at 0
1441 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1460 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1442 patch applied partially
1461 patch applied partially
1443 (fix the .rej files and run `hg commit --amend`)
1462 (fix the .rej files and run `hg commit --amend`)
1444 [1]
1463 [1]
1445 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1464 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1446 @ four [Rataxes] 0: +0/-0
1465 @ four [Rataxes] 0: +0/-0
1447 |
1466 |
1448 o a new base [test] 1: +1/-1
1467 o a new base [test] 1: +1/-1
1449 |
1468 |
1450 | o extended jungle [Cornelius] 0: +0/-0
1469 | o extended jungle [Cornelius] 0: +0/-0
1451 |/
1470 |/
1452 | o four [Rataxes] 0: +0/-0
1471 | o four [Rataxes] 0: +0/-0
1453 |/
1472 |/
1454 | o five [Arthur] 1: +1/-0
1473 | o five [Arthur] 1: +1/-0
1455 |/
1474 |/
1456 | o extended jungle [Cornelius] 1: +1/-0
1475 | o extended jungle [Cornelius] 1: +1/-0
1457 | |
1476 | |
1458 | o jungle [Zephir] 1: +1/-0
1477 | o jungle [Zephir] 1: +1/-0
1459 | |
1478 | |
1460 | o five [Arthur] 2: +2/-1
1479 | o five [Arthur] 2: +2/-1
1461 | |
1480 | |
1462 | o four [Rataxes] 1: +1/-1
1481 | o four [Rataxes] 1: +1/-1
1463 | |
1482 | |
1464 | o three [Celeste] 1: +1/-1
1483 | o three [Celeste] 1: +1/-1
1465 |/
1484 |/
1466 o initial [Babar] 2: +8/-0
1485 o initial [Babar] 2: +8/-0
1467
1486
1468 $ hg export
1487 $ hg export
1469 # HG changeset patch
1488 # HG changeset patch
1470 # User Rataxes
1489 # User Rataxes
1471 # Date 0 0
1490 # Date 0 0
1472 # Thu Jan 01 00:00:00 1970 +0000
1491 # Thu Jan 01 00:00:00 1970 +0000
1473 # Node ID a9d7b6d0ffbb4eb12b7d5939250fcd42e8930a1d
1492 # Node ID a9d7b6d0ffbb4eb12b7d5939250fcd42e8930a1d
1474 # Parent f59f8d2e95a8ca5b1b4ca64320140da85f3b44fd
1493 # Parent f59f8d2e95a8ca5b1b4ca64320140da85f3b44fd
1475 four
1494 four
1476
1495
1477 $ hg status -c .
1496 $ hg status -c .
1478 C a
1497 C a
1479 C b
1498 C b
General Comments 0
You need to be logged in to leave comments. Login now