##// END OF EJS Templates
import: use "getcommiteditor()" instead of explicit editor choice...
FUJIWARA Katsunori -
r21417:308aaeb9 default
parent child Browse files
Show More
@@ -1,2481 +1,2479
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 lock as lockmod
16 import lock as lockmod
17
17
18 def parsealiases(cmd):
18 def parsealiases(cmd):
19 return cmd.lstrip("^").split("|")
19 return cmd.lstrip("^").split("|")
20
20
21 def findpossible(cmd, table, strict=False):
21 def findpossible(cmd, table, strict=False):
22 """
22 """
23 Return cmd -> (aliases, command table entry)
23 Return cmd -> (aliases, command table entry)
24 for each matching command.
24 for each matching command.
25 Return debug commands (or their aliases) only if no normal command matches.
25 Return debug commands (or their aliases) only if no normal command matches.
26 """
26 """
27 choice = {}
27 choice = {}
28 debugchoice = {}
28 debugchoice = {}
29
29
30 if cmd in table:
30 if cmd in table:
31 # short-circuit exact matches, "log" alias beats "^log|history"
31 # short-circuit exact matches, "log" alias beats "^log|history"
32 keys = [cmd]
32 keys = [cmd]
33 else:
33 else:
34 keys = table.keys()
34 keys = table.keys()
35
35
36 for e in keys:
36 for e in keys:
37 aliases = parsealiases(e)
37 aliases = parsealiases(e)
38 found = None
38 found = None
39 if cmd in aliases:
39 if cmd in aliases:
40 found = cmd
40 found = cmd
41 elif not strict:
41 elif not strict:
42 for a in aliases:
42 for a in aliases:
43 if a.startswith(cmd):
43 if a.startswith(cmd):
44 found = a
44 found = a
45 break
45 break
46 if found is not None:
46 if found is not None:
47 if aliases[0].startswith("debug") or found.startswith("debug"):
47 if aliases[0].startswith("debug") or found.startswith("debug"):
48 debugchoice[found] = (aliases, table[e])
48 debugchoice[found] = (aliases, table[e])
49 else:
49 else:
50 choice[found] = (aliases, table[e])
50 choice[found] = (aliases, table[e])
51
51
52 if not choice and debugchoice:
52 if not choice and debugchoice:
53 choice = debugchoice
53 choice = debugchoice
54
54
55 return choice
55 return choice
56
56
57 def findcmd(cmd, table, strict=True):
57 def findcmd(cmd, table, strict=True):
58 """Return (aliases, command table entry) for command string."""
58 """Return (aliases, command table entry) for command string."""
59 choice = findpossible(cmd, table, strict)
59 choice = findpossible(cmd, table, strict)
60
60
61 if cmd in choice:
61 if cmd in choice:
62 return choice[cmd]
62 return choice[cmd]
63
63
64 if len(choice) > 1:
64 if len(choice) > 1:
65 clist = choice.keys()
65 clist = choice.keys()
66 clist.sort()
66 clist.sort()
67 raise error.AmbiguousCommand(cmd, clist)
67 raise error.AmbiguousCommand(cmd, clist)
68
68
69 if choice:
69 if choice:
70 return choice.values()[0]
70 return choice.values()[0]
71
71
72 raise error.UnknownCommand(cmd)
72 raise error.UnknownCommand(cmd)
73
73
74 def findrepo(p):
74 def findrepo(p):
75 while not os.path.isdir(os.path.join(p, ".hg")):
75 while not os.path.isdir(os.path.join(p, ".hg")):
76 oldp, p = p, os.path.dirname(p)
76 oldp, p = p, os.path.dirname(p)
77 if p == oldp:
77 if p == oldp:
78 return None
78 return None
79
79
80 return p
80 return p
81
81
82 def bailifchanged(repo):
82 def bailifchanged(repo):
83 if repo.dirstate.p2() != nullid:
83 if repo.dirstate.p2() != nullid:
84 raise util.Abort(_('outstanding uncommitted merge'))
84 raise util.Abort(_('outstanding uncommitted merge'))
85 modified, added, removed, deleted = repo.status()[:4]
85 modified, added, removed, deleted = repo.status()[:4]
86 if modified or added or removed or deleted:
86 if modified or added or removed or deleted:
87 raise util.Abort(_('uncommitted changes'))
87 raise util.Abort(_('uncommitted changes'))
88 ctx = repo[None]
88 ctx = repo[None]
89 for s in sorted(ctx.substate):
89 for s in sorted(ctx.substate):
90 if ctx.sub(s).dirty():
90 if ctx.sub(s).dirty():
91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92
92
93 def logmessage(ui, opts):
93 def logmessage(ui, opts):
94 """ get the log message according to -m and -l option """
94 """ get the log message according to -m and -l option """
95 message = opts.get('message')
95 message = opts.get('message')
96 logfile = opts.get('logfile')
96 logfile = opts.get('logfile')
97
97
98 if message and logfile:
98 if message and logfile:
99 raise util.Abort(_('options --message and --logfile are mutually '
99 raise util.Abort(_('options --message and --logfile are mutually '
100 'exclusive'))
100 'exclusive'))
101 if not message and logfile:
101 if not message and logfile:
102 try:
102 try:
103 if logfile == '-':
103 if logfile == '-':
104 message = ui.fin.read()
104 message = ui.fin.read()
105 else:
105 else:
106 message = '\n'.join(util.readfile(logfile).splitlines())
106 message = '\n'.join(util.readfile(logfile).splitlines())
107 except IOError, inst:
107 except IOError, inst:
108 raise util.Abort(_("can't read commit message '%s': %s") %
108 raise util.Abort(_("can't read commit message '%s': %s") %
109 (logfile, inst.strerror))
109 (logfile, inst.strerror))
110 return message
110 return message
111
111
112 def getcommiteditor(edit=False, **opts):
112 def getcommiteditor(edit=False, **opts):
113 """get appropriate commit message editor according to '--edit' option"""
113 """get appropriate commit message editor according to '--edit' option"""
114 if edit:
114 if edit:
115 return commitforceeditor
115 return commitforceeditor
116 else:
116 else:
117 return commiteditor
117 return commiteditor
118
118
119 def loglimit(opts):
119 def loglimit(opts):
120 """get the log limit according to option -l/--limit"""
120 """get the log limit according to option -l/--limit"""
121 limit = opts.get('limit')
121 limit = opts.get('limit')
122 if limit:
122 if limit:
123 try:
123 try:
124 limit = int(limit)
124 limit = int(limit)
125 except ValueError:
125 except ValueError:
126 raise util.Abort(_('limit must be a positive integer'))
126 raise util.Abort(_('limit must be a positive integer'))
127 if limit <= 0:
127 if limit <= 0:
128 raise util.Abort(_('limit must be positive'))
128 raise util.Abort(_('limit must be positive'))
129 else:
129 else:
130 limit = None
130 limit = None
131 return limit
131 return limit
132
132
133 def makefilename(repo, pat, node, desc=None,
133 def makefilename(repo, pat, node, desc=None,
134 total=None, seqno=None, revwidth=None, pathname=None):
134 total=None, seqno=None, revwidth=None, pathname=None):
135 node_expander = {
135 node_expander = {
136 'H': lambda: hex(node),
136 'H': lambda: hex(node),
137 'R': lambda: str(repo.changelog.rev(node)),
137 'R': lambda: str(repo.changelog.rev(node)),
138 'h': lambda: short(node),
138 'h': lambda: short(node),
139 'm': lambda: re.sub('[^\w]', '_', str(desc))
139 'm': lambda: re.sub('[^\w]', '_', str(desc))
140 }
140 }
141 expander = {
141 expander = {
142 '%': lambda: '%',
142 '%': lambda: '%',
143 'b': lambda: os.path.basename(repo.root),
143 'b': lambda: os.path.basename(repo.root),
144 }
144 }
145
145
146 try:
146 try:
147 if node:
147 if node:
148 expander.update(node_expander)
148 expander.update(node_expander)
149 if node:
149 if node:
150 expander['r'] = (lambda:
150 expander['r'] = (lambda:
151 str(repo.changelog.rev(node)).zfill(revwidth or 0))
151 str(repo.changelog.rev(node)).zfill(revwidth or 0))
152 if total is not None:
152 if total is not None:
153 expander['N'] = lambda: str(total)
153 expander['N'] = lambda: str(total)
154 if seqno is not None:
154 if seqno is not None:
155 expander['n'] = lambda: str(seqno)
155 expander['n'] = lambda: str(seqno)
156 if total is not None and seqno is not None:
156 if total is not None and seqno is not None:
157 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
157 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
158 if pathname is not None:
158 if pathname is not None:
159 expander['s'] = lambda: os.path.basename(pathname)
159 expander['s'] = lambda: os.path.basename(pathname)
160 expander['d'] = lambda: os.path.dirname(pathname) or '.'
160 expander['d'] = lambda: os.path.dirname(pathname) or '.'
161 expander['p'] = lambda: pathname
161 expander['p'] = lambda: pathname
162
162
163 newname = []
163 newname = []
164 patlen = len(pat)
164 patlen = len(pat)
165 i = 0
165 i = 0
166 while i < patlen:
166 while i < patlen:
167 c = pat[i]
167 c = pat[i]
168 if c == '%':
168 if c == '%':
169 i += 1
169 i += 1
170 c = pat[i]
170 c = pat[i]
171 c = expander[c]()
171 c = expander[c]()
172 newname.append(c)
172 newname.append(c)
173 i += 1
173 i += 1
174 return ''.join(newname)
174 return ''.join(newname)
175 except KeyError, inst:
175 except KeyError, inst:
176 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
176 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
177 inst.args[0])
177 inst.args[0])
178
178
179 def makefileobj(repo, pat, node=None, desc=None, total=None,
179 def makefileobj(repo, pat, node=None, desc=None, total=None,
180 seqno=None, revwidth=None, mode='wb', modemap=None,
180 seqno=None, revwidth=None, mode='wb', modemap=None,
181 pathname=None):
181 pathname=None):
182
182
183 writable = mode not in ('r', 'rb')
183 writable = mode not in ('r', 'rb')
184
184
185 if not pat or pat == '-':
185 if not pat or pat == '-':
186 fp = writable and repo.ui.fout or repo.ui.fin
186 fp = writable and repo.ui.fout or repo.ui.fin
187 if util.safehasattr(fp, 'fileno'):
187 if util.safehasattr(fp, 'fileno'):
188 return os.fdopen(os.dup(fp.fileno()), mode)
188 return os.fdopen(os.dup(fp.fileno()), mode)
189 else:
189 else:
190 # if this fp can't be duped properly, return
190 # if this fp can't be duped properly, return
191 # a dummy object that can be closed
191 # a dummy object that can be closed
192 class wrappedfileobj(object):
192 class wrappedfileobj(object):
193 noop = lambda x: None
193 noop = lambda x: None
194 def __init__(self, f):
194 def __init__(self, f):
195 self.f = f
195 self.f = f
196 def __getattr__(self, attr):
196 def __getattr__(self, attr):
197 if attr == 'close':
197 if attr == 'close':
198 return self.noop
198 return self.noop
199 else:
199 else:
200 return getattr(self.f, attr)
200 return getattr(self.f, attr)
201
201
202 return wrappedfileobj(fp)
202 return wrappedfileobj(fp)
203 if util.safehasattr(pat, 'write') and writable:
203 if util.safehasattr(pat, 'write') and writable:
204 return pat
204 return pat
205 if util.safehasattr(pat, 'read') and 'r' in mode:
205 if util.safehasattr(pat, 'read') and 'r' in mode:
206 return pat
206 return pat
207 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
207 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
208 if modemap is not None:
208 if modemap is not None:
209 mode = modemap.get(fn, mode)
209 mode = modemap.get(fn, mode)
210 if mode == 'wb':
210 if mode == 'wb':
211 modemap[fn] = 'ab'
211 modemap[fn] = 'ab'
212 return open(fn, mode)
212 return open(fn, mode)
213
213
214 def openrevlog(repo, cmd, file_, opts):
214 def openrevlog(repo, cmd, file_, opts):
215 """opens the changelog, manifest, a filelog or a given revlog"""
215 """opens the changelog, manifest, a filelog or a given revlog"""
216 cl = opts['changelog']
216 cl = opts['changelog']
217 mf = opts['manifest']
217 mf = opts['manifest']
218 msg = None
218 msg = None
219 if cl and mf:
219 if cl and mf:
220 msg = _('cannot specify --changelog and --manifest at the same time')
220 msg = _('cannot specify --changelog and --manifest at the same time')
221 elif cl or mf:
221 elif cl or mf:
222 if file_:
222 if file_:
223 msg = _('cannot specify filename with --changelog or --manifest')
223 msg = _('cannot specify filename with --changelog or --manifest')
224 elif not repo:
224 elif not repo:
225 msg = _('cannot specify --changelog or --manifest '
225 msg = _('cannot specify --changelog or --manifest '
226 'without a repository')
226 'without a repository')
227 if msg:
227 if msg:
228 raise util.Abort(msg)
228 raise util.Abort(msg)
229
229
230 r = None
230 r = None
231 if repo:
231 if repo:
232 if cl:
232 if cl:
233 r = repo.unfiltered().changelog
233 r = repo.unfiltered().changelog
234 elif mf:
234 elif mf:
235 r = repo.manifest
235 r = repo.manifest
236 elif file_:
236 elif file_:
237 filelog = repo.file(file_)
237 filelog = repo.file(file_)
238 if len(filelog):
238 if len(filelog):
239 r = filelog
239 r = filelog
240 if not r:
240 if not r:
241 if not file_:
241 if not file_:
242 raise error.CommandError(cmd, _('invalid arguments'))
242 raise error.CommandError(cmd, _('invalid arguments'))
243 if not os.path.isfile(file_):
243 if not os.path.isfile(file_):
244 raise util.Abort(_("revlog '%s' not found") % file_)
244 raise util.Abort(_("revlog '%s' not found") % file_)
245 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
245 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
246 file_[:-2] + ".i")
246 file_[:-2] + ".i")
247 return r
247 return r
248
248
249 def copy(ui, repo, pats, opts, rename=False):
249 def copy(ui, repo, pats, opts, rename=False):
250 # called with the repo lock held
250 # called with the repo lock held
251 #
251 #
252 # hgsep => pathname that uses "/" to separate directories
252 # hgsep => pathname that uses "/" to separate directories
253 # ossep => pathname that uses os.sep to separate directories
253 # ossep => pathname that uses os.sep to separate directories
254 cwd = repo.getcwd()
254 cwd = repo.getcwd()
255 targets = {}
255 targets = {}
256 after = opts.get("after")
256 after = opts.get("after")
257 dryrun = opts.get("dry_run")
257 dryrun = opts.get("dry_run")
258 wctx = repo[None]
258 wctx = repo[None]
259
259
260 def walkpat(pat):
260 def walkpat(pat):
261 srcs = []
261 srcs = []
262 badstates = after and '?' or '?r'
262 badstates = after and '?' or '?r'
263 m = scmutil.match(repo[None], [pat], opts, globbed=True)
263 m = scmutil.match(repo[None], [pat], opts, globbed=True)
264 for abs in repo.walk(m):
264 for abs in repo.walk(m):
265 state = repo.dirstate[abs]
265 state = repo.dirstate[abs]
266 rel = m.rel(abs)
266 rel = m.rel(abs)
267 exact = m.exact(abs)
267 exact = m.exact(abs)
268 if state in badstates:
268 if state in badstates:
269 if exact and state == '?':
269 if exact and state == '?':
270 ui.warn(_('%s: not copying - file is not managed\n') % rel)
270 ui.warn(_('%s: not copying - file is not managed\n') % rel)
271 if exact and state == 'r':
271 if exact and state == 'r':
272 ui.warn(_('%s: not copying - file has been marked for'
272 ui.warn(_('%s: not copying - file has been marked for'
273 ' remove\n') % rel)
273 ' remove\n') % rel)
274 continue
274 continue
275 # abs: hgsep
275 # abs: hgsep
276 # rel: ossep
276 # rel: ossep
277 srcs.append((abs, rel, exact))
277 srcs.append((abs, rel, exact))
278 return srcs
278 return srcs
279
279
280 # abssrc: hgsep
280 # abssrc: hgsep
281 # relsrc: ossep
281 # relsrc: ossep
282 # otarget: ossep
282 # otarget: ossep
283 def copyfile(abssrc, relsrc, otarget, exact):
283 def copyfile(abssrc, relsrc, otarget, exact):
284 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
284 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
285 if '/' in abstarget:
285 if '/' in abstarget:
286 # We cannot normalize abstarget itself, this would prevent
286 # We cannot normalize abstarget itself, this would prevent
287 # case only renames, like a => A.
287 # case only renames, like a => A.
288 abspath, absname = abstarget.rsplit('/', 1)
288 abspath, absname = abstarget.rsplit('/', 1)
289 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
289 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
290 reltarget = repo.pathto(abstarget, cwd)
290 reltarget = repo.pathto(abstarget, cwd)
291 target = repo.wjoin(abstarget)
291 target = repo.wjoin(abstarget)
292 src = repo.wjoin(abssrc)
292 src = repo.wjoin(abssrc)
293 state = repo.dirstate[abstarget]
293 state = repo.dirstate[abstarget]
294
294
295 scmutil.checkportable(ui, abstarget)
295 scmutil.checkportable(ui, abstarget)
296
296
297 # check for collisions
297 # check for collisions
298 prevsrc = targets.get(abstarget)
298 prevsrc = targets.get(abstarget)
299 if prevsrc is not None:
299 if prevsrc is not None:
300 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
300 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
301 (reltarget, repo.pathto(abssrc, cwd),
301 (reltarget, repo.pathto(abssrc, cwd),
302 repo.pathto(prevsrc, cwd)))
302 repo.pathto(prevsrc, cwd)))
303 return
303 return
304
304
305 # check for overwrites
305 # check for overwrites
306 exists = os.path.lexists(target)
306 exists = os.path.lexists(target)
307 samefile = False
307 samefile = False
308 if exists and abssrc != abstarget:
308 if exists and abssrc != abstarget:
309 if (repo.dirstate.normalize(abssrc) ==
309 if (repo.dirstate.normalize(abssrc) ==
310 repo.dirstate.normalize(abstarget)):
310 repo.dirstate.normalize(abstarget)):
311 if not rename:
311 if not rename:
312 ui.warn(_("%s: can't copy - same file\n") % reltarget)
312 ui.warn(_("%s: can't copy - same file\n") % reltarget)
313 return
313 return
314 exists = False
314 exists = False
315 samefile = True
315 samefile = True
316
316
317 if not after and exists or after and state in 'mn':
317 if not after and exists or after and state in 'mn':
318 if not opts['force']:
318 if not opts['force']:
319 ui.warn(_('%s: not overwriting - file exists\n') %
319 ui.warn(_('%s: not overwriting - file exists\n') %
320 reltarget)
320 reltarget)
321 return
321 return
322
322
323 if after:
323 if after:
324 if not exists:
324 if not exists:
325 if rename:
325 if rename:
326 ui.warn(_('%s: not recording move - %s does not exist\n') %
326 ui.warn(_('%s: not recording move - %s does not exist\n') %
327 (relsrc, reltarget))
327 (relsrc, reltarget))
328 else:
328 else:
329 ui.warn(_('%s: not recording copy - %s does not exist\n') %
329 ui.warn(_('%s: not recording copy - %s does not exist\n') %
330 (relsrc, reltarget))
330 (relsrc, reltarget))
331 return
331 return
332 elif not dryrun:
332 elif not dryrun:
333 try:
333 try:
334 if exists:
334 if exists:
335 os.unlink(target)
335 os.unlink(target)
336 targetdir = os.path.dirname(target) or '.'
336 targetdir = os.path.dirname(target) or '.'
337 if not os.path.isdir(targetdir):
337 if not os.path.isdir(targetdir):
338 os.makedirs(targetdir)
338 os.makedirs(targetdir)
339 if samefile:
339 if samefile:
340 tmp = target + "~hgrename"
340 tmp = target + "~hgrename"
341 os.rename(src, tmp)
341 os.rename(src, tmp)
342 os.rename(tmp, target)
342 os.rename(tmp, target)
343 else:
343 else:
344 util.copyfile(src, target)
344 util.copyfile(src, target)
345 srcexists = True
345 srcexists = True
346 except IOError, inst:
346 except IOError, inst:
347 if inst.errno == errno.ENOENT:
347 if inst.errno == errno.ENOENT:
348 ui.warn(_('%s: deleted in working copy\n') % relsrc)
348 ui.warn(_('%s: deleted in working copy\n') % relsrc)
349 srcexists = False
349 srcexists = False
350 else:
350 else:
351 ui.warn(_('%s: cannot copy - %s\n') %
351 ui.warn(_('%s: cannot copy - %s\n') %
352 (relsrc, inst.strerror))
352 (relsrc, inst.strerror))
353 return True # report a failure
353 return True # report a failure
354
354
355 if ui.verbose or not exact:
355 if ui.verbose or not exact:
356 if rename:
356 if rename:
357 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
357 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
358 else:
358 else:
359 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
359 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
360
360
361 targets[abstarget] = abssrc
361 targets[abstarget] = abssrc
362
362
363 # fix up dirstate
363 # fix up dirstate
364 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
364 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
365 dryrun=dryrun, cwd=cwd)
365 dryrun=dryrun, cwd=cwd)
366 if rename and not dryrun:
366 if rename and not dryrun:
367 if not after and srcexists and not samefile:
367 if not after and srcexists and not samefile:
368 util.unlinkpath(repo.wjoin(abssrc))
368 util.unlinkpath(repo.wjoin(abssrc))
369 wctx.forget([abssrc])
369 wctx.forget([abssrc])
370
370
371 # pat: ossep
371 # pat: ossep
372 # dest ossep
372 # dest ossep
373 # srcs: list of (hgsep, hgsep, ossep, bool)
373 # srcs: list of (hgsep, hgsep, ossep, bool)
374 # return: function that takes hgsep and returns ossep
374 # return: function that takes hgsep and returns ossep
375 def targetpathfn(pat, dest, srcs):
375 def targetpathfn(pat, dest, srcs):
376 if os.path.isdir(pat):
376 if os.path.isdir(pat):
377 abspfx = pathutil.canonpath(repo.root, cwd, pat)
377 abspfx = pathutil.canonpath(repo.root, cwd, pat)
378 abspfx = util.localpath(abspfx)
378 abspfx = util.localpath(abspfx)
379 if destdirexists:
379 if destdirexists:
380 striplen = len(os.path.split(abspfx)[0])
380 striplen = len(os.path.split(abspfx)[0])
381 else:
381 else:
382 striplen = len(abspfx)
382 striplen = len(abspfx)
383 if striplen:
383 if striplen:
384 striplen += len(os.sep)
384 striplen += len(os.sep)
385 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
385 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
386 elif destdirexists:
386 elif destdirexists:
387 res = lambda p: os.path.join(dest,
387 res = lambda p: os.path.join(dest,
388 os.path.basename(util.localpath(p)))
388 os.path.basename(util.localpath(p)))
389 else:
389 else:
390 res = lambda p: dest
390 res = lambda p: dest
391 return res
391 return res
392
392
393 # pat: ossep
393 # pat: ossep
394 # dest ossep
394 # dest ossep
395 # srcs: list of (hgsep, hgsep, ossep, bool)
395 # srcs: list of (hgsep, hgsep, ossep, bool)
396 # return: function that takes hgsep and returns ossep
396 # return: function that takes hgsep and returns ossep
397 def targetpathafterfn(pat, dest, srcs):
397 def targetpathafterfn(pat, dest, srcs):
398 if matchmod.patkind(pat):
398 if matchmod.patkind(pat):
399 # a mercurial pattern
399 # a mercurial pattern
400 res = lambda p: os.path.join(dest,
400 res = lambda p: os.path.join(dest,
401 os.path.basename(util.localpath(p)))
401 os.path.basename(util.localpath(p)))
402 else:
402 else:
403 abspfx = pathutil.canonpath(repo.root, cwd, pat)
403 abspfx = pathutil.canonpath(repo.root, cwd, pat)
404 if len(abspfx) < len(srcs[0][0]):
404 if len(abspfx) < len(srcs[0][0]):
405 # A directory. Either the target path contains the last
405 # A directory. Either the target path contains the last
406 # component of the source path or it does not.
406 # component of the source path or it does not.
407 def evalpath(striplen):
407 def evalpath(striplen):
408 score = 0
408 score = 0
409 for s in srcs:
409 for s in srcs:
410 t = os.path.join(dest, util.localpath(s[0])[striplen:])
410 t = os.path.join(dest, util.localpath(s[0])[striplen:])
411 if os.path.lexists(t):
411 if os.path.lexists(t):
412 score += 1
412 score += 1
413 return score
413 return score
414
414
415 abspfx = util.localpath(abspfx)
415 abspfx = util.localpath(abspfx)
416 striplen = len(abspfx)
416 striplen = len(abspfx)
417 if striplen:
417 if striplen:
418 striplen += len(os.sep)
418 striplen += len(os.sep)
419 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
419 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
420 score = evalpath(striplen)
420 score = evalpath(striplen)
421 striplen1 = len(os.path.split(abspfx)[0])
421 striplen1 = len(os.path.split(abspfx)[0])
422 if striplen1:
422 if striplen1:
423 striplen1 += len(os.sep)
423 striplen1 += len(os.sep)
424 if evalpath(striplen1) > score:
424 if evalpath(striplen1) > score:
425 striplen = striplen1
425 striplen = striplen1
426 res = lambda p: os.path.join(dest,
426 res = lambda p: os.path.join(dest,
427 util.localpath(p)[striplen:])
427 util.localpath(p)[striplen:])
428 else:
428 else:
429 # a file
429 # a file
430 if destdirexists:
430 if 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
437
438 pats = scmutil.expandpats(pats)
438 pats = scmutil.expandpats(pats)
439 if not pats:
439 if not pats:
440 raise util.Abort(_('no source or destination specified'))
440 raise util.Abort(_('no source or destination specified'))
441 if len(pats) == 1:
441 if len(pats) == 1:
442 raise util.Abort(_('no destination specified'))
442 raise util.Abort(_('no destination specified'))
443 dest = pats.pop()
443 dest = pats.pop()
444 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
444 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
445 if not destdirexists:
445 if not destdirexists:
446 if len(pats) > 1 or matchmod.patkind(pats[0]):
446 if len(pats) > 1 or matchmod.patkind(pats[0]):
447 raise util.Abort(_('with multiple sources, destination must be an '
447 raise util.Abort(_('with multiple sources, destination must be an '
448 'existing directory'))
448 'existing directory'))
449 if util.endswithsep(dest):
449 if util.endswithsep(dest):
450 raise util.Abort(_('destination %s is not a directory') % dest)
450 raise util.Abort(_('destination %s is not a directory') % dest)
451
451
452 tfn = targetpathfn
452 tfn = targetpathfn
453 if after:
453 if after:
454 tfn = targetpathafterfn
454 tfn = targetpathafterfn
455 copylist = []
455 copylist = []
456 for pat in pats:
456 for pat in pats:
457 srcs = walkpat(pat)
457 srcs = walkpat(pat)
458 if not srcs:
458 if not srcs:
459 continue
459 continue
460 copylist.append((tfn(pat, dest, srcs), srcs))
460 copylist.append((tfn(pat, dest, srcs), srcs))
461 if not copylist:
461 if not copylist:
462 raise util.Abort(_('no files to copy'))
462 raise util.Abort(_('no files to copy'))
463
463
464 errors = 0
464 errors = 0
465 for targetpath, srcs in copylist:
465 for targetpath, srcs in copylist:
466 for abssrc, relsrc, exact in srcs:
466 for abssrc, relsrc, exact in srcs:
467 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
467 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
468 errors += 1
468 errors += 1
469
469
470 if errors:
470 if errors:
471 ui.warn(_('(consider using --after)\n'))
471 ui.warn(_('(consider using --after)\n'))
472
472
473 return errors != 0
473 return errors != 0
474
474
475 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
475 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
476 runargs=None, appendpid=False):
476 runargs=None, appendpid=False):
477 '''Run a command as a service.'''
477 '''Run a command as a service.'''
478
478
479 def writepid(pid):
479 def writepid(pid):
480 if opts['pid_file']:
480 if opts['pid_file']:
481 mode = appendpid and 'a' or 'w'
481 mode = appendpid and 'a' or 'w'
482 fp = open(opts['pid_file'], mode)
482 fp = open(opts['pid_file'], mode)
483 fp.write(str(pid) + '\n')
483 fp.write(str(pid) + '\n')
484 fp.close()
484 fp.close()
485
485
486 if opts['daemon'] and not opts['daemon_pipefds']:
486 if opts['daemon'] and not opts['daemon_pipefds']:
487 # Signal child process startup with file removal
487 # Signal child process startup with file removal
488 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
488 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
489 os.close(lockfd)
489 os.close(lockfd)
490 try:
490 try:
491 if not runargs:
491 if not runargs:
492 runargs = util.hgcmd() + sys.argv[1:]
492 runargs = util.hgcmd() + sys.argv[1:]
493 runargs.append('--daemon-pipefds=%s' % lockpath)
493 runargs.append('--daemon-pipefds=%s' % lockpath)
494 # Don't pass --cwd to the child process, because we've already
494 # Don't pass --cwd to the child process, because we've already
495 # changed directory.
495 # changed directory.
496 for i in xrange(1, len(runargs)):
496 for i in xrange(1, len(runargs)):
497 if runargs[i].startswith('--cwd='):
497 if runargs[i].startswith('--cwd='):
498 del runargs[i]
498 del runargs[i]
499 break
499 break
500 elif runargs[i].startswith('--cwd'):
500 elif runargs[i].startswith('--cwd'):
501 del runargs[i:i + 2]
501 del runargs[i:i + 2]
502 break
502 break
503 def condfn():
503 def condfn():
504 return not os.path.exists(lockpath)
504 return not os.path.exists(lockpath)
505 pid = util.rundetached(runargs, condfn)
505 pid = util.rundetached(runargs, condfn)
506 if pid < 0:
506 if pid < 0:
507 raise util.Abort(_('child process failed to start'))
507 raise util.Abort(_('child process failed to start'))
508 writepid(pid)
508 writepid(pid)
509 finally:
509 finally:
510 try:
510 try:
511 os.unlink(lockpath)
511 os.unlink(lockpath)
512 except OSError, e:
512 except OSError, e:
513 if e.errno != errno.ENOENT:
513 if e.errno != errno.ENOENT:
514 raise
514 raise
515 if parentfn:
515 if parentfn:
516 return parentfn(pid)
516 return parentfn(pid)
517 else:
517 else:
518 return
518 return
519
519
520 if initfn:
520 if initfn:
521 initfn()
521 initfn()
522
522
523 if not opts['daemon']:
523 if not opts['daemon']:
524 writepid(os.getpid())
524 writepid(os.getpid())
525
525
526 if opts['daemon_pipefds']:
526 if opts['daemon_pipefds']:
527 lockpath = opts['daemon_pipefds']
527 lockpath = opts['daemon_pipefds']
528 try:
528 try:
529 os.setsid()
529 os.setsid()
530 except AttributeError:
530 except AttributeError:
531 pass
531 pass
532 os.unlink(lockpath)
532 os.unlink(lockpath)
533 util.hidewindow()
533 util.hidewindow()
534 sys.stdout.flush()
534 sys.stdout.flush()
535 sys.stderr.flush()
535 sys.stderr.flush()
536
536
537 nullfd = os.open(os.devnull, os.O_RDWR)
537 nullfd = os.open(os.devnull, os.O_RDWR)
538 logfilefd = nullfd
538 logfilefd = nullfd
539 if logfile:
539 if logfile:
540 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
540 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
541 os.dup2(nullfd, 0)
541 os.dup2(nullfd, 0)
542 os.dup2(logfilefd, 1)
542 os.dup2(logfilefd, 1)
543 os.dup2(logfilefd, 2)
543 os.dup2(logfilefd, 2)
544 if nullfd not in (0, 1, 2):
544 if nullfd not in (0, 1, 2):
545 os.close(nullfd)
545 os.close(nullfd)
546 if logfile and logfilefd not in (0, 1, 2):
546 if logfile and logfilefd not in (0, 1, 2):
547 os.close(logfilefd)
547 os.close(logfilefd)
548
548
549 if runfn:
549 if runfn:
550 return runfn()
550 return runfn()
551
551
552 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
552 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
553 """Utility function used by commands.import to import a single patch
553 """Utility function used by commands.import to import a single patch
554
554
555 This function is explicitly defined here to help the evolve extension to
555 This function is explicitly defined here to help the evolve extension to
556 wrap this part of the import logic.
556 wrap this part of the import logic.
557
557
558 The API is currently a bit ugly because it a simple code translation from
558 The API is currently a bit ugly because it a simple code translation from
559 the import command. Feel free to make it better.
559 the import command. Feel free to make it better.
560
560
561 :hunk: a patch (as a binary string)
561 :hunk: a patch (as a binary string)
562 :parents: nodes that will be parent of the created commit
562 :parents: nodes that will be parent of the created commit
563 :opts: the full dict of option passed to the import command
563 :opts: the full dict of option passed to the import command
564 :msgs: list to save commit message to.
564 :msgs: list to save commit message to.
565 (used in case we need to save it when failing)
565 (used in case we need to save it when failing)
566 :updatefunc: a function that update a repo to a given node
566 :updatefunc: a function that update a repo to a given node
567 updatefunc(<repo>, <node>)
567 updatefunc(<repo>, <node>)
568 """
568 """
569 tmpname, message, user, date, branch, nodeid, p1, p2 = \
569 tmpname, message, user, date, branch, nodeid, p1, p2 = \
570 patch.extract(ui, hunk)
570 patch.extract(ui, hunk)
571
571
572 editor = commiteditor
572 editor = getcommiteditor(**opts)
573 if opts.get('edit'):
574 editor = commitforceeditor
575 update = not opts.get('bypass')
573 update = not opts.get('bypass')
576 strip = opts["strip"]
574 strip = opts["strip"]
577 sim = float(opts.get('similarity') or 0)
575 sim = float(opts.get('similarity') or 0)
578 if not tmpname:
576 if not tmpname:
579 return (None, None)
577 return (None, None)
580 msg = _('applied to working directory')
578 msg = _('applied to working directory')
581
579
582 try:
580 try:
583 cmdline_message = logmessage(ui, opts)
581 cmdline_message = logmessage(ui, opts)
584 if cmdline_message:
582 if cmdline_message:
585 # pickup the cmdline msg
583 # pickup the cmdline msg
586 message = cmdline_message
584 message = cmdline_message
587 elif message:
585 elif message:
588 # pickup the patch msg
586 # pickup the patch msg
589 message = message.strip()
587 message = message.strip()
590 else:
588 else:
591 # launch the editor
589 # launch the editor
592 message = None
590 message = None
593 ui.debug('message:\n%s\n' % message)
591 ui.debug('message:\n%s\n' % message)
594
592
595 if len(parents) == 1:
593 if len(parents) == 1:
596 parents.append(repo[nullid])
594 parents.append(repo[nullid])
597 if opts.get('exact'):
595 if opts.get('exact'):
598 if not nodeid or not p1:
596 if not nodeid or not p1:
599 raise util.Abort(_('not a Mercurial patch'))
597 raise util.Abort(_('not a Mercurial patch'))
600 p1 = repo[p1]
598 p1 = repo[p1]
601 p2 = repo[p2 or nullid]
599 p2 = repo[p2 or nullid]
602 elif p2:
600 elif p2:
603 try:
601 try:
604 p1 = repo[p1]
602 p1 = repo[p1]
605 p2 = repo[p2]
603 p2 = repo[p2]
606 # Without any options, consider p2 only if the
604 # Without any options, consider p2 only if the
607 # patch is being applied on top of the recorded
605 # patch is being applied on top of the recorded
608 # first parent.
606 # first parent.
609 if p1 != parents[0]:
607 if p1 != parents[0]:
610 p1 = parents[0]
608 p1 = parents[0]
611 p2 = repo[nullid]
609 p2 = repo[nullid]
612 except error.RepoError:
610 except error.RepoError:
613 p1, p2 = parents
611 p1, p2 = parents
614 else:
612 else:
615 p1, p2 = parents
613 p1, p2 = parents
616
614
617 n = None
615 n = None
618 if update:
616 if update:
619 if p1 != parents[0]:
617 if p1 != parents[0]:
620 updatefunc(repo, p1.node())
618 updatefunc(repo, p1.node())
621 if p2 != parents[1]:
619 if p2 != parents[1]:
622 repo.setparents(p1.node(), p2.node())
620 repo.setparents(p1.node(), p2.node())
623
621
624 if opts.get('exact') or opts.get('import_branch'):
622 if opts.get('exact') or opts.get('import_branch'):
625 repo.dirstate.setbranch(branch or 'default')
623 repo.dirstate.setbranch(branch or 'default')
626
624
627 files = set()
625 files = set()
628 patch.patch(ui, repo, tmpname, strip=strip, files=files,
626 patch.patch(ui, repo, tmpname, strip=strip, files=files,
629 eolmode=None, similarity=sim / 100.0)
627 eolmode=None, similarity=sim / 100.0)
630 files = list(files)
628 files = list(files)
631 if opts.get('no_commit'):
629 if opts.get('no_commit'):
632 if message:
630 if message:
633 msgs.append(message)
631 msgs.append(message)
634 else:
632 else:
635 if opts.get('exact') or p2:
633 if opts.get('exact') or p2:
636 # If you got here, you either use --force and know what
634 # If you got here, you either use --force and know what
637 # you are doing or used --exact or a merge patch while
635 # you are doing or used --exact or a merge patch while
638 # being updated to its first parent.
636 # being updated to its first parent.
639 m = None
637 m = None
640 else:
638 else:
641 m = scmutil.matchfiles(repo, files or [])
639 m = scmutil.matchfiles(repo, files or [])
642 n = repo.commit(message, opts.get('user') or user,
640 n = repo.commit(message, opts.get('user') or user,
643 opts.get('date') or date, match=m,
641 opts.get('date') or date, match=m,
644 editor=editor)
642 editor=editor)
645 else:
643 else:
646 if opts.get('exact') or opts.get('import_branch'):
644 if opts.get('exact') or opts.get('import_branch'):
647 branch = branch or 'default'
645 branch = branch or 'default'
648 else:
646 else:
649 branch = p1.branch()
647 branch = p1.branch()
650 store = patch.filestore()
648 store = patch.filestore()
651 try:
649 try:
652 files = set()
650 files = set()
653 try:
651 try:
654 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
652 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
655 files, eolmode=None)
653 files, eolmode=None)
656 except patch.PatchError, e:
654 except patch.PatchError, e:
657 raise util.Abort(str(e))
655 raise util.Abort(str(e))
658 memctx = context.makememctx(repo, (p1.node(), p2.node()),
656 memctx = context.makememctx(repo, (p1.node(), p2.node()),
659 message,
657 message,
660 opts.get('user') or user,
658 opts.get('user') or user,
661 opts.get('date') or date,
659 opts.get('date') or date,
662 branch, files, store,
660 branch, files, store,
663 editor=commiteditor)
661 editor=getcommiteditor())
664 n = memctx.commit()
662 n = memctx.commit()
665 finally:
663 finally:
666 store.close()
664 store.close()
667 if opts.get('exact') and hex(n) != nodeid:
665 if opts.get('exact') and hex(n) != nodeid:
668 raise util.Abort(_('patch is damaged or loses information'))
666 raise util.Abort(_('patch is damaged or loses information'))
669 if n:
667 if n:
670 # i18n: refers to a short changeset id
668 # i18n: refers to a short changeset id
671 msg = _('created %s') % short(n)
669 msg = _('created %s') % short(n)
672 return (msg, n)
670 return (msg, n)
673 finally:
671 finally:
674 os.unlink(tmpname)
672 os.unlink(tmpname)
675
673
676 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
674 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
677 opts=None):
675 opts=None):
678 '''export changesets as hg patches.'''
676 '''export changesets as hg patches.'''
679
677
680 total = len(revs)
678 total = len(revs)
681 revwidth = max([len(str(rev)) for rev in revs])
679 revwidth = max([len(str(rev)) for rev in revs])
682 filemode = {}
680 filemode = {}
683
681
684 def single(rev, seqno, fp):
682 def single(rev, seqno, fp):
685 ctx = repo[rev]
683 ctx = repo[rev]
686 node = ctx.node()
684 node = ctx.node()
687 parents = [p.node() for p in ctx.parents() if p]
685 parents = [p.node() for p in ctx.parents() if p]
688 branch = ctx.branch()
686 branch = ctx.branch()
689 if switch_parent:
687 if switch_parent:
690 parents.reverse()
688 parents.reverse()
691 prev = (parents and parents[0]) or nullid
689 prev = (parents and parents[0]) or nullid
692
690
693 shouldclose = False
691 shouldclose = False
694 if not fp and len(template) > 0:
692 if not fp and len(template) > 0:
695 desc_lines = ctx.description().rstrip().split('\n')
693 desc_lines = ctx.description().rstrip().split('\n')
696 desc = desc_lines[0] #Commit always has a first line.
694 desc = desc_lines[0] #Commit always has a first line.
697 fp = makefileobj(repo, template, node, desc=desc, total=total,
695 fp = makefileobj(repo, template, node, desc=desc, total=total,
698 seqno=seqno, revwidth=revwidth, mode='wb',
696 seqno=seqno, revwidth=revwidth, mode='wb',
699 modemap=filemode)
697 modemap=filemode)
700 if fp != template:
698 if fp != template:
701 shouldclose = True
699 shouldclose = True
702 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
700 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
703 repo.ui.note("%s\n" % fp.name)
701 repo.ui.note("%s\n" % fp.name)
704
702
705 if not fp:
703 if not fp:
706 write = repo.ui.write
704 write = repo.ui.write
707 else:
705 else:
708 def write(s, **kw):
706 def write(s, **kw):
709 fp.write(s)
707 fp.write(s)
710
708
711
709
712 write("# HG changeset patch\n")
710 write("# HG changeset patch\n")
713 write("# User %s\n" % ctx.user())
711 write("# User %s\n" % ctx.user())
714 write("# Date %d %d\n" % ctx.date())
712 write("# Date %d %d\n" % ctx.date())
715 write("# %s\n" % util.datestr(ctx.date()))
713 write("# %s\n" % util.datestr(ctx.date()))
716 if branch and branch != 'default':
714 if branch and branch != 'default':
717 write("# Branch %s\n" % branch)
715 write("# Branch %s\n" % branch)
718 write("# Node ID %s\n" % hex(node))
716 write("# Node ID %s\n" % hex(node))
719 write("# Parent %s\n" % hex(prev))
717 write("# Parent %s\n" % hex(prev))
720 if len(parents) > 1:
718 if len(parents) > 1:
721 write("# Parent %s\n" % hex(parents[1]))
719 write("# Parent %s\n" % hex(parents[1]))
722 write(ctx.description().rstrip())
720 write(ctx.description().rstrip())
723 write("\n\n")
721 write("\n\n")
724
722
725 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
723 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
726 write(chunk, label=label)
724 write(chunk, label=label)
727
725
728 if shouldclose:
726 if shouldclose:
729 fp.close()
727 fp.close()
730
728
731 for seqno, rev in enumerate(revs):
729 for seqno, rev in enumerate(revs):
732 single(rev, seqno + 1, fp)
730 single(rev, seqno + 1, fp)
733
731
734 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
732 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
735 changes=None, stat=False, fp=None, prefix='',
733 changes=None, stat=False, fp=None, prefix='',
736 listsubrepos=False):
734 listsubrepos=False):
737 '''show diff or diffstat.'''
735 '''show diff or diffstat.'''
738 if fp is None:
736 if fp is None:
739 write = ui.write
737 write = ui.write
740 else:
738 else:
741 def write(s, **kw):
739 def write(s, **kw):
742 fp.write(s)
740 fp.write(s)
743
741
744 if stat:
742 if stat:
745 diffopts = diffopts.copy(context=0)
743 diffopts = diffopts.copy(context=0)
746 width = 80
744 width = 80
747 if not ui.plain():
745 if not ui.plain():
748 width = ui.termwidth()
746 width = ui.termwidth()
749 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
747 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
750 prefix=prefix)
748 prefix=prefix)
751 for chunk, label in patch.diffstatui(util.iterlines(chunks),
749 for chunk, label in patch.diffstatui(util.iterlines(chunks),
752 width=width,
750 width=width,
753 git=diffopts.git):
751 git=diffopts.git):
754 write(chunk, label=label)
752 write(chunk, label=label)
755 else:
753 else:
756 for chunk, label in patch.diffui(repo, node1, node2, match,
754 for chunk, label in patch.diffui(repo, node1, node2, match,
757 changes, diffopts, prefix=prefix):
755 changes, diffopts, prefix=prefix):
758 write(chunk, label=label)
756 write(chunk, label=label)
759
757
760 if listsubrepos:
758 if listsubrepos:
761 ctx1 = repo[node1]
759 ctx1 = repo[node1]
762 ctx2 = repo[node2]
760 ctx2 = repo[node2]
763 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
761 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
764 tempnode2 = node2
762 tempnode2 = node2
765 try:
763 try:
766 if node2 is not None:
764 if node2 is not None:
767 tempnode2 = ctx2.substate[subpath][1]
765 tempnode2 = ctx2.substate[subpath][1]
768 except KeyError:
766 except KeyError:
769 # A subrepo that existed in node1 was deleted between node1 and
767 # A subrepo that existed in node1 was deleted between node1 and
770 # node2 (inclusive). Thus, ctx2's substate won't contain that
768 # node2 (inclusive). Thus, ctx2's substate won't contain that
771 # subpath. The best we can do is to ignore it.
769 # subpath. The best we can do is to ignore it.
772 tempnode2 = None
770 tempnode2 = None
773 submatch = matchmod.narrowmatcher(subpath, match)
771 submatch = matchmod.narrowmatcher(subpath, match)
774 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
772 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
775 stat=stat, fp=fp, prefix=prefix)
773 stat=stat, fp=fp, prefix=prefix)
776
774
777 class changeset_printer(object):
775 class changeset_printer(object):
778 '''show changeset information when templating not requested.'''
776 '''show changeset information when templating not requested.'''
779
777
780 def __init__(self, ui, repo, patch, diffopts, buffered):
778 def __init__(self, ui, repo, patch, diffopts, buffered):
781 self.ui = ui
779 self.ui = ui
782 self.repo = repo
780 self.repo = repo
783 self.buffered = buffered
781 self.buffered = buffered
784 self.patch = patch
782 self.patch = patch
785 self.diffopts = diffopts
783 self.diffopts = diffopts
786 self.header = {}
784 self.header = {}
787 self.hunk = {}
785 self.hunk = {}
788 self.lastheader = None
786 self.lastheader = None
789 self.footer = None
787 self.footer = None
790
788
791 def flush(self, rev):
789 def flush(self, rev):
792 if rev in self.header:
790 if rev in self.header:
793 h = self.header[rev]
791 h = self.header[rev]
794 if h != self.lastheader:
792 if h != self.lastheader:
795 self.lastheader = h
793 self.lastheader = h
796 self.ui.write(h)
794 self.ui.write(h)
797 del self.header[rev]
795 del self.header[rev]
798 if rev in self.hunk:
796 if rev in self.hunk:
799 self.ui.write(self.hunk[rev])
797 self.ui.write(self.hunk[rev])
800 del self.hunk[rev]
798 del self.hunk[rev]
801 return 1
799 return 1
802 return 0
800 return 0
803
801
804 def close(self):
802 def close(self):
805 if self.footer:
803 if self.footer:
806 self.ui.write(self.footer)
804 self.ui.write(self.footer)
807
805
808 def show(self, ctx, copies=None, matchfn=None, **props):
806 def show(self, ctx, copies=None, matchfn=None, **props):
809 if self.buffered:
807 if self.buffered:
810 self.ui.pushbuffer()
808 self.ui.pushbuffer()
811 self._show(ctx, copies, matchfn, props)
809 self._show(ctx, copies, matchfn, props)
812 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
810 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
813 else:
811 else:
814 self._show(ctx, copies, matchfn, props)
812 self._show(ctx, copies, matchfn, props)
815
813
816 def _show(self, ctx, copies, matchfn, props):
814 def _show(self, ctx, copies, matchfn, props):
817 '''show a single changeset or file revision'''
815 '''show a single changeset or file revision'''
818 changenode = ctx.node()
816 changenode = ctx.node()
819 rev = ctx.rev()
817 rev = ctx.rev()
820
818
821 if self.ui.quiet:
819 if self.ui.quiet:
822 self.ui.write("%d:%s\n" % (rev, short(changenode)),
820 self.ui.write("%d:%s\n" % (rev, short(changenode)),
823 label='log.node')
821 label='log.node')
824 return
822 return
825
823
826 log = self.repo.changelog
824 log = self.repo.changelog
827 date = util.datestr(ctx.date())
825 date = util.datestr(ctx.date())
828
826
829 hexfunc = self.ui.debugflag and hex or short
827 hexfunc = self.ui.debugflag and hex or short
830
828
831 parents = [(p, hexfunc(log.node(p)))
829 parents = [(p, hexfunc(log.node(p)))
832 for p in self._meaningful_parentrevs(log, rev)]
830 for p in self._meaningful_parentrevs(log, rev)]
833
831
834 # i18n: column positioning for "hg log"
832 # i18n: column positioning for "hg log"
835 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
833 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
836 label='log.changeset changeset.%s' % ctx.phasestr())
834 label='log.changeset changeset.%s' % ctx.phasestr())
837
835
838 branch = ctx.branch()
836 branch = ctx.branch()
839 # don't show the default branch name
837 # don't show the default branch name
840 if branch != 'default':
838 if branch != 'default':
841 # i18n: column positioning for "hg log"
839 # i18n: column positioning for "hg log"
842 self.ui.write(_("branch: %s\n") % branch,
840 self.ui.write(_("branch: %s\n") % branch,
843 label='log.branch')
841 label='log.branch')
844 for bookmark in self.repo.nodebookmarks(changenode):
842 for bookmark in self.repo.nodebookmarks(changenode):
845 # i18n: column positioning for "hg log"
843 # i18n: column positioning for "hg log"
846 self.ui.write(_("bookmark: %s\n") % bookmark,
844 self.ui.write(_("bookmark: %s\n") % bookmark,
847 label='log.bookmark')
845 label='log.bookmark')
848 for tag in self.repo.nodetags(changenode):
846 for tag in self.repo.nodetags(changenode):
849 # i18n: column positioning for "hg log"
847 # i18n: column positioning for "hg log"
850 self.ui.write(_("tag: %s\n") % tag,
848 self.ui.write(_("tag: %s\n") % tag,
851 label='log.tag')
849 label='log.tag')
852 if self.ui.debugflag and ctx.phase():
850 if self.ui.debugflag and ctx.phase():
853 # i18n: column positioning for "hg log"
851 # i18n: column positioning for "hg log"
854 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
852 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
855 label='log.phase')
853 label='log.phase')
856 for parent in parents:
854 for parent in parents:
857 # i18n: column positioning for "hg log"
855 # i18n: column positioning for "hg log"
858 self.ui.write(_("parent: %d:%s\n") % parent,
856 self.ui.write(_("parent: %d:%s\n") % parent,
859 label='log.parent changeset.%s' % ctx.phasestr())
857 label='log.parent changeset.%s' % ctx.phasestr())
860
858
861 if self.ui.debugflag:
859 if self.ui.debugflag:
862 mnode = ctx.manifestnode()
860 mnode = ctx.manifestnode()
863 # i18n: column positioning for "hg log"
861 # i18n: column positioning for "hg log"
864 self.ui.write(_("manifest: %d:%s\n") %
862 self.ui.write(_("manifest: %d:%s\n") %
865 (self.repo.manifest.rev(mnode), hex(mnode)),
863 (self.repo.manifest.rev(mnode), hex(mnode)),
866 label='ui.debug log.manifest')
864 label='ui.debug log.manifest')
867 # i18n: column positioning for "hg log"
865 # i18n: column positioning for "hg log"
868 self.ui.write(_("user: %s\n") % ctx.user(),
866 self.ui.write(_("user: %s\n") % ctx.user(),
869 label='log.user')
867 label='log.user')
870 # i18n: column positioning for "hg log"
868 # i18n: column positioning for "hg log"
871 self.ui.write(_("date: %s\n") % date,
869 self.ui.write(_("date: %s\n") % date,
872 label='log.date')
870 label='log.date')
873
871
874 if self.ui.debugflag:
872 if self.ui.debugflag:
875 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
873 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
876 for key, value in zip([# i18n: column positioning for "hg log"
874 for key, value in zip([# i18n: column positioning for "hg log"
877 _("files:"),
875 _("files:"),
878 # i18n: column positioning for "hg log"
876 # i18n: column positioning for "hg log"
879 _("files+:"),
877 _("files+:"),
880 # i18n: column positioning for "hg log"
878 # i18n: column positioning for "hg log"
881 _("files-:")], files):
879 _("files-:")], files):
882 if value:
880 if value:
883 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
881 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
884 label='ui.debug log.files')
882 label='ui.debug log.files')
885 elif ctx.files() and self.ui.verbose:
883 elif ctx.files() and self.ui.verbose:
886 # i18n: column positioning for "hg log"
884 # i18n: column positioning for "hg log"
887 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
885 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
888 label='ui.note log.files')
886 label='ui.note log.files')
889 if copies and self.ui.verbose:
887 if copies and self.ui.verbose:
890 copies = ['%s (%s)' % c for c in copies]
888 copies = ['%s (%s)' % c for c in copies]
891 # i18n: column positioning for "hg log"
889 # i18n: column positioning for "hg log"
892 self.ui.write(_("copies: %s\n") % ' '.join(copies),
890 self.ui.write(_("copies: %s\n") % ' '.join(copies),
893 label='ui.note log.copies')
891 label='ui.note log.copies')
894
892
895 extra = ctx.extra()
893 extra = ctx.extra()
896 if extra and self.ui.debugflag:
894 if extra and self.ui.debugflag:
897 for key, value in sorted(extra.items()):
895 for key, value in sorted(extra.items()):
898 # i18n: column positioning for "hg log"
896 # i18n: column positioning for "hg log"
899 self.ui.write(_("extra: %s=%s\n")
897 self.ui.write(_("extra: %s=%s\n")
900 % (key, value.encode('string_escape')),
898 % (key, value.encode('string_escape')),
901 label='ui.debug log.extra')
899 label='ui.debug log.extra')
902
900
903 description = ctx.description().strip()
901 description = ctx.description().strip()
904 if description:
902 if description:
905 if self.ui.verbose:
903 if self.ui.verbose:
906 self.ui.write(_("description:\n"),
904 self.ui.write(_("description:\n"),
907 label='ui.note log.description')
905 label='ui.note log.description')
908 self.ui.write(description,
906 self.ui.write(description,
909 label='ui.note log.description')
907 label='ui.note log.description')
910 self.ui.write("\n\n")
908 self.ui.write("\n\n")
911 else:
909 else:
912 # i18n: column positioning for "hg log"
910 # i18n: column positioning for "hg log"
913 self.ui.write(_("summary: %s\n") %
911 self.ui.write(_("summary: %s\n") %
914 description.splitlines()[0],
912 description.splitlines()[0],
915 label='log.summary')
913 label='log.summary')
916 self.ui.write("\n")
914 self.ui.write("\n")
917
915
918 self.showpatch(changenode, matchfn)
916 self.showpatch(changenode, matchfn)
919
917
920 def showpatch(self, node, matchfn):
918 def showpatch(self, node, matchfn):
921 if not matchfn:
919 if not matchfn:
922 matchfn = self.patch
920 matchfn = self.patch
923 if matchfn:
921 if matchfn:
924 stat = self.diffopts.get('stat')
922 stat = self.diffopts.get('stat')
925 diff = self.diffopts.get('patch')
923 diff = self.diffopts.get('patch')
926 diffopts = patch.diffopts(self.ui, self.diffopts)
924 diffopts = patch.diffopts(self.ui, self.diffopts)
927 prev = self.repo.changelog.parents(node)[0]
925 prev = self.repo.changelog.parents(node)[0]
928 if stat:
926 if stat:
929 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
927 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
930 match=matchfn, stat=True)
928 match=matchfn, stat=True)
931 if diff:
929 if diff:
932 if stat:
930 if stat:
933 self.ui.write("\n")
931 self.ui.write("\n")
934 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
932 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
935 match=matchfn, stat=False)
933 match=matchfn, stat=False)
936 self.ui.write("\n")
934 self.ui.write("\n")
937
935
938 def _meaningful_parentrevs(self, log, rev):
936 def _meaningful_parentrevs(self, log, rev):
939 """Return list of meaningful (or all if debug) parentrevs for rev.
937 """Return list of meaningful (or all if debug) parentrevs for rev.
940
938
941 For merges (two non-nullrev revisions) both parents are meaningful.
939 For merges (two non-nullrev revisions) both parents are meaningful.
942 Otherwise the first parent revision is considered meaningful if it
940 Otherwise the first parent revision is considered meaningful if it
943 is not the preceding revision.
941 is not the preceding revision.
944 """
942 """
945 parents = log.parentrevs(rev)
943 parents = log.parentrevs(rev)
946 if not self.ui.debugflag and parents[1] == nullrev:
944 if not self.ui.debugflag and parents[1] == nullrev:
947 if parents[0] >= rev - 1:
945 if parents[0] >= rev - 1:
948 parents = []
946 parents = []
949 else:
947 else:
950 parents = [parents[0]]
948 parents = [parents[0]]
951 return parents
949 return parents
952
950
953
951
954 class changeset_templater(changeset_printer):
952 class changeset_templater(changeset_printer):
955 '''format changeset information.'''
953 '''format changeset information.'''
956
954
957 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
955 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
958 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
956 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
959 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
957 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
960 defaulttempl = {
958 defaulttempl = {
961 'parent': '{rev}:{node|formatnode} ',
959 'parent': '{rev}:{node|formatnode} ',
962 'manifest': '{rev}:{node|formatnode}',
960 'manifest': '{rev}:{node|formatnode}',
963 'file_copy': '{name} ({source})',
961 'file_copy': '{name} ({source})',
964 'extra': '{key}={value|stringescape}'
962 'extra': '{key}={value|stringescape}'
965 }
963 }
966 # filecopy is preserved for compatibility reasons
964 # filecopy is preserved for compatibility reasons
967 defaulttempl['filecopy'] = defaulttempl['file_copy']
965 defaulttempl['filecopy'] = defaulttempl['file_copy']
968 self.t = templater.templater(mapfile, {'formatnode': formatnode},
966 self.t = templater.templater(mapfile, {'formatnode': formatnode},
969 cache=defaulttempl)
967 cache=defaulttempl)
970 if tmpl:
968 if tmpl:
971 self.t.cache['changeset'] = tmpl
969 self.t.cache['changeset'] = tmpl
972
970
973 self.cache = {}
971 self.cache = {}
974
972
975 def _meaningful_parentrevs(self, ctx):
973 def _meaningful_parentrevs(self, ctx):
976 """Return list of meaningful (or all if debug) parentrevs for rev.
974 """Return list of meaningful (or all if debug) parentrevs for rev.
977 """
975 """
978 parents = ctx.parents()
976 parents = ctx.parents()
979 if len(parents) > 1:
977 if len(parents) > 1:
980 return parents
978 return parents
981 if self.ui.debugflag:
979 if self.ui.debugflag:
982 return [parents[0], self.repo['null']]
980 return [parents[0], self.repo['null']]
983 if parents[0].rev() >= ctx.rev() - 1:
981 if parents[0].rev() >= ctx.rev() - 1:
984 return []
982 return []
985 return parents
983 return parents
986
984
987 def _show(self, ctx, copies, matchfn, props):
985 def _show(self, ctx, copies, matchfn, props):
988 '''show a single changeset or file revision'''
986 '''show a single changeset or file revision'''
989
987
990 showlist = templatekw.showlist
988 showlist = templatekw.showlist
991
989
992 # showparents() behaviour depends on ui trace level which
990 # showparents() behaviour depends on ui trace level which
993 # causes unexpected behaviours at templating level and makes
991 # causes unexpected behaviours at templating level and makes
994 # it harder to extract it in a standalone function. Its
992 # it harder to extract it in a standalone function. Its
995 # behaviour cannot be changed so leave it here for now.
993 # behaviour cannot be changed so leave it here for now.
996 def showparents(**args):
994 def showparents(**args):
997 ctx = args['ctx']
995 ctx = args['ctx']
998 parents = [[('rev', p.rev()), ('node', p.hex())]
996 parents = [[('rev', p.rev()), ('node', p.hex())]
999 for p in self._meaningful_parentrevs(ctx)]
997 for p in self._meaningful_parentrevs(ctx)]
1000 return showlist('parent', parents, **args)
998 return showlist('parent', parents, **args)
1001
999
1002 props = props.copy()
1000 props = props.copy()
1003 props.update(templatekw.keywords)
1001 props.update(templatekw.keywords)
1004 props['parents'] = showparents
1002 props['parents'] = showparents
1005 props['templ'] = self.t
1003 props['templ'] = self.t
1006 props['ctx'] = ctx
1004 props['ctx'] = ctx
1007 props['repo'] = self.repo
1005 props['repo'] = self.repo
1008 props['revcache'] = {'copies': copies}
1006 props['revcache'] = {'copies': copies}
1009 props['cache'] = self.cache
1007 props['cache'] = self.cache
1010
1008
1011 # find correct templates for current mode
1009 # find correct templates for current mode
1012
1010
1013 tmplmodes = [
1011 tmplmodes = [
1014 (True, None),
1012 (True, None),
1015 (self.ui.verbose, 'verbose'),
1013 (self.ui.verbose, 'verbose'),
1016 (self.ui.quiet, 'quiet'),
1014 (self.ui.quiet, 'quiet'),
1017 (self.ui.debugflag, 'debug'),
1015 (self.ui.debugflag, 'debug'),
1018 ]
1016 ]
1019
1017
1020 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1018 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1021 for mode, postfix in tmplmodes:
1019 for mode, postfix in tmplmodes:
1022 for type in types:
1020 for type in types:
1023 cur = postfix and ('%s_%s' % (type, postfix)) or type
1021 cur = postfix and ('%s_%s' % (type, postfix)) or type
1024 if mode and cur in self.t:
1022 if mode and cur in self.t:
1025 types[type] = cur
1023 types[type] = cur
1026
1024
1027 try:
1025 try:
1028
1026
1029 # write header
1027 # write header
1030 if types['header']:
1028 if types['header']:
1031 h = templater.stringify(self.t(types['header'], **props))
1029 h = templater.stringify(self.t(types['header'], **props))
1032 if self.buffered:
1030 if self.buffered:
1033 self.header[ctx.rev()] = h
1031 self.header[ctx.rev()] = h
1034 else:
1032 else:
1035 if self.lastheader != h:
1033 if self.lastheader != h:
1036 self.lastheader = h
1034 self.lastheader = h
1037 self.ui.write(h)
1035 self.ui.write(h)
1038
1036
1039 # write changeset metadata, then patch if requested
1037 # write changeset metadata, then patch if requested
1040 key = types['changeset']
1038 key = types['changeset']
1041 self.ui.write(templater.stringify(self.t(key, **props)))
1039 self.ui.write(templater.stringify(self.t(key, **props)))
1042 self.showpatch(ctx.node(), matchfn)
1040 self.showpatch(ctx.node(), matchfn)
1043
1041
1044 if types['footer']:
1042 if types['footer']:
1045 if not self.footer:
1043 if not self.footer:
1046 self.footer = templater.stringify(self.t(types['footer'],
1044 self.footer = templater.stringify(self.t(types['footer'],
1047 **props))
1045 **props))
1048
1046
1049 except KeyError, inst:
1047 except KeyError, inst:
1050 msg = _("%s: no key named '%s'")
1048 msg = _("%s: no key named '%s'")
1051 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1049 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1052 except SyntaxError, inst:
1050 except SyntaxError, inst:
1053 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1051 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1054
1052
1055 def gettemplate(ui, tmpl, style):
1053 def gettemplate(ui, tmpl, style):
1056 """
1054 """
1057 Find the template matching the given template spec or style.
1055 Find the template matching the given template spec or style.
1058 """
1056 """
1059
1057
1060 # ui settings
1058 # ui settings
1061 if not tmpl and not style:
1059 if not tmpl and not style:
1062 tmpl = ui.config('ui', 'logtemplate')
1060 tmpl = ui.config('ui', 'logtemplate')
1063 if tmpl:
1061 if tmpl:
1064 try:
1062 try:
1065 tmpl = templater.parsestring(tmpl)
1063 tmpl = templater.parsestring(tmpl)
1066 except SyntaxError:
1064 except SyntaxError:
1067 tmpl = templater.parsestring(tmpl, quoted=False)
1065 tmpl = templater.parsestring(tmpl, quoted=False)
1068 return tmpl, None
1066 return tmpl, None
1069 else:
1067 else:
1070 style = util.expandpath(ui.config('ui', 'style', ''))
1068 style = util.expandpath(ui.config('ui', 'style', ''))
1071
1069
1072 if style:
1070 if style:
1073 mapfile = style
1071 mapfile = style
1074 if not os.path.split(mapfile)[0]:
1072 if not os.path.split(mapfile)[0]:
1075 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1073 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1076 or templater.templatepath(mapfile))
1074 or templater.templatepath(mapfile))
1077 if mapname:
1075 if mapname:
1078 mapfile = mapname
1076 mapfile = mapname
1079 return None, mapfile
1077 return None, mapfile
1080
1078
1081 if not tmpl:
1079 if not tmpl:
1082 return None, None
1080 return None, None
1083
1081
1084 # looks like a literal template?
1082 # looks like a literal template?
1085 if '{' in tmpl:
1083 if '{' in tmpl:
1086 return tmpl, None
1084 return tmpl, None
1087
1085
1088 # perhaps a stock style?
1086 # perhaps a stock style?
1089 if not os.path.split(tmpl)[0]:
1087 if not os.path.split(tmpl)[0]:
1090 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1088 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1091 or templater.templatepath(tmpl))
1089 or templater.templatepath(tmpl))
1092 if mapname and os.path.isfile(mapname):
1090 if mapname and os.path.isfile(mapname):
1093 return None, mapname
1091 return None, mapname
1094
1092
1095 # perhaps it's a reference to [templates]
1093 # perhaps it's a reference to [templates]
1096 t = ui.config('templates', tmpl)
1094 t = ui.config('templates', tmpl)
1097 if t:
1095 if t:
1098 try:
1096 try:
1099 tmpl = templater.parsestring(t)
1097 tmpl = templater.parsestring(t)
1100 except SyntaxError:
1098 except SyntaxError:
1101 tmpl = templater.parsestring(t, quoted=False)
1099 tmpl = templater.parsestring(t, quoted=False)
1102 return tmpl, None
1100 return tmpl, None
1103
1101
1104 # perhaps it's a path to a map or a template
1102 # perhaps it's a path to a map or a template
1105 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1103 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1106 # is it a mapfile for a style?
1104 # is it a mapfile for a style?
1107 if os.path.basename(tmpl).startswith("map-"):
1105 if os.path.basename(tmpl).startswith("map-"):
1108 return None, os.path.realpath(tmpl)
1106 return None, os.path.realpath(tmpl)
1109 tmpl = open(tmpl).read()
1107 tmpl = open(tmpl).read()
1110 return tmpl, None
1108 return tmpl, None
1111
1109
1112 # constant string?
1110 # constant string?
1113 return tmpl, None
1111 return tmpl, None
1114
1112
1115 def show_changeset(ui, repo, opts, buffered=False):
1113 def show_changeset(ui, repo, opts, buffered=False):
1116 """show one changeset using template or regular display.
1114 """show one changeset using template or regular display.
1117
1115
1118 Display format will be the first non-empty hit of:
1116 Display format will be the first non-empty hit of:
1119 1. option 'template'
1117 1. option 'template'
1120 2. option 'style'
1118 2. option 'style'
1121 3. [ui] setting 'logtemplate'
1119 3. [ui] setting 'logtemplate'
1122 4. [ui] setting 'style'
1120 4. [ui] setting 'style'
1123 If all of these values are either the unset or the empty string,
1121 If all of these values are either the unset or the empty string,
1124 regular display via changeset_printer() is done.
1122 regular display via changeset_printer() is done.
1125 """
1123 """
1126 # options
1124 # options
1127 patch = None
1125 patch = None
1128 if opts.get('patch') or opts.get('stat'):
1126 if opts.get('patch') or opts.get('stat'):
1129 patch = scmutil.matchall(repo)
1127 patch = scmutil.matchall(repo)
1130
1128
1131 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1129 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1132
1130
1133 if not tmpl and not mapfile:
1131 if not tmpl and not mapfile:
1134 return changeset_printer(ui, repo, patch, opts, buffered)
1132 return changeset_printer(ui, repo, patch, opts, buffered)
1135
1133
1136 try:
1134 try:
1137 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1135 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1138 except SyntaxError, inst:
1136 except SyntaxError, inst:
1139 raise util.Abort(inst.args[0])
1137 raise util.Abort(inst.args[0])
1140 return t
1138 return t
1141
1139
1142 def showmarker(ui, marker):
1140 def showmarker(ui, marker):
1143 """utility function to display obsolescence marker in a readable way
1141 """utility function to display obsolescence marker in a readable way
1144
1142
1145 To be used by debug function."""
1143 To be used by debug function."""
1146 ui.write(hex(marker.precnode()))
1144 ui.write(hex(marker.precnode()))
1147 for repl in marker.succnodes():
1145 for repl in marker.succnodes():
1148 ui.write(' ')
1146 ui.write(' ')
1149 ui.write(hex(repl))
1147 ui.write(hex(repl))
1150 ui.write(' %X ' % marker._data[2])
1148 ui.write(' %X ' % marker._data[2])
1151 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1149 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1152 sorted(marker.metadata().items()))))
1150 sorted(marker.metadata().items()))))
1153 ui.write('\n')
1151 ui.write('\n')
1154
1152
1155 def finddate(ui, repo, date):
1153 def finddate(ui, repo, date):
1156 """Find the tipmost changeset that matches the given date spec"""
1154 """Find the tipmost changeset that matches the given date spec"""
1157
1155
1158 df = util.matchdate(date)
1156 df = util.matchdate(date)
1159 m = scmutil.matchall(repo)
1157 m = scmutil.matchall(repo)
1160 results = {}
1158 results = {}
1161
1159
1162 def prep(ctx, fns):
1160 def prep(ctx, fns):
1163 d = ctx.date()
1161 d = ctx.date()
1164 if df(d[0]):
1162 if df(d[0]):
1165 results[ctx.rev()] = d
1163 results[ctx.rev()] = d
1166
1164
1167 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1165 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1168 rev = ctx.rev()
1166 rev = ctx.rev()
1169 if rev in results:
1167 if rev in results:
1170 ui.status(_("found revision %s from %s\n") %
1168 ui.status(_("found revision %s from %s\n") %
1171 (rev, util.datestr(results[rev])))
1169 (rev, util.datestr(results[rev])))
1172 return str(rev)
1170 return str(rev)
1173
1171
1174 raise util.Abort(_("revision matching date not found"))
1172 raise util.Abort(_("revision matching date not found"))
1175
1173
1176 def increasingwindows(windowsize=8, sizelimit=512):
1174 def increasingwindows(windowsize=8, sizelimit=512):
1177 while True:
1175 while True:
1178 yield windowsize
1176 yield windowsize
1179 if windowsize < sizelimit:
1177 if windowsize < sizelimit:
1180 windowsize *= 2
1178 windowsize *= 2
1181
1179
1182 class FileWalkError(Exception):
1180 class FileWalkError(Exception):
1183 pass
1181 pass
1184
1182
1185 def walkfilerevs(repo, match, follow, revs, fncache):
1183 def walkfilerevs(repo, match, follow, revs, fncache):
1186 '''Walks the file history for the matched files.
1184 '''Walks the file history for the matched files.
1187
1185
1188 Returns the changeset revs that are involved in the file history.
1186 Returns the changeset revs that are involved in the file history.
1189
1187
1190 Throws FileWalkError if the file history can't be walked using
1188 Throws FileWalkError if the file history can't be walked using
1191 filelogs alone.
1189 filelogs alone.
1192 '''
1190 '''
1193 wanted = set()
1191 wanted = set()
1194 copies = []
1192 copies = []
1195 minrev, maxrev = min(revs), max(revs)
1193 minrev, maxrev = min(revs), max(revs)
1196 def filerevgen(filelog, last):
1194 def filerevgen(filelog, last):
1197 """
1195 """
1198 Only files, no patterns. Check the history of each file.
1196 Only files, no patterns. Check the history of each file.
1199
1197
1200 Examines filelog entries within minrev, maxrev linkrev range
1198 Examines filelog entries within minrev, maxrev linkrev range
1201 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1199 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1202 tuples in backwards order
1200 tuples in backwards order
1203 """
1201 """
1204 cl_count = len(repo)
1202 cl_count = len(repo)
1205 revs = []
1203 revs = []
1206 for j in xrange(0, last + 1):
1204 for j in xrange(0, last + 1):
1207 linkrev = filelog.linkrev(j)
1205 linkrev = filelog.linkrev(j)
1208 if linkrev < minrev:
1206 if linkrev < minrev:
1209 continue
1207 continue
1210 # only yield rev for which we have the changelog, it can
1208 # only yield rev for which we have the changelog, it can
1211 # happen while doing "hg log" during a pull or commit
1209 # happen while doing "hg log" during a pull or commit
1212 if linkrev >= cl_count:
1210 if linkrev >= cl_count:
1213 break
1211 break
1214
1212
1215 parentlinkrevs = []
1213 parentlinkrevs = []
1216 for p in filelog.parentrevs(j):
1214 for p in filelog.parentrevs(j):
1217 if p != nullrev:
1215 if p != nullrev:
1218 parentlinkrevs.append(filelog.linkrev(p))
1216 parentlinkrevs.append(filelog.linkrev(p))
1219 n = filelog.node(j)
1217 n = filelog.node(j)
1220 revs.append((linkrev, parentlinkrevs,
1218 revs.append((linkrev, parentlinkrevs,
1221 follow and filelog.renamed(n)))
1219 follow and filelog.renamed(n)))
1222
1220
1223 return reversed(revs)
1221 return reversed(revs)
1224 def iterfiles():
1222 def iterfiles():
1225 pctx = repo['.']
1223 pctx = repo['.']
1226 for filename in match.files():
1224 for filename in match.files():
1227 if follow:
1225 if follow:
1228 if filename not in pctx:
1226 if filename not in pctx:
1229 raise util.Abort(_('cannot follow file not in parent '
1227 raise util.Abort(_('cannot follow file not in parent '
1230 'revision: "%s"') % filename)
1228 'revision: "%s"') % filename)
1231 yield filename, pctx[filename].filenode()
1229 yield filename, pctx[filename].filenode()
1232 else:
1230 else:
1233 yield filename, None
1231 yield filename, None
1234 for filename_node in copies:
1232 for filename_node in copies:
1235 yield filename_node
1233 yield filename_node
1236
1234
1237 for file_, node in iterfiles():
1235 for file_, node in iterfiles():
1238 filelog = repo.file(file_)
1236 filelog = repo.file(file_)
1239 if not len(filelog):
1237 if not len(filelog):
1240 if node is None:
1238 if node is None:
1241 # A zero count may be a directory or deleted file, so
1239 # A zero count may be a directory or deleted file, so
1242 # try to find matching entries on the slow path.
1240 # try to find matching entries on the slow path.
1243 if follow:
1241 if follow:
1244 raise util.Abort(
1242 raise util.Abort(
1245 _('cannot follow nonexistent file: "%s"') % file_)
1243 _('cannot follow nonexistent file: "%s"') % file_)
1246 raise FileWalkError("Cannot walk via filelog")
1244 raise FileWalkError("Cannot walk via filelog")
1247 else:
1245 else:
1248 continue
1246 continue
1249
1247
1250 if node is None:
1248 if node is None:
1251 last = len(filelog) - 1
1249 last = len(filelog) - 1
1252 else:
1250 else:
1253 last = filelog.rev(node)
1251 last = filelog.rev(node)
1254
1252
1255
1253
1256 # keep track of all ancestors of the file
1254 # keep track of all ancestors of the file
1257 ancestors = set([filelog.linkrev(last)])
1255 ancestors = set([filelog.linkrev(last)])
1258
1256
1259 # iterate from latest to oldest revision
1257 # iterate from latest to oldest revision
1260 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1258 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1261 if not follow:
1259 if not follow:
1262 if rev > maxrev:
1260 if rev > maxrev:
1263 continue
1261 continue
1264 else:
1262 else:
1265 # Note that last might not be the first interesting
1263 # Note that last might not be the first interesting
1266 # rev to us:
1264 # rev to us:
1267 # if the file has been changed after maxrev, we'll
1265 # if the file has been changed after maxrev, we'll
1268 # have linkrev(last) > maxrev, and we still need
1266 # have linkrev(last) > maxrev, and we still need
1269 # to explore the file graph
1267 # to explore the file graph
1270 if rev not in ancestors:
1268 if rev not in ancestors:
1271 continue
1269 continue
1272 # XXX insert 1327 fix here
1270 # XXX insert 1327 fix here
1273 if flparentlinkrevs:
1271 if flparentlinkrevs:
1274 ancestors.update(flparentlinkrevs)
1272 ancestors.update(flparentlinkrevs)
1275
1273
1276 fncache.setdefault(rev, []).append(file_)
1274 fncache.setdefault(rev, []).append(file_)
1277 wanted.add(rev)
1275 wanted.add(rev)
1278 if copied:
1276 if copied:
1279 copies.append(copied)
1277 copies.append(copied)
1280
1278
1281 return wanted
1279 return wanted
1282
1280
1283 def walkchangerevs(repo, match, opts, prepare):
1281 def walkchangerevs(repo, match, opts, prepare):
1284 '''Iterate over files and the revs in which they changed.
1282 '''Iterate over files and the revs in which they changed.
1285
1283
1286 Callers most commonly need to iterate backwards over the history
1284 Callers most commonly need to iterate backwards over the history
1287 in which they are interested. Doing so has awful (quadratic-looking)
1285 in which they are interested. Doing so has awful (quadratic-looking)
1288 performance, so we use iterators in a "windowed" way.
1286 performance, so we use iterators in a "windowed" way.
1289
1287
1290 We walk a window of revisions in the desired order. Within the
1288 We walk a window of revisions in the desired order. Within the
1291 window, we first walk forwards to gather data, then in the desired
1289 window, we first walk forwards to gather data, then in the desired
1292 order (usually backwards) to display it.
1290 order (usually backwards) to display it.
1293
1291
1294 This function returns an iterator yielding contexts. Before
1292 This function returns an iterator yielding contexts. Before
1295 yielding each context, the iterator will first call the prepare
1293 yielding each context, the iterator will first call the prepare
1296 function on each context in the window in forward order.'''
1294 function on each context in the window in forward order.'''
1297
1295
1298 follow = opts.get('follow') or opts.get('follow_first')
1296 follow = opts.get('follow') or opts.get('follow_first')
1299
1297
1300 if opts.get('rev'):
1298 if opts.get('rev'):
1301 revs = scmutil.revrange(repo, opts.get('rev'))
1299 revs = scmutil.revrange(repo, opts.get('rev'))
1302 elif follow:
1300 elif follow:
1303 revs = repo.revs('reverse(:.)')
1301 revs = repo.revs('reverse(:.)')
1304 else:
1302 else:
1305 revs = revset.spanset(repo)
1303 revs = revset.spanset(repo)
1306 revs.reverse()
1304 revs.reverse()
1307 if not revs:
1305 if not revs:
1308 return []
1306 return []
1309 wanted = set()
1307 wanted = set()
1310 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1308 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1311 fncache = {}
1309 fncache = {}
1312 change = repo.changectx
1310 change = repo.changectx
1313
1311
1314 # First step is to fill wanted, the set of revisions that we want to yield.
1312 # First step is to fill wanted, the set of revisions that we want to yield.
1315 # When it does not induce extra cost, we also fill fncache for revisions in
1313 # When it does not induce extra cost, we also fill fncache for revisions in
1316 # wanted: a cache of filenames that were changed (ctx.files()) and that
1314 # wanted: a cache of filenames that were changed (ctx.files()) and that
1317 # match the file filtering conditions.
1315 # match the file filtering conditions.
1318
1316
1319 if not slowpath and not match.files():
1317 if not slowpath and not match.files():
1320 # No files, no patterns. Display all revs.
1318 # No files, no patterns. Display all revs.
1321 wanted = revs
1319 wanted = revs
1322
1320
1323 if not slowpath and match.files():
1321 if not slowpath and match.files():
1324 # We only have to read through the filelog to find wanted revisions
1322 # We only have to read through the filelog to find wanted revisions
1325
1323
1326 try:
1324 try:
1327 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1325 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1328 except FileWalkError:
1326 except FileWalkError:
1329 slowpath = True
1327 slowpath = True
1330
1328
1331 # We decided to fall back to the slowpath because at least one
1329 # We decided to fall back to the slowpath because at least one
1332 # of the paths was not a file. Check to see if at least one of them
1330 # of the paths was not a file. Check to see if at least one of them
1333 # existed in history, otherwise simply return
1331 # existed in history, otherwise simply return
1334 for path in match.files():
1332 for path in match.files():
1335 if path == '.' or path in repo.store:
1333 if path == '.' or path in repo.store:
1336 break
1334 break
1337 else:
1335 else:
1338 return []
1336 return []
1339
1337
1340 if slowpath:
1338 if slowpath:
1341 # We have to read the changelog to match filenames against
1339 # We have to read the changelog to match filenames against
1342 # changed files
1340 # changed files
1343
1341
1344 if follow:
1342 if follow:
1345 raise util.Abort(_('can only follow copies/renames for explicit '
1343 raise util.Abort(_('can only follow copies/renames for explicit '
1346 'filenames'))
1344 'filenames'))
1347
1345
1348 # The slow path checks files modified in every changeset.
1346 # The slow path checks files modified in every changeset.
1349 # This is really slow on large repos, so compute the set lazily.
1347 # This is really slow on large repos, so compute the set lazily.
1350 class lazywantedset(object):
1348 class lazywantedset(object):
1351 def __init__(self):
1349 def __init__(self):
1352 self.set = set()
1350 self.set = set()
1353 self.revs = set(revs)
1351 self.revs = set(revs)
1354
1352
1355 # No need to worry about locality here because it will be accessed
1353 # No need to worry about locality here because it will be accessed
1356 # in the same order as the increasing window below.
1354 # in the same order as the increasing window below.
1357 def __contains__(self, value):
1355 def __contains__(self, value):
1358 if value in self.set:
1356 if value in self.set:
1359 return True
1357 return True
1360 elif not value in self.revs:
1358 elif not value in self.revs:
1361 return False
1359 return False
1362 else:
1360 else:
1363 self.revs.discard(value)
1361 self.revs.discard(value)
1364 ctx = change(value)
1362 ctx = change(value)
1365 matches = filter(match, ctx.files())
1363 matches = filter(match, ctx.files())
1366 if matches:
1364 if matches:
1367 fncache[value] = matches
1365 fncache[value] = matches
1368 self.set.add(value)
1366 self.set.add(value)
1369 return True
1367 return True
1370 return False
1368 return False
1371
1369
1372 def discard(self, value):
1370 def discard(self, value):
1373 self.revs.discard(value)
1371 self.revs.discard(value)
1374 self.set.discard(value)
1372 self.set.discard(value)
1375
1373
1376 wanted = lazywantedset()
1374 wanted = lazywantedset()
1377
1375
1378 class followfilter(object):
1376 class followfilter(object):
1379 def __init__(self, onlyfirst=False):
1377 def __init__(self, onlyfirst=False):
1380 self.startrev = nullrev
1378 self.startrev = nullrev
1381 self.roots = set()
1379 self.roots = set()
1382 self.onlyfirst = onlyfirst
1380 self.onlyfirst = onlyfirst
1383
1381
1384 def match(self, rev):
1382 def match(self, rev):
1385 def realparents(rev):
1383 def realparents(rev):
1386 if self.onlyfirst:
1384 if self.onlyfirst:
1387 return repo.changelog.parentrevs(rev)[0:1]
1385 return repo.changelog.parentrevs(rev)[0:1]
1388 else:
1386 else:
1389 return filter(lambda x: x != nullrev,
1387 return filter(lambda x: x != nullrev,
1390 repo.changelog.parentrevs(rev))
1388 repo.changelog.parentrevs(rev))
1391
1389
1392 if self.startrev == nullrev:
1390 if self.startrev == nullrev:
1393 self.startrev = rev
1391 self.startrev = rev
1394 return True
1392 return True
1395
1393
1396 if rev > self.startrev:
1394 if rev > self.startrev:
1397 # forward: all descendants
1395 # forward: all descendants
1398 if not self.roots:
1396 if not self.roots:
1399 self.roots.add(self.startrev)
1397 self.roots.add(self.startrev)
1400 for parent in realparents(rev):
1398 for parent in realparents(rev):
1401 if parent in self.roots:
1399 if parent in self.roots:
1402 self.roots.add(rev)
1400 self.roots.add(rev)
1403 return True
1401 return True
1404 else:
1402 else:
1405 # backwards: all parents
1403 # backwards: all parents
1406 if not self.roots:
1404 if not self.roots:
1407 self.roots.update(realparents(self.startrev))
1405 self.roots.update(realparents(self.startrev))
1408 if rev in self.roots:
1406 if rev in self.roots:
1409 self.roots.remove(rev)
1407 self.roots.remove(rev)
1410 self.roots.update(realparents(rev))
1408 self.roots.update(realparents(rev))
1411 return True
1409 return True
1412
1410
1413 return False
1411 return False
1414
1412
1415 # it might be worthwhile to do this in the iterator if the rev range
1413 # it might be worthwhile to do this in the iterator if the rev range
1416 # is descending and the prune args are all within that range
1414 # is descending and the prune args are all within that range
1417 for rev in opts.get('prune', ()):
1415 for rev in opts.get('prune', ()):
1418 rev = repo[rev].rev()
1416 rev = repo[rev].rev()
1419 ff = followfilter()
1417 ff = followfilter()
1420 stop = min(revs[0], revs[-1])
1418 stop = min(revs[0], revs[-1])
1421 for x in xrange(rev, stop - 1, -1):
1419 for x in xrange(rev, stop - 1, -1):
1422 if ff.match(x):
1420 if ff.match(x):
1423 wanted = wanted - [x]
1421 wanted = wanted - [x]
1424
1422
1425 # Now that wanted is correctly initialized, we can iterate over the
1423 # Now that wanted is correctly initialized, we can iterate over the
1426 # revision range, yielding only revisions in wanted.
1424 # revision range, yielding only revisions in wanted.
1427 def iterate():
1425 def iterate():
1428 if follow and not match.files():
1426 if follow and not match.files():
1429 ff = followfilter(onlyfirst=opts.get('follow_first'))
1427 ff = followfilter(onlyfirst=opts.get('follow_first'))
1430 def want(rev):
1428 def want(rev):
1431 return ff.match(rev) and rev in wanted
1429 return ff.match(rev) and rev in wanted
1432 else:
1430 else:
1433 def want(rev):
1431 def want(rev):
1434 return rev in wanted
1432 return rev in wanted
1435
1433
1436 it = iter(revs)
1434 it = iter(revs)
1437 stopiteration = False
1435 stopiteration = False
1438 for windowsize in increasingwindows():
1436 for windowsize in increasingwindows():
1439 nrevs = []
1437 nrevs = []
1440 for i in xrange(windowsize):
1438 for i in xrange(windowsize):
1441 try:
1439 try:
1442 rev = it.next()
1440 rev = it.next()
1443 if want(rev):
1441 if want(rev):
1444 nrevs.append(rev)
1442 nrevs.append(rev)
1445 except (StopIteration):
1443 except (StopIteration):
1446 stopiteration = True
1444 stopiteration = True
1447 break
1445 break
1448 for rev in sorted(nrevs):
1446 for rev in sorted(nrevs):
1449 fns = fncache.get(rev)
1447 fns = fncache.get(rev)
1450 ctx = change(rev)
1448 ctx = change(rev)
1451 if not fns:
1449 if not fns:
1452 def fns_generator():
1450 def fns_generator():
1453 for f in ctx.files():
1451 for f in ctx.files():
1454 if match(f):
1452 if match(f):
1455 yield f
1453 yield f
1456 fns = fns_generator()
1454 fns = fns_generator()
1457 prepare(ctx, fns)
1455 prepare(ctx, fns)
1458 for rev in nrevs:
1456 for rev in nrevs:
1459 yield change(rev)
1457 yield change(rev)
1460
1458
1461 if stopiteration:
1459 if stopiteration:
1462 break
1460 break
1463
1461
1464 return iterate()
1462 return iterate()
1465
1463
1466 def _makelogfilematcher(repo, pats, followfirst):
1464 def _makelogfilematcher(repo, pats, followfirst):
1467 # When displaying a revision with --patch --follow FILE, we have
1465 # When displaying a revision with --patch --follow FILE, we have
1468 # to know which file of the revision must be diffed. With
1466 # to know which file of the revision must be diffed. With
1469 # --follow, we want the names of the ancestors of FILE in the
1467 # --follow, we want the names of the ancestors of FILE in the
1470 # revision, stored in "fcache". "fcache" is populated by
1468 # revision, stored in "fcache". "fcache" is populated by
1471 # reproducing the graph traversal already done by --follow revset
1469 # reproducing the graph traversal already done by --follow revset
1472 # and relating linkrevs to file names (which is not "correct" but
1470 # and relating linkrevs to file names (which is not "correct" but
1473 # good enough).
1471 # good enough).
1474 fcache = {}
1472 fcache = {}
1475 fcacheready = [False]
1473 fcacheready = [False]
1476 pctx = repo['.']
1474 pctx = repo['.']
1477 wctx = repo[None]
1475 wctx = repo[None]
1478
1476
1479 def populate():
1477 def populate():
1480 for fn in pats:
1478 for fn in pats:
1481 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1479 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1482 for c in i:
1480 for c in i:
1483 fcache.setdefault(c.linkrev(), set()).add(c.path())
1481 fcache.setdefault(c.linkrev(), set()).add(c.path())
1484
1482
1485 def filematcher(rev):
1483 def filematcher(rev):
1486 if not fcacheready[0]:
1484 if not fcacheready[0]:
1487 # Lazy initialization
1485 # Lazy initialization
1488 fcacheready[0] = True
1486 fcacheready[0] = True
1489 populate()
1487 populate()
1490 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1488 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1491
1489
1492 return filematcher
1490 return filematcher
1493
1491
1494 def _makelogrevset(repo, pats, opts, revs):
1492 def _makelogrevset(repo, pats, opts, revs):
1495 """Return (expr, filematcher) where expr is a revset string built
1493 """Return (expr, filematcher) where expr is a revset string built
1496 from log options and file patterns or None. If --stat or --patch
1494 from log options and file patterns or None. If --stat or --patch
1497 are not passed filematcher is None. Otherwise it is a callable
1495 are not passed filematcher is None. Otherwise it is a callable
1498 taking a revision number and returning a match objects filtering
1496 taking a revision number and returning a match objects filtering
1499 the files to be detailed when displaying the revision.
1497 the files to be detailed when displaying the revision.
1500 """
1498 """
1501 opt2revset = {
1499 opt2revset = {
1502 'no_merges': ('not merge()', None),
1500 'no_merges': ('not merge()', None),
1503 'only_merges': ('merge()', None),
1501 'only_merges': ('merge()', None),
1504 '_ancestors': ('ancestors(%(val)s)', None),
1502 '_ancestors': ('ancestors(%(val)s)', None),
1505 '_fancestors': ('_firstancestors(%(val)s)', None),
1503 '_fancestors': ('_firstancestors(%(val)s)', None),
1506 '_descendants': ('descendants(%(val)s)', None),
1504 '_descendants': ('descendants(%(val)s)', None),
1507 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1505 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1508 '_matchfiles': ('_matchfiles(%(val)s)', None),
1506 '_matchfiles': ('_matchfiles(%(val)s)', None),
1509 'date': ('date(%(val)r)', None),
1507 'date': ('date(%(val)r)', None),
1510 'branch': ('branch(%(val)r)', ' or '),
1508 'branch': ('branch(%(val)r)', ' or '),
1511 '_patslog': ('filelog(%(val)r)', ' or '),
1509 '_patslog': ('filelog(%(val)r)', ' or '),
1512 '_patsfollow': ('follow(%(val)r)', ' or '),
1510 '_patsfollow': ('follow(%(val)r)', ' or '),
1513 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1511 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1514 'keyword': ('keyword(%(val)r)', ' or '),
1512 'keyword': ('keyword(%(val)r)', ' or '),
1515 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1513 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1516 'user': ('user(%(val)r)', ' or '),
1514 'user': ('user(%(val)r)', ' or '),
1517 }
1515 }
1518
1516
1519 opts = dict(opts)
1517 opts = dict(opts)
1520 # follow or not follow?
1518 # follow or not follow?
1521 follow = opts.get('follow') or opts.get('follow_first')
1519 follow = opts.get('follow') or opts.get('follow_first')
1522 followfirst = opts.get('follow_first') and 1 or 0
1520 followfirst = opts.get('follow_first') and 1 or 0
1523 # --follow with FILE behaviour depends on revs...
1521 # --follow with FILE behaviour depends on revs...
1524 it = iter(revs)
1522 it = iter(revs)
1525 startrev = it.next()
1523 startrev = it.next()
1526 try:
1524 try:
1527 followdescendants = startrev < it.next()
1525 followdescendants = startrev < it.next()
1528 except (StopIteration):
1526 except (StopIteration):
1529 followdescendants = False
1527 followdescendants = False
1530
1528
1531 # branch and only_branch are really aliases and must be handled at
1529 # branch and only_branch are really aliases and must be handled at
1532 # the same time
1530 # the same time
1533 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1531 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1534 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1532 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1535 # pats/include/exclude are passed to match.match() directly in
1533 # pats/include/exclude are passed to match.match() directly in
1536 # _matchfiles() revset but walkchangerevs() builds its matcher with
1534 # _matchfiles() revset but walkchangerevs() builds its matcher with
1537 # scmutil.match(). The difference is input pats are globbed on
1535 # scmutil.match(). The difference is input pats are globbed on
1538 # platforms without shell expansion (windows).
1536 # platforms without shell expansion (windows).
1539 pctx = repo[None]
1537 pctx = repo[None]
1540 match, pats = scmutil.matchandpats(pctx, pats, opts)
1538 match, pats = scmutil.matchandpats(pctx, pats, opts)
1541 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1539 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1542 if not slowpath:
1540 if not slowpath:
1543 for f in match.files():
1541 for f in match.files():
1544 if follow and f not in pctx:
1542 if follow and f not in pctx:
1545 raise util.Abort(_('cannot follow file not in parent '
1543 raise util.Abort(_('cannot follow file not in parent '
1546 'revision: "%s"') % f)
1544 'revision: "%s"') % f)
1547 filelog = repo.file(f)
1545 filelog = repo.file(f)
1548 if not filelog:
1546 if not filelog:
1549 # A zero count may be a directory or deleted file, so
1547 # A zero count may be a directory or deleted file, so
1550 # try to find matching entries on the slow path.
1548 # try to find matching entries on the slow path.
1551 if follow:
1549 if follow:
1552 raise util.Abort(
1550 raise util.Abort(
1553 _('cannot follow nonexistent file: "%s"') % f)
1551 _('cannot follow nonexistent file: "%s"') % f)
1554 slowpath = True
1552 slowpath = True
1555
1553
1556 # We decided to fall back to the slowpath because at least one
1554 # We decided to fall back to the slowpath because at least one
1557 # of the paths was not a file. Check to see if at least one of them
1555 # of the paths was not a file. Check to see if at least one of them
1558 # existed in history - in that case, we'll continue down the
1556 # existed in history - in that case, we'll continue down the
1559 # slowpath; otherwise, we can turn off the slowpath
1557 # slowpath; otherwise, we can turn off the slowpath
1560 if slowpath:
1558 if slowpath:
1561 for path in match.files():
1559 for path in match.files():
1562 if path == '.' or path in repo.store:
1560 if path == '.' or path in repo.store:
1563 break
1561 break
1564 else:
1562 else:
1565 slowpath = False
1563 slowpath = False
1566
1564
1567 if slowpath:
1565 if slowpath:
1568 # See walkchangerevs() slow path.
1566 # See walkchangerevs() slow path.
1569 #
1567 #
1570 if follow:
1568 if follow:
1571 raise util.Abort(_('can only follow copies/renames for explicit '
1569 raise util.Abort(_('can only follow copies/renames for explicit '
1572 'filenames'))
1570 'filenames'))
1573 # pats/include/exclude cannot be represented as separate
1571 # pats/include/exclude cannot be represented as separate
1574 # revset expressions as their filtering logic applies at file
1572 # revset expressions as their filtering logic applies at file
1575 # level. For instance "-I a -X a" matches a revision touching
1573 # level. For instance "-I a -X a" matches a revision touching
1576 # "a" and "b" while "file(a) and not file(b)" does
1574 # "a" and "b" while "file(a) and not file(b)" does
1577 # not. Besides, filesets are evaluated against the working
1575 # not. Besides, filesets are evaluated against the working
1578 # directory.
1576 # directory.
1579 matchargs = ['r:', 'd:relpath']
1577 matchargs = ['r:', 'd:relpath']
1580 for p in pats:
1578 for p in pats:
1581 matchargs.append('p:' + p)
1579 matchargs.append('p:' + p)
1582 for p in opts.get('include', []):
1580 for p in opts.get('include', []):
1583 matchargs.append('i:' + p)
1581 matchargs.append('i:' + p)
1584 for p in opts.get('exclude', []):
1582 for p in opts.get('exclude', []):
1585 matchargs.append('x:' + p)
1583 matchargs.append('x:' + p)
1586 matchargs = ','.join(('%r' % p) for p in matchargs)
1584 matchargs = ','.join(('%r' % p) for p in matchargs)
1587 opts['_matchfiles'] = matchargs
1585 opts['_matchfiles'] = matchargs
1588 else:
1586 else:
1589 if follow:
1587 if follow:
1590 fpats = ('_patsfollow', '_patsfollowfirst')
1588 fpats = ('_patsfollow', '_patsfollowfirst')
1591 fnopats = (('_ancestors', '_fancestors'),
1589 fnopats = (('_ancestors', '_fancestors'),
1592 ('_descendants', '_fdescendants'))
1590 ('_descendants', '_fdescendants'))
1593 if pats:
1591 if pats:
1594 # follow() revset interprets its file argument as a
1592 # follow() revset interprets its file argument as a
1595 # manifest entry, so use match.files(), not pats.
1593 # manifest entry, so use match.files(), not pats.
1596 opts[fpats[followfirst]] = list(match.files())
1594 opts[fpats[followfirst]] = list(match.files())
1597 else:
1595 else:
1598 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1596 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1599 else:
1597 else:
1600 opts['_patslog'] = list(pats)
1598 opts['_patslog'] = list(pats)
1601
1599
1602 filematcher = None
1600 filematcher = None
1603 if opts.get('patch') or opts.get('stat'):
1601 if opts.get('patch') or opts.get('stat'):
1604 if follow:
1602 if follow:
1605 filematcher = _makelogfilematcher(repo, pats, followfirst)
1603 filematcher = _makelogfilematcher(repo, pats, followfirst)
1606 else:
1604 else:
1607 filematcher = lambda rev: match
1605 filematcher = lambda rev: match
1608
1606
1609 expr = []
1607 expr = []
1610 for op, val in opts.iteritems():
1608 for op, val in opts.iteritems():
1611 if not val:
1609 if not val:
1612 continue
1610 continue
1613 if op not in opt2revset:
1611 if op not in opt2revset:
1614 continue
1612 continue
1615 revop, andor = opt2revset[op]
1613 revop, andor = opt2revset[op]
1616 if '%(val)' not in revop:
1614 if '%(val)' not in revop:
1617 expr.append(revop)
1615 expr.append(revop)
1618 else:
1616 else:
1619 if not isinstance(val, list):
1617 if not isinstance(val, list):
1620 e = revop % {'val': val}
1618 e = revop % {'val': val}
1621 else:
1619 else:
1622 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1620 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1623 expr.append(e)
1621 expr.append(e)
1624
1622
1625 if expr:
1623 if expr:
1626 expr = '(' + ' and '.join(expr) + ')'
1624 expr = '(' + ' and '.join(expr) + ')'
1627 else:
1625 else:
1628 expr = None
1626 expr = None
1629 return expr, filematcher
1627 return expr, filematcher
1630
1628
1631 def getgraphlogrevs(repo, pats, opts):
1629 def getgraphlogrevs(repo, pats, opts):
1632 """Return (revs, expr, filematcher) where revs is an iterable of
1630 """Return (revs, expr, filematcher) where revs is an iterable of
1633 revision numbers, expr is a revset string built from log options
1631 revision numbers, expr is a revset string built from log options
1634 and file patterns or None, and used to filter 'revs'. If --stat or
1632 and file patterns or None, and used to filter 'revs'. If --stat or
1635 --patch are not passed filematcher is None. Otherwise it is a
1633 --patch are not passed filematcher is None. Otherwise it is a
1636 callable taking a revision number and returning a match objects
1634 callable taking a revision number and returning a match objects
1637 filtering the files to be detailed when displaying the revision.
1635 filtering the files to be detailed when displaying the revision.
1638 """
1636 """
1639 if not len(repo):
1637 if not len(repo):
1640 return [], None, None
1638 return [], None, None
1641 limit = loglimit(opts)
1639 limit = loglimit(opts)
1642 # Default --rev value depends on --follow but --follow behaviour
1640 # Default --rev value depends on --follow but --follow behaviour
1643 # depends on revisions resolved from --rev...
1641 # depends on revisions resolved from --rev...
1644 follow = opts.get('follow') or opts.get('follow_first')
1642 follow = opts.get('follow') or opts.get('follow_first')
1645 possiblyunsorted = False # whether revs might need sorting
1643 possiblyunsorted = False # whether revs might need sorting
1646 if opts.get('rev'):
1644 if opts.get('rev'):
1647 revs = scmutil.revrange(repo, opts['rev'])
1645 revs = scmutil.revrange(repo, opts['rev'])
1648 # Don't sort here because _makelogrevset might depend on the
1646 # Don't sort here because _makelogrevset might depend on the
1649 # order of revs
1647 # order of revs
1650 possiblyunsorted = True
1648 possiblyunsorted = True
1651 else:
1649 else:
1652 if follow and len(repo) > 0:
1650 if follow and len(repo) > 0:
1653 revs = repo.revs('reverse(:.)')
1651 revs = repo.revs('reverse(:.)')
1654 else:
1652 else:
1655 revs = revset.spanset(repo)
1653 revs = revset.spanset(repo)
1656 revs.reverse()
1654 revs.reverse()
1657 if not revs:
1655 if not revs:
1658 return revset.baseset(), None, None
1656 return revset.baseset(), None, None
1659 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1657 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1660 if possiblyunsorted:
1658 if possiblyunsorted:
1661 revs.sort(reverse=True)
1659 revs.sort(reverse=True)
1662 if expr:
1660 if expr:
1663 # Revset matchers often operate faster on revisions in changelog
1661 # Revset matchers often operate faster on revisions in changelog
1664 # order, because most filters deal with the changelog.
1662 # order, because most filters deal with the changelog.
1665 revs.reverse()
1663 revs.reverse()
1666 matcher = revset.match(repo.ui, expr)
1664 matcher = revset.match(repo.ui, expr)
1667 # Revset matches can reorder revisions. "A or B" typically returns
1665 # Revset matches can reorder revisions. "A or B" typically returns
1668 # returns the revision matching A then the revision matching B. Sort
1666 # returns the revision matching A then the revision matching B. Sort
1669 # again to fix that.
1667 # again to fix that.
1670 revs = matcher(repo, revs)
1668 revs = matcher(repo, revs)
1671 revs.sort(reverse=True)
1669 revs.sort(reverse=True)
1672 if limit is not None:
1670 if limit is not None:
1673 limitedrevs = revset.baseset()
1671 limitedrevs = revset.baseset()
1674 for idx, rev in enumerate(revs):
1672 for idx, rev in enumerate(revs):
1675 if idx >= limit:
1673 if idx >= limit:
1676 break
1674 break
1677 limitedrevs.append(rev)
1675 limitedrevs.append(rev)
1678 revs = limitedrevs
1676 revs = limitedrevs
1679
1677
1680 return revs, expr, filematcher
1678 return revs, expr, filematcher
1681
1679
1682 def getlogrevs(repo, pats, opts):
1680 def getlogrevs(repo, pats, opts):
1683 """Return (revs, expr, filematcher) where revs is an iterable of
1681 """Return (revs, expr, filematcher) where revs is an iterable of
1684 revision numbers, expr is a revset string built from log options
1682 revision numbers, expr is a revset string built from log options
1685 and file patterns or None, and used to filter 'revs'. If --stat or
1683 and file patterns or None, and used to filter 'revs'. If --stat or
1686 --patch are not passed filematcher is None. Otherwise it is a
1684 --patch are not passed filematcher is None. Otherwise it is a
1687 callable taking a revision number and returning a match objects
1685 callable taking a revision number and returning a match objects
1688 filtering the files to be detailed when displaying the revision.
1686 filtering the files to be detailed when displaying the revision.
1689 """
1687 """
1690 limit = loglimit(opts)
1688 limit = loglimit(opts)
1691 # Default --rev value depends on --follow but --follow behaviour
1689 # Default --rev value depends on --follow but --follow behaviour
1692 # depends on revisions resolved from --rev...
1690 # depends on revisions resolved from --rev...
1693 follow = opts.get('follow') or opts.get('follow_first')
1691 follow = opts.get('follow') or opts.get('follow_first')
1694 if opts.get('rev'):
1692 if opts.get('rev'):
1695 revs = scmutil.revrange(repo, opts['rev'])
1693 revs = scmutil.revrange(repo, opts['rev'])
1696 elif follow:
1694 elif follow:
1697 revs = revset.baseset(repo.revs('reverse(:.)'))
1695 revs = revset.baseset(repo.revs('reverse(:.)'))
1698 else:
1696 else:
1699 revs = revset.spanset(repo)
1697 revs = revset.spanset(repo)
1700 revs.reverse()
1698 revs.reverse()
1701 if not revs:
1699 if not revs:
1702 return revset.baseset([]), None, None
1700 return revset.baseset([]), None, None
1703 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1701 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1704 if expr:
1702 if expr:
1705 # Revset matchers often operate faster on revisions in changelog
1703 # Revset matchers often operate faster on revisions in changelog
1706 # order, because most filters deal with the changelog.
1704 # order, because most filters deal with the changelog.
1707 if not opts.get('rev'):
1705 if not opts.get('rev'):
1708 revs.reverse()
1706 revs.reverse()
1709 matcher = revset.match(repo.ui, expr)
1707 matcher = revset.match(repo.ui, expr)
1710 # Revset matches can reorder revisions. "A or B" typically returns
1708 # Revset matches can reorder revisions. "A or B" typically returns
1711 # returns the revision matching A then the revision matching B. Sort
1709 # returns the revision matching A then the revision matching B. Sort
1712 # again to fix that.
1710 # again to fix that.
1713 revs = matcher(repo, revs)
1711 revs = matcher(repo, revs)
1714 if not opts.get('rev'):
1712 if not opts.get('rev'):
1715 revs.sort(reverse=True)
1713 revs.sort(reverse=True)
1716 if limit is not None:
1714 if limit is not None:
1717 count = 0
1715 count = 0
1718 limitedrevs = revset.baseset([])
1716 limitedrevs = revset.baseset([])
1719 it = iter(revs)
1717 it = iter(revs)
1720 while count < limit:
1718 while count < limit:
1721 try:
1719 try:
1722 limitedrevs.append(it.next())
1720 limitedrevs.append(it.next())
1723 except (StopIteration):
1721 except (StopIteration):
1724 break
1722 break
1725 count += 1
1723 count += 1
1726 revs = limitedrevs
1724 revs = limitedrevs
1727
1725
1728 return revs, expr, filematcher
1726 return revs, expr, filematcher
1729
1727
1730 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1728 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1731 filematcher=None):
1729 filematcher=None):
1732 seen, state = [], graphmod.asciistate()
1730 seen, state = [], graphmod.asciistate()
1733 for rev, type, ctx, parents in dag:
1731 for rev, type, ctx, parents in dag:
1734 char = 'o'
1732 char = 'o'
1735 if ctx.node() in showparents:
1733 if ctx.node() in showparents:
1736 char = '@'
1734 char = '@'
1737 elif ctx.obsolete():
1735 elif ctx.obsolete():
1738 char = 'x'
1736 char = 'x'
1739 copies = None
1737 copies = None
1740 if getrenamed and ctx.rev():
1738 if getrenamed and ctx.rev():
1741 copies = []
1739 copies = []
1742 for fn in ctx.files():
1740 for fn in ctx.files():
1743 rename = getrenamed(fn, ctx.rev())
1741 rename = getrenamed(fn, ctx.rev())
1744 if rename:
1742 if rename:
1745 copies.append((fn, rename[0]))
1743 copies.append((fn, rename[0]))
1746 revmatchfn = None
1744 revmatchfn = None
1747 if filematcher is not None:
1745 if filematcher is not None:
1748 revmatchfn = filematcher(ctx.rev())
1746 revmatchfn = filematcher(ctx.rev())
1749 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1747 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1750 lines = displayer.hunk.pop(rev).split('\n')
1748 lines = displayer.hunk.pop(rev).split('\n')
1751 if not lines[-1]:
1749 if not lines[-1]:
1752 del lines[-1]
1750 del lines[-1]
1753 displayer.flush(rev)
1751 displayer.flush(rev)
1754 edges = edgefn(type, char, lines, seen, rev, parents)
1752 edges = edgefn(type, char, lines, seen, rev, parents)
1755 for type, char, lines, coldata in edges:
1753 for type, char, lines, coldata in edges:
1756 graphmod.ascii(ui, state, type, char, lines, coldata)
1754 graphmod.ascii(ui, state, type, char, lines, coldata)
1757 displayer.close()
1755 displayer.close()
1758
1756
1759 def graphlog(ui, repo, *pats, **opts):
1757 def graphlog(ui, repo, *pats, **opts):
1760 # Parameters are identical to log command ones
1758 # Parameters are identical to log command ones
1761 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1759 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1762 revdag = graphmod.dagwalker(repo, revs)
1760 revdag = graphmod.dagwalker(repo, revs)
1763
1761
1764 getrenamed = None
1762 getrenamed = None
1765 if opts.get('copies'):
1763 if opts.get('copies'):
1766 endrev = None
1764 endrev = None
1767 if opts.get('rev'):
1765 if opts.get('rev'):
1768 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1766 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1769 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1767 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1770 displayer = show_changeset(ui, repo, opts, buffered=True)
1768 displayer = show_changeset(ui, repo, opts, buffered=True)
1771 showparents = [ctx.node() for ctx in repo[None].parents()]
1769 showparents = [ctx.node() for ctx in repo[None].parents()]
1772 displaygraph(ui, revdag, displayer, showparents,
1770 displaygraph(ui, revdag, displayer, showparents,
1773 graphmod.asciiedges, getrenamed, filematcher)
1771 graphmod.asciiedges, getrenamed, filematcher)
1774
1772
1775 def checkunsupportedgraphflags(pats, opts):
1773 def checkunsupportedgraphflags(pats, opts):
1776 for op in ["newest_first"]:
1774 for op in ["newest_first"]:
1777 if op in opts and opts[op]:
1775 if op in opts and opts[op]:
1778 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1776 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1779 % op.replace("_", "-"))
1777 % op.replace("_", "-"))
1780
1778
1781 def graphrevs(repo, nodes, opts):
1779 def graphrevs(repo, nodes, opts):
1782 limit = loglimit(opts)
1780 limit = loglimit(opts)
1783 nodes.reverse()
1781 nodes.reverse()
1784 if limit is not None:
1782 if limit is not None:
1785 nodes = nodes[:limit]
1783 nodes = nodes[:limit]
1786 return graphmod.nodes(repo, nodes)
1784 return graphmod.nodes(repo, nodes)
1787
1785
1788 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1786 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1789 join = lambda f: os.path.join(prefix, f)
1787 join = lambda f: os.path.join(prefix, f)
1790 bad = []
1788 bad = []
1791 oldbad = match.bad
1789 oldbad = match.bad
1792 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1790 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1793 names = []
1791 names = []
1794 wctx = repo[None]
1792 wctx = repo[None]
1795 cca = None
1793 cca = None
1796 abort, warn = scmutil.checkportabilityalert(ui)
1794 abort, warn = scmutil.checkportabilityalert(ui)
1797 if abort or warn:
1795 if abort or warn:
1798 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1796 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1799 for f in repo.walk(match):
1797 for f in repo.walk(match):
1800 exact = match.exact(f)
1798 exact = match.exact(f)
1801 if exact or not explicitonly and f not in repo.dirstate:
1799 if exact or not explicitonly and f not in repo.dirstate:
1802 if cca:
1800 if cca:
1803 cca(f)
1801 cca(f)
1804 names.append(f)
1802 names.append(f)
1805 if ui.verbose or not exact:
1803 if ui.verbose or not exact:
1806 ui.status(_('adding %s\n') % match.rel(join(f)))
1804 ui.status(_('adding %s\n') % match.rel(join(f)))
1807
1805
1808 for subpath in sorted(wctx.substate):
1806 for subpath in sorted(wctx.substate):
1809 sub = wctx.sub(subpath)
1807 sub = wctx.sub(subpath)
1810 try:
1808 try:
1811 submatch = matchmod.narrowmatcher(subpath, match)
1809 submatch = matchmod.narrowmatcher(subpath, match)
1812 if listsubrepos:
1810 if listsubrepos:
1813 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1811 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1814 False))
1812 False))
1815 else:
1813 else:
1816 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1814 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1817 True))
1815 True))
1818 except error.LookupError:
1816 except error.LookupError:
1819 ui.status(_("skipping missing subrepository: %s\n")
1817 ui.status(_("skipping missing subrepository: %s\n")
1820 % join(subpath))
1818 % join(subpath))
1821
1819
1822 if not dryrun:
1820 if not dryrun:
1823 rejected = wctx.add(names, prefix)
1821 rejected = wctx.add(names, prefix)
1824 bad.extend(f for f in rejected if f in match.files())
1822 bad.extend(f for f in rejected if f in match.files())
1825 return bad
1823 return bad
1826
1824
1827 def forget(ui, repo, match, prefix, explicitonly):
1825 def forget(ui, repo, match, prefix, explicitonly):
1828 join = lambda f: os.path.join(prefix, f)
1826 join = lambda f: os.path.join(prefix, f)
1829 bad = []
1827 bad = []
1830 oldbad = match.bad
1828 oldbad = match.bad
1831 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1829 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1832 wctx = repo[None]
1830 wctx = repo[None]
1833 forgot = []
1831 forgot = []
1834 s = repo.status(match=match, clean=True)
1832 s = repo.status(match=match, clean=True)
1835 forget = sorted(s[0] + s[1] + s[3] + s[6])
1833 forget = sorted(s[0] + s[1] + s[3] + s[6])
1836 if explicitonly:
1834 if explicitonly:
1837 forget = [f for f in forget if match.exact(f)]
1835 forget = [f for f in forget if match.exact(f)]
1838
1836
1839 for subpath in sorted(wctx.substate):
1837 for subpath in sorted(wctx.substate):
1840 sub = wctx.sub(subpath)
1838 sub = wctx.sub(subpath)
1841 try:
1839 try:
1842 submatch = matchmod.narrowmatcher(subpath, match)
1840 submatch = matchmod.narrowmatcher(subpath, match)
1843 subbad, subforgot = sub.forget(ui, submatch, prefix)
1841 subbad, subforgot = sub.forget(ui, submatch, prefix)
1844 bad.extend([subpath + '/' + f for f in subbad])
1842 bad.extend([subpath + '/' + f for f in subbad])
1845 forgot.extend([subpath + '/' + f for f in subforgot])
1843 forgot.extend([subpath + '/' + f for f in subforgot])
1846 except error.LookupError:
1844 except error.LookupError:
1847 ui.status(_("skipping missing subrepository: %s\n")
1845 ui.status(_("skipping missing subrepository: %s\n")
1848 % join(subpath))
1846 % join(subpath))
1849
1847
1850 if not explicitonly:
1848 if not explicitonly:
1851 for f in match.files():
1849 for f in match.files():
1852 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1850 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1853 if f not in forgot:
1851 if f not in forgot:
1854 if os.path.exists(match.rel(join(f))):
1852 if os.path.exists(match.rel(join(f))):
1855 ui.warn(_('not removing %s: '
1853 ui.warn(_('not removing %s: '
1856 'file is already untracked\n')
1854 'file is already untracked\n')
1857 % match.rel(join(f)))
1855 % match.rel(join(f)))
1858 bad.append(f)
1856 bad.append(f)
1859
1857
1860 for f in forget:
1858 for f in forget:
1861 if ui.verbose or not match.exact(f):
1859 if ui.verbose or not match.exact(f):
1862 ui.status(_('removing %s\n') % match.rel(join(f)))
1860 ui.status(_('removing %s\n') % match.rel(join(f)))
1863
1861
1864 rejected = wctx.forget(forget, prefix)
1862 rejected = wctx.forget(forget, prefix)
1865 bad.extend(f for f in rejected if f in match.files())
1863 bad.extend(f for f in rejected if f in match.files())
1866 forgot.extend(forget)
1864 forgot.extend(forget)
1867 return bad, forgot
1865 return bad, forgot
1868
1866
1869 def cat(ui, repo, ctx, matcher, prefix, **opts):
1867 def cat(ui, repo, ctx, matcher, prefix, **opts):
1870 err = 1
1868 err = 1
1871
1869
1872 def write(path):
1870 def write(path):
1873 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1871 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1874 pathname=os.path.join(prefix, path))
1872 pathname=os.path.join(prefix, path))
1875 data = ctx[path].data()
1873 data = ctx[path].data()
1876 if opts.get('decode'):
1874 if opts.get('decode'):
1877 data = repo.wwritedata(path, data)
1875 data = repo.wwritedata(path, data)
1878 fp.write(data)
1876 fp.write(data)
1879 fp.close()
1877 fp.close()
1880
1878
1881 # Automation often uses hg cat on single files, so special case it
1879 # Automation often uses hg cat on single files, so special case it
1882 # for performance to avoid the cost of parsing the manifest.
1880 # for performance to avoid the cost of parsing the manifest.
1883 if len(matcher.files()) == 1 and not matcher.anypats():
1881 if len(matcher.files()) == 1 and not matcher.anypats():
1884 file = matcher.files()[0]
1882 file = matcher.files()[0]
1885 mf = repo.manifest
1883 mf = repo.manifest
1886 mfnode = ctx._changeset[0]
1884 mfnode = ctx._changeset[0]
1887 if mf.find(mfnode, file)[0]:
1885 if mf.find(mfnode, file)[0]:
1888 write(file)
1886 write(file)
1889 return 0
1887 return 0
1890
1888
1891 # Don't warn about "missing" files that are really in subrepos
1889 # Don't warn about "missing" files that are really in subrepos
1892 bad = matcher.bad
1890 bad = matcher.bad
1893
1891
1894 def badfn(path, msg):
1892 def badfn(path, msg):
1895 for subpath in ctx.substate:
1893 for subpath in ctx.substate:
1896 if path.startswith(subpath):
1894 if path.startswith(subpath):
1897 return
1895 return
1898 bad(path, msg)
1896 bad(path, msg)
1899
1897
1900 matcher.bad = badfn
1898 matcher.bad = badfn
1901
1899
1902 for abs in ctx.walk(matcher):
1900 for abs in ctx.walk(matcher):
1903 write(abs)
1901 write(abs)
1904 err = 0
1902 err = 0
1905
1903
1906 matcher.bad = bad
1904 matcher.bad = bad
1907
1905
1908 for subpath in sorted(ctx.substate):
1906 for subpath in sorted(ctx.substate):
1909 sub = ctx.sub(subpath)
1907 sub = ctx.sub(subpath)
1910 try:
1908 try:
1911 submatch = matchmod.narrowmatcher(subpath, matcher)
1909 submatch = matchmod.narrowmatcher(subpath, matcher)
1912
1910
1913 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1911 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1914 **opts):
1912 **opts):
1915 err = 0
1913 err = 0
1916 except error.RepoLookupError:
1914 except error.RepoLookupError:
1917 ui.status(_("skipping missing subrepository: %s\n")
1915 ui.status(_("skipping missing subrepository: %s\n")
1918 % os.path.join(prefix, subpath))
1916 % os.path.join(prefix, subpath))
1919
1917
1920 return err
1918 return err
1921
1919
1922 def duplicatecopies(repo, rev, fromrev):
1920 def duplicatecopies(repo, rev, fromrev):
1923 '''reproduce copies from fromrev to rev in the dirstate'''
1921 '''reproduce copies from fromrev to rev in the dirstate'''
1924 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1922 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1925 # copies.pathcopies returns backward renames, so dst might not
1923 # copies.pathcopies returns backward renames, so dst might not
1926 # actually be in the dirstate
1924 # actually be in the dirstate
1927 if repo.dirstate[dst] in "nma":
1925 if repo.dirstate[dst] in "nma":
1928 repo.dirstate.copy(src, dst)
1926 repo.dirstate.copy(src, dst)
1929
1927
1930 def commit(ui, repo, commitfunc, pats, opts):
1928 def commit(ui, repo, commitfunc, pats, opts):
1931 '''commit the specified files or all outstanding changes'''
1929 '''commit the specified files or all outstanding changes'''
1932 date = opts.get('date')
1930 date = opts.get('date')
1933 if date:
1931 if date:
1934 opts['date'] = util.parsedate(date)
1932 opts['date'] = util.parsedate(date)
1935 message = logmessage(ui, opts)
1933 message = logmessage(ui, opts)
1936
1934
1937 # extract addremove carefully -- this function can be called from a command
1935 # extract addremove carefully -- this function can be called from a command
1938 # that doesn't support addremove
1936 # that doesn't support addremove
1939 if opts.get('addremove'):
1937 if opts.get('addremove'):
1940 scmutil.addremove(repo, pats, opts)
1938 scmutil.addremove(repo, pats, opts)
1941
1939
1942 return commitfunc(ui, repo, message,
1940 return commitfunc(ui, repo, message,
1943 scmutil.match(repo[None], pats, opts), opts)
1941 scmutil.match(repo[None], pats, opts), opts)
1944
1942
1945 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1943 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1946 ui.note(_('amending changeset %s\n') % old)
1944 ui.note(_('amending changeset %s\n') % old)
1947 base = old.p1()
1945 base = old.p1()
1948
1946
1949 wlock = lock = newid = None
1947 wlock = lock = newid = None
1950 try:
1948 try:
1951 wlock = repo.wlock()
1949 wlock = repo.wlock()
1952 lock = repo.lock()
1950 lock = repo.lock()
1953 tr = repo.transaction('amend')
1951 tr = repo.transaction('amend')
1954 try:
1952 try:
1955 # See if we got a message from -m or -l, if not, open the editor
1953 # See if we got a message from -m or -l, if not, open the editor
1956 # with the message of the changeset to amend
1954 # with the message of the changeset to amend
1957 message = logmessage(ui, opts)
1955 message = logmessage(ui, opts)
1958 # ensure logfile does not conflict with later enforcement of the
1956 # ensure logfile does not conflict with later enforcement of the
1959 # message. potential logfile content has been processed by
1957 # message. potential logfile content has been processed by
1960 # `logmessage` anyway.
1958 # `logmessage` anyway.
1961 opts.pop('logfile')
1959 opts.pop('logfile')
1962 # First, do a regular commit to record all changes in the working
1960 # First, do a regular commit to record all changes in the working
1963 # directory (if there are any)
1961 # directory (if there are any)
1964 ui.callhooks = False
1962 ui.callhooks = False
1965 currentbookmark = repo._bookmarkcurrent
1963 currentbookmark = repo._bookmarkcurrent
1966 try:
1964 try:
1967 repo._bookmarkcurrent = None
1965 repo._bookmarkcurrent = None
1968 opts['message'] = 'temporary amend commit for %s' % old
1966 opts['message'] = 'temporary amend commit for %s' % old
1969 node = commit(ui, repo, commitfunc, pats, opts)
1967 node = commit(ui, repo, commitfunc, pats, opts)
1970 finally:
1968 finally:
1971 repo._bookmarkcurrent = currentbookmark
1969 repo._bookmarkcurrent = currentbookmark
1972 ui.callhooks = True
1970 ui.callhooks = True
1973 ctx = repo[node]
1971 ctx = repo[node]
1974
1972
1975 # Participating changesets:
1973 # Participating changesets:
1976 #
1974 #
1977 # node/ctx o - new (intermediate) commit that contains changes
1975 # node/ctx o - new (intermediate) commit that contains changes
1978 # | from working dir to go into amending commit
1976 # | from working dir to go into amending commit
1979 # | (or a workingctx if there were no changes)
1977 # | (or a workingctx if there were no changes)
1980 # |
1978 # |
1981 # old o - changeset to amend
1979 # old o - changeset to amend
1982 # |
1980 # |
1983 # base o - parent of amending changeset
1981 # base o - parent of amending changeset
1984
1982
1985 # Update extra dict from amended commit (e.g. to preserve graft
1983 # Update extra dict from amended commit (e.g. to preserve graft
1986 # source)
1984 # source)
1987 extra.update(old.extra())
1985 extra.update(old.extra())
1988
1986
1989 # Also update it from the intermediate commit or from the wctx
1987 # Also update it from the intermediate commit or from the wctx
1990 extra.update(ctx.extra())
1988 extra.update(ctx.extra())
1991
1989
1992 if len(old.parents()) > 1:
1990 if len(old.parents()) > 1:
1993 # ctx.files() isn't reliable for merges, so fall back to the
1991 # ctx.files() isn't reliable for merges, so fall back to the
1994 # slower repo.status() method
1992 # slower repo.status() method
1995 files = set([fn for st in repo.status(base, old)[:3]
1993 files = set([fn for st in repo.status(base, old)[:3]
1996 for fn in st])
1994 for fn in st])
1997 else:
1995 else:
1998 files = set(old.files())
1996 files = set(old.files())
1999
1997
2000 # Second, we use either the commit we just did, or if there were no
1998 # Second, we use either the commit we just did, or if there were no
2001 # changes the parent of the working directory as the version of the
1999 # changes the parent of the working directory as the version of the
2002 # files in the final amend commit
2000 # files in the final amend commit
2003 if node:
2001 if node:
2004 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2002 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2005
2003
2006 user = ctx.user()
2004 user = ctx.user()
2007 date = ctx.date()
2005 date = ctx.date()
2008 # Recompute copies (avoid recording a -> b -> a)
2006 # Recompute copies (avoid recording a -> b -> a)
2009 copied = copies.pathcopies(base, ctx)
2007 copied = copies.pathcopies(base, ctx)
2010
2008
2011 # Prune files which were reverted by the updates: if old
2009 # Prune files which were reverted by the updates: if old
2012 # introduced file X and our intermediate commit, node,
2010 # introduced file X and our intermediate commit, node,
2013 # renamed that file, then those two files are the same and
2011 # renamed that file, then those two files are the same and
2014 # we can discard X from our list of files. Likewise if X
2012 # we can discard X from our list of files. Likewise if X
2015 # was deleted, it's no longer relevant
2013 # was deleted, it's no longer relevant
2016 files.update(ctx.files())
2014 files.update(ctx.files())
2017
2015
2018 def samefile(f):
2016 def samefile(f):
2019 if f in ctx.manifest():
2017 if f in ctx.manifest():
2020 a = ctx.filectx(f)
2018 a = ctx.filectx(f)
2021 if f in base.manifest():
2019 if f in base.manifest():
2022 b = base.filectx(f)
2020 b = base.filectx(f)
2023 return (not a.cmp(b)
2021 return (not a.cmp(b)
2024 and a.flags() == b.flags())
2022 and a.flags() == b.flags())
2025 else:
2023 else:
2026 return False
2024 return False
2027 else:
2025 else:
2028 return f not in base.manifest()
2026 return f not in base.manifest()
2029 files = [f for f in files if not samefile(f)]
2027 files = [f for f in files if not samefile(f)]
2030
2028
2031 def filectxfn(repo, ctx_, path):
2029 def filectxfn(repo, ctx_, path):
2032 try:
2030 try:
2033 fctx = ctx[path]
2031 fctx = ctx[path]
2034 flags = fctx.flags()
2032 flags = fctx.flags()
2035 mctx = context.memfilectx(fctx.path(), fctx.data(),
2033 mctx = context.memfilectx(fctx.path(), fctx.data(),
2036 islink='l' in flags,
2034 islink='l' in flags,
2037 isexec='x' in flags,
2035 isexec='x' in flags,
2038 copied=copied.get(path))
2036 copied=copied.get(path))
2039 return mctx
2037 return mctx
2040 except KeyError:
2038 except KeyError:
2041 raise IOError
2039 raise IOError
2042 else:
2040 else:
2043 ui.note(_('copying changeset %s to %s\n') % (old, base))
2041 ui.note(_('copying changeset %s to %s\n') % (old, base))
2044
2042
2045 # Use version of files as in the old cset
2043 # Use version of files as in the old cset
2046 def filectxfn(repo, ctx_, path):
2044 def filectxfn(repo, ctx_, path):
2047 try:
2045 try:
2048 return old.filectx(path)
2046 return old.filectx(path)
2049 except KeyError:
2047 except KeyError:
2050 raise IOError
2048 raise IOError
2051
2049
2052 user = opts.get('user') or old.user()
2050 user = opts.get('user') or old.user()
2053 date = opts.get('date') or old.date()
2051 date = opts.get('date') or old.date()
2054 editor = getcommiteditor(**opts)
2052 editor = getcommiteditor(**opts)
2055 if not message:
2053 if not message:
2056 editor = getcommiteditor(edit=True)
2054 editor = getcommiteditor(edit=True)
2057 message = old.description()
2055 message = old.description()
2058
2056
2059 pureextra = extra.copy()
2057 pureextra = extra.copy()
2060 extra['amend_source'] = old.hex()
2058 extra['amend_source'] = old.hex()
2061
2059
2062 new = context.memctx(repo,
2060 new = context.memctx(repo,
2063 parents=[base.node(), old.p2().node()],
2061 parents=[base.node(), old.p2().node()],
2064 text=message,
2062 text=message,
2065 files=files,
2063 files=files,
2066 filectxfn=filectxfn,
2064 filectxfn=filectxfn,
2067 user=user,
2065 user=user,
2068 date=date,
2066 date=date,
2069 extra=extra,
2067 extra=extra,
2070 editor=editor)
2068 editor=editor)
2071
2069
2072 newdesc = changelog.stripdesc(new.description())
2070 newdesc = changelog.stripdesc(new.description())
2073 if ((not node)
2071 if ((not node)
2074 and newdesc == old.description()
2072 and newdesc == old.description()
2075 and user == old.user()
2073 and user == old.user()
2076 and date == old.date()
2074 and date == old.date()
2077 and pureextra == old.extra()):
2075 and pureextra == old.extra()):
2078 # nothing changed. continuing here would create a new node
2076 # nothing changed. continuing here would create a new node
2079 # anyway because of the amend_source noise.
2077 # anyway because of the amend_source noise.
2080 #
2078 #
2081 # This not what we expect from amend.
2079 # This not what we expect from amend.
2082 return old.node()
2080 return old.node()
2083
2081
2084 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2082 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2085 try:
2083 try:
2086 if opts.get('secret'):
2084 if opts.get('secret'):
2087 commitphase = 'secret'
2085 commitphase = 'secret'
2088 else:
2086 else:
2089 commitphase = old.phase()
2087 commitphase = old.phase()
2090 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2088 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2091 newid = repo.commitctx(new)
2089 newid = repo.commitctx(new)
2092 finally:
2090 finally:
2093 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2091 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2094 if newid != old.node():
2092 if newid != old.node():
2095 # Reroute the working copy parent to the new changeset
2093 # Reroute the working copy parent to the new changeset
2096 repo.setparents(newid, nullid)
2094 repo.setparents(newid, nullid)
2097
2095
2098 # Move bookmarks from old parent to amend commit
2096 # Move bookmarks from old parent to amend commit
2099 bms = repo.nodebookmarks(old.node())
2097 bms = repo.nodebookmarks(old.node())
2100 if bms:
2098 if bms:
2101 marks = repo._bookmarks
2099 marks = repo._bookmarks
2102 for bm in bms:
2100 for bm in bms:
2103 marks[bm] = newid
2101 marks[bm] = newid
2104 marks.write()
2102 marks.write()
2105 #commit the whole amend process
2103 #commit the whole amend process
2106 if obsolete._enabled and newid != old.node():
2104 if obsolete._enabled and newid != old.node():
2107 # mark the new changeset as successor of the rewritten one
2105 # mark the new changeset as successor of the rewritten one
2108 new = repo[newid]
2106 new = repo[newid]
2109 obs = [(old, (new,))]
2107 obs = [(old, (new,))]
2110 if node:
2108 if node:
2111 obs.append((ctx, ()))
2109 obs.append((ctx, ()))
2112
2110
2113 obsolete.createmarkers(repo, obs)
2111 obsolete.createmarkers(repo, obs)
2114 tr.close()
2112 tr.close()
2115 finally:
2113 finally:
2116 tr.release()
2114 tr.release()
2117 if (not obsolete._enabled) and newid != old.node():
2115 if (not obsolete._enabled) and newid != old.node():
2118 # Strip the intermediate commit (if there was one) and the amended
2116 # Strip the intermediate commit (if there was one) and the amended
2119 # commit
2117 # commit
2120 if node:
2118 if node:
2121 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2119 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2122 ui.note(_('stripping amended changeset %s\n') % old)
2120 ui.note(_('stripping amended changeset %s\n') % old)
2123 repair.strip(ui, repo, old.node(), topic='amend-backup')
2121 repair.strip(ui, repo, old.node(), topic='amend-backup')
2124 finally:
2122 finally:
2125 if newid is None:
2123 if newid is None:
2126 repo.dirstate.invalidate()
2124 repo.dirstate.invalidate()
2127 lockmod.release(lock, wlock)
2125 lockmod.release(lock, wlock)
2128 return newid
2126 return newid
2129
2127
2130 def commiteditor(repo, ctx, subs):
2128 def commiteditor(repo, ctx, subs):
2131 if ctx.description():
2129 if ctx.description():
2132 return ctx.description()
2130 return ctx.description()
2133 return commitforceeditor(repo, ctx, subs)
2131 return commitforceeditor(repo, ctx, subs)
2134
2132
2135 def commitforceeditor(repo, ctx, subs):
2133 def commitforceeditor(repo, ctx, subs):
2136 edittext = []
2134 edittext = []
2137 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2135 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2138 if ctx.description():
2136 if ctx.description():
2139 edittext.append(ctx.description())
2137 edittext.append(ctx.description())
2140 edittext.append("")
2138 edittext.append("")
2141 edittext.append("") # Empty line between message and comments.
2139 edittext.append("") # Empty line between message and comments.
2142 edittext.append(_("HG: Enter commit message."
2140 edittext.append(_("HG: Enter commit message."
2143 " Lines beginning with 'HG:' are removed."))
2141 " Lines beginning with 'HG:' are removed."))
2144 edittext.append(_("HG: Leave message empty to abort commit."))
2142 edittext.append(_("HG: Leave message empty to abort commit."))
2145 edittext.append("HG: --")
2143 edittext.append("HG: --")
2146 edittext.append(_("HG: user: %s") % ctx.user())
2144 edittext.append(_("HG: user: %s") % ctx.user())
2147 if ctx.p2():
2145 if ctx.p2():
2148 edittext.append(_("HG: branch merge"))
2146 edittext.append(_("HG: branch merge"))
2149 if ctx.branch():
2147 if ctx.branch():
2150 edittext.append(_("HG: branch '%s'") % ctx.branch())
2148 edittext.append(_("HG: branch '%s'") % ctx.branch())
2151 if bookmarks.iscurrent(repo):
2149 if bookmarks.iscurrent(repo):
2152 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2150 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2153 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2151 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2154 edittext.extend([_("HG: added %s") % f for f in added])
2152 edittext.extend([_("HG: added %s") % f for f in added])
2155 edittext.extend([_("HG: changed %s") % f for f in modified])
2153 edittext.extend([_("HG: changed %s") % f for f in modified])
2156 edittext.extend([_("HG: removed %s") % f for f in removed])
2154 edittext.extend([_("HG: removed %s") % f for f in removed])
2157 if not added and not modified and not removed:
2155 if not added and not modified and not removed:
2158 edittext.append(_("HG: no files changed"))
2156 edittext.append(_("HG: no files changed"))
2159 edittext.append("")
2157 edittext.append("")
2160 # run editor in the repository root
2158 # run editor in the repository root
2161 olddir = os.getcwd()
2159 olddir = os.getcwd()
2162 os.chdir(repo.root)
2160 os.chdir(repo.root)
2163 text = repo.ui.edit("\n".join(edittext), ctx.user(), ctx.extra())
2161 text = repo.ui.edit("\n".join(edittext), ctx.user(), ctx.extra())
2164 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2162 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2165 os.chdir(olddir)
2163 os.chdir(olddir)
2166
2164
2167 if not text.strip():
2165 if not text.strip():
2168 raise util.Abort(_("empty commit message"))
2166 raise util.Abort(_("empty commit message"))
2169
2167
2170 return text
2168 return text
2171
2169
2172 def commitstatus(repo, node, branch, bheads=None, opts={}):
2170 def commitstatus(repo, node, branch, bheads=None, opts={}):
2173 ctx = repo[node]
2171 ctx = repo[node]
2174 parents = ctx.parents()
2172 parents = ctx.parents()
2175
2173
2176 if (not opts.get('amend') and bheads and node not in bheads and not
2174 if (not opts.get('amend') and bheads and node not in bheads and not
2177 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2175 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2178 repo.ui.status(_('created new head\n'))
2176 repo.ui.status(_('created new head\n'))
2179 # The message is not printed for initial roots. For the other
2177 # The message is not printed for initial roots. For the other
2180 # changesets, it is printed in the following situations:
2178 # changesets, it is printed in the following situations:
2181 #
2179 #
2182 # Par column: for the 2 parents with ...
2180 # Par column: for the 2 parents with ...
2183 # N: null or no parent
2181 # N: null or no parent
2184 # B: parent is on another named branch
2182 # B: parent is on another named branch
2185 # C: parent is a regular non head changeset
2183 # C: parent is a regular non head changeset
2186 # H: parent was a branch head of the current branch
2184 # H: parent was a branch head of the current branch
2187 # Msg column: whether we print "created new head" message
2185 # Msg column: whether we print "created new head" message
2188 # In the following, it is assumed that there already exists some
2186 # In the following, it is assumed that there already exists some
2189 # initial branch heads of the current branch, otherwise nothing is
2187 # initial branch heads of the current branch, otherwise nothing is
2190 # printed anyway.
2188 # printed anyway.
2191 #
2189 #
2192 # Par Msg Comment
2190 # Par Msg Comment
2193 # N N y additional topo root
2191 # N N y additional topo root
2194 #
2192 #
2195 # B N y additional branch root
2193 # B N y additional branch root
2196 # C N y additional topo head
2194 # C N y additional topo head
2197 # H N n usual case
2195 # H N n usual case
2198 #
2196 #
2199 # B B y weird additional branch root
2197 # B B y weird additional branch root
2200 # C B y branch merge
2198 # C B y branch merge
2201 # H B n merge with named branch
2199 # H B n merge with named branch
2202 #
2200 #
2203 # C C y additional head from merge
2201 # C C y additional head from merge
2204 # C H n merge with a head
2202 # C H n merge with a head
2205 #
2203 #
2206 # H H n head merge: head count decreases
2204 # H H n head merge: head count decreases
2207
2205
2208 if not opts.get('close_branch'):
2206 if not opts.get('close_branch'):
2209 for r in parents:
2207 for r in parents:
2210 if r.closesbranch() and r.branch() == branch:
2208 if r.closesbranch() and r.branch() == branch:
2211 repo.ui.status(_('reopening closed branch head %d\n') % r)
2209 repo.ui.status(_('reopening closed branch head %d\n') % r)
2212
2210
2213 if repo.ui.debugflag:
2211 if repo.ui.debugflag:
2214 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2212 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2215 elif repo.ui.verbose:
2213 elif repo.ui.verbose:
2216 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2214 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2217
2215
2218 def revert(ui, repo, ctx, parents, *pats, **opts):
2216 def revert(ui, repo, ctx, parents, *pats, **opts):
2219 parent, p2 = parents
2217 parent, p2 = parents
2220 node = ctx.node()
2218 node = ctx.node()
2221
2219
2222 mf = ctx.manifest()
2220 mf = ctx.manifest()
2223 if node == parent:
2221 if node == parent:
2224 pmf = mf
2222 pmf = mf
2225 else:
2223 else:
2226 pmf = None
2224 pmf = None
2227
2225
2228 # need all matching names in dirstate and manifest of target rev,
2226 # need all matching names in dirstate and manifest of target rev,
2229 # so have to walk both. do not print errors if files exist in one
2227 # so have to walk both. do not print errors if files exist in one
2230 # but not other.
2228 # but not other.
2231
2229
2232 names = {}
2230 names = {}
2233
2231
2234 wlock = repo.wlock()
2232 wlock = repo.wlock()
2235 try:
2233 try:
2236 # walk dirstate.
2234 # walk dirstate.
2237
2235
2238 m = scmutil.match(repo[None], pats, opts)
2236 m = scmutil.match(repo[None], pats, opts)
2239 m.bad = lambda x, y: False
2237 m.bad = lambda x, y: False
2240 for abs in repo.walk(m):
2238 for abs in repo.walk(m):
2241 names[abs] = m.rel(abs), m.exact(abs)
2239 names[abs] = m.rel(abs), m.exact(abs)
2242
2240
2243 # walk target manifest.
2241 # walk target manifest.
2244
2242
2245 def badfn(path, msg):
2243 def badfn(path, msg):
2246 if path in names:
2244 if path in names:
2247 return
2245 return
2248 if path in ctx.substate:
2246 if path in ctx.substate:
2249 return
2247 return
2250 path_ = path + '/'
2248 path_ = path + '/'
2251 for f in names:
2249 for f in names:
2252 if f.startswith(path_):
2250 if f.startswith(path_):
2253 return
2251 return
2254 ui.warn("%s: %s\n" % (m.rel(path), msg))
2252 ui.warn("%s: %s\n" % (m.rel(path), msg))
2255
2253
2256 m = scmutil.match(ctx, pats, opts)
2254 m = scmutil.match(ctx, pats, opts)
2257 m.bad = badfn
2255 m.bad = badfn
2258 for abs in ctx.walk(m):
2256 for abs in ctx.walk(m):
2259 if abs not in names:
2257 if abs not in names:
2260 names[abs] = m.rel(abs), m.exact(abs)
2258 names[abs] = m.rel(abs), m.exact(abs)
2261
2259
2262 # get the list of subrepos that must be reverted
2260 # get the list of subrepos that must be reverted
2263 targetsubs = sorted(s for s in ctx.substate if m(s))
2261 targetsubs = sorted(s for s in ctx.substate if m(s))
2264 m = scmutil.matchfiles(repo, names)
2262 m = scmutil.matchfiles(repo, names)
2265 changes = repo.status(match=m)[:4]
2263 changes = repo.status(match=m)[:4]
2266 modified, added, removed, deleted = map(set, changes)
2264 modified, added, removed, deleted = map(set, changes)
2267
2265
2268 # if f is a rename, also revert the source
2266 # if f is a rename, also revert the source
2269 cwd = repo.getcwd()
2267 cwd = repo.getcwd()
2270 for f in added:
2268 for f in added:
2271 src = repo.dirstate.copied(f)
2269 src = repo.dirstate.copied(f)
2272 if src and src not in names and repo.dirstate[src] == 'r':
2270 if src and src not in names and repo.dirstate[src] == 'r':
2273 removed.add(src)
2271 removed.add(src)
2274 names[src] = (repo.pathto(src, cwd), True)
2272 names[src] = (repo.pathto(src, cwd), True)
2275
2273
2276 def removeforget(abs):
2274 def removeforget(abs):
2277 if repo.dirstate[abs] == 'a':
2275 if repo.dirstate[abs] == 'a':
2278 return _('forgetting %s\n')
2276 return _('forgetting %s\n')
2279 return _('removing %s\n')
2277 return _('removing %s\n')
2280
2278
2281 revert = ([], _('reverting %s\n'))
2279 revert = ([], _('reverting %s\n'))
2282 add = ([], _('adding %s\n'))
2280 add = ([], _('adding %s\n'))
2283 remove = ([], removeforget)
2281 remove = ([], removeforget)
2284 undelete = ([], _('undeleting %s\n'))
2282 undelete = ([], _('undeleting %s\n'))
2285
2283
2286 disptable = (
2284 disptable = (
2287 # dispatch table:
2285 # dispatch table:
2288 # file state
2286 # file state
2289 # action if in target manifest
2287 # action if in target manifest
2290 # action if not in target manifest
2288 # action if not in target manifest
2291 # make backup if in target manifest
2289 # make backup if in target manifest
2292 # make backup if not in target manifest
2290 # make backup if not in target manifest
2293 (modified, revert, remove, True, True),
2291 (modified, revert, remove, True, True),
2294 (added, revert, remove, True, False),
2292 (added, revert, remove, True, False),
2295 (removed, undelete, None, True, False),
2293 (removed, undelete, None, True, False),
2296 (deleted, revert, remove, False, False),
2294 (deleted, revert, remove, False, False),
2297 )
2295 )
2298
2296
2299 for abs, (rel, exact) in sorted(names.items()):
2297 for abs, (rel, exact) in sorted(names.items()):
2300 mfentry = mf.get(abs)
2298 mfentry = mf.get(abs)
2301 target = repo.wjoin(abs)
2299 target = repo.wjoin(abs)
2302 def handle(xlist, dobackup):
2300 def handle(xlist, dobackup):
2303 xlist[0].append(abs)
2301 xlist[0].append(abs)
2304 if (dobackup and not opts.get('no_backup') and
2302 if (dobackup and not opts.get('no_backup') and
2305 os.path.lexists(target) and
2303 os.path.lexists(target) and
2306 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2304 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2307 bakname = "%s.orig" % rel
2305 bakname = "%s.orig" % rel
2308 ui.note(_('saving current version of %s as %s\n') %
2306 ui.note(_('saving current version of %s as %s\n') %
2309 (rel, bakname))
2307 (rel, bakname))
2310 if not opts.get('dry_run'):
2308 if not opts.get('dry_run'):
2311 util.rename(target, bakname)
2309 util.rename(target, bakname)
2312 if ui.verbose or not exact:
2310 if ui.verbose or not exact:
2313 msg = xlist[1]
2311 msg = xlist[1]
2314 if not isinstance(msg, basestring):
2312 if not isinstance(msg, basestring):
2315 msg = msg(abs)
2313 msg = msg(abs)
2316 ui.status(msg % rel)
2314 ui.status(msg % rel)
2317 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2315 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2318 if abs not in table:
2316 if abs not in table:
2319 continue
2317 continue
2320 # file has changed in dirstate
2318 # file has changed in dirstate
2321 if mfentry:
2319 if mfentry:
2322 handle(hitlist, backuphit)
2320 handle(hitlist, backuphit)
2323 elif misslist is not None:
2321 elif misslist is not None:
2324 handle(misslist, backupmiss)
2322 handle(misslist, backupmiss)
2325 break
2323 break
2326 else:
2324 else:
2327 if abs not in repo.dirstate:
2325 if abs not in repo.dirstate:
2328 if mfentry:
2326 if mfentry:
2329 handle(add, True)
2327 handle(add, True)
2330 elif exact:
2328 elif exact:
2331 ui.warn(_('file not managed: %s\n') % rel)
2329 ui.warn(_('file not managed: %s\n') % rel)
2332 continue
2330 continue
2333 # file has not changed in dirstate
2331 # file has not changed in dirstate
2334 if node == parent:
2332 if node == parent:
2335 if exact:
2333 if exact:
2336 ui.warn(_('no changes needed to %s\n') % rel)
2334 ui.warn(_('no changes needed to %s\n') % rel)
2337 continue
2335 continue
2338 if pmf is None:
2336 if pmf is None:
2339 # only need parent manifest in this unlikely case,
2337 # only need parent manifest in this unlikely case,
2340 # so do not read by default
2338 # so do not read by default
2341 pmf = repo[parent].manifest()
2339 pmf = repo[parent].manifest()
2342 if abs in pmf and mfentry:
2340 if abs in pmf and mfentry:
2343 # if version of file is same in parent and target
2341 # if version of file is same in parent and target
2344 # manifests, do nothing
2342 # manifests, do nothing
2345 if (pmf[abs] != mfentry or
2343 if (pmf[abs] != mfentry or
2346 pmf.flags(abs) != mf.flags(abs)):
2344 pmf.flags(abs) != mf.flags(abs)):
2347 handle(revert, False)
2345 handle(revert, False)
2348 else:
2346 else:
2349 handle(remove, False)
2347 handle(remove, False)
2350 if not opts.get('dry_run'):
2348 if not opts.get('dry_run'):
2351 _performrevert(repo, parents, ctx, revert, add, remove, undelete)
2349 _performrevert(repo, parents, ctx, revert, add, remove, undelete)
2352
2350
2353 if targetsubs:
2351 if targetsubs:
2354 # Revert the subrepos on the revert list
2352 # Revert the subrepos on the revert list
2355 for sub in targetsubs:
2353 for sub in targetsubs:
2356 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2354 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2357 finally:
2355 finally:
2358 wlock.release()
2356 wlock.release()
2359
2357
2360 def _performrevert(repo, parents, ctx, revert, add, remove, undelete):
2358 def _performrevert(repo, parents, ctx, revert, add, remove, undelete):
2361 """function that actually perform all the action computed for revert
2359 """function that actually perform all the action computed for revert
2362
2360
2363 This is an independent function to let extension to plug in and react to
2361 This is an independent function to let extension to plug in and react to
2364 the imminent revert.
2362 the imminent revert.
2365
2363
2366 Make sure you have the working directory locked when calling this function.
2364 Make sure you have the working directory locked when calling this function.
2367 """
2365 """
2368 parent, p2 = parents
2366 parent, p2 = parents
2369 node = ctx.node()
2367 node = ctx.node()
2370 def checkout(f):
2368 def checkout(f):
2371 fc = ctx[f]
2369 fc = ctx[f]
2372 repo.wwrite(f, fc.data(), fc.flags())
2370 repo.wwrite(f, fc.data(), fc.flags())
2373
2371
2374 audit_path = pathutil.pathauditor(repo.root)
2372 audit_path = pathutil.pathauditor(repo.root)
2375 for f in remove[0]:
2373 for f in remove[0]:
2376 if repo.dirstate[f] == 'a':
2374 if repo.dirstate[f] == 'a':
2377 repo.dirstate.drop(f)
2375 repo.dirstate.drop(f)
2378 continue
2376 continue
2379 audit_path(f)
2377 audit_path(f)
2380 try:
2378 try:
2381 util.unlinkpath(repo.wjoin(f))
2379 util.unlinkpath(repo.wjoin(f))
2382 except OSError:
2380 except OSError:
2383 pass
2381 pass
2384 repo.dirstate.remove(f)
2382 repo.dirstate.remove(f)
2385
2383
2386 normal = None
2384 normal = None
2387 if node == parent:
2385 if node == parent:
2388 # We're reverting to our parent. If possible, we'd like status
2386 # We're reverting to our parent. If possible, we'd like status
2389 # to report the file as clean. We have to use normallookup for
2387 # to report the file as clean. We have to use normallookup for
2390 # merges to avoid losing information about merged/dirty files.
2388 # merges to avoid losing information about merged/dirty files.
2391 if p2 != nullid:
2389 if p2 != nullid:
2392 normal = repo.dirstate.normallookup
2390 normal = repo.dirstate.normallookup
2393 else:
2391 else:
2394 normal = repo.dirstate.normal
2392 normal = repo.dirstate.normal
2395 for f in revert[0]:
2393 for f in revert[0]:
2396 checkout(f)
2394 checkout(f)
2397 if normal:
2395 if normal:
2398 normal(f)
2396 normal(f)
2399
2397
2400 for f in add[0]:
2398 for f in add[0]:
2401 checkout(f)
2399 checkout(f)
2402 repo.dirstate.add(f)
2400 repo.dirstate.add(f)
2403
2401
2404 normal = repo.dirstate.normallookup
2402 normal = repo.dirstate.normallookup
2405 if node == parent and p2 == nullid:
2403 if node == parent and p2 == nullid:
2406 normal = repo.dirstate.normal
2404 normal = repo.dirstate.normal
2407 for f in undelete[0]:
2405 for f in undelete[0]:
2408 checkout(f)
2406 checkout(f)
2409 normal(f)
2407 normal(f)
2410
2408
2411 copied = copies.pathcopies(repo[parent], ctx)
2409 copied = copies.pathcopies(repo[parent], ctx)
2412
2410
2413 for f in add[0] + undelete[0] + revert[0]:
2411 for f in add[0] + undelete[0] + revert[0]:
2414 if f in copied:
2412 if f in copied:
2415 repo.dirstate.copy(copied[f], f)
2413 repo.dirstate.copy(copied[f], f)
2416
2414
2417 def command(table):
2415 def command(table):
2418 '''returns a function object bound to table which can be used as
2416 '''returns a function object bound to table which can be used as
2419 a decorator for populating table as a command table'''
2417 a decorator for populating table as a command table'''
2420
2418
2421 def cmd(name, options=(), synopsis=None):
2419 def cmd(name, options=(), synopsis=None):
2422 def decorator(func):
2420 def decorator(func):
2423 if synopsis:
2421 if synopsis:
2424 table[name] = func, list(options), synopsis
2422 table[name] = func, list(options), synopsis
2425 else:
2423 else:
2426 table[name] = func, list(options)
2424 table[name] = func, list(options)
2427 return func
2425 return func
2428 return decorator
2426 return decorator
2429
2427
2430 return cmd
2428 return cmd
2431
2429
2432 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2430 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2433 # commands.outgoing. "missing" is "missing" of the result of
2431 # commands.outgoing. "missing" is "missing" of the result of
2434 # "findcommonoutgoing()"
2432 # "findcommonoutgoing()"
2435 outgoinghooks = util.hooks()
2433 outgoinghooks = util.hooks()
2436
2434
2437 # a list of (ui, repo) functions called by commands.summary
2435 # a list of (ui, repo) functions called by commands.summary
2438 summaryhooks = util.hooks()
2436 summaryhooks = util.hooks()
2439
2437
2440 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2438 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2441 #
2439 #
2442 # functions should return tuple of booleans below, if 'changes' is None:
2440 # functions should return tuple of booleans below, if 'changes' is None:
2443 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2441 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2444 #
2442 #
2445 # otherwise, 'changes' is a tuple of tuples below:
2443 # otherwise, 'changes' is a tuple of tuples below:
2446 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2444 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2447 # - (desturl, destbranch, destpeer, outgoing)
2445 # - (desturl, destbranch, destpeer, outgoing)
2448 summaryremotehooks = util.hooks()
2446 summaryremotehooks = util.hooks()
2449
2447
2450 # A list of state files kept by multistep operations like graft.
2448 # A list of state files kept by multistep operations like graft.
2451 # Since graft cannot be aborted, it is considered 'clearable' by update.
2449 # Since graft cannot be aborted, it is considered 'clearable' by update.
2452 # note: bisect is intentionally excluded
2450 # note: bisect is intentionally excluded
2453 # (state file, clearable, allowcommit, error, hint)
2451 # (state file, clearable, allowcommit, error, hint)
2454 unfinishedstates = [
2452 unfinishedstates = [
2455 ('graftstate', True, False, _('graft in progress'),
2453 ('graftstate', True, False, _('graft in progress'),
2456 _("use 'hg graft --continue' or 'hg update' to abort")),
2454 _("use 'hg graft --continue' or 'hg update' to abort")),
2457 ('updatestate', True, False, _('last update was interrupted'),
2455 ('updatestate', True, False, _('last update was interrupted'),
2458 _("use 'hg update' to get a consistent checkout"))
2456 _("use 'hg update' to get a consistent checkout"))
2459 ]
2457 ]
2460
2458
2461 def checkunfinished(repo, commit=False):
2459 def checkunfinished(repo, commit=False):
2462 '''Look for an unfinished multistep operation, like graft, and abort
2460 '''Look for an unfinished multistep operation, like graft, and abort
2463 if found. It's probably good to check this right before
2461 if found. It's probably good to check this right before
2464 bailifchanged().
2462 bailifchanged().
2465 '''
2463 '''
2466 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2464 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2467 if commit and allowcommit:
2465 if commit and allowcommit:
2468 continue
2466 continue
2469 if repo.vfs.exists(f):
2467 if repo.vfs.exists(f):
2470 raise util.Abort(msg, hint=hint)
2468 raise util.Abort(msg, hint=hint)
2471
2469
2472 def clearunfinished(repo):
2470 def clearunfinished(repo):
2473 '''Check for unfinished operations (as above), and clear the ones
2471 '''Check for unfinished operations (as above), and clear the ones
2474 that are clearable.
2472 that are clearable.
2475 '''
2473 '''
2476 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2474 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2477 if not clearable and repo.vfs.exists(f):
2475 if not clearable and repo.vfs.exists(f):
2478 raise util.Abort(msg, hint=hint)
2476 raise util.Abort(msg, hint=hint)
2479 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2477 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2480 if clearable and repo.vfs.exists(f):
2478 if clearable and repo.vfs.exists(f):
2481 util.unlink(repo.join(f))
2479 util.unlink(repo.join(f))
@@ -1,269 +1,275
1 $ echo "[extensions]" >> $HGRCPATH
1 $ echo "[extensions]" >> $HGRCPATH
2 $ echo "purge=" >> $HGRCPATH
2 $ echo "purge=" >> $HGRCPATH
3
3
4 $ shortlog() {
4 $ shortlog() {
5 > hg log -G --template '{rev}:{node|short} {author} {date|hgdate} - {branch} - {desc|firstline}\n'
5 > hg log -G --template '{rev}:{node|short} {author} {date|hgdate} - {branch} - {desc|firstline}\n'
6 > }
6 > }
7
7
8 Test --bypass with other options
8 Test --bypass with other options
9
9
10 $ hg init repo-options
10 $ hg init repo-options
11 $ cd repo-options
11 $ cd repo-options
12 $ echo a > a
12 $ echo a > a
13 $ hg ci -Am adda
13 $ hg ci -Am adda
14 adding a
14 adding a
15 $ echo a >> a
15 $ echo a >> a
16 $ hg branch foo
16 $ hg branch foo
17 marked working directory as branch foo
17 marked working directory as branch foo
18 (branches are permanent and global, did you want a bookmark?)
18 (branches are permanent and global, did you want a bookmark?)
19 $ hg ci -Am changea
19 $ hg ci -Am changea
20 $ hg export . > ../test.diff
20 $ hg export . > ../test.diff
21 $ hg up null
21 $ hg up null
22 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
22 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
23
23
24 Test importing an existing revision
24 Test importing an existing revision
25 (this also tests that editor is not invoked for '--bypass', if the
26 patch contains the commit message, regardless of '--edit')
25
27
26 $ hg import --bypass --exact ../test.diff
28 $ HGEDITOR=cat hg import --bypass --exact --edit ../test.diff
27 applying ../test.diff
29 applying ../test.diff
28 $ shortlog
30 $ shortlog
29 o 1:4e322f7ce8e3 test 0 0 - foo - changea
31 o 1:4e322f7ce8e3 test 0 0 - foo - changea
30 |
32 |
31 o 0:07f494440405 test 0 0 - default - adda
33 o 0:07f494440405 test 0 0 - default - adda
32
34
33
35
34 Test failure without --exact
36 Test failure without --exact
35
37
36 $ hg import --bypass ../test.diff
38 $ hg import --bypass ../test.diff
37 applying ../test.diff
39 applying ../test.diff
38 unable to find 'a' for patching
40 unable to find 'a' for patching
39 abort: patch failed to apply
41 abort: patch failed to apply
40 [255]
42 [255]
41 $ hg st
43 $ hg st
42 $ shortlog
44 $ shortlog
43 o 1:4e322f7ce8e3 test 0 0 - foo - changea
45 o 1:4e322f7ce8e3 test 0 0 - foo - changea
44 |
46 |
45 o 0:07f494440405 test 0 0 - default - adda
47 o 0:07f494440405 test 0 0 - default - adda
46
48
47
49
48 Test --user, --date and --message
50 Test --user, --date and --message
49
51
50 $ hg up 0
52 $ hg up 0
51 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
53 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
52 $ hg import --bypass --u test2 -d '1 0' -m patch2 ../test.diff
54 $ hg import --bypass --u test2 -d '1 0' -m patch2 ../test.diff
53 applying ../test.diff
55 applying ../test.diff
54 $ cat .hg/last-message.txt
56 $ cat .hg/last-message.txt
55 patch2 (no-eol)
57 patch2 (no-eol)
56 $ shortlog
58 $ shortlog
57 o 2:2e127d1da504 test2 1 0 - default - patch2
59 o 2:2e127d1da504 test2 1 0 - default - patch2
58 |
60 |
59 | o 1:4e322f7ce8e3 test 0 0 - foo - changea
61 | o 1:4e322f7ce8e3 test 0 0 - foo - changea
60 |/
62 |/
61 @ 0:07f494440405 test 0 0 - default - adda
63 @ 0:07f494440405 test 0 0 - default - adda
62
64
63 $ hg rollback
65 $ hg rollback
64 repository tip rolled back to revision 1 (undo import)
66 repository tip rolled back to revision 1 (undo import)
65
67
66 Test --import-branch
68 Test --import-branch
67
69
68 $ hg import --bypass --import-branch ../test.diff
70 $ hg import --bypass --import-branch ../test.diff
69 applying ../test.diff
71 applying ../test.diff
70 $ shortlog
72 $ shortlog
71 o 1:4e322f7ce8e3 test 0 0 - foo - changea
73 o 1:4e322f7ce8e3 test 0 0 - foo - changea
72 |
74 |
73 @ 0:07f494440405 test 0 0 - default - adda
75 @ 0:07f494440405 test 0 0 - default - adda
74
76
75 $ hg rollback
77 $ hg rollback
76 repository tip rolled back to revision 1 (undo import)
78 repository tip rolled back to revision 1 (undo import)
77
79
78 Test --strip
80 Test --strip
79
81
80 $ hg import --bypass --strip 0 - <<EOF
82 $ hg import --bypass --strip 0 - <<EOF
81 > # HG changeset patch
83 > # HG changeset patch
82 > # User test
84 > # User test
83 > # Date 0 0
85 > # Date 0 0
84 > # Branch foo
86 > # Branch foo
85 > # Node ID 4e322f7ce8e3e4203950eac9ece27bf7e45ffa6c
87 > # Node ID 4e322f7ce8e3e4203950eac9ece27bf7e45ffa6c
86 > # Parent 07f4944404050f47db2e5c5071e0e84e7a27bba9
88 > # Parent 07f4944404050f47db2e5c5071e0e84e7a27bba9
87 > changea
89 > changea
88 >
90 >
89 > diff -r 07f494440405 -r 4e322f7ce8e3 a
91 > diff -r 07f494440405 -r 4e322f7ce8e3 a
90 > --- a Thu Jan 01 00:00:00 1970 +0000
92 > --- a Thu Jan 01 00:00:00 1970 +0000
91 > +++ a Thu Jan 01 00:00:00 1970 +0000
93 > +++ a Thu Jan 01 00:00:00 1970 +0000
92 > @@ -1,1 +1,2 @@
94 > @@ -1,1 +1,2 @@
93 > a
95 > a
94 > +a
96 > +a
95 > EOF
97 > EOF
96 applying patch from stdin
98 applying patch from stdin
97 $ hg rollback
99 $ hg rollback
98 repository tip rolled back to revision 1 (undo import)
100 repository tip rolled back to revision 1 (undo import)
99
101
100 Test unsupported combinations
102 Test unsupported combinations
101
103
102 $ hg import --bypass --no-commit ../test.diff
104 $ hg import --bypass --no-commit ../test.diff
103 abort: cannot use --no-commit with --bypass
105 abort: cannot use --no-commit with --bypass
104 [255]
106 [255]
105 $ hg import --bypass --similarity 50 ../test.diff
107 $ hg import --bypass --similarity 50 ../test.diff
106 abort: cannot use --similarity with --bypass
108 abort: cannot use --similarity with --bypass
107 [255]
109 [255]
108
110
109 Test commit editor
111 Test commit editor
112 (this also tests that editor is invoked, if the patch doesn't contain
113 the commit message, regardless of '--edit')
110
114
111 $ cat > ../test.diff <<EOF
115 $ cat > ../test.diff <<EOF
112 > diff -r 07f494440405 -r 4e322f7ce8e3 a
116 > diff -r 07f494440405 -r 4e322f7ce8e3 a
113 > --- a/a Thu Jan 01 00:00:00 1970 +0000
117 > --- a/a Thu Jan 01 00:00:00 1970 +0000
114 > +++ b/a Thu Jan 01 00:00:00 1970 +0000
118 > +++ b/a Thu Jan 01 00:00:00 1970 +0000
115 > @@ -1,1 +1,2 @@
119 > @@ -1,1 +1,2 @@
116 > -a
120 > -a
117 > +b
121 > +b
118 > +c
122 > +c
119 > EOF
123 > EOF
120 $ HGEDITOR=cat hg import --bypass ../test.diff
124 $ HGEDITOR=cat hg import --bypass ../test.diff
121 applying ../test.diff
125 applying ../test.diff
122
126
123
127
124 HG: Enter commit message. Lines beginning with 'HG:' are removed.
128 HG: Enter commit message. Lines beginning with 'HG:' are removed.
125 HG: Leave message empty to abort commit.
129 HG: Leave message empty to abort commit.
126 HG: --
130 HG: --
127 HG: user: test
131 HG: user: test
128 HG: branch 'default'
132 HG: branch 'default'
129 HG: changed a
133 HG: changed a
130 abort: empty commit message
134 abort: empty commit message
131 [255]
135 [255]
132
136
133 Test patch.eol is handled
137 Test patch.eol is handled
138 (this also tests that editor is not invoked for '--bypass', if the
139 commit message is explicitly specified, regardless of '--edit')
134
140
135 $ python -c 'file("a", "wb").write("a\r\n")'
141 $ python -c 'file("a", "wb").write("a\r\n")'
136 $ hg ci -m makeacrlf
142 $ hg ci -m makeacrlf
137 $ hg import -m 'should fail because of eol' --bypass ../test.diff
143 $ HGEDITOR=cat hg import -m 'should fail because of eol' --edit --bypass ../test.diff
138 applying ../test.diff
144 applying ../test.diff
139 patching file a
145 patching file a
140 Hunk #1 FAILED at 0
146 Hunk #1 FAILED at 0
141 abort: patch failed to apply
147 abort: patch failed to apply
142 [255]
148 [255]
143 $ hg --config patch.eol=auto import -d '0 0' -m 'test patch.eol' --bypass ../test.diff
149 $ hg --config patch.eol=auto import -d '0 0' -m 'test patch.eol' --bypass ../test.diff
144 applying ../test.diff
150 applying ../test.diff
145 $ shortlog
151 $ shortlog
146 o 3:c606edafba99 test 0 0 - default - test patch.eol
152 o 3:c606edafba99 test 0 0 - default - test patch.eol
147 |
153 |
148 @ 2:872023de769d test 0 0 - default - makeacrlf
154 @ 2:872023de769d test 0 0 - default - makeacrlf
149 |
155 |
150 | o 1:4e322f7ce8e3 test 0 0 - foo - changea
156 | o 1:4e322f7ce8e3 test 0 0 - foo - changea
151 |/
157 |/
152 o 0:07f494440405 test 0 0 - default - adda
158 o 0:07f494440405 test 0 0 - default - adda
153
159
154
160
155 Test applying multiple patches
161 Test applying multiple patches
156
162
157 $ hg up -qC 0
163 $ hg up -qC 0
158 $ echo e > e
164 $ echo e > e
159 $ hg ci -Am adde
165 $ hg ci -Am adde
160 adding e
166 adding e
161 created new head
167 created new head
162 $ hg export . > ../patch1.diff
168 $ hg export . > ../patch1.diff
163 $ hg up -qC 1
169 $ hg up -qC 1
164 $ echo f > f
170 $ echo f > f
165 $ hg ci -Am addf
171 $ hg ci -Am addf
166 adding f
172 adding f
167 $ hg export . > ../patch2.diff
173 $ hg export . > ../patch2.diff
168 $ cd ..
174 $ cd ..
169 $ hg clone -r1 repo-options repo-multi1
175 $ hg clone -r1 repo-options repo-multi1
170 adding changesets
176 adding changesets
171 adding manifests
177 adding manifests
172 adding file changes
178 adding file changes
173 added 2 changesets with 2 changes to 1 files
179 added 2 changesets with 2 changes to 1 files
174 updating to branch foo
180 updating to branch foo
175 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
181 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
176 $ cd repo-multi1
182 $ cd repo-multi1
177 $ hg up 0
183 $ hg up 0
178 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
184 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
179 $ hg import --bypass ../patch1.diff ../patch2.diff
185 $ hg import --bypass ../patch1.diff ../patch2.diff
180 applying ../patch1.diff
186 applying ../patch1.diff
181 applying ../patch2.diff
187 applying ../patch2.diff
182 $ shortlog
188 $ shortlog
183 o 3:bc8ca3f8a7c4 test 0 0 - default - addf
189 o 3:bc8ca3f8a7c4 test 0 0 - default - addf
184 |
190 |
185 o 2:16581080145e test 0 0 - default - adde
191 o 2:16581080145e test 0 0 - default - adde
186 |
192 |
187 | o 1:4e322f7ce8e3 test 0 0 - foo - changea
193 | o 1:4e322f7ce8e3 test 0 0 - foo - changea
188 |/
194 |/
189 @ 0:07f494440405 test 0 0 - default - adda
195 @ 0:07f494440405 test 0 0 - default - adda
190
196
191
197
192 Test applying multiple patches with --exact
198 Test applying multiple patches with --exact
193
199
194 $ cd ..
200 $ cd ..
195 $ hg clone -r1 repo-options repo-multi2
201 $ hg clone -r1 repo-options repo-multi2
196 adding changesets
202 adding changesets
197 adding manifests
203 adding manifests
198 adding file changes
204 adding file changes
199 added 2 changesets with 2 changes to 1 files
205 added 2 changesets with 2 changes to 1 files
200 updating to branch foo
206 updating to branch foo
201 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
207 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
202 $ cd repo-multi2
208 $ cd repo-multi2
203 $ hg import --bypass --exact ../patch1.diff ../patch2.diff
209 $ hg import --bypass --exact ../patch1.diff ../patch2.diff
204 applying ../patch1.diff
210 applying ../patch1.diff
205 applying ../patch2.diff
211 applying ../patch2.diff
206 $ shortlog
212 $ shortlog
207 o 3:d60cb8989666 test 0 0 - foo - addf
213 o 3:d60cb8989666 test 0 0 - foo - addf
208 |
214 |
209 | o 2:16581080145e test 0 0 - default - adde
215 | o 2:16581080145e test 0 0 - default - adde
210 | |
216 | |
211 @ | 1:4e322f7ce8e3 test 0 0 - foo - changea
217 @ | 1:4e322f7ce8e3 test 0 0 - foo - changea
212 |/
218 |/
213 o 0:07f494440405 test 0 0 - default - adda
219 o 0:07f494440405 test 0 0 - default - adda
214
220
215
221
216 $ cd ..
222 $ cd ..
217
223
218 #if symlink execbit
224 #if symlink execbit
219
225
220 Test complicated patch with --exact
226 Test complicated patch with --exact
221
227
222 $ hg init repo-exact
228 $ hg init repo-exact
223 $ cd repo-exact
229 $ cd repo-exact
224 $ echo a > a
230 $ echo a > a
225 $ echo c > c
231 $ echo c > c
226 $ echo d > d
232 $ echo d > d
227 $ echo e > e
233 $ echo e > e
228 $ echo f > f
234 $ echo f > f
229 $ chmod +x f
235 $ chmod +x f
230 $ ln -s c linkc
236 $ ln -s c linkc
231 $ hg ci -Am t
237 $ hg ci -Am t
232 adding a
238 adding a
233 adding c
239 adding c
234 adding d
240 adding d
235 adding e
241 adding e
236 adding f
242 adding f
237 adding linkc
243 adding linkc
238 $ hg cp a aa1
244 $ hg cp a aa1
239 $ echo b >> a
245 $ echo b >> a
240 $ echo b > b
246 $ echo b > b
241 $ hg add b
247 $ hg add b
242 $ hg cp a aa2
248 $ hg cp a aa2
243 $ echo aa >> aa2
249 $ echo aa >> aa2
244 $ chmod +x e
250 $ chmod +x e
245 $ chmod -x f
251 $ chmod -x f
246 $ ln -s a linka
252 $ ln -s a linka
247 $ hg rm d
253 $ hg rm d
248 $ hg rm linkc
254 $ hg rm linkc
249 $ hg mv c cc
255 $ hg mv c cc
250 $ hg ci -m patch
256 $ hg ci -m patch
251 $ hg export --git . > ../test.diff
257 $ hg export --git . > ../test.diff
252 $ hg up -C null
258 $ hg up -C null
253 0 files updated, 0 files merged, 7 files removed, 0 files unresolved
259 0 files updated, 0 files merged, 7 files removed, 0 files unresolved
254 $ hg purge
260 $ hg purge
255 $ hg st
261 $ hg st
256 $ hg import --bypass --exact ../test.diff
262 $ hg import --bypass --exact ../test.diff
257 applying ../test.diff
263 applying ../test.diff
258
264
259 The patch should have matched the exported revision and generated no additional
265 The patch should have matched the exported revision and generated no additional
260 data. If not, diff both heads to debug it.
266 data. If not, diff both heads to debug it.
261
267
262 $ shortlog
268 $ shortlog
263 o 1:2978fd5c8aa4 test 0 0 - default - patch
269 o 1:2978fd5c8aa4 test 0 0 - default - patch
264 |
270 |
265 o 0:a0e19e636a43 test 0 0 - default - t
271 o 0:a0e19e636a43 test 0 0 - default - t
266
272
267 #endif
273 #endif
268
274
269 $ cd ..
275 $ cd ..
@@ -1,1158 +1,1185
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
27 commit message and '--edit' is not specified)
26
28
27 $ hg clone -r0 a b
29 $ hg clone -r0 a b
28 adding changesets
30 adding changesets
29 adding manifests
31 adding manifests
30 adding file changes
32 adding file changes
31 added 1 changesets with 2 changes to 2 files
33 added 1 changesets with 2 changes to 2 files
32 updating to branch default
34 updating to branch default
33 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
34 $ hg --cwd b import ../exported-tip.patch
36 $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
35 applying ../exported-tip.patch
37 applying ../exported-tip.patch
36
38
37 message and committer and date should be same
39 message and committer and date should be same
38
40
39 $ hg --cwd b tip
41 $ hg --cwd b tip
40 changeset: 1:1d4bd90af0e4
42 changeset: 1:1d4bd90af0e4
41 tag: tip
43 tag: tip
42 user: someone
44 user: someone
43 date: Thu Jan 01 00:00:01 1970 +0000
45 date: Thu Jan 01 00:00:01 1970 +0000
44 summary: second change
46 summary: second change
45
47
46 $ rm -r b
48 $ rm -r b
47
49
48
50
49 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,
53 regardless of the commit message in the patch)
50
54
51 $ cat > dummypatch.py <<EOF
55 $ cat > dummypatch.py <<EOF
52 > print 'patching file a'
56 > print 'patching file a'
53 > file('a', 'wb').write('line2\n')
57 > file('a', 'wb').write('line2\n')
54 > EOF
58 > EOF
55 $ hg clone -r0 a b
59 $ hg clone -r0 a b
56 adding changesets
60 adding changesets
57 adding manifests
61 adding manifests
58 adding file changes
62 adding file changes
59 added 1 changesets with 2 changes to 2 files
63 added 1 changesets with 2 changes to 2 files
60 updating to branch default
64 updating to branch default
61 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
62 $ hg --config ui.patch='python ../dummypatch.py' --cwd b import ../exported-tip.patch
66 $ HGEDITOR=cat hg --config ui.patch='python ../dummypatch.py' --cwd b import --edit ../exported-tip.patch
63 applying ../exported-tip.patch
67 applying ../exported-tip.patch
68 second change
69
70
71 HG: Enter commit message. Lines beginning with 'HG:' are removed.
72 HG: Leave message empty to abort commit.
73 HG: --
74 HG: user: someone
75 HG: branch 'default'
76 HG: changed a
64 $ cat b/a
77 $ cat b/a
65 line2
78 line2
66 $ rm -r b
79 $ rm -r b
67
80
68
81
69 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
84 the commit message, regardless of '--edit')
70
85
71 $ hg clone -r0 a b
86 $ hg clone -r0 a b
72 adding changesets
87 adding changesets
73 adding manifests
88 adding manifests
74 adding file changes
89 adding file changes
75 added 1 changesets with 2 changes to 2 files
90 added 1 changesets with 2 changes to 2 files
76 updating to branch default
91 updating to branch default
77 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
78 $ hg --cwd b import ../diffed-tip.patch
93 $ HGEDITOR=cat hg --cwd b import ../diffed-tip.patch
79 applying ../diffed-tip.patch
94 applying ../diffed-tip.patch
95
96
97 HG: Enter commit message. Lines beginning with 'HG:' are removed.
98 HG: Leave message empty to abort commit.
99 HG: --
100 HG: user: test
101 HG: branch 'default'
102 HG: changed a
80 abort: empty commit message
103 abort: empty commit message
81 [255]
104 [255]
82 $ rm -r b
105 $ rm -r b
83
106
84
107
85 import of plain diff should be ok with message
108 import of plain diff should be ok with message
86
109
87 $ hg clone -r0 a b
110 $ hg clone -r0 a b
88 adding changesets
111 adding changesets
89 adding manifests
112 adding manifests
90 adding file changes
113 adding file changes
91 added 1 changesets with 2 changes to 2 files
114 added 1 changesets with 2 changes to 2 files
92 updating to branch default
115 updating to branch default
93 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 $ hg --cwd b import -mpatch ../diffed-tip.patch
117 $ hg --cwd b import -mpatch ../diffed-tip.patch
95 applying ../diffed-tip.patch
118 applying ../diffed-tip.patch
96 $ rm -r b
119 $ rm -r b
97
120
98
121
99 import of plain diff with specific date and user
122 import of plain diff with specific date and user
123 (this also tests that editor is not invoked, if
124 '--message'/'--logfile' is specified and '--edit' is not)
100
125
101 $ hg clone -r0 a b
126 $ hg clone -r0 a b
102 adding changesets
127 adding changesets
103 adding manifests
128 adding manifests
104 adding file changes
129 adding file changes
105 added 1 changesets with 2 changes to 2 files
130 added 1 changesets with 2 changes to 2 files
106 updating to branch default
131 updating to branch default
107 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
132 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
108 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
133 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
109 applying ../diffed-tip.patch
134 applying ../diffed-tip.patch
110 $ hg -R b tip -pv
135 $ hg -R b tip -pv
111 changeset: 1:ca68f19f3a40
136 changeset: 1:ca68f19f3a40
112 tag: tip
137 tag: tip
113 user: user@nowhere.net
138 user: user@nowhere.net
114 date: Thu Jan 01 00:00:01 1970 +0000
139 date: Thu Jan 01 00:00:01 1970 +0000
115 files: a
140 files: a
116 description:
141 description:
117 patch
142 patch
118
143
119
144
120 diff -r 80971e65b431 -r ca68f19f3a40 a
145 diff -r 80971e65b431 -r ca68f19f3a40 a
121 --- a/a Thu Jan 01 00:00:00 1970 +0000
146 --- a/a Thu Jan 01 00:00:00 1970 +0000
122 +++ b/a Thu Jan 01 00:00:01 1970 +0000
147 +++ b/a Thu Jan 01 00:00:01 1970 +0000
123 @@ -1,1 +1,2 @@
148 @@ -1,1 +1,2 @@
124 line 1
149 line 1
125 +line 2
150 +line 2
126
151
127 $ rm -r b
152 $ rm -r b
128
153
129
154
130 import of plain diff should be ok with --no-commit
155 import of plain diff should be ok with --no-commit
156 (this also tests that editor is not invoked, if '--no-commit' is
157 specified, regardless of '--edit')
131
158
132 $ hg clone -r0 a b
159 $ hg clone -r0 a b
133 adding changesets
160 adding changesets
134 adding manifests
161 adding manifests
135 adding file changes
162 adding file changes
136 added 1 changesets with 2 changes to 2 files
163 added 1 changesets with 2 changes to 2 files
137 updating to branch default
164 updating to branch default
138 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
165 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
139 $ hg --cwd b import --no-commit ../diffed-tip.patch
166 $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
140 applying ../diffed-tip.patch
167 applying ../diffed-tip.patch
141 $ hg --cwd b diff --nodates
168 $ hg --cwd b diff --nodates
142 diff -r 80971e65b431 a
169 diff -r 80971e65b431 a
143 --- a/a
170 --- a/a
144 +++ b/a
171 +++ b/a
145 @@ -1,1 +1,2 @@
172 @@ -1,1 +1,2 @@
146 line 1
173 line 1
147 +line 2
174 +line 2
148 $ rm -r b
175 $ rm -r b
149
176
150
177
151 import of malformed plain diff should fail
178 import of malformed plain diff should fail
152
179
153 $ hg clone -r0 a b
180 $ hg clone -r0 a b
154 adding changesets
181 adding changesets
155 adding manifests
182 adding manifests
156 adding file changes
183 adding file changes
157 added 1 changesets with 2 changes to 2 files
184 added 1 changesets with 2 changes to 2 files
158 updating to branch default
185 updating to branch default
159 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
160 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
187 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
161 $ hg --cwd b import -mpatch ../broken.patch
188 $ hg --cwd b import -mpatch ../broken.patch
162 applying ../broken.patch
189 applying ../broken.patch
163 abort: bad hunk #1
190 abort: bad hunk #1
164 [255]
191 [255]
165 $ rm -r b
192 $ rm -r b
166
193
167
194
168 hg -R repo import
195 hg -R repo import
169 put the clone in a subdir - having a directory named "a"
196 put the clone in a subdir - having a directory named "a"
170 used to hide a bug.
197 used to hide a bug.
171
198
172 $ mkdir dir
199 $ mkdir dir
173 $ hg clone -r0 a dir/b
200 $ hg clone -r0 a dir/b
174 adding changesets
201 adding changesets
175 adding manifests
202 adding manifests
176 adding file changes
203 adding file changes
177 added 1 changesets with 2 changes to 2 files
204 added 1 changesets with 2 changes to 2 files
178 updating to branch default
205 updating to branch default
179 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
206 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
180 $ cd dir
207 $ cd dir
181 $ hg -R b import ../exported-tip.patch
208 $ hg -R b import ../exported-tip.patch
182 applying ../exported-tip.patch
209 applying ../exported-tip.patch
183 $ cd ..
210 $ cd ..
184 $ rm -r dir
211 $ rm -r dir
185
212
186
213
187 import from stdin
214 import from stdin
188
215
189 $ hg clone -r0 a b
216 $ hg clone -r0 a b
190 adding changesets
217 adding changesets
191 adding manifests
218 adding manifests
192 adding file changes
219 adding file changes
193 added 1 changesets with 2 changes to 2 files
220 added 1 changesets with 2 changes to 2 files
194 updating to branch default
221 updating to branch default
195 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
196 $ hg --cwd b import - < exported-tip.patch
223 $ hg --cwd b import - < exported-tip.patch
197 applying patch from stdin
224 applying patch from stdin
198 $ rm -r b
225 $ rm -r b
199
226
200
227
201 import two patches in one stream
228 import two patches in one stream
202
229
203 $ hg init b
230 $ hg init b
204 $ hg --cwd a export 0:tip | hg --cwd b import -
231 $ hg --cwd a export 0:tip | hg --cwd b import -
205 applying patch from stdin
232 applying patch from stdin
206 $ hg --cwd a id
233 $ hg --cwd a id
207 1d4bd90af0e4 tip
234 1d4bd90af0e4 tip
208 $ hg --cwd b id
235 $ hg --cwd b id
209 1d4bd90af0e4 tip
236 1d4bd90af0e4 tip
210 $ rm -r b
237 $ rm -r b
211
238
212
239
213 override commit message
240 override commit message
214
241
215 $ hg clone -r0 a b
242 $ hg clone -r0 a b
216 adding changesets
243 adding changesets
217 adding manifests
244 adding manifests
218 adding file changes
245 adding file changes
219 added 1 changesets with 2 changes to 2 files
246 added 1 changesets with 2 changes to 2 files
220 updating to branch default
247 updating to branch default
221 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
248 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 $ hg --cwd b import -m 'override' - < exported-tip.patch
249 $ hg --cwd b import -m 'override' - < exported-tip.patch
223 applying patch from stdin
250 applying patch from stdin
224 $ hg --cwd b tip | grep override
251 $ hg --cwd b tip | grep override
225 summary: override
252 summary: override
226 $ rm -r b
253 $ rm -r b
227
254
228 $ cat > mkmsg.py <<EOF
255 $ cat > mkmsg.py <<EOF
229 > import email.Message, sys
256 > import email.Message, sys
230 > msg = email.Message.Message()
257 > msg = email.Message.Message()
231 > patch = open(sys.argv[1], 'rb').read()
258 > patch = open(sys.argv[1], 'rb').read()
232 > msg.set_payload('email commit message\n' + patch)
259 > msg.set_payload('email commit message\n' + patch)
233 > msg['Subject'] = 'email patch'
260 > msg['Subject'] = 'email patch'
234 > msg['From'] = 'email patcher'
261 > msg['From'] = 'email patcher'
235 > file(sys.argv[2], 'wb').write(msg.as_string())
262 > file(sys.argv[2], 'wb').write(msg.as_string())
236 > EOF
263 > EOF
237
264
238
265
239 plain diff in email, subject, message body
266 plain diff in email, subject, message body
240
267
241 $ hg clone -r0 a b
268 $ hg clone -r0 a b
242 adding changesets
269 adding changesets
243 adding manifests
270 adding manifests
244 adding file changes
271 adding file changes
245 added 1 changesets with 2 changes to 2 files
272 added 1 changesets with 2 changes to 2 files
246 updating to branch default
273 updating to branch default
247 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
274 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
248 $ python mkmsg.py diffed-tip.patch msg.patch
275 $ python mkmsg.py diffed-tip.patch msg.patch
249 $ hg --cwd b import ../msg.patch
276 $ hg --cwd b import ../msg.patch
250 applying ../msg.patch
277 applying ../msg.patch
251 $ hg --cwd b tip | grep email
278 $ hg --cwd b tip | grep email
252 user: email patcher
279 user: email patcher
253 summary: email patch
280 summary: email patch
254 $ rm -r b
281 $ rm -r b
255
282
256
283
257 plain diff in email, no subject, message body
284 plain diff in email, no subject, message body
258
285
259 $ hg clone -r0 a b
286 $ hg clone -r0 a b
260 adding changesets
287 adding changesets
261 adding manifests
288 adding manifests
262 adding file changes
289 adding file changes
263 added 1 changesets with 2 changes to 2 files
290 added 1 changesets with 2 changes to 2 files
264 updating to branch default
291 updating to branch default
265 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
292 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
266 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
293 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
267 applying patch from stdin
294 applying patch from stdin
268 $ rm -r b
295 $ rm -r b
269
296
270
297
271 plain diff in email, subject, no message body
298 plain diff in email, subject, no message body
272
299
273 $ hg clone -r0 a b
300 $ hg clone -r0 a b
274 adding changesets
301 adding changesets
275 adding manifests
302 adding manifests
276 adding file changes
303 adding file changes
277 added 1 changesets with 2 changes to 2 files
304 added 1 changesets with 2 changes to 2 files
278 updating to branch default
305 updating to branch default
279 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
306 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
280 $ grep -v '^email ' msg.patch | hg --cwd b import -
307 $ grep -v '^email ' msg.patch | hg --cwd b import -
281 applying patch from stdin
308 applying patch from stdin
282 $ rm -r b
309 $ rm -r b
283
310
284
311
285 plain diff in email, no subject, no message body, should fail
312 plain diff in email, no subject, no message body, should fail
286
313
287 $ hg clone -r0 a b
314 $ hg clone -r0 a b
288 adding changesets
315 adding changesets
289 adding manifests
316 adding manifests
290 adding file changes
317 adding file changes
291 added 1 changesets with 2 changes to 2 files
318 added 1 changesets with 2 changes to 2 files
292 updating to branch default
319 updating to branch default
293 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
320 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
294 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
321 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
295 applying patch from stdin
322 applying patch from stdin
296 abort: empty commit message
323 abort: empty commit message
297 [255]
324 [255]
298 $ rm -r b
325 $ rm -r b
299
326
300
327
301 hg export in email, should use patch header
328 hg export in email, should use patch header
302
329
303 $ hg clone -r0 a b
330 $ hg clone -r0 a b
304 adding changesets
331 adding changesets
305 adding manifests
332 adding manifests
306 adding file changes
333 adding file changes
307 added 1 changesets with 2 changes to 2 files
334 added 1 changesets with 2 changes to 2 files
308 updating to branch default
335 updating to branch default
309 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
336 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
310 $ python mkmsg.py exported-tip.patch msg.patch
337 $ python mkmsg.py exported-tip.patch msg.patch
311 $ cat msg.patch | hg --cwd b import -
338 $ cat msg.patch | hg --cwd b import -
312 applying patch from stdin
339 applying patch from stdin
313 $ hg --cwd b tip | grep second
340 $ hg --cwd b tip | grep second
314 summary: second change
341 summary: second change
315 $ rm -r b
342 $ rm -r b
316
343
317
344
318 subject: duplicate detection, removal of [PATCH]
345 subject: duplicate detection, removal of [PATCH]
319 The '---' tests the gitsendmail handling without proper mail headers
346 The '---' tests the gitsendmail handling without proper mail headers
320
347
321 $ cat > mkmsg2.py <<EOF
348 $ cat > mkmsg2.py <<EOF
322 > import email.Message, sys
349 > import email.Message, sys
323 > msg = email.Message.Message()
350 > msg = email.Message.Message()
324 > patch = open(sys.argv[1], 'rb').read()
351 > patch = open(sys.argv[1], 'rb').read()
325 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
352 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
326 > msg['Subject'] = '[PATCH] email patch'
353 > msg['Subject'] = '[PATCH] email patch'
327 > msg['From'] = 'email patcher'
354 > msg['From'] = 'email patcher'
328 > file(sys.argv[2], 'wb').write(msg.as_string())
355 > file(sys.argv[2], 'wb').write(msg.as_string())
329 > EOF
356 > EOF
330
357
331
358
332 plain diff in email, [PATCH] subject, message body with subject
359 plain diff in email, [PATCH] subject, message body with subject
333
360
334 $ hg clone -r0 a b
361 $ hg clone -r0 a b
335 adding changesets
362 adding changesets
336 adding manifests
363 adding manifests
337 adding file changes
364 adding file changes
338 added 1 changesets with 2 changes to 2 files
365 added 1 changesets with 2 changes to 2 files
339 updating to branch default
366 updating to branch default
340 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
367 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
341 $ python mkmsg2.py diffed-tip.patch msg.patch
368 $ python mkmsg2.py diffed-tip.patch msg.patch
342 $ cat msg.patch | hg --cwd b import -
369 $ cat msg.patch | hg --cwd b import -
343 applying patch from stdin
370 applying patch from stdin
344 $ hg --cwd b tip --template '{desc}\n'
371 $ hg --cwd b tip --template '{desc}\n'
345 email patch
372 email patch
346
373
347 next line
374 next line
348 $ rm -r b
375 $ rm -r b
349
376
350
377
351 Issue963: Parent of working dir incorrect after import of multiple
378 Issue963: Parent of working dir incorrect after import of multiple
352 patches and rollback
379 patches and rollback
353
380
354 We weren't backing up the correct dirstate file when importing many
381 We weren't backing up the correct dirstate file when importing many
355 patches: import patch1 patch2; rollback
382 patches: import patch1 patch2; rollback
356
383
357 $ echo line 3 >> a/a
384 $ echo line 3 >> a/a
358 $ hg --cwd a ci -m'third change'
385 $ hg --cwd a ci -m'third change'
359 $ hg --cwd a export -o '../patch%R' 1 2
386 $ hg --cwd a export -o '../patch%R' 1 2
360 $ hg clone -qr0 a b
387 $ hg clone -qr0 a b
361 $ hg --cwd b parents --template 'parent: {rev}\n'
388 $ hg --cwd b parents --template 'parent: {rev}\n'
362 parent: 0
389 parent: 0
363 $ hg --cwd b import -v ../patch1 ../patch2
390 $ hg --cwd b import -v ../patch1 ../patch2
364 applying ../patch1
391 applying ../patch1
365 patching file a
392 patching file a
366 a
393 a
367 created 1d4bd90af0e4
394 created 1d4bd90af0e4
368 applying ../patch2
395 applying ../patch2
369 patching file a
396 patching file a
370 a
397 a
371 created 6d019af21222
398 created 6d019af21222
372 $ hg --cwd b rollback
399 $ hg --cwd b rollback
373 repository tip rolled back to revision 0 (undo import)
400 repository tip rolled back to revision 0 (undo import)
374 working directory now based on revision 0
401 working directory now based on revision 0
375 $ hg --cwd b parents --template 'parent: {rev}\n'
402 $ hg --cwd b parents --template 'parent: {rev}\n'
376 parent: 0
403 parent: 0
377 $ rm -r b
404 $ rm -r b
378
405
379
406
380 importing a patch in a subdirectory failed at the commit stage
407 importing a patch in a subdirectory failed at the commit stage
381
408
382 $ echo line 2 >> a/d1/d2/a
409 $ echo line 2 >> a/d1/d2/a
383 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
410 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
384
411
385 hg import in a subdirectory
412 hg import in a subdirectory
386
413
387 $ hg clone -r0 a b
414 $ hg clone -r0 a b
388 adding changesets
415 adding changesets
389 adding manifests
416 adding manifests
390 adding file changes
417 adding file changes
391 added 1 changesets with 2 changes to 2 files
418 added 1 changesets with 2 changes to 2 files
392 updating to branch default
419 updating to branch default
393 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
420 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
394 $ hg --cwd a export tip > tmp
421 $ hg --cwd a export tip > tmp
395 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
422 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
396 $ dir=`pwd`
423 $ dir=`pwd`
397 $ cd b/d1/d2 2>&1 > /dev/null
424 $ cd b/d1/d2 2>&1 > /dev/null
398 $ hg import ../../../subdir-tip.patch
425 $ hg import ../../../subdir-tip.patch
399 applying ../../../subdir-tip.patch
426 applying ../../../subdir-tip.patch
400 $ cd "$dir"
427 $ cd "$dir"
401
428
402 message should be 'subdir change'
429 message should be 'subdir change'
403 committer should be 'someoneelse'
430 committer should be 'someoneelse'
404
431
405 $ hg --cwd b tip
432 $ hg --cwd b tip
406 changeset: 1:3577f5aea227
433 changeset: 1:3577f5aea227
407 tag: tip
434 tag: tip
408 user: someoneelse
435 user: someoneelse
409 date: Thu Jan 01 00:00:01 1970 +0000
436 date: Thu Jan 01 00:00:01 1970 +0000
410 summary: subdir change
437 summary: subdir change
411
438
412
439
413 should be empty
440 should be empty
414
441
415 $ hg --cwd b status
442 $ hg --cwd b status
416
443
417
444
418 Test fuzziness (ambiguous patch location, fuzz=2)
445 Test fuzziness (ambiguous patch location, fuzz=2)
419
446
420 $ hg init fuzzy
447 $ hg init fuzzy
421 $ cd fuzzy
448 $ cd fuzzy
422 $ echo line1 > a
449 $ echo line1 > a
423 $ echo line0 >> a
450 $ echo line0 >> a
424 $ echo line3 >> a
451 $ echo line3 >> a
425 $ hg ci -Am adda
452 $ hg ci -Am adda
426 adding a
453 adding a
427 $ echo line1 > a
454 $ echo line1 > a
428 $ echo line2 >> a
455 $ echo line2 >> a
429 $ echo line0 >> a
456 $ echo line0 >> a
430 $ echo line3 >> a
457 $ echo line3 >> a
431 $ hg ci -m change a
458 $ hg ci -m change a
432 $ hg export tip > fuzzy-tip.patch
459 $ hg export tip > fuzzy-tip.patch
433 $ hg up -C 0
460 $ hg up -C 0
434 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
461 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
435 $ echo line1 > a
462 $ echo line1 > a
436 $ echo line0 >> a
463 $ echo line0 >> a
437 $ echo line1 >> a
464 $ echo line1 >> a
438 $ echo line0 >> a
465 $ echo line0 >> a
439 $ hg ci -m brancha
466 $ hg ci -m brancha
440 created new head
467 created new head
441 $ hg import --no-commit -v fuzzy-tip.patch
468 $ hg import --no-commit -v fuzzy-tip.patch
442 applying fuzzy-tip.patch
469 applying fuzzy-tip.patch
443 patching file a
470 patching file a
444 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
471 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
445 applied to working directory
472 applied to working directory
446 $ hg revert -a
473 $ hg revert -a
447 reverting a
474 reverting a
448
475
449
476
450 import with --no-commit should have written .hg/last-message.txt
477 import with --no-commit should have written .hg/last-message.txt
451
478
452 $ cat .hg/last-message.txt
479 $ cat .hg/last-message.txt
453 change (no-eol)
480 change (no-eol)
454
481
455
482
456 test fuzziness with eol=auto
483 test fuzziness with eol=auto
457
484
458 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
485 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
459 applying fuzzy-tip.patch
486 applying fuzzy-tip.patch
460 patching file a
487 patching file a
461 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
488 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
462 applied to working directory
489 applied to working directory
463 $ cd ..
490 $ cd ..
464
491
465
492
466 Test hunk touching empty files (issue906)
493 Test hunk touching empty files (issue906)
467
494
468 $ hg init empty
495 $ hg init empty
469 $ cd empty
496 $ cd empty
470 $ touch a
497 $ touch a
471 $ touch b1
498 $ touch b1
472 $ touch c1
499 $ touch c1
473 $ echo d > d
500 $ echo d > d
474 $ hg ci -Am init
501 $ hg ci -Am init
475 adding a
502 adding a
476 adding b1
503 adding b1
477 adding c1
504 adding c1
478 adding d
505 adding d
479 $ echo a > a
506 $ echo a > a
480 $ echo b > b1
507 $ echo b > b1
481 $ hg mv b1 b2
508 $ hg mv b1 b2
482 $ echo c > c1
509 $ echo c > c1
483 $ hg copy c1 c2
510 $ hg copy c1 c2
484 $ rm d
511 $ rm d
485 $ touch d
512 $ touch d
486 $ hg diff --git
513 $ hg diff --git
487 diff --git a/a b/a
514 diff --git a/a b/a
488 --- a/a
515 --- a/a
489 +++ b/a
516 +++ b/a
490 @@ -0,0 +1,1 @@
517 @@ -0,0 +1,1 @@
491 +a
518 +a
492 diff --git a/b1 b/b2
519 diff --git a/b1 b/b2
493 rename from b1
520 rename from b1
494 rename to b2
521 rename to b2
495 --- a/b1
522 --- a/b1
496 +++ b/b2
523 +++ b/b2
497 @@ -0,0 +1,1 @@
524 @@ -0,0 +1,1 @@
498 +b
525 +b
499 diff --git a/c1 b/c1
526 diff --git a/c1 b/c1
500 --- a/c1
527 --- a/c1
501 +++ b/c1
528 +++ b/c1
502 @@ -0,0 +1,1 @@
529 @@ -0,0 +1,1 @@
503 +c
530 +c
504 diff --git a/c1 b/c2
531 diff --git a/c1 b/c2
505 copy from c1
532 copy from c1
506 copy to c2
533 copy to c2
507 --- a/c1
534 --- a/c1
508 +++ b/c2
535 +++ b/c2
509 @@ -0,0 +1,1 @@
536 @@ -0,0 +1,1 @@
510 +c
537 +c
511 diff --git a/d b/d
538 diff --git a/d b/d
512 --- a/d
539 --- a/d
513 +++ b/d
540 +++ b/d
514 @@ -1,1 +0,0 @@
541 @@ -1,1 +0,0 @@
515 -d
542 -d
516 $ hg ci -m empty
543 $ hg ci -m empty
517 $ hg export --git tip > empty.diff
544 $ hg export --git tip > empty.diff
518 $ hg up -C 0
545 $ hg up -C 0
519 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
546 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
520 $ hg import empty.diff
547 $ hg import empty.diff
521 applying empty.diff
548 applying empty.diff
522 $ for name in a b1 b2 c1 c2 d; do
549 $ for name in a b1 b2 c1 c2 d; do
523 > echo % $name file
550 > echo % $name file
524 > test -f $name && cat $name
551 > test -f $name && cat $name
525 > done
552 > done
526 % a file
553 % a file
527 a
554 a
528 % b1 file
555 % b1 file
529 % b2 file
556 % b2 file
530 b
557 b
531 % c1 file
558 % c1 file
532 c
559 c
533 % c2 file
560 % c2 file
534 c
561 c
535 % d file
562 % d file
536 $ cd ..
563 $ cd ..
537
564
538
565
539 Test importing a patch ending with a binary file removal
566 Test importing a patch ending with a binary file removal
540
567
541 $ hg init binaryremoval
568 $ hg init binaryremoval
542 $ cd binaryremoval
569 $ cd binaryremoval
543 $ echo a > a
570 $ echo a > a
544 $ python -c "file('b', 'wb').write('a\x00b')"
571 $ python -c "file('b', 'wb').write('a\x00b')"
545 $ hg ci -Am addall
572 $ hg ci -Am addall
546 adding a
573 adding a
547 adding b
574 adding b
548 $ hg rm a
575 $ hg rm a
549 $ hg rm b
576 $ hg rm b
550 $ hg st
577 $ hg st
551 R a
578 R a
552 R b
579 R b
553 $ hg ci -m remove
580 $ hg ci -m remove
554 $ hg export --git . > remove.diff
581 $ hg export --git . > remove.diff
555 $ cat remove.diff | grep git
582 $ cat remove.diff | grep git
556 diff --git a/a b/a
583 diff --git a/a b/a
557 diff --git a/b b/b
584 diff --git a/b b/b
558 $ hg up -C 0
585 $ hg up -C 0
559 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
586 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
560 $ hg import remove.diff
587 $ hg import remove.diff
561 applying remove.diff
588 applying remove.diff
562 $ hg manifest
589 $ hg manifest
563 $ cd ..
590 $ cd ..
564
591
565
592
566 Issue927: test update+rename with common name
593 Issue927: test update+rename with common name
567
594
568 $ hg init t
595 $ hg init t
569 $ cd t
596 $ cd t
570 $ touch a
597 $ touch a
571 $ hg ci -Am t
598 $ hg ci -Am t
572 adding a
599 adding a
573 $ echo a > a
600 $ echo a > a
574
601
575 Here, bfile.startswith(afile)
602 Here, bfile.startswith(afile)
576
603
577 $ hg copy a a2
604 $ hg copy a a2
578 $ hg ci -m copya
605 $ hg ci -m copya
579 $ hg export --git tip > copy.diff
606 $ hg export --git tip > copy.diff
580 $ hg up -C 0
607 $ hg up -C 0
581 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
608 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
582 $ hg import copy.diff
609 $ hg import copy.diff
583 applying copy.diff
610 applying copy.diff
584
611
585 a should contain an 'a'
612 a should contain an 'a'
586
613
587 $ cat a
614 $ cat a
588 a
615 a
589
616
590 and a2 should have duplicated it
617 and a2 should have duplicated it
591
618
592 $ cat a2
619 $ cat a2
593 a
620 a
594 $ cd ..
621 $ cd ..
595
622
596
623
597 test -p0
624 test -p0
598
625
599 $ hg init p0
626 $ hg init p0
600 $ cd p0
627 $ cd p0
601 $ echo a > a
628 $ echo a > a
602 $ hg ci -Am t
629 $ hg ci -Am t
603 adding a
630 adding a
604 $ hg import -p foo
631 $ hg import -p foo
605 abort: invalid value 'foo' for option -p, expected int
632 abort: invalid value 'foo' for option -p, expected int
606 [255]
633 [255]
607 $ hg import -p0 - << EOF
634 $ hg import -p0 - << EOF
608 > foobar
635 > foobar
609 > --- a Sat Apr 12 22:43:58 2008 -0400
636 > --- a Sat Apr 12 22:43:58 2008 -0400
610 > +++ a Sat Apr 12 22:44:05 2008 -0400
637 > +++ a Sat Apr 12 22:44:05 2008 -0400
611 > @@ -1,1 +1,1 @@
638 > @@ -1,1 +1,1 @@
612 > -a
639 > -a
613 > +bb
640 > +bb
614 > EOF
641 > EOF
615 applying patch from stdin
642 applying patch from stdin
616 $ hg status
643 $ hg status
617 $ cat a
644 $ cat a
618 bb
645 bb
619 $ cd ..
646 $ cd ..
620
647
621
648
622 test paths outside repo root
649 test paths outside repo root
623
650
624 $ mkdir outside
651 $ mkdir outside
625 $ touch outside/foo
652 $ touch outside/foo
626 $ hg init inside
653 $ hg init inside
627 $ cd inside
654 $ cd inside
628 $ hg import - <<EOF
655 $ hg import - <<EOF
629 > diff --git a/a b/b
656 > diff --git a/a b/b
630 > rename from ../outside/foo
657 > rename from ../outside/foo
631 > rename to bar
658 > rename to bar
632 > EOF
659 > EOF
633 applying patch from stdin
660 applying patch from stdin
634 abort: path contains illegal component: ../outside/foo (glob)
661 abort: path contains illegal component: ../outside/foo (glob)
635 [255]
662 [255]
636 $ cd ..
663 $ cd ..
637
664
638
665
639 test import with similarity and git and strip (issue295 et al.)
666 test import with similarity and git and strip (issue295 et al.)
640
667
641 $ hg init sim
668 $ hg init sim
642 $ cd sim
669 $ cd sim
643 $ echo 'this is a test' > a
670 $ echo 'this is a test' > a
644 $ hg ci -Ama
671 $ hg ci -Ama
645 adding a
672 adding a
646 $ cat > ../rename.diff <<EOF
673 $ cat > ../rename.diff <<EOF
647 > diff --git a/foo/a b/foo/a
674 > diff --git a/foo/a b/foo/a
648 > deleted file mode 100644
675 > deleted file mode 100644
649 > --- a/foo/a
676 > --- a/foo/a
650 > +++ /dev/null
677 > +++ /dev/null
651 > @@ -1,1 +0,0 @@
678 > @@ -1,1 +0,0 @@
652 > -this is a test
679 > -this is a test
653 > diff --git a/foo/b b/foo/b
680 > diff --git a/foo/b b/foo/b
654 > new file mode 100644
681 > new file mode 100644
655 > --- /dev/null
682 > --- /dev/null
656 > +++ b/foo/b
683 > +++ b/foo/b
657 > @@ -0,0 +1,2 @@
684 > @@ -0,0 +1,2 @@
658 > +this is a test
685 > +this is a test
659 > +foo
686 > +foo
660 > EOF
687 > EOF
661 $ hg import --no-commit -v -s 1 ../rename.diff -p2
688 $ hg import --no-commit -v -s 1 ../rename.diff -p2
662 applying ../rename.diff
689 applying ../rename.diff
663 patching file a
690 patching file a
664 patching file b
691 patching file b
665 adding b
692 adding b
666 recording removal of a as rename to b (88% similar)
693 recording removal of a as rename to b (88% similar)
667 applied to working directory
694 applied to working directory
668 $ hg st -C
695 $ hg st -C
669 A b
696 A b
670 a
697 a
671 R a
698 R a
672 $ hg revert -a
699 $ hg revert -a
673 undeleting a
700 undeleting a
674 forgetting b
701 forgetting b
675 $ rm b
702 $ rm b
676 $ hg import --no-commit -v -s 100 ../rename.diff -p2
703 $ hg import --no-commit -v -s 100 ../rename.diff -p2
677 applying ../rename.diff
704 applying ../rename.diff
678 patching file a
705 patching file a
679 patching file b
706 patching file b
680 adding b
707 adding b
681 applied to working directory
708 applied to working directory
682 $ hg st -C
709 $ hg st -C
683 A b
710 A b
684 R a
711 R a
685 $ cd ..
712 $ cd ..
686
713
687
714
688 Issue1495: add empty file from the end of patch
715 Issue1495: add empty file from the end of patch
689
716
690 $ hg init addemptyend
717 $ hg init addemptyend
691 $ cd addemptyend
718 $ cd addemptyend
692 $ touch a
719 $ touch a
693 $ hg addremove
720 $ hg addremove
694 adding a
721 adding a
695 $ hg ci -m "commit"
722 $ hg ci -m "commit"
696 $ cat > a.patch <<EOF
723 $ cat > a.patch <<EOF
697 > add a, b
724 > add a, b
698 > diff --git a/a b/a
725 > diff --git a/a b/a
699 > --- a/a
726 > --- a/a
700 > +++ b/a
727 > +++ b/a
701 > @@ -0,0 +1,1 @@
728 > @@ -0,0 +1,1 @@
702 > +a
729 > +a
703 > diff --git a/b b/b
730 > diff --git a/b b/b
704 > new file mode 100644
731 > new file mode 100644
705 > EOF
732 > EOF
706 $ hg import --no-commit a.patch
733 $ hg import --no-commit a.patch
707 applying a.patch
734 applying a.patch
708
735
709 apply a good patch followed by an empty patch (mainly to ensure
736 apply a good patch followed by an empty patch (mainly to ensure
710 that dirstate is *not* updated when import crashes)
737 that dirstate is *not* updated when import crashes)
711 $ hg update -q -C .
738 $ hg update -q -C .
712 $ rm b
739 $ rm b
713 $ touch empty.patch
740 $ touch empty.patch
714 $ hg import a.patch empty.patch
741 $ hg import a.patch empty.patch
715 applying a.patch
742 applying a.patch
716 applying empty.patch
743 applying empty.patch
717 transaction abort!
744 transaction abort!
718 rollback completed
745 rollback completed
719 abort: empty.patch: no diffs found
746 abort: empty.patch: no diffs found
720 [255]
747 [255]
721 $ hg tip --template '{rev} {desc|firstline}\n'
748 $ hg tip --template '{rev} {desc|firstline}\n'
722 0 commit
749 0 commit
723 $ hg -q status
750 $ hg -q status
724 M a
751 M a
725 $ cd ..
752 $ cd ..
726
753
727 create file when source is not /dev/null
754 create file when source is not /dev/null
728
755
729 $ cat > create.patch <<EOF
756 $ cat > create.patch <<EOF
730 > diff -Naur proj-orig/foo proj-new/foo
757 > diff -Naur proj-orig/foo proj-new/foo
731 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
758 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
732 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
759 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
733 > @@ -0,0 +1,1 @@
760 > @@ -0,0 +1,1 @@
734 > +a
761 > +a
735 > EOF
762 > EOF
736
763
737 some people have patches like the following too
764 some people have patches like the following too
738
765
739 $ cat > create2.patch <<EOF
766 $ cat > create2.patch <<EOF
740 > diff -Naur proj-orig/foo proj-new/foo
767 > diff -Naur proj-orig/foo proj-new/foo
741 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
768 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
742 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
769 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
743 > @@ -0,0 +1,1 @@
770 > @@ -0,0 +1,1 @@
744 > +a
771 > +a
745 > EOF
772 > EOF
746 $ hg init oddcreate
773 $ hg init oddcreate
747 $ cd oddcreate
774 $ cd oddcreate
748 $ hg import --no-commit ../create.patch
775 $ hg import --no-commit ../create.patch
749 applying ../create.patch
776 applying ../create.patch
750 $ cat foo
777 $ cat foo
751 a
778 a
752 $ rm foo
779 $ rm foo
753 $ hg revert foo
780 $ hg revert foo
754 $ hg import --no-commit ../create2.patch
781 $ hg import --no-commit ../create2.patch
755 applying ../create2.patch
782 applying ../create2.patch
756 $ cat foo
783 $ cat foo
757 a
784 a
758
785
759 $ cd ..
786 $ cd ..
760
787
761 Issue1859: first line mistaken for email headers
788 Issue1859: first line mistaken for email headers
762
789
763 $ hg init emailconfusion
790 $ hg init emailconfusion
764 $ cd emailconfusion
791 $ cd emailconfusion
765 $ cat > a.patch <<EOF
792 $ cat > a.patch <<EOF
766 > module: summary
793 > module: summary
767 >
794 >
768 > description
795 > description
769 >
796 >
770 >
797 >
771 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
798 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
772 > --- /dev/null
799 > --- /dev/null
773 > +++ b/a
800 > +++ b/a
774 > @@ -0,0 +1,1 @@
801 > @@ -0,0 +1,1 @@
775 > +a
802 > +a
776 > EOF
803 > EOF
777 $ hg import -d '0 0' a.patch
804 $ hg import -d '0 0' a.patch
778 applying a.patch
805 applying a.patch
779 $ hg parents -v
806 $ hg parents -v
780 changeset: 0:5a681217c0ad
807 changeset: 0:5a681217c0ad
781 tag: tip
808 tag: tip
782 user: test
809 user: test
783 date: Thu Jan 01 00:00:00 1970 +0000
810 date: Thu Jan 01 00:00:00 1970 +0000
784 files: a
811 files: a
785 description:
812 description:
786 module: summary
813 module: summary
787
814
788 description
815 description
789
816
790
817
791 $ cd ..
818 $ cd ..
792
819
793
820
794 in commit message
821 in commit message
795
822
796 $ hg init commitconfusion
823 $ hg init commitconfusion
797 $ cd commitconfusion
824 $ cd commitconfusion
798 $ cat > a.patch <<EOF
825 $ cat > a.patch <<EOF
799 > module: summary
826 > module: summary
800 >
827 >
801 > --- description
828 > --- description
802 >
829 >
803 > diff --git a/a b/a
830 > diff --git a/a b/a
804 > new file mode 100644
831 > new file mode 100644
805 > --- /dev/null
832 > --- /dev/null
806 > +++ b/a
833 > +++ b/a
807 > @@ -0,0 +1,1 @@
834 > @@ -0,0 +1,1 @@
808 > +a
835 > +a
809 > EOF
836 > EOF
810 > hg import -d '0 0' a.patch
837 > hg import -d '0 0' a.patch
811 > hg parents -v
838 > hg parents -v
812 > cd ..
839 > cd ..
813 >
840 >
814 > echo '% tricky header splitting'
841 > echo '% tricky header splitting'
815 > cat > trickyheaders.patch <<EOF
842 > cat > trickyheaders.patch <<EOF
816 > From: User A <user@a>
843 > From: User A <user@a>
817 > Subject: [PATCH] from: tricky!
844 > Subject: [PATCH] from: tricky!
818 >
845 >
819 > # HG changeset patch
846 > # HG changeset patch
820 > # User User B
847 > # User User B
821 > # Date 1266264441 18000
848 > # Date 1266264441 18000
822 > # Branch stable
849 > # Branch stable
823 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
850 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
824 > # Parent 0000000000000000000000000000000000000000
851 > # Parent 0000000000000000000000000000000000000000
825 > from: tricky!
852 > from: tricky!
826 >
853 >
827 > That is not a header.
854 > That is not a header.
828 >
855 >
829 > diff -r 000000000000 -r f2be6a1170ac foo
856 > diff -r 000000000000 -r f2be6a1170ac foo
830 > --- /dev/null
857 > --- /dev/null
831 > +++ b/foo
858 > +++ b/foo
832 > @@ -0,0 +1,1 @@
859 > @@ -0,0 +1,1 @@
833 > +foo
860 > +foo
834 > EOF
861 > EOF
835 applying a.patch
862 applying a.patch
836 changeset: 0:f34d9187897d
863 changeset: 0:f34d9187897d
837 tag: tip
864 tag: tip
838 user: test
865 user: test
839 date: Thu Jan 01 00:00:00 1970 +0000
866 date: Thu Jan 01 00:00:00 1970 +0000
840 files: a
867 files: a
841 description:
868 description:
842 module: summary
869 module: summary
843
870
844
871
845 % tricky header splitting
872 % tricky header splitting
846
873
847 $ hg init trickyheaders
874 $ hg init trickyheaders
848 $ cd trickyheaders
875 $ cd trickyheaders
849 $ hg import -d '0 0' ../trickyheaders.patch
876 $ hg import -d '0 0' ../trickyheaders.patch
850 applying ../trickyheaders.patch
877 applying ../trickyheaders.patch
851 $ hg export --git tip
878 $ hg export --git tip
852 # HG changeset patch
879 # HG changeset patch
853 # User User B
880 # User User B
854 # Date 0 0
881 # Date 0 0
855 # Thu Jan 01 00:00:00 1970 +0000
882 # Thu Jan 01 00:00:00 1970 +0000
856 # Node ID eb56ab91903632294ac504838508cb370c0901d2
883 # Node ID eb56ab91903632294ac504838508cb370c0901d2
857 # Parent 0000000000000000000000000000000000000000
884 # Parent 0000000000000000000000000000000000000000
858 from: tricky!
885 from: tricky!
859
886
860 That is not a header.
887 That is not a header.
861
888
862 diff --git a/foo b/foo
889 diff --git a/foo b/foo
863 new file mode 100644
890 new file mode 100644
864 --- /dev/null
891 --- /dev/null
865 +++ b/foo
892 +++ b/foo
866 @@ -0,0 +1,1 @@
893 @@ -0,0 +1,1 @@
867 +foo
894 +foo
868 $ cd ..
895 $ cd ..
869
896
870
897
871 Issue2102: hg export and hg import speak different languages
898 Issue2102: hg export and hg import speak different languages
872
899
873 $ hg init issue2102
900 $ hg init issue2102
874 $ cd issue2102
901 $ cd issue2102
875 $ mkdir -p src/cmd/gc
902 $ mkdir -p src/cmd/gc
876 $ touch src/cmd/gc/mksys.bash
903 $ touch src/cmd/gc/mksys.bash
877 $ hg ci -Am init
904 $ hg ci -Am init
878 adding src/cmd/gc/mksys.bash
905 adding src/cmd/gc/mksys.bash
879 $ hg import - <<EOF
906 $ hg import - <<EOF
880 > # HG changeset patch
907 > # HG changeset patch
881 > # User Rob Pike
908 > # User Rob Pike
882 > # Date 1216685449 25200
909 > # Date 1216685449 25200
883 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
910 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
884 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
911 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
885 > help management of empty pkg and lib directories in perforce
912 > help management of empty pkg and lib directories in perforce
886 >
913 >
887 > R=gri
914 > R=gri
888 > DELTA=4 (4 added, 0 deleted, 0 changed)
915 > DELTA=4 (4 added, 0 deleted, 0 changed)
889 > OCL=13328
916 > OCL=13328
890 > CL=13328
917 > CL=13328
891 >
918 >
892 > diff --git a/lib/place-holder b/lib/place-holder
919 > diff --git a/lib/place-holder b/lib/place-holder
893 > new file mode 100644
920 > new file mode 100644
894 > --- /dev/null
921 > --- /dev/null
895 > +++ b/lib/place-holder
922 > +++ b/lib/place-holder
896 > @@ -0,0 +1,2 @@
923 > @@ -0,0 +1,2 @@
897 > +perforce does not maintain empty directories.
924 > +perforce does not maintain empty directories.
898 > +this file helps.
925 > +this file helps.
899 > diff --git a/pkg/place-holder b/pkg/place-holder
926 > diff --git a/pkg/place-holder b/pkg/place-holder
900 > new file mode 100644
927 > new file mode 100644
901 > --- /dev/null
928 > --- /dev/null
902 > +++ b/pkg/place-holder
929 > +++ b/pkg/place-holder
903 > @@ -0,0 +1,2 @@
930 > @@ -0,0 +1,2 @@
904 > +perforce does not maintain empty directories.
931 > +perforce does not maintain empty directories.
905 > +this file helps.
932 > +this file helps.
906 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
933 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
907 > old mode 100644
934 > old mode 100644
908 > new mode 100755
935 > new mode 100755
909 > EOF
936 > EOF
910 applying patch from stdin
937 applying patch from stdin
911
938
912 #if execbit
939 #if execbit
913
940
914 $ hg sum
941 $ hg sum
915 parent: 1:d59915696727 tip
942 parent: 1:d59915696727 tip
916 help management of empty pkg and lib directories in perforce
943 help management of empty pkg and lib directories in perforce
917 branch: default
944 branch: default
918 commit: (clean)
945 commit: (clean)
919 update: (current)
946 update: (current)
920
947
921 $ hg diff --git -c tip
948 $ hg diff --git -c tip
922 diff --git a/lib/place-holder b/lib/place-holder
949 diff --git a/lib/place-holder b/lib/place-holder
923 new file mode 100644
950 new file mode 100644
924 --- /dev/null
951 --- /dev/null
925 +++ b/lib/place-holder
952 +++ b/lib/place-holder
926 @@ -0,0 +1,2 @@
953 @@ -0,0 +1,2 @@
927 +perforce does not maintain empty directories.
954 +perforce does not maintain empty directories.
928 +this file helps.
955 +this file helps.
929 diff --git a/pkg/place-holder b/pkg/place-holder
956 diff --git a/pkg/place-holder b/pkg/place-holder
930 new file mode 100644
957 new file mode 100644
931 --- /dev/null
958 --- /dev/null
932 +++ b/pkg/place-holder
959 +++ b/pkg/place-holder
933 @@ -0,0 +1,2 @@
960 @@ -0,0 +1,2 @@
934 +perforce does not maintain empty directories.
961 +perforce does not maintain empty directories.
935 +this file helps.
962 +this file helps.
936 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
963 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
937 old mode 100644
964 old mode 100644
938 new mode 100755
965 new mode 100755
939
966
940 #else
967 #else
941
968
942 $ hg sum
969 $ hg sum
943 parent: 1:28f089cc9ccc tip
970 parent: 1:28f089cc9ccc tip
944 help management of empty pkg and lib directories in perforce
971 help management of empty pkg and lib directories in perforce
945 branch: default
972 branch: default
946 commit: (clean)
973 commit: (clean)
947 update: (current)
974 update: (current)
948
975
949 $ hg diff --git -c tip
976 $ hg diff --git -c tip
950 diff --git a/lib/place-holder b/lib/place-holder
977 diff --git a/lib/place-holder b/lib/place-holder
951 new file mode 100644
978 new file mode 100644
952 --- /dev/null
979 --- /dev/null
953 +++ b/lib/place-holder
980 +++ b/lib/place-holder
954 @@ -0,0 +1,2 @@
981 @@ -0,0 +1,2 @@
955 +perforce does not maintain empty directories.
982 +perforce does not maintain empty directories.
956 +this file helps.
983 +this file helps.
957 diff --git a/pkg/place-holder b/pkg/place-holder
984 diff --git a/pkg/place-holder b/pkg/place-holder
958 new file mode 100644
985 new file mode 100644
959 --- /dev/null
986 --- /dev/null
960 +++ b/pkg/place-holder
987 +++ b/pkg/place-holder
961 @@ -0,0 +1,2 @@
988 @@ -0,0 +1,2 @@
962 +perforce does not maintain empty directories.
989 +perforce does not maintain empty directories.
963 +this file helps.
990 +this file helps.
964
991
965 /* The mode change for mksys.bash is missing here, because on platforms */
992 /* The mode change for mksys.bash is missing here, because on platforms */
966 /* that don't support execbits, mode changes in patches are ignored when */
993 /* that don't support execbits, mode changes in patches are ignored when */
967 /* they are imported. This is obviously also the reason for why the hash */
994 /* they are imported. This is obviously also the reason for why the hash */
968 /* in the created changeset is different to the one you see above the */
995 /* in the created changeset is different to the one you see above the */
969 /* #else clause */
996 /* #else clause */
970
997
971 #endif
998 #endif
972 $ cd ..
999 $ cd ..
973
1000
974
1001
975 diff lines looking like headers
1002 diff lines looking like headers
976
1003
977 $ hg init difflineslikeheaders
1004 $ hg init difflineslikeheaders
978 $ cd difflineslikeheaders
1005 $ cd difflineslikeheaders
979 $ echo a >a
1006 $ echo a >a
980 $ echo b >b
1007 $ echo b >b
981 $ echo c >c
1008 $ echo c >c
982 $ hg ci -Am1
1009 $ hg ci -Am1
983 adding a
1010 adding a
984 adding b
1011 adding b
985 adding c
1012 adding c
986
1013
987 $ echo "key: value" >>a
1014 $ echo "key: value" >>a
988 $ echo "key: value" >>b
1015 $ echo "key: value" >>b
989 $ echo "foo" >>c
1016 $ echo "foo" >>c
990 $ hg ci -m2
1017 $ hg ci -m2
991
1018
992 $ hg up -C 0
1019 $ hg up -C 0
993 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1020 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
994 $ hg diff --git -c1 >want
1021 $ hg diff --git -c1 >want
995 $ hg diff -c1 | hg import --no-commit -
1022 $ hg diff -c1 | hg import --no-commit -
996 applying patch from stdin
1023 applying patch from stdin
997 $ hg diff --git >have
1024 $ hg diff --git >have
998 $ diff want have
1025 $ diff want have
999 $ cd ..
1026 $ cd ..
1000
1027
1001 import a unified diff with no lines of context (diff -U0)
1028 import a unified diff with no lines of context (diff -U0)
1002
1029
1003 $ hg init diffzero
1030 $ hg init diffzero
1004 $ cd diffzero
1031 $ cd diffzero
1005 $ cat > f << EOF
1032 $ cat > f << EOF
1006 > c2
1033 > c2
1007 > c4
1034 > c4
1008 > c5
1035 > c5
1009 > EOF
1036 > EOF
1010 $ hg commit -Am0
1037 $ hg commit -Am0
1011 adding f
1038 adding f
1012
1039
1013 $ hg import --no-commit - << EOF
1040 $ hg import --no-commit - << EOF
1014 > # HG changeset patch
1041 > # HG changeset patch
1015 > # User test
1042 > # User test
1016 > # Date 0 0
1043 > # Date 0 0
1017 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
1044 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
1018 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
1045 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
1019 > 1
1046 > 1
1020 > diff -r 8679a12a975b -r f4974ab632f3 f
1047 > diff -r 8679a12a975b -r f4974ab632f3 f
1021 > --- a/f Thu Jan 01 00:00:00 1970 +0000
1048 > --- a/f Thu Jan 01 00:00:00 1970 +0000
1022 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
1049 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
1023 > @@ -0,0 +1,1 @@
1050 > @@ -0,0 +1,1 @@
1024 > +c1
1051 > +c1
1025 > @@ -1,0 +3,1 @@
1052 > @@ -1,0 +3,1 @@
1026 > +c3
1053 > +c3
1027 > @@ -3,1 +4,0 @@
1054 > @@ -3,1 +4,0 @@
1028 > -c5
1055 > -c5
1029 > EOF
1056 > EOF
1030 applying patch from stdin
1057 applying patch from stdin
1031
1058
1032 $ cat f
1059 $ cat f
1033 c1
1060 c1
1034 c2
1061 c2
1035 c3
1062 c3
1036 c4
1063 c4
1037
1064
1038 $ cd ..
1065 $ cd ..
1039
1066
1040 no segfault while importing a unified diff which start line is zero but chunk
1067 no segfault while importing a unified diff which start line is zero but chunk
1041 size is non-zero
1068 size is non-zero
1042
1069
1043 $ hg init startlinezero
1070 $ hg init startlinezero
1044 $ cd startlinezero
1071 $ cd startlinezero
1045 $ echo foo > foo
1072 $ echo foo > foo
1046 $ hg commit -Amfoo
1073 $ hg commit -Amfoo
1047 adding foo
1074 adding foo
1048
1075
1049 $ hg import --no-commit - << EOF
1076 $ hg import --no-commit - << EOF
1050 > diff a/foo b/foo
1077 > diff a/foo b/foo
1051 > --- a/foo
1078 > --- a/foo
1052 > +++ b/foo
1079 > +++ b/foo
1053 > @@ -0,1 +0,1 @@
1080 > @@ -0,1 +0,1 @@
1054 > foo
1081 > foo
1055 > EOF
1082 > EOF
1056 applying patch from stdin
1083 applying patch from stdin
1057
1084
1058 $ cd ..
1085 $ cd ..
1059
1086
1060 Test corner case involving fuzz and skew
1087 Test corner case involving fuzz and skew
1061
1088
1062 $ hg init morecornercases
1089 $ hg init morecornercases
1063 $ cd morecornercases
1090 $ cd morecornercases
1064
1091
1065 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1092 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1066 > diff --git a/a b/a
1093 > diff --git a/a b/a
1067 > --- a/a
1094 > --- a/a
1068 > +++ b/a
1095 > +++ b/a
1069 > @@ -1,0 +1,1 @@
1096 > @@ -1,0 +1,1 @@
1070 > +line
1097 > +line
1071 > EOF
1098 > EOF
1072
1099
1073 $ cat > 02-no-context-middle-of-file.diff <<EOF
1100 $ cat > 02-no-context-middle-of-file.diff <<EOF
1074 > diff --git a/a b/a
1101 > diff --git a/a b/a
1075 > --- a/a
1102 > --- a/a
1076 > +++ b/a
1103 > +++ b/a
1077 > @@ -1,1 +1,1 @@
1104 > @@ -1,1 +1,1 @@
1078 > -2
1105 > -2
1079 > +add some skew
1106 > +add some skew
1080 > @@ -2,0 +2,1 @@
1107 > @@ -2,0 +2,1 @@
1081 > +line
1108 > +line
1082 > EOF
1109 > EOF
1083
1110
1084 $ cat > 03-no-context-end-of-file.diff <<EOF
1111 $ cat > 03-no-context-end-of-file.diff <<EOF
1085 > diff --git a/a b/a
1112 > diff --git a/a b/a
1086 > --- a/a
1113 > --- a/a
1087 > +++ b/a
1114 > +++ b/a
1088 > @@ -10,0 +10,1 @@
1115 > @@ -10,0 +10,1 @@
1089 > +line
1116 > +line
1090 > EOF
1117 > EOF
1091
1118
1092 $ cat > 04-middle-of-file-completely-fuzzed.diff <<EOF
1119 $ cat > 04-middle-of-file-completely-fuzzed.diff <<EOF
1093 > diff --git a/a b/a
1120 > diff --git a/a b/a
1094 > --- a/a
1121 > --- a/a
1095 > +++ b/a
1122 > +++ b/a
1096 > @@ -1,1 +1,1 @@
1123 > @@ -1,1 +1,1 @@
1097 > -2
1124 > -2
1098 > +add some skew
1125 > +add some skew
1099 > @@ -2,2 +2,3 @@
1126 > @@ -2,2 +2,3 @@
1100 > not matching, should fuzz
1127 > not matching, should fuzz
1101 > ... a bit
1128 > ... a bit
1102 > +line
1129 > +line
1103 > EOF
1130 > EOF
1104
1131
1105 $ cat > a <<EOF
1132 $ cat > a <<EOF
1106 > 1
1133 > 1
1107 > 2
1134 > 2
1108 > 3
1135 > 3
1109 > 4
1136 > 4
1110 > EOF
1137 > EOF
1111 $ hg ci -Am adda a
1138 $ hg ci -Am adda a
1112 $ for p in *.diff; do
1139 $ for p in *.diff; do
1113 > hg import -v --no-commit $p
1140 > hg import -v --no-commit $p
1114 > cat a
1141 > cat a
1115 > hg revert -aqC a
1142 > hg revert -aqC a
1116 > # patch -p1 < $p
1143 > # patch -p1 < $p
1117 > # cat a
1144 > # cat a
1118 > # hg revert -aC a
1145 > # hg revert -aC a
1119 > done
1146 > done
1120 applying 01-no-context-beginning-of-file.diff
1147 applying 01-no-context-beginning-of-file.diff
1121 patching file a
1148 patching file a
1122 applied to working directory
1149 applied to working directory
1123 1
1150 1
1124 line
1151 line
1125 2
1152 2
1126 3
1153 3
1127 4
1154 4
1128 applying 02-no-context-middle-of-file.diff
1155 applying 02-no-context-middle-of-file.diff
1129 patching file a
1156 patching file a
1130 Hunk #1 succeeded at 2 (offset 1 lines).
1157 Hunk #1 succeeded at 2 (offset 1 lines).
1131 Hunk #2 succeeded at 4 (offset 1 lines).
1158 Hunk #2 succeeded at 4 (offset 1 lines).
1132 applied to working directory
1159 applied to working directory
1133 1
1160 1
1134 add some skew
1161 add some skew
1135 3
1162 3
1136 line
1163 line
1137 4
1164 4
1138 applying 03-no-context-end-of-file.diff
1165 applying 03-no-context-end-of-file.diff
1139 patching file a
1166 patching file a
1140 Hunk #1 succeeded at 5 (offset -6 lines).
1167 Hunk #1 succeeded at 5 (offset -6 lines).
1141 applied to working directory
1168 applied to working directory
1142 1
1169 1
1143 2
1170 2
1144 3
1171 3
1145 4
1172 4
1146 line
1173 line
1147 applying 04-middle-of-file-completely-fuzzed.diff
1174 applying 04-middle-of-file-completely-fuzzed.diff
1148 patching file a
1175 patching file a
1149 Hunk #1 succeeded at 2 (offset 1 lines).
1176 Hunk #1 succeeded at 2 (offset 1 lines).
1150 Hunk #2 succeeded at 5 with fuzz 2 (offset 1 lines).
1177 Hunk #2 succeeded at 5 with fuzz 2 (offset 1 lines).
1151 applied to working directory
1178 applied to working directory
1152 1
1179 1
1153 add some skew
1180 add some skew
1154 3
1181 3
1155 4
1182 4
1156 line
1183 line
1157
1184
1158 $ cd ..
1185 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now