##// END OF EJS Templates
cmdutil: add support for evolution "troubles" display in changeset_printer...
Denis Laxalde -
r30695:f05ede08 default
parent child Browse files
Show More
@@ -1,3454 +1,3462 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12 import re
12 import re
13 import tempfile
13 import tempfile
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 bin,
17 bin,
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 )
22 )
23
23
24 from . import (
24 from . import (
25 bookmarks,
25 bookmarks,
26 changelog,
26 changelog,
27 copies,
27 copies,
28 crecord as crecordmod,
28 crecord as crecordmod,
29 dirstateguard as dirstateguardmod,
29 dirstateguard as dirstateguardmod,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 graphmod,
33 graphmod,
34 lock as lockmod,
34 lock as lockmod,
35 match as matchmod,
35 match as matchmod,
36 mergeutil,
36 mergeutil,
37 obsolete,
37 obsolete,
38 patch,
38 patch,
39 pathutil,
39 pathutil,
40 phases,
40 phases,
41 pycompat,
41 pycompat,
42 repair,
42 repair,
43 revlog,
43 revlog,
44 revset,
44 revset,
45 scmutil,
45 scmutil,
46 templatekw,
46 templatekw,
47 templater,
47 templater,
48 util,
48 util,
49 )
49 )
50 stringio = util.stringio
50 stringio = util.stringio
51
51
52 def ishunk(x):
52 def ishunk(x):
53 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
53 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
54 return isinstance(x, hunkclasses)
54 return isinstance(x, hunkclasses)
55
55
56 def newandmodified(chunks, originalchunks):
56 def newandmodified(chunks, originalchunks):
57 newlyaddedandmodifiedfiles = set()
57 newlyaddedandmodifiedfiles = set()
58 for chunk in chunks:
58 for chunk in chunks:
59 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
59 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
60 originalchunks:
60 originalchunks:
61 newlyaddedandmodifiedfiles.add(chunk.header.filename())
61 newlyaddedandmodifiedfiles.add(chunk.header.filename())
62 return newlyaddedandmodifiedfiles
62 return newlyaddedandmodifiedfiles
63
63
64 def parsealiases(cmd):
64 def parsealiases(cmd):
65 return cmd.lstrip("^").split("|")
65 return cmd.lstrip("^").split("|")
66
66
67 def setupwrapcolorwrite(ui):
67 def setupwrapcolorwrite(ui):
68 # wrap ui.write so diff output can be labeled/colorized
68 # wrap ui.write so diff output can be labeled/colorized
69 def wrapwrite(orig, *args, **kw):
69 def wrapwrite(orig, *args, **kw):
70 label = kw.pop('label', '')
70 label = kw.pop('label', '')
71 for chunk, l in patch.difflabel(lambda: args):
71 for chunk, l in patch.difflabel(lambda: args):
72 orig(chunk, label=label + l)
72 orig(chunk, label=label + l)
73
73
74 oldwrite = ui.write
74 oldwrite = ui.write
75 def wrap(*args, **kwargs):
75 def wrap(*args, **kwargs):
76 return wrapwrite(oldwrite, *args, **kwargs)
76 return wrapwrite(oldwrite, *args, **kwargs)
77 setattr(ui, 'write', wrap)
77 setattr(ui, 'write', wrap)
78 return oldwrite
78 return oldwrite
79
79
80 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
80 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
81 if usecurses:
81 if usecurses:
82 if testfile:
82 if testfile:
83 recordfn = crecordmod.testdecorator(testfile,
83 recordfn = crecordmod.testdecorator(testfile,
84 crecordmod.testchunkselector)
84 crecordmod.testchunkselector)
85 else:
85 else:
86 recordfn = crecordmod.chunkselector
86 recordfn = crecordmod.chunkselector
87
87
88 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
88 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
89
89
90 else:
90 else:
91 return patch.filterpatch(ui, originalhunks, operation)
91 return patch.filterpatch(ui, originalhunks, operation)
92
92
93 def recordfilter(ui, originalhunks, operation=None):
93 def recordfilter(ui, originalhunks, operation=None):
94 """ Prompts the user to filter the originalhunks and return a list of
94 """ Prompts the user to filter the originalhunks and return a list of
95 selected hunks.
95 selected hunks.
96 *operation* is used for to build ui messages to indicate the user what
96 *operation* is used for to build ui messages to indicate the user what
97 kind of filtering they are doing: reverting, committing, shelving, etc.
97 kind of filtering they are doing: reverting, committing, shelving, etc.
98 (see patch.filterpatch).
98 (see patch.filterpatch).
99 """
99 """
100 usecurses = crecordmod.checkcurses(ui)
100 usecurses = crecordmod.checkcurses(ui)
101 testfile = ui.config('experimental', 'crecordtest', None)
101 testfile = ui.config('experimental', 'crecordtest', None)
102 oldwrite = setupwrapcolorwrite(ui)
102 oldwrite = setupwrapcolorwrite(ui)
103 try:
103 try:
104 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
104 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
105 testfile, operation)
105 testfile, operation)
106 finally:
106 finally:
107 ui.write = oldwrite
107 ui.write = oldwrite
108 return newchunks, newopts
108 return newchunks, newopts
109
109
110 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
110 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
111 filterfn, *pats, **opts):
111 filterfn, *pats, **opts):
112 from . import merge as mergemod
112 from . import merge as mergemod
113 if not ui.interactive():
113 if not ui.interactive():
114 if cmdsuggest:
114 if cmdsuggest:
115 msg = _('running non-interactively, use %s instead') % cmdsuggest
115 msg = _('running non-interactively, use %s instead') % cmdsuggest
116 else:
116 else:
117 msg = _('running non-interactively')
117 msg = _('running non-interactively')
118 raise error.Abort(msg)
118 raise error.Abort(msg)
119
119
120 # make sure username is set before going interactive
120 # make sure username is set before going interactive
121 if not opts.get('user'):
121 if not opts.get('user'):
122 ui.username() # raise exception, username not provided
122 ui.username() # raise exception, username not provided
123
123
124 def recordfunc(ui, repo, message, match, opts):
124 def recordfunc(ui, repo, message, match, opts):
125 """This is generic record driver.
125 """This is generic record driver.
126
126
127 Its job is to interactively filter local changes, and
127 Its job is to interactively filter local changes, and
128 accordingly prepare working directory into a state in which the
128 accordingly prepare working directory into a state in which the
129 job can be delegated to a non-interactive commit command such as
129 job can be delegated to a non-interactive commit command such as
130 'commit' or 'qrefresh'.
130 'commit' or 'qrefresh'.
131
131
132 After the actual job is done by non-interactive command, the
132 After the actual job is done by non-interactive command, the
133 working directory is restored to its original state.
133 working directory is restored to its original state.
134
134
135 In the end we'll record interesting changes, and everything else
135 In the end we'll record interesting changes, and everything else
136 will be left in place, so the user can continue working.
136 will be left in place, so the user can continue working.
137 """
137 """
138
138
139 checkunfinished(repo, commit=True)
139 checkunfinished(repo, commit=True)
140 wctx = repo[None]
140 wctx = repo[None]
141 merge = len(wctx.parents()) > 1
141 merge = len(wctx.parents()) > 1
142 if merge:
142 if merge:
143 raise error.Abort(_('cannot partially commit a merge '
143 raise error.Abort(_('cannot partially commit a merge '
144 '(use "hg commit" instead)'))
144 '(use "hg commit" instead)'))
145
145
146 def fail(f, msg):
146 def fail(f, msg):
147 raise error.Abort('%s: %s' % (f, msg))
147 raise error.Abort('%s: %s' % (f, msg))
148
148
149 force = opts.get('force')
149 force = opts.get('force')
150 if not force:
150 if not force:
151 vdirs = []
151 vdirs = []
152 match.explicitdir = vdirs.append
152 match.explicitdir = vdirs.append
153 match.bad = fail
153 match.bad = fail
154
154
155 status = repo.status(match=match)
155 status = repo.status(match=match)
156 if not force:
156 if not force:
157 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
157 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
158 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
158 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
159 diffopts.nodates = True
159 diffopts.nodates = True
160 diffopts.git = True
160 diffopts.git = True
161 diffopts.showfunc = True
161 diffopts.showfunc = True
162 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
162 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
163 originalchunks = patch.parsepatch(originaldiff)
163 originalchunks = patch.parsepatch(originaldiff)
164
164
165 # 1. filter patch, since we are intending to apply subset of it
165 # 1. filter patch, since we are intending to apply subset of it
166 try:
166 try:
167 chunks, newopts = filterfn(ui, originalchunks)
167 chunks, newopts = filterfn(ui, originalchunks)
168 except patch.PatchError as err:
168 except patch.PatchError as err:
169 raise error.Abort(_('error parsing patch: %s') % err)
169 raise error.Abort(_('error parsing patch: %s') % err)
170 opts.update(newopts)
170 opts.update(newopts)
171
171
172 # We need to keep a backup of files that have been newly added and
172 # We need to keep a backup of files that have been newly added and
173 # modified during the recording process because there is a previous
173 # modified during the recording process because there is a previous
174 # version without the edit in the workdir
174 # version without the edit in the workdir
175 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
175 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
176 contenders = set()
176 contenders = set()
177 for h in chunks:
177 for h in chunks:
178 try:
178 try:
179 contenders.update(set(h.files()))
179 contenders.update(set(h.files()))
180 except AttributeError:
180 except AttributeError:
181 pass
181 pass
182
182
183 changed = status.modified + status.added + status.removed
183 changed = status.modified + status.added + status.removed
184 newfiles = [f for f in changed if f in contenders]
184 newfiles = [f for f in changed if f in contenders]
185 if not newfiles:
185 if not newfiles:
186 ui.status(_('no changes to record\n'))
186 ui.status(_('no changes to record\n'))
187 return 0
187 return 0
188
188
189 modified = set(status.modified)
189 modified = set(status.modified)
190
190
191 # 2. backup changed files, so we can restore them in the end
191 # 2. backup changed files, so we can restore them in the end
192
192
193 if backupall:
193 if backupall:
194 tobackup = changed
194 tobackup = changed
195 else:
195 else:
196 tobackup = [f for f in newfiles if f in modified or f in \
196 tobackup = [f for f in newfiles if f in modified or f in \
197 newlyaddedandmodifiedfiles]
197 newlyaddedandmodifiedfiles]
198 backups = {}
198 backups = {}
199 if tobackup:
199 if tobackup:
200 backupdir = repo.join('record-backups')
200 backupdir = repo.join('record-backups')
201 try:
201 try:
202 os.mkdir(backupdir)
202 os.mkdir(backupdir)
203 except OSError as err:
203 except OSError as err:
204 if err.errno != errno.EEXIST:
204 if err.errno != errno.EEXIST:
205 raise
205 raise
206 try:
206 try:
207 # backup continues
207 # backup continues
208 for f in tobackup:
208 for f in tobackup:
209 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
209 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
210 dir=backupdir)
210 dir=backupdir)
211 os.close(fd)
211 os.close(fd)
212 ui.debug('backup %r as %r\n' % (f, tmpname))
212 ui.debug('backup %r as %r\n' % (f, tmpname))
213 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
213 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
214 backups[f] = tmpname
214 backups[f] = tmpname
215
215
216 fp = stringio()
216 fp = stringio()
217 for c in chunks:
217 for c in chunks:
218 fname = c.filename()
218 fname = c.filename()
219 if fname in backups:
219 if fname in backups:
220 c.write(fp)
220 c.write(fp)
221 dopatch = fp.tell()
221 dopatch = fp.tell()
222 fp.seek(0)
222 fp.seek(0)
223
223
224 # 2.5 optionally review / modify patch in text editor
224 # 2.5 optionally review / modify patch in text editor
225 if opts.get('review', False):
225 if opts.get('review', False):
226 patchtext = (crecordmod.diffhelptext
226 patchtext = (crecordmod.diffhelptext
227 + crecordmod.patchhelptext
227 + crecordmod.patchhelptext
228 + fp.read())
228 + fp.read())
229 reviewedpatch = ui.edit(patchtext, "",
229 reviewedpatch = ui.edit(patchtext, "",
230 extra={"suffix": ".diff"})
230 extra={"suffix": ".diff"})
231 fp.truncate(0)
231 fp.truncate(0)
232 fp.write(reviewedpatch)
232 fp.write(reviewedpatch)
233 fp.seek(0)
233 fp.seek(0)
234
234
235 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
235 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
236 # 3a. apply filtered patch to clean repo (clean)
236 # 3a. apply filtered patch to clean repo (clean)
237 if backups:
237 if backups:
238 # Equivalent to hg.revert
238 # Equivalent to hg.revert
239 m = scmutil.matchfiles(repo, backups.keys())
239 m = scmutil.matchfiles(repo, backups.keys())
240 mergemod.update(repo, repo.dirstate.p1(),
240 mergemod.update(repo, repo.dirstate.p1(),
241 False, True, matcher=m)
241 False, True, matcher=m)
242
242
243 # 3b. (apply)
243 # 3b. (apply)
244 if dopatch:
244 if dopatch:
245 try:
245 try:
246 ui.debug('applying patch\n')
246 ui.debug('applying patch\n')
247 ui.debug(fp.getvalue())
247 ui.debug(fp.getvalue())
248 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
248 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
249 except patch.PatchError as err:
249 except patch.PatchError as err:
250 raise error.Abort(str(err))
250 raise error.Abort(str(err))
251 del fp
251 del fp
252
252
253 # 4. We prepared working directory according to filtered
253 # 4. We prepared working directory according to filtered
254 # patch. Now is the time to delegate the job to
254 # patch. Now is the time to delegate the job to
255 # commit/qrefresh or the like!
255 # commit/qrefresh or the like!
256
256
257 # Make all of the pathnames absolute.
257 # Make all of the pathnames absolute.
258 newfiles = [repo.wjoin(nf) for nf in newfiles]
258 newfiles = [repo.wjoin(nf) for nf in newfiles]
259 return commitfunc(ui, repo, *newfiles, **opts)
259 return commitfunc(ui, repo, *newfiles, **opts)
260 finally:
260 finally:
261 # 5. finally restore backed-up files
261 # 5. finally restore backed-up files
262 try:
262 try:
263 dirstate = repo.dirstate
263 dirstate = repo.dirstate
264 for realname, tmpname in backups.iteritems():
264 for realname, tmpname in backups.iteritems():
265 ui.debug('restoring %r to %r\n' % (tmpname, realname))
265 ui.debug('restoring %r to %r\n' % (tmpname, realname))
266
266
267 if dirstate[realname] == 'n':
267 if dirstate[realname] == 'n':
268 # without normallookup, restoring timestamp
268 # without normallookup, restoring timestamp
269 # may cause partially committed files
269 # may cause partially committed files
270 # to be treated as unmodified
270 # to be treated as unmodified
271 dirstate.normallookup(realname)
271 dirstate.normallookup(realname)
272
272
273 # copystat=True here and above are a hack to trick any
273 # copystat=True here and above are a hack to trick any
274 # editors that have f open that we haven't modified them.
274 # editors that have f open that we haven't modified them.
275 #
275 #
276 # Also note that this racy as an editor could notice the
276 # Also note that this racy as an editor could notice the
277 # file's mtime before we've finished writing it.
277 # file's mtime before we've finished writing it.
278 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
278 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
279 os.unlink(tmpname)
279 os.unlink(tmpname)
280 if tobackup:
280 if tobackup:
281 os.rmdir(backupdir)
281 os.rmdir(backupdir)
282 except OSError:
282 except OSError:
283 pass
283 pass
284
284
285 def recordinwlock(ui, repo, message, match, opts):
285 def recordinwlock(ui, repo, message, match, opts):
286 with repo.wlock():
286 with repo.wlock():
287 return recordfunc(ui, repo, message, match, opts)
287 return recordfunc(ui, repo, message, match, opts)
288
288
289 return commit(ui, repo, recordinwlock, pats, opts)
289 return commit(ui, repo, recordinwlock, pats, opts)
290
290
291 def findpossible(cmd, table, strict=False):
291 def findpossible(cmd, table, strict=False):
292 """
292 """
293 Return cmd -> (aliases, command table entry)
293 Return cmd -> (aliases, command table entry)
294 for each matching command.
294 for each matching command.
295 Return debug commands (or their aliases) only if no normal command matches.
295 Return debug commands (or their aliases) only if no normal command matches.
296 """
296 """
297 choice = {}
297 choice = {}
298 debugchoice = {}
298 debugchoice = {}
299
299
300 if cmd in table:
300 if cmd in table:
301 # short-circuit exact matches, "log" alias beats "^log|history"
301 # short-circuit exact matches, "log" alias beats "^log|history"
302 keys = [cmd]
302 keys = [cmd]
303 else:
303 else:
304 keys = table.keys()
304 keys = table.keys()
305
305
306 allcmds = []
306 allcmds = []
307 for e in keys:
307 for e in keys:
308 aliases = parsealiases(e)
308 aliases = parsealiases(e)
309 allcmds.extend(aliases)
309 allcmds.extend(aliases)
310 found = None
310 found = None
311 if cmd in aliases:
311 if cmd in aliases:
312 found = cmd
312 found = cmd
313 elif not strict:
313 elif not strict:
314 for a in aliases:
314 for a in aliases:
315 if a.startswith(cmd):
315 if a.startswith(cmd):
316 found = a
316 found = a
317 break
317 break
318 if found is not None:
318 if found is not None:
319 if aliases[0].startswith("debug") or found.startswith("debug"):
319 if aliases[0].startswith("debug") or found.startswith("debug"):
320 debugchoice[found] = (aliases, table[e])
320 debugchoice[found] = (aliases, table[e])
321 else:
321 else:
322 choice[found] = (aliases, table[e])
322 choice[found] = (aliases, table[e])
323
323
324 if not choice and debugchoice:
324 if not choice and debugchoice:
325 choice = debugchoice
325 choice = debugchoice
326
326
327 return choice, allcmds
327 return choice, allcmds
328
328
329 def findcmd(cmd, table, strict=True):
329 def findcmd(cmd, table, strict=True):
330 """Return (aliases, command table entry) for command string."""
330 """Return (aliases, command table entry) for command string."""
331 choice, allcmds = findpossible(cmd, table, strict)
331 choice, allcmds = findpossible(cmd, table, strict)
332
332
333 if cmd in choice:
333 if cmd in choice:
334 return choice[cmd]
334 return choice[cmd]
335
335
336 if len(choice) > 1:
336 if len(choice) > 1:
337 clist = choice.keys()
337 clist = choice.keys()
338 clist.sort()
338 clist.sort()
339 raise error.AmbiguousCommand(cmd, clist)
339 raise error.AmbiguousCommand(cmd, clist)
340
340
341 if choice:
341 if choice:
342 return choice.values()[0]
342 return choice.values()[0]
343
343
344 raise error.UnknownCommand(cmd, allcmds)
344 raise error.UnknownCommand(cmd, allcmds)
345
345
346 def findrepo(p):
346 def findrepo(p):
347 while not os.path.isdir(os.path.join(p, ".hg")):
347 while not os.path.isdir(os.path.join(p, ".hg")):
348 oldp, p = p, os.path.dirname(p)
348 oldp, p = p, os.path.dirname(p)
349 if p == oldp:
349 if p == oldp:
350 return None
350 return None
351
351
352 return p
352 return p
353
353
354 def bailifchanged(repo, merge=True):
354 def bailifchanged(repo, merge=True):
355 if merge and repo.dirstate.p2() != nullid:
355 if merge and repo.dirstate.p2() != nullid:
356 raise error.Abort(_('outstanding uncommitted merge'))
356 raise error.Abort(_('outstanding uncommitted merge'))
357 modified, added, removed, deleted = repo.status()[:4]
357 modified, added, removed, deleted = repo.status()[:4]
358 if modified or added or removed or deleted:
358 if modified or added or removed or deleted:
359 raise error.Abort(_('uncommitted changes'))
359 raise error.Abort(_('uncommitted changes'))
360 ctx = repo[None]
360 ctx = repo[None]
361 for s in sorted(ctx.substate):
361 for s in sorted(ctx.substate):
362 ctx.sub(s).bailifchanged()
362 ctx.sub(s).bailifchanged()
363
363
364 def logmessage(ui, opts):
364 def logmessage(ui, opts):
365 """ get the log message according to -m and -l option """
365 """ get the log message according to -m and -l option """
366 message = opts.get('message')
366 message = opts.get('message')
367 logfile = opts.get('logfile')
367 logfile = opts.get('logfile')
368
368
369 if message and logfile:
369 if message and logfile:
370 raise error.Abort(_('options --message and --logfile are mutually '
370 raise error.Abort(_('options --message and --logfile are mutually '
371 'exclusive'))
371 'exclusive'))
372 if not message and logfile:
372 if not message and logfile:
373 try:
373 try:
374 if logfile == '-':
374 if logfile == '-':
375 message = ui.fin.read()
375 message = ui.fin.read()
376 else:
376 else:
377 message = '\n'.join(util.readfile(logfile).splitlines())
377 message = '\n'.join(util.readfile(logfile).splitlines())
378 except IOError as inst:
378 except IOError as inst:
379 raise error.Abort(_("can't read commit message '%s': %s") %
379 raise error.Abort(_("can't read commit message '%s': %s") %
380 (logfile, inst.strerror))
380 (logfile, inst.strerror))
381 return message
381 return message
382
382
383 def mergeeditform(ctxorbool, baseformname):
383 def mergeeditform(ctxorbool, baseformname):
384 """return appropriate editform name (referencing a committemplate)
384 """return appropriate editform name (referencing a committemplate)
385
385
386 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
386 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
387 merging is committed.
387 merging is committed.
388
388
389 This returns baseformname with '.merge' appended if it is a merge,
389 This returns baseformname with '.merge' appended if it is a merge,
390 otherwise '.normal' is appended.
390 otherwise '.normal' is appended.
391 """
391 """
392 if isinstance(ctxorbool, bool):
392 if isinstance(ctxorbool, bool):
393 if ctxorbool:
393 if ctxorbool:
394 return baseformname + ".merge"
394 return baseformname + ".merge"
395 elif 1 < len(ctxorbool.parents()):
395 elif 1 < len(ctxorbool.parents()):
396 return baseformname + ".merge"
396 return baseformname + ".merge"
397
397
398 return baseformname + ".normal"
398 return baseformname + ".normal"
399
399
400 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
400 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
401 editform='', **opts):
401 editform='', **opts):
402 """get appropriate commit message editor according to '--edit' option
402 """get appropriate commit message editor according to '--edit' option
403
403
404 'finishdesc' is a function to be called with edited commit message
404 'finishdesc' is a function to be called with edited commit message
405 (= 'description' of the new changeset) just after editing, but
405 (= 'description' of the new changeset) just after editing, but
406 before checking empty-ness. It should return actual text to be
406 before checking empty-ness. It should return actual text to be
407 stored into history. This allows to change description before
407 stored into history. This allows to change description before
408 storing.
408 storing.
409
409
410 'extramsg' is a extra message to be shown in the editor instead of
410 'extramsg' is a extra message to be shown in the editor instead of
411 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
411 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
412 is automatically added.
412 is automatically added.
413
413
414 'editform' is a dot-separated list of names, to distinguish
414 'editform' is a dot-separated list of names, to distinguish
415 the purpose of commit text editing.
415 the purpose of commit text editing.
416
416
417 'getcommiteditor' returns 'commitforceeditor' regardless of
417 'getcommiteditor' returns 'commitforceeditor' regardless of
418 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
418 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
419 they are specific for usage in MQ.
419 they are specific for usage in MQ.
420 """
420 """
421 if edit or finishdesc or extramsg:
421 if edit or finishdesc or extramsg:
422 return lambda r, c, s: commitforceeditor(r, c, s,
422 return lambda r, c, s: commitforceeditor(r, c, s,
423 finishdesc=finishdesc,
423 finishdesc=finishdesc,
424 extramsg=extramsg,
424 extramsg=extramsg,
425 editform=editform)
425 editform=editform)
426 elif editform:
426 elif editform:
427 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
427 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
428 else:
428 else:
429 return commiteditor
429 return commiteditor
430
430
431 def loglimit(opts):
431 def loglimit(opts):
432 """get the log limit according to option -l/--limit"""
432 """get the log limit according to option -l/--limit"""
433 limit = opts.get('limit')
433 limit = opts.get('limit')
434 if limit:
434 if limit:
435 try:
435 try:
436 limit = int(limit)
436 limit = int(limit)
437 except ValueError:
437 except ValueError:
438 raise error.Abort(_('limit must be a positive integer'))
438 raise error.Abort(_('limit must be a positive integer'))
439 if limit <= 0:
439 if limit <= 0:
440 raise error.Abort(_('limit must be positive'))
440 raise error.Abort(_('limit must be positive'))
441 else:
441 else:
442 limit = None
442 limit = None
443 return limit
443 return limit
444
444
445 def makefilename(repo, pat, node, desc=None,
445 def makefilename(repo, pat, node, desc=None,
446 total=None, seqno=None, revwidth=None, pathname=None):
446 total=None, seqno=None, revwidth=None, pathname=None):
447 node_expander = {
447 node_expander = {
448 'H': lambda: hex(node),
448 'H': lambda: hex(node),
449 'R': lambda: str(repo.changelog.rev(node)),
449 'R': lambda: str(repo.changelog.rev(node)),
450 'h': lambda: short(node),
450 'h': lambda: short(node),
451 'm': lambda: re.sub('[^\w]', '_', str(desc))
451 'm': lambda: re.sub('[^\w]', '_', str(desc))
452 }
452 }
453 expander = {
453 expander = {
454 '%': lambda: '%',
454 '%': lambda: '%',
455 'b': lambda: os.path.basename(repo.root),
455 'b': lambda: os.path.basename(repo.root),
456 }
456 }
457
457
458 try:
458 try:
459 if node:
459 if node:
460 expander.update(node_expander)
460 expander.update(node_expander)
461 if node:
461 if node:
462 expander['r'] = (lambda:
462 expander['r'] = (lambda:
463 str(repo.changelog.rev(node)).zfill(revwidth or 0))
463 str(repo.changelog.rev(node)).zfill(revwidth or 0))
464 if total is not None:
464 if total is not None:
465 expander['N'] = lambda: str(total)
465 expander['N'] = lambda: str(total)
466 if seqno is not None:
466 if seqno is not None:
467 expander['n'] = lambda: str(seqno)
467 expander['n'] = lambda: str(seqno)
468 if total is not None and seqno is not None:
468 if total is not None and seqno is not None:
469 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
469 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
470 if pathname is not None:
470 if pathname is not None:
471 expander['s'] = lambda: os.path.basename(pathname)
471 expander['s'] = lambda: os.path.basename(pathname)
472 expander['d'] = lambda: os.path.dirname(pathname) or '.'
472 expander['d'] = lambda: os.path.dirname(pathname) or '.'
473 expander['p'] = lambda: pathname
473 expander['p'] = lambda: pathname
474
474
475 newname = []
475 newname = []
476 patlen = len(pat)
476 patlen = len(pat)
477 i = 0
477 i = 0
478 while i < patlen:
478 while i < patlen:
479 c = pat[i]
479 c = pat[i]
480 if c == '%':
480 if c == '%':
481 i += 1
481 i += 1
482 c = pat[i]
482 c = pat[i]
483 c = expander[c]()
483 c = expander[c]()
484 newname.append(c)
484 newname.append(c)
485 i += 1
485 i += 1
486 return ''.join(newname)
486 return ''.join(newname)
487 except KeyError as inst:
487 except KeyError as inst:
488 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
488 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
489 inst.args[0])
489 inst.args[0])
490
490
491 class _unclosablefile(object):
491 class _unclosablefile(object):
492 def __init__(self, fp):
492 def __init__(self, fp):
493 self._fp = fp
493 self._fp = fp
494
494
495 def close(self):
495 def close(self):
496 pass
496 pass
497
497
498 def __iter__(self):
498 def __iter__(self):
499 return iter(self._fp)
499 return iter(self._fp)
500
500
501 def __getattr__(self, attr):
501 def __getattr__(self, attr):
502 return getattr(self._fp, attr)
502 return getattr(self._fp, attr)
503
503
504 def __enter__(self):
504 def __enter__(self):
505 return self
505 return self
506
506
507 def __exit__(self, exc_type, exc_value, exc_tb):
507 def __exit__(self, exc_type, exc_value, exc_tb):
508 pass
508 pass
509
509
510 def makefileobj(repo, pat, node=None, desc=None, total=None,
510 def makefileobj(repo, pat, node=None, desc=None, total=None,
511 seqno=None, revwidth=None, mode='wb', modemap=None,
511 seqno=None, revwidth=None, mode='wb', modemap=None,
512 pathname=None):
512 pathname=None):
513
513
514 writable = mode not in ('r', 'rb')
514 writable = mode not in ('r', 'rb')
515
515
516 if not pat or pat == '-':
516 if not pat or pat == '-':
517 if writable:
517 if writable:
518 fp = repo.ui.fout
518 fp = repo.ui.fout
519 else:
519 else:
520 fp = repo.ui.fin
520 fp = repo.ui.fin
521 return _unclosablefile(fp)
521 return _unclosablefile(fp)
522 if util.safehasattr(pat, 'write') and writable:
522 if util.safehasattr(pat, 'write') and writable:
523 return pat
523 return pat
524 if util.safehasattr(pat, 'read') and 'r' in mode:
524 if util.safehasattr(pat, 'read') and 'r' in mode:
525 return pat
525 return pat
526 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
526 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
527 if modemap is not None:
527 if modemap is not None:
528 mode = modemap.get(fn, mode)
528 mode = modemap.get(fn, mode)
529 if mode == 'wb':
529 if mode == 'wb':
530 modemap[fn] = 'ab'
530 modemap[fn] = 'ab'
531 return open(fn, mode)
531 return open(fn, mode)
532
532
533 def openrevlog(repo, cmd, file_, opts):
533 def openrevlog(repo, cmd, file_, opts):
534 """opens the changelog, manifest, a filelog or a given revlog"""
534 """opens the changelog, manifest, a filelog or a given revlog"""
535 cl = opts['changelog']
535 cl = opts['changelog']
536 mf = opts['manifest']
536 mf = opts['manifest']
537 dir = opts['dir']
537 dir = opts['dir']
538 msg = None
538 msg = None
539 if cl and mf:
539 if cl and mf:
540 msg = _('cannot specify --changelog and --manifest at the same time')
540 msg = _('cannot specify --changelog and --manifest at the same time')
541 elif cl and dir:
541 elif cl and dir:
542 msg = _('cannot specify --changelog and --dir at the same time')
542 msg = _('cannot specify --changelog and --dir at the same time')
543 elif cl or mf or dir:
543 elif cl or mf or dir:
544 if file_:
544 if file_:
545 msg = _('cannot specify filename with --changelog or --manifest')
545 msg = _('cannot specify filename with --changelog or --manifest')
546 elif not repo:
546 elif not repo:
547 msg = _('cannot specify --changelog or --manifest or --dir '
547 msg = _('cannot specify --changelog or --manifest or --dir '
548 'without a repository')
548 'without a repository')
549 if msg:
549 if msg:
550 raise error.Abort(msg)
550 raise error.Abort(msg)
551
551
552 r = None
552 r = None
553 if repo:
553 if repo:
554 if cl:
554 if cl:
555 r = repo.unfiltered().changelog
555 r = repo.unfiltered().changelog
556 elif dir:
556 elif dir:
557 if 'treemanifest' not in repo.requirements:
557 if 'treemanifest' not in repo.requirements:
558 raise error.Abort(_("--dir can only be used on repos with "
558 raise error.Abort(_("--dir can only be used on repos with "
559 "treemanifest enabled"))
559 "treemanifest enabled"))
560 dirlog = repo.manifestlog._revlog.dirlog(dir)
560 dirlog = repo.manifestlog._revlog.dirlog(dir)
561 if len(dirlog):
561 if len(dirlog):
562 r = dirlog
562 r = dirlog
563 elif mf:
563 elif mf:
564 r = repo.manifestlog._revlog
564 r = repo.manifestlog._revlog
565 elif file_:
565 elif file_:
566 filelog = repo.file(file_)
566 filelog = repo.file(file_)
567 if len(filelog):
567 if len(filelog):
568 r = filelog
568 r = filelog
569 if not r:
569 if not r:
570 if not file_:
570 if not file_:
571 raise error.CommandError(cmd, _('invalid arguments'))
571 raise error.CommandError(cmd, _('invalid arguments'))
572 if not os.path.isfile(file_):
572 if not os.path.isfile(file_):
573 raise error.Abort(_("revlog '%s' not found") % file_)
573 raise error.Abort(_("revlog '%s' not found") % file_)
574 r = revlog.revlog(scmutil.opener(pycompat.getcwd(), audit=False),
574 r = revlog.revlog(scmutil.opener(pycompat.getcwd(), audit=False),
575 file_[:-2] + ".i")
575 file_[:-2] + ".i")
576 return r
576 return r
577
577
578 def copy(ui, repo, pats, opts, rename=False):
578 def copy(ui, repo, pats, opts, rename=False):
579 # called with the repo lock held
579 # called with the repo lock held
580 #
580 #
581 # hgsep => pathname that uses "/" to separate directories
581 # hgsep => pathname that uses "/" to separate directories
582 # ossep => pathname that uses os.sep to separate directories
582 # ossep => pathname that uses os.sep to separate directories
583 cwd = repo.getcwd()
583 cwd = repo.getcwd()
584 targets = {}
584 targets = {}
585 after = opts.get("after")
585 after = opts.get("after")
586 dryrun = opts.get("dry_run")
586 dryrun = opts.get("dry_run")
587 wctx = repo[None]
587 wctx = repo[None]
588
588
589 def walkpat(pat):
589 def walkpat(pat):
590 srcs = []
590 srcs = []
591 if after:
591 if after:
592 badstates = '?'
592 badstates = '?'
593 else:
593 else:
594 badstates = '?r'
594 badstates = '?r'
595 m = scmutil.match(repo[None], [pat], opts, globbed=True)
595 m = scmutil.match(repo[None], [pat], opts, globbed=True)
596 for abs in repo.walk(m):
596 for abs in repo.walk(m):
597 state = repo.dirstate[abs]
597 state = repo.dirstate[abs]
598 rel = m.rel(abs)
598 rel = m.rel(abs)
599 exact = m.exact(abs)
599 exact = m.exact(abs)
600 if state in badstates:
600 if state in badstates:
601 if exact and state == '?':
601 if exact and state == '?':
602 ui.warn(_('%s: not copying - file is not managed\n') % rel)
602 ui.warn(_('%s: not copying - file is not managed\n') % rel)
603 if exact and state == 'r':
603 if exact and state == 'r':
604 ui.warn(_('%s: not copying - file has been marked for'
604 ui.warn(_('%s: not copying - file has been marked for'
605 ' remove\n') % rel)
605 ' remove\n') % rel)
606 continue
606 continue
607 # abs: hgsep
607 # abs: hgsep
608 # rel: ossep
608 # rel: ossep
609 srcs.append((abs, rel, exact))
609 srcs.append((abs, rel, exact))
610 return srcs
610 return srcs
611
611
612 # abssrc: hgsep
612 # abssrc: hgsep
613 # relsrc: ossep
613 # relsrc: ossep
614 # otarget: ossep
614 # otarget: ossep
615 def copyfile(abssrc, relsrc, otarget, exact):
615 def copyfile(abssrc, relsrc, otarget, exact):
616 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
616 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
617 if '/' in abstarget:
617 if '/' in abstarget:
618 # We cannot normalize abstarget itself, this would prevent
618 # We cannot normalize abstarget itself, this would prevent
619 # case only renames, like a => A.
619 # case only renames, like a => A.
620 abspath, absname = abstarget.rsplit('/', 1)
620 abspath, absname = abstarget.rsplit('/', 1)
621 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
621 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
622 reltarget = repo.pathto(abstarget, cwd)
622 reltarget = repo.pathto(abstarget, cwd)
623 target = repo.wjoin(abstarget)
623 target = repo.wjoin(abstarget)
624 src = repo.wjoin(abssrc)
624 src = repo.wjoin(abssrc)
625 state = repo.dirstate[abstarget]
625 state = repo.dirstate[abstarget]
626
626
627 scmutil.checkportable(ui, abstarget)
627 scmutil.checkportable(ui, abstarget)
628
628
629 # check for collisions
629 # check for collisions
630 prevsrc = targets.get(abstarget)
630 prevsrc = targets.get(abstarget)
631 if prevsrc is not None:
631 if prevsrc is not None:
632 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
632 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
633 (reltarget, repo.pathto(abssrc, cwd),
633 (reltarget, repo.pathto(abssrc, cwd),
634 repo.pathto(prevsrc, cwd)))
634 repo.pathto(prevsrc, cwd)))
635 return
635 return
636
636
637 # check for overwrites
637 # check for overwrites
638 exists = os.path.lexists(target)
638 exists = os.path.lexists(target)
639 samefile = False
639 samefile = False
640 if exists and abssrc != abstarget:
640 if exists and abssrc != abstarget:
641 if (repo.dirstate.normalize(abssrc) ==
641 if (repo.dirstate.normalize(abssrc) ==
642 repo.dirstate.normalize(abstarget)):
642 repo.dirstate.normalize(abstarget)):
643 if not rename:
643 if not rename:
644 ui.warn(_("%s: can't copy - same file\n") % reltarget)
644 ui.warn(_("%s: can't copy - same file\n") % reltarget)
645 return
645 return
646 exists = False
646 exists = False
647 samefile = True
647 samefile = True
648
648
649 if not after and exists or after and state in 'mn':
649 if not after and exists or after and state in 'mn':
650 if not opts['force']:
650 if not opts['force']:
651 if state in 'mn':
651 if state in 'mn':
652 msg = _('%s: not overwriting - file already committed\n')
652 msg = _('%s: not overwriting - file already committed\n')
653 if after:
653 if after:
654 flags = '--after --force'
654 flags = '--after --force'
655 else:
655 else:
656 flags = '--force'
656 flags = '--force'
657 if rename:
657 if rename:
658 hint = _('(hg rename %s to replace the file by '
658 hint = _('(hg rename %s to replace the file by '
659 'recording a rename)\n') % flags
659 'recording a rename)\n') % flags
660 else:
660 else:
661 hint = _('(hg copy %s to replace the file by '
661 hint = _('(hg copy %s to replace the file by '
662 'recording a copy)\n') % flags
662 'recording a copy)\n') % flags
663 else:
663 else:
664 msg = _('%s: not overwriting - file exists\n')
664 msg = _('%s: not overwriting - file exists\n')
665 if rename:
665 if rename:
666 hint = _('(hg rename --after to record the rename)\n')
666 hint = _('(hg rename --after to record the rename)\n')
667 else:
667 else:
668 hint = _('(hg copy --after to record the copy)\n')
668 hint = _('(hg copy --after to record the copy)\n')
669 ui.warn(msg % reltarget)
669 ui.warn(msg % reltarget)
670 ui.warn(hint)
670 ui.warn(hint)
671 return
671 return
672
672
673 if after:
673 if after:
674 if not exists:
674 if not exists:
675 if rename:
675 if rename:
676 ui.warn(_('%s: not recording move - %s does not exist\n') %
676 ui.warn(_('%s: not recording move - %s does not exist\n') %
677 (relsrc, reltarget))
677 (relsrc, reltarget))
678 else:
678 else:
679 ui.warn(_('%s: not recording copy - %s does not exist\n') %
679 ui.warn(_('%s: not recording copy - %s does not exist\n') %
680 (relsrc, reltarget))
680 (relsrc, reltarget))
681 return
681 return
682 elif not dryrun:
682 elif not dryrun:
683 try:
683 try:
684 if exists:
684 if exists:
685 os.unlink(target)
685 os.unlink(target)
686 targetdir = os.path.dirname(target) or '.'
686 targetdir = os.path.dirname(target) or '.'
687 if not os.path.isdir(targetdir):
687 if not os.path.isdir(targetdir):
688 os.makedirs(targetdir)
688 os.makedirs(targetdir)
689 if samefile:
689 if samefile:
690 tmp = target + "~hgrename"
690 tmp = target + "~hgrename"
691 os.rename(src, tmp)
691 os.rename(src, tmp)
692 os.rename(tmp, target)
692 os.rename(tmp, target)
693 else:
693 else:
694 util.copyfile(src, target)
694 util.copyfile(src, target)
695 srcexists = True
695 srcexists = True
696 except IOError as inst:
696 except IOError as inst:
697 if inst.errno == errno.ENOENT:
697 if inst.errno == errno.ENOENT:
698 ui.warn(_('%s: deleted in working directory\n') % relsrc)
698 ui.warn(_('%s: deleted in working directory\n') % relsrc)
699 srcexists = False
699 srcexists = False
700 else:
700 else:
701 ui.warn(_('%s: cannot copy - %s\n') %
701 ui.warn(_('%s: cannot copy - %s\n') %
702 (relsrc, inst.strerror))
702 (relsrc, inst.strerror))
703 return True # report a failure
703 return True # report a failure
704
704
705 if ui.verbose or not exact:
705 if ui.verbose or not exact:
706 if rename:
706 if rename:
707 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
707 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
708 else:
708 else:
709 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
709 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
710
710
711 targets[abstarget] = abssrc
711 targets[abstarget] = abssrc
712
712
713 # fix up dirstate
713 # fix up dirstate
714 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
714 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
715 dryrun=dryrun, cwd=cwd)
715 dryrun=dryrun, cwd=cwd)
716 if rename and not dryrun:
716 if rename and not dryrun:
717 if not after and srcexists and not samefile:
717 if not after and srcexists and not samefile:
718 util.unlinkpath(repo.wjoin(abssrc))
718 util.unlinkpath(repo.wjoin(abssrc))
719 wctx.forget([abssrc])
719 wctx.forget([abssrc])
720
720
721 # pat: ossep
721 # pat: ossep
722 # dest ossep
722 # dest ossep
723 # srcs: list of (hgsep, hgsep, ossep, bool)
723 # srcs: list of (hgsep, hgsep, ossep, bool)
724 # return: function that takes hgsep and returns ossep
724 # return: function that takes hgsep and returns ossep
725 def targetpathfn(pat, dest, srcs):
725 def targetpathfn(pat, dest, srcs):
726 if os.path.isdir(pat):
726 if os.path.isdir(pat):
727 abspfx = pathutil.canonpath(repo.root, cwd, pat)
727 abspfx = pathutil.canonpath(repo.root, cwd, pat)
728 abspfx = util.localpath(abspfx)
728 abspfx = util.localpath(abspfx)
729 if destdirexists:
729 if destdirexists:
730 striplen = len(os.path.split(abspfx)[0])
730 striplen = len(os.path.split(abspfx)[0])
731 else:
731 else:
732 striplen = len(abspfx)
732 striplen = len(abspfx)
733 if striplen:
733 if striplen:
734 striplen += len(pycompat.ossep)
734 striplen += len(pycompat.ossep)
735 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
735 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
736 elif destdirexists:
736 elif destdirexists:
737 res = lambda p: os.path.join(dest,
737 res = lambda p: os.path.join(dest,
738 os.path.basename(util.localpath(p)))
738 os.path.basename(util.localpath(p)))
739 else:
739 else:
740 res = lambda p: dest
740 res = lambda p: dest
741 return res
741 return res
742
742
743 # pat: ossep
743 # pat: ossep
744 # dest ossep
744 # dest ossep
745 # srcs: list of (hgsep, hgsep, ossep, bool)
745 # srcs: list of (hgsep, hgsep, ossep, bool)
746 # return: function that takes hgsep and returns ossep
746 # return: function that takes hgsep and returns ossep
747 def targetpathafterfn(pat, dest, srcs):
747 def targetpathafterfn(pat, dest, srcs):
748 if matchmod.patkind(pat):
748 if matchmod.patkind(pat):
749 # a mercurial pattern
749 # a mercurial pattern
750 res = lambda p: os.path.join(dest,
750 res = lambda p: os.path.join(dest,
751 os.path.basename(util.localpath(p)))
751 os.path.basename(util.localpath(p)))
752 else:
752 else:
753 abspfx = pathutil.canonpath(repo.root, cwd, pat)
753 abspfx = pathutil.canonpath(repo.root, cwd, pat)
754 if len(abspfx) < len(srcs[0][0]):
754 if len(abspfx) < len(srcs[0][0]):
755 # A directory. Either the target path contains the last
755 # A directory. Either the target path contains the last
756 # component of the source path or it does not.
756 # component of the source path or it does not.
757 def evalpath(striplen):
757 def evalpath(striplen):
758 score = 0
758 score = 0
759 for s in srcs:
759 for s in srcs:
760 t = os.path.join(dest, util.localpath(s[0])[striplen:])
760 t = os.path.join(dest, util.localpath(s[0])[striplen:])
761 if os.path.lexists(t):
761 if os.path.lexists(t):
762 score += 1
762 score += 1
763 return score
763 return score
764
764
765 abspfx = util.localpath(abspfx)
765 abspfx = util.localpath(abspfx)
766 striplen = len(abspfx)
766 striplen = len(abspfx)
767 if striplen:
767 if striplen:
768 striplen += len(pycompat.ossep)
768 striplen += len(pycompat.ossep)
769 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
769 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
770 score = evalpath(striplen)
770 score = evalpath(striplen)
771 striplen1 = len(os.path.split(abspfx)[0])
771 striplen1 = len(os.path.split(abspfx)[0])
772 if striplen1:
772 if striplen1:
773 striplen1 += len(pycompat.ossep)
773 striplen1 += len(pycompat.ossep)
774 if evalpath(striplen1) > score:
774 if evalpath(striplen1) > score:
775 striplen = striplen1
775 striplen = striplen1
776 res = lambda p: os.path.join(dest,
776 res = lambda p: os.path.join(dest,
777 util.localpath(p)[striplen:])
777 util.localpath(p)[striplen:])
778 else:
778 else:
779 # a file
779 # a file
780 if destdirexists:
780 if destdirexists:
781 res = lambda p: os.path.join(dest,
781 res = lambda p: os.path.join(dest,
782 os.path.basename(util.localpath(p)))
782 os.path.basename(util.localpath(p)))
783 else:
783 else:
784 res = lambda p: dest
784 res = lambda p: dest
785 return res
785 return res
786
786
787 pats = scmutil.expandpats(pats)
787 pats = scmutil.expandpats(pats)
788 if not pats:
788 if not pats:
789 raise error.Abort(_('no source or destination specified'))
789 raise error.Abort(_('no source or destination specified'))
790 if len(pats) == 1:
790 if len(pats) == 1:
791 raise error.Abort(_('no destination specified'))
791 raise error.Abort(_('no destination specified'))
792 dest = pats.pop()
792 dest = pats.pop()
793 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
793 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
794 if not destdirexists:
794 if not destdirexists:
795 if len(pats) > 1 or matchmod.patkind(pats[0]):
795 if len(pats) > 1 or matchmod.patkind(pats[0]):
796 raise error.Abort(_('with multiple sources, destination must be an '
796 raise error.Abort(_('with multiple sources, destination must be an '
797 'existing directory'))
797 'existing directory'))
798 if util.endswithsep(dest):
798 if util.endswithsep(dest):
799 raise error.Abort(_('destination %s is not a directory') % dest)
799 raise error.Abort(_('destination %s is not a directory') % dest)
800
800
801 tfn = targetpathfn
801 tfn = targetpathfn
802 if after:
802 if after:
803 tfn = targetpathafterfn
803 tfn = targetpathafterfn
804 copylist = []
804 copylist = []
805 for pat in pats:
805 for pat in pats:
806 srcs = walkpat(pat)
806 srcs = walkpat(pat)
807 if not srcs:
807 if not srcs:
808 continue
808 continue
809 copylist.append((tfn(pat, dest, srcs), srcs))
809 copylist.append((tfn(pat, dest, srcs), srcs))
810 if not copylist:
810 if not copylist:
811 raise error.Abort(_('no files to copy'))
811 raise error.Abort(_('no files to copy'))
812
812
813 errors = 0
813 errors = 0
814 for targetpath, srcs in copylist:
814 for targetpath, srcs in copylist:
815 for abssrc, relsrc, exact in srcs:
815 for abssrc, relsrc, exact in srcs:
816 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
816 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
817 errors += 1
817 errors += 1
818
818
819 if errors:
819 if errors:
820 ui.warn(_('(consider using --after)\n'))
820 ui.warn(_('(consider using --after)\n'))
821
821
822 return errors != 0
822 return errors != 0
823
823
824 ## facility to let extension process additional data into an import patch
824 ## facility to let extension process additional data into an import patch
825 # list of identifier to be executed in order
825 # list of identifier to be executed in order
826 extrapreimport = [] # run before commit
826 extrapreimport = [] # run before commit
827 extrapostimport = [] # run after commit
827 extrapostimport = [] # run after commit
828 # mapping from identifier to actual import function
828 # mapping from identifier to actual import function
829 #
829 #
830 # 'preimport' are run before the commit is made and are provided the following
830 # 'preimport' are run before the commit is made and are provided the following
831 # arguments:
831 # arguments:
832 # - repo: the localrepository instance,
832 # - repo: the localrepository instance,
833 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
833 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
834 # - extra: the future extra dictionary of the changeset, please mutate it,
834 # - extra: the future extra dictionary of the changeset, please mutate it,
835 # - opts: the import options.
835 # - opts: the import options.
836 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
836 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
837 # mutation of in memory commit and more. Feel free to rework the code to get
837 # mutation of in memory commit and more. Feel free to rework the code to get
838 # there.
838 # there.
839 extrapreimportmap = {}
839 extrapreimportmap = {}
840 # 'postimport' are run after the commit is made and are provided the following
840 # 'postimport' are run after the commit is made and are provided the following
841 # argument:
841 # argument:
842 # - ctx: the changectx created by import.
842 # - ctx: the changectx created by import.
843 extrapostimportmap = {}
843 extrapostimportmap = {}
844
844
845 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
845 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
846 """Utility function used by commands.import to import a single patch
846 """Utility function used by commands.import to import a single patch
847
847
848 This function is explicitly defined here to help the evolve extension to
848 This function is explicitly defined here to help the evolve extension to
849 wrap this part of the import logic.
849 wrap this part of the import logic.
850
850
851 The API is currently a bit ugly because it a simple code translation from
851 The API is currently a bit ugly because it a simple code translation from
852 the import command. Feel free to make it better.
852 the import command. Feel free to make it better.
853
853
854 :hunk: a patch (as a binary string)
854 :hunk: a patch (as a binary string)
855 :parents: nodes that will be parent of the created commit
855 :parents: nodes that will be parent of the created commit
856 :opts: the full dict of option passed to the import command
856 :opts: the full dict of option passed to the import command
857 :msgs: list to save commit message to.
857 :msgs: list to save commit message to.
858 (used in case we need to save it when failing)
858 (used in case we need to save it when failing)
859 :updatefunc: a function that update a repo to a given node
859 :updatefunc: a function that update a repo to a given node
860 updatefunc(<repo>, <node>)
860 updatefunc(<repo>, <node>)
861 """
861 """
862 # avoid cycle context -> subrepo -> cmdutil
862 # avoid cycle context -> subrepo -> cmdutil
863 from . import context
863 from . import context
864 extractdata = patch.extract(ui, hunk)
864 extractdata = patch.extract(ui, hunk)
865 tmpname = extractdata.get('filename')
865 tmpname = extractdata.get('filename')
866 message = extractdata.get('message')
866 message = extractdata.get('message')
867 user = opts.get('user') or extractdata.get('user')
867 user = opts.get('user') or extractdata.get('user')
868 date = opts.get('date') or extractdata.get('date')
868 date = opts.get('date') or extractdata.get('date')
869 branch = extractdata.get('branch')
869 branch = extractdata.get('branch')
870 nodeid = extractdata.get('nodeid')
870 nodeid = extractdata.get('nodeid')
871 p1 = extractdata.get('p1')
871 p1 = extractdata.get('p1')
872 p2 = extractdata.get('p2')
872 p2 = extractdata.get('p2')
873
873
874 nocommit = opts.get('no_commit')
874 nocommit = opts.get('no_commit')
875 importbranch = opts.get('import_branch')
875 importbranch = opts.get('import_branch')
876 update = not opts.get('bypass')
876 update = not opts.get('bypass')
877 strip = opts["strip"]
877 strip = opts["strip"]
878 prefix = opts["prefix"]
878 prefix = opts["prefix"]
879 sim = float(opts.get('similarity') or 0)
879 sim = float(opts.get('similarity') or 0)
880 if not tmpname:
880 if not tmpname:
881 return (None, None, False)
881 return (None, None, False)
882
882
883 rejects = False
883 rejects = False
884
884
885 try:
885 try:
886 cmdline_message = logmessage(ui, opts)
886 cmdline_message = logmessage(ui, opts)
887 if cmdline_message:
887 if cmdline_message:
888 # pickup the cmdline msg
888 # pickup the cmdline msg
889 message = cmdline_message
889 message = cmdline_message
890 elif message:
890 elif message:
891 # pickup the patch msg
891 # pickup the patch msg
892 message = message.strip()
892 message = message.strip()
893 else:
893 else:
894 # launch the editor
894 # launch the editor
895 message = None
895 message = None
896 ui.debug('message:\n%s\n' % message)
896 ui.debug('message:\n%s\n' % message)
897
897
898 if len(parents) == 1:
898 if len(parents) == 1:
899 parents.append(repo[nullid])
899 parents.append(repo[nullid])
900 if opts.get('exact'):
900 if opts.get('exact'):
901 if not nodeid or not p1:
901 if not nodeid or not p1:
902 raise error.Abort(_('not a Mercurial patch'))
902 raise error.Abort(_('not a Mercurial patch'))
903 p1 = repo[p1]
903 p1 = repo[p1]
904 p2 = repo[p2 or nullid]
904 p2 = repo[p2 or nullid]
905 elif p2:
905 elif p2:
906 try:
906 try:
907 p1 = repo[p1]
907 p1 = repo[p1]
908 p2 = repo[p2]
908 p2 = repo[p2]
909 # Without any options, consider p2 only if the
909 # Without any options, consider p2 only if the
910 # patch is being applied on top of the recorded
910 # patch is being applied on top of the recorded
911 # first parent.
911 # first parent.
912 if p1 != parents[0]:
912 if p1 != parents[0]:
913 p1 = parents[0]
913 p1 = parents[0]
914 p2 = repo[nullid]
914 p2 = repo[nullid]
915 except error.RepoError:
915 except error.RepoError:
916 p1, p2 = parents
916 p1, p2 = parents
917 if p2.node() == nullid:
917 if p2.node() == nullid:
918 ui.warn(_("warning: import the patch as a normal revision\n"
918 ui.warn(_("warning: import the patch as a normal revision\n"
919 "(use --exact to import the patch as a merge)\n"))
919 "(use --exact to import the patch as a merge)\n"))
920 else:
920 else:
921 p1, p2 = parents
921 p1, p2 = parents
922
922
923 n = None
923 n = None
924 if update:
924 if update:
925 if p1 != parents[0]:
925 if p1 != parents[0]:
926 updatefunc(repo, p1.node())
926 updatefunc(repo, p1.node())
927 if p2 != parents[1]:
927 if p2 != parents[1]:
928 repo.setparents(p1.node(), p2.node())
928 repo.setparents(p1.node(), p2.node())
929
929
930 if opts.get('exact') or importbranch:
930 if opts.get('exact') or importbranch:
931 repo.dirstate.setbranch(branch or 'default')
931 repo.dirstate.setbranch(branch or 'default')
932
932
933 partial = opts.get('partial', False)
933 partial = opts.get('partial', False)
934 files = set()
934 files = set()
935 try:
935 try:
936 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
936 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
937 files=files, eolmode=None, similarity=sim / 100.0)
937 files=files, eolmode=None, similarity=sim / 100.0)
938 except patch.PatchError as e:
938 except patch.PatchError as e:
939 if not partial:
939 if not partial:
940 raise error.Abort(str(e))
940 raise error.Abort(str(e))
941 if partial:
941 if partial:
942 rejects = True
942 rejects = True
943
943
944 files = list(files)
944 files = list(files)
945 if nocommit:
945 if nocommit:
946 if message:
946 if message:
947 msgs.append(message)
947 msgs.append(message)
948 else:
948 else:
949 if opts.get('exact') or p2:
949 if opts.get('exact') or p2:
950 # If you got here, you either use --force and know what
950 # If you got here, you either use --force and know what
951 # you are doing or used --exact or a merge patch while
951 # you are doing or used --exact or a merge patch while
952 # being updated to its first parent.
952 # being updated to its first parent.
953 m = None
953 m = None
954 else:
954 else:
955 m = scmutil.matchfiles(repo, files or [])
955 m = scmutil.matchfiles(repo, files or [])
956 editform = mergeeditform(repo[None], 'import.normal')
956 editform = mergeeditform(repo[None], 'import.normal')
957 if opts.get('exact'):
957 if opts.get('exact'):
958 editor = None
958 editor = None
959 else:
959 else:
960 editor = getcommiteditor(editform=editform, **opts)
960 editor = getcommiteditor(editform=editform, **opts)
961 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
961 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
962 extra = {}
962 extra = {}
963 for idfunc in extrapreimport:
963 for idfunc in extrapreimport:
964 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
964 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
965 try:
965 try:
966 if partial:
966 if partial:
967 repo.ui.setconfig('ui', 'allowemptycommit', True)
967 repo.ui.setconfig('ui', 'allowemptycommit', True)
968 n = repo.commit(message, user,
968 n = repo.commit(message, user,
969 date, match=m,
969 date, match=m,
970 editor=editor, extra=extra)
970 editor=editor, extra=extra)
971 for idfunc in extrapostimport:
971 for idfunc in extrapostimport:
972 extrapostimportmap[idfunc](repo[n])
972 extrapostimportmap[idfunc](repo[n])
973 finally:
973 finally:
974 repo.ui.restoreconfig(allowemptyback)
974 repo.ui.restoreconfig(allowemptyback)
975 else:
975 else:
976 if opts.get('exact') or importbranch:
976 if opts.get('exact') or importbranch:
977 branch = branch or 'default'
977 branch = branch or 'default'
978 else:
978 else:
979 branch = p1.branch()
979 branch = p1.branch()
980 store = patch.filestore()
980 store = patch.filestore()
981 try:
981 try:
982 files = set()
982 files = set()
983 try:
983 try:
984 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
984 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
985 files, eolmode=None)
985 files, eolmode=None)
986 except patch.PatchError as e:
986 except patch.PatchError as e:
987 raise error.Abort(str(e))
987 raise error.Abort(str(e))
988 if opts.get('exact'):
988 if opts.get('exact'):
989 editor = None
989 editor = None
990 else:
990 else:
991 editor = getcommiteditor(editform='import.bypass')
991 editor = getcommiteditor(editform='import.bypass')
992 memctx = context.makememctx(repo, (p1.node(), p2.node()),
992 memctx = context.makememctx(repo, (p1.node(), p2.node()),
993 message,
993 message,
994 user,
994 user,
995 date,
995 date,
996 branch, files, store,
996 branch, files, store,
997 editor=editor)
997 editor=editor)
998 n = memctx.commit()
998 n = memctx.commit()
999 finally:
999 finally:
1000 store.close()
1000 store.close()
1001 if opts.get('exact') and nocommit:
1001 if opts.get('exact') and nocommit:
1002 # --exact with --no-commit is still useful in that it does merge
1002 # --exact with --no-commit is still useful in that it does merge
1003 # and branch bits
1003 # and branch bits
1004 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1004 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1005 elif opts.get('exact') and hex(n) != nodeid:
1005 elif opts.get('exact') and hex(n) != nodeid:
1006 raise error.Abort(_('patch is damaged or loses information'))
1006 raise error.Abort(_('patch is damaged or loses information'))
1007 msg = _('applied to working directory')
1007 msg = _('applied to working directory')
1008 if n:
1008 if n:
1009 # i18n: refers to a short changeset id
1009 # i18n: refers to a short changeset id
1010 msg = _('created %s') % short(n)
1010 msg = _('created %s') % short(n)
1011 return (msg, n, rejects)
1011 return (msg, n, rejects)
1012 finally:
1012 finally:
1013 os.unlink(tmpname)
1013 os.unlink(tmpname)
1014
1014
1015 # facility to let extensions include additional data in an exported patch
1015 # facility to let extensions include additional data in an exported patch
1016 # list of identifiers to be executed in order
1016 # list of identifiers to be executed in order
1017 extraexport = []
1017 extraexport = []
1018 # mapping from identifier to actual export function
1018 # mapping from identifier to actual export function
1019 # function as to return a string to be added to the header or None
1019 # function as to return a string to be added to the header or None
1020 # it is given two arguments (sequencenumber, changectx)
1020 # it is given two arguments (sequencenumber, changectx)
1021 extraexportmap = {}
1021 extraexportmap = {}
1022
1022
1023 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1023 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1024 opts=None, match=None):
1024 opts=None, match=None):
1025 '''export changesets as hg patches.'''
1025 '''export changesets as hg patches.'''
1026
1026
1027 total = len(revs)
1027 total = len(revs)
1028 revwidth = max([len(str(rev)) for rev in revs])
1028 revwidth = max([len(str(rev)) for rev in revs])
1029 filemode = {}
1029 filemode = {}
1030
1030
1031 def single(rev, seqno, fp):
1031 def single(rev, seqno, fp):
1032 ctx = repo[rev]
1032 ctx = repo[rev]
1033 node = ctx.node()
1033 node = ctx.node()
1034 parents = [p.node() for p in ctx.parents() if p]
1034 parents = [p.node() for p in ctx.parents() if p]
1035 branch = ctx.branch()
1035 branch = ctx.branch()
1036 if switch_parent:
1036 if switch_parent:
1037 parents.reverse()
1037 parents.reverse()
1038
1038
1039 if parents:
1039 if parents:
1040 prev = parents[0]
1040 prev = parents[0]
1041 else:
1041 else:
1042 prev = nullid
1042 prev = nullid
1043
1043
1044 shouldclose = False
1044 shouldclose = False
1045 if not fp and len(template) > 0:
1045 if not fp and len(template) > 0:
1046 desc_lines = ctx.description().rstrip().split('\n')
1046 desc_lines = ctx.description().rstrip().split('\n')
1047 desc = desc_lines[0] #Commit always has a first line.
1047 desc = desc_lines[0] #Commit always has a first line.
1048 fp = makefileobj(repo, template, node, desc=desc, total=total,
1048 fp = makefileobj(repo, template, node, desc=desc, total=total,
1049 seqno=seqno, revwidth=revwidth, mode='wb',
1049 seqno=seqno, revwidth=revwidth, mode='wb',
1050 modemap=filemode)
1050 modemap=filemode)
1051 shouldclose = True
1051 shouldclose = True
1052 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1052 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1053 repo.ui.note("%s\n" % fp.name)
1053 repo.ui.note("%s\n" % fp.name)
1054
1054
1055 if not fp:
1055 if not fp:
1056 write = repo.ui.write
1056 write = repo.ui.write
1057 else:
1057 else:
1058 def write(s, **kw):
1058 def write(s, **kw):
1059 fp.write(s)
1059 fp.write(s)
1060
1060
1061 write("# HG changeset patch\n")
1061 write("# HG changeset patch\n")
1062 write("# User %s\n" % ctx.user())
1062 write("# User %s\n" % ctx.user())
1063 write("# Date %d %d\n" % ctx.date())
1063 write("# Date %d %d\n" % ctx.date())
1064 write("# %s\n" % util.datestr(ctx.date()))
1064 write("# %s\n" % util.datestr(ctx.date()))
1065 if branch and branch != 'default':
1065 if branch and branch != 'default':
1066 write("# Branch %s\n" % branch)
1066 write("# Branch %s\n" % branch)
1067 write("# Node ID %s\n" % hex(node))
1067 write("# Node ID %s\n" % hex(node))
1068 write("# Parent %s\n" % hex(prev))
1068 write("# Parent %s\n" % hex(prev))
1069 if len(parents) > 1:
1069 if len(parents) > 1:
1070 write("# Parent %s\n" % hex(parents[1]))
1070 write("# Parent %s\n" % hex(parents[1]))
1071
1071
1072 for headerid in extraexport:
1072 for headerid in extraexport:
1073 header = extraexportmap[headerid](seqno, ctx)
1073 header = extraexportmap[headerid](seqno, ctx)
1074 if header is not None:
1074 if header is not None:
1075 write('# %s\n' % header)
1075 write('# %s\n' % header)
1076 write(ctx.description().rstrip())
1076 write(ctx.description().rstrip())
1077 write("\n\n")
1077 write("\n\n")
1078
1078
1079 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1079 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1080 write(chunk, label=label)
1080 write(chunk, label=label)
1081
1081
1082 if shouldclose:
1082 if shouldclose:
1083 fp.close()
1083 fp.close()
1084
1084
1085 for seqno, rev in enumerate(revs):
1085 for seqno, rev in enumerate(revs):
1086 single(rev, seqno + 1, fp)
1086 single(rev, seqno + 1, fp)
1087
1087
1088 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1088 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1089 changes=None, stat=False, fp=None, prefix='',
1089 changes=None, stat=False, fp=None, prefix='',
1090 root='', listsubrepos=False):
1090 root='', listsubrepos=False):
1091 '''show diff or diffstat.'''
1091 '''show diff or diffstat.'''
1092 if fp is None:
1092 if fp is None:
1093 write = ui.write
1093 write = ui.write
1094 else:
1094 else:
1095 def write(s, **kw):
1095 def write(s, **kw):
1096 fp.write(s)
1096 fp.write(s)
1097
1097
1098 if root:
1098 if root:
1099 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1099 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1100 else:
1100 else:
1101 relroot = ''
1101 relroot = ''
1102 if relroot != '':
1102 if relroot != '':
1103 # XXX relative roots currently don't work if the root is within a
1103 # XXX relative roots currently don't work if the root is within a
1104 # subrepo
1104 # subrepo
1105 uirelroot = match.uipath(relroot)
1105 uirelroot = match.uipath(relroot)
1106 relroot += '/'
1106 relroot += '/'
1107 for matchroot in match.files():
1107 for matchroot in match.files():
1108 if not matchroot.startswith(relroot):
1108 if not matchroot.startswith(relroot):
1109 ui.warn(_('warning: %s not inside relative root %s\n') % (
1109 ui.warn(_('warning: %s not inside relative root %s\n') % (
1110 match.uipath(matchroot), uirelroot))
1110 match.uipath(matchroot), uirelroot))
1111
1111
1112 if stat:
1112 if stat:
1113 diffopts = diffopts.copy(context=0)
1113 diffopts = diffopts.copy(context=0)
1114 width = 80
1114 width = 80
1115 if not ui.plain():
1115 if not ui.plain():
1116 width = ui.termwidth()
1116 width = ui.termwidth()
1117 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1117 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1118 prefix=prefix, relroot=relroot)
1118 prefix=prefix, relroot=relroot)
1119 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1119 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1120 width=width):
1120 width=width):
1121 write(chunk, label=label)
1121 write(chunk, label=label)
1122 else:
1122 else:
1123 for chunk, label in patch.diffui(repo, node1, node2, match,
1123 for chunk, label in patch.diffui(repo, node1, node2, match,
1124 changes, diffopts, prefix=prefix,
1124 changes, diffopts, prefix=prefix,
1125 relroot=relroot):
1125 relroot=relroot):
1126 write(chunk, label=label)
1126 write(chunk, label=label)
1127
1127
1128 if listsubrepos:
1128 if listsubrepos:
1129 ctx1 = repo[node1]
1129 ctx1 = repo[node1]
1130 ctx2 = repo[node2]
1130 ctx2 = repo[node2]
1131 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1131 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1132 tempnode2 = node2
1132 tempnode2 = node2
1133 try:
1133 try:
1134 if node2 is not None:
1134 if node2 is not None:
1135 tempnode2 = ctx2.substate[subpath][1]
1135 tempnode2 = ctx2.substate[subpath][1]
1136 except KeyError:
1136 except KeyError:
1137 # A subrepo that existed in node1 was deleted between node1 and
1137 # A subrepo that existed in node1 was deleted between node1 and
1138 # node2 (inclusive). Thus, ctx2's substate won't contain that
1138 # node2 (inclusive). Thus, ctx2's substate won't contain that
1139 # subpath. The best we can do is to ignore it.
1139 # subpath. The best we can do is to ignore it.
1140 tempnode2 = None
1140 tempnode2 = None
1141 submatch = matchmod.subdirmatcher(subpath, match)
1141 submatch = matchmod.subdirmatcher(subpath, match)
1142 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1142 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1143 stat=stat, fp=fp, prefix=prefix)
1143 stat=stat, fp=fp, prefix=prefix)
1144
1144
1145 def _changesetlabels(ctx):
1145 def _changesetlabels(ctx):
1146 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1146 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1147 if ctx.troubled():
1148 labels.append('changeset.troubled')
1149 for trouble in ctx.troubles():
1150 labels.append('trouble.%s' % trouble)
1147 return ' '.join(labels)
1151 return ' '.join(labels)
1148
1152
1149 class changeset_printer(object):
1153 class changeset_printer(object):
1150 '''show changeset information when templating not requested.'''
1154 '''show changeset information when templating not requested.'''
1151
1155
1152 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1156 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1153 self.ui = ui
1157 self.ui = ui
1154 self.repo = repo
1158 self.repo = repo
1155 self.buffered = buffered
1159 self.buffered = buffered
1156 self.matchfn = matchfn
1160 self.matchfn = matchfn
1157 self.diffopts = diffopts
1161 self.diffopts = diffopts
1158 self.header = {}
1162 self.header = {}
1159 self.hunk = {}
1163 self.hunk = {}
1160 self.lastheader = None
1164 self.lastheader = None
1161 self.footer = None
1165 self.footer = None
1162
1166
1163 def flush(self, ctx):
1167 def flush(self, ctx):
1164 rev = ctx.rev()
1168 rev = ctx.rev()
1165 if rev in self.header:
1169 if rev in self.header:
1166 h = self.header[rev]
1170 h = self.header[rev]
1167 if h != self.lastheader:
1171 if h != self.lastheader:
1168 self.lastheader = h
1172 self.lastheader = h
1169 self.ui.write(h)
1173 self.ui.write(h)
1170 del self.header[rev]
1174 del self.header[rev]
1171 if rev in self.hunk:
1175 if rev in self.hunk:
1172 self.ui.write(self.hunk[rev])
1176 self.ui.write(self.hunk[rev])
1173 del self.hunk[rev]
1177 del self.hunk[rev]
1174 return 1
1178 return 1
1175 return 0
1179 return 0
1176
1180
1177 def close(self):
1181 def close(self):
1178 if self.footer:
1182 if self.footer:
1179 self.ui.write(self.footer)
1183 self.ui.write(self.footer)
1180
1184
1181 def show(self, ctx, copies=None, matchfn=None, **props):
1185 def show(self, ctx, copies=None, matchfn=None, **props):
1182 if self.buffered:
1186 if self.buffered:
1183 self.ui.pushbuffer(labeled=True)
1187 self.ui.pushbuffer(labeled=True)
1184 self._show(ctx, copies, matchfn, props)
1188 self._show(ctx, copies, matchfn, props)
1185 self.hunk[ctx.rev()] = self.ui.popbuffer()
1189 self.hunk[ctx.rev()] = self.ui.popbuffer()
1186 else:
1190 else:
1187 self._show(ctx, copies, matchfn, props)
1191 self._show(ctx, copies, matchfn, props)
1188
1192
1189 def _show(self, ctx, copies, matchfn, props):
1193 def _show(self, ctx, copies, matchfn, props):
1190 '''show a single changeset or file revision'''
1194 '''show a single changeset or file revision'''
1191 changenode = ctx.node()
1195 changenode = ctx.node()
1192 rev = ctx.rev()
1196 rev = ctx.rev()
1193 if self.ui.debugflag:
1197 if self.ui.debugflag:
1194 hexfunc = hex
1198 hexfunc = hex
1195 else:
1199 else:
1196 hexfunc = short
1200 hexfunc = short
1197 # as of now, wctx.node() and wctx.rev() return None, but we want to
1201 # as of now, wctx.node() and wctx.rev() return None, but we want to
1198 # show the same values as {node} and {rev} templatekw
1202 # show the same values as {node} and {rev} templatekw
1199 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1203 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1200
1204
1201 if self.ui.quiet:
1205 if self.ui.quiet:
1202 self.ui.write("%d:%s\n" % revnode, label='log.node')
1206 self.ui.write("%d:%s\n" % revnode, label='log.node')
1203 return
1207 return
1204
1208
1205 date = util.datestr(ctx.date())
1209 date = util.datestr(ctx.date())
1206
1210
1207 # i18n: column positioning for "hg log"
1211 # i18n: column positioning for "hg log"
1208 self.ui.write(_("changeset: %d:%s\n") % revnode,
1212 self.ui.write(_("changeset: %d:%s\n") % revnode,
1209 label=_changesetlabels(ctx))
1213 label=_changesetlabels(ctx))
1210
1214
1211 # branches are shown first before any other names due to backwards
1215 # branches are shown first before any other names due to backwards
1212 # compatibility
1216 # compatibility
1213 branch = ctx.branch()
1217 branch = ctx.branch()
1214 # don't show the default branch name
1218 # don't show the default branch name
1215 if branch != 'default':
1219 if branch != 'default':
1216 # i18n: column positioning for "hg log"
1220 # i18n: column positioning for "hg log"
1217 self.ui.write(_("branch: %s\n") % branch,
1221 self.ui.write(_("branch: %s\n") % branch,
1218 label='log.branch')
1222 label='log.branch')
1219
1223
1220 for nsname, ns in self.repo.names.iteritems():
1224 for nsname, ns in self.repo.names.iteritems():
1221 # branches has special logic already handled above, so here we just
1225 # branches has special logic already handled above, so here we just
1222 # skip it
1226 # skip it
1223 if nsname == 'branches':
1227 if nsname == 'branches':
1224 continue
1228 continue
1225 # we will use the templatename as the color name since those two
1229 # we will use the templatename as the color name since those two
1226 # should be the same
1230 # should be the same
1227 for name in ns.names(self.repo, changenode):
1231 for name in ns.names(self.repo, changenode):
1228 self.ui.write(ns.logfmt % name,
1232 self.ui.write(ns.logfmt % name,
1229 label='log.%s' % ns.colorname)
1233 label='log.%s' % ns.colorname)
1230 if self.ui.debugflag:
1234 if self.ui.debugflag:
1231 # i18n: column positioning for "hg log"
1235 # i18n: column positioning for "hg log"
1232 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1236 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1233 label='log.phase')
1237 label='log.phase')
1234 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1238 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1235 label = 'log.parent changeset.%s' % pctx.phasestr()
1239 label = 'log.parent changeset.%s' % pctx.phasestr()
1236 # i18n: column positioning for "hg log"
1240 # i18n: column positioning for "hg log"
1237 self.ui.write(_("parent: %d:%s\n")
1241 self.ui.write(_("parent: %d:%s\n")
1238 % (pctx.rev(), hexfunc(pctx.node())),
1242 % (pctx.rev(), hexfunc(pctx.node())),
1239 label=label)
1243 label=label)
1240
1244
1241 if self.ui.debugflag and rev is not None:
1245 if self.ui.debugflag and rev is not None:
1242 mnode = ctx.manifestnode()
1246 mnode = ctx.manifestnode()
1243 # i18n: column positioning for "hg log"
1247 # i18n: column positioning for "hg log"
1244 self.ui.write(_("manifest: %d:%s\n") %
1248 self.ui.write(_("manifest: %d:%s\n") %
1245 (self.repo.manifestlog._revlog.rev(mnode),
1249 (self.repo.manifestlog._revlog.rev(mnode),
1246 hex(mnode)),
1250 hex(mnode)),
1247 label='ui.debug log.manifest')
1251 label='ui.debug log.manifest')
1248 # i18n: column positioning for "hg log"
1252 # i18n: column positioning for "hg log"
1249 self.ui.write(_("user: %s\n") % ctx.user(),
1253 self.ui.write(_("user: %s\n") % ctx.user(),
1250 label='log.user')
1254 label='log.user')
1251 # i18n: column positioning for "hg log"
1255 # i18n: column positioning for "hg log"
1252 self.ui.write(_("date: %s\n") % date,
1256 self.ui.write(_("date: %s\n") % date,
1253 label='log.date')
1257 label='log.date')
1254
1258
1259 if ctx.troubled():
1260 self.ui.write(_("trouble: %s\n") % ', '.join(ctx.troubles()),
1261 label='ui.note log.trouble')
1262
1255 if self.ui.debugflag:
1263 if self.ui.debugflag:
1256 files = ctx.p1().status(ctx)[:3]
1264 files = ctx.p1().status(ctx)[:3]
1257 for key, value in zip([# i18n: column positioning for "hg log"
1265 for key, value in zip([# i18n: column positioning for "hg log"
1258 _("files:"),
1266 _("files:"),
1259 # i18n: column positioning for "hg log"
1267 # i18n: column positioning for "hg log"
1260 _("files+:"),
1268 _("files+:"),
1261 # i18n: column positioning for "hg log"
1269 # i18n: column positioning for "hg log"
1262 _("files-:")], files):
1270 _("files-:")], files):
1263 if value:
1271 if value:
1264 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1272 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1265 label='ui.debug log.files')
1273 label='ui.debug log.files')
1266 elif ctx.files() and self.ui.verbose:
1274 elif ctx.files() and self.ui.verbose:
1267 # i18n: column positioning for "hg log"
1275 # i18n: column positioning for "hg log"
1268 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1276 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1269 label='ui.note log.files')
1277 label='ui.note log.files')
1270 if copies and self.ui.verbose:
1278 if copies and self.ui.verbose:
1271 copies = ['%s (%s)' % c for c in copies]
1279 copies = ['%s (%s)' % c for c in copies]
1272 # i18n: column positioning for "hg log"
1280 # i18n: column positioning for "hg log"
1273 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1281 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1274 label='ui.note log.copies')
1282 label='ui.note log.copies')
1275
1283
1276 extra = ctx.extra()
1284 extra = ctx.extra()
1277 if extra and self.ui.debugflag:
1285 if extra and self.ui.debugflag:
1278 for key, value in sorted(extra.items()):
1286 for key, value in sorted(extra.items()):
1279 # i18n: column positioning for "hg log"
1287 # i18n: column positioning for "hg log"
1280 self.ui.write(_("extra: %s=%s\n")
1288 self.ui.write(_("extra: %s=%s\n")
1281 % (key, value.encode('string_escape')),
1289 % (key, value.encode('string_escape')),
1282 label='ui.debug log.extra')
1290 label='ui.debug log.extra')
1283
1291
1284 description = ctx.description().strip()
1292 description = ctx.description().strip()
1285 if description:
1293 if description:
1286 if self.ui.verbose:
1294 if self.ui.verbose:
1287 self.ui.write(_("description:\n"),
1295 self.ui.write(_("description:\n"),
1288 label='ui.note log.description')
1296 label='ui.note log.description')
1289 self.ui.write(description,
1297 self.ui.write(description,
1290 label='ui.note log.description')
1298 label='ui.note log.description')
1291 self.ui.write("\n\n")
1299 self.ui.write("\n\n")
1292 else:
1300 else:
1293 # i18n: column positioning for "hg log"
1301 # i18n: column positioning for "hg log"
1294 self.ui.write(_("summary: %s\n") %
1302 self.ui.write(_("summary: %s\n") %
1295 description.splitlines()[0],
1303 description.splitlines()[0],
1296 label='log.summary')
1304 label='log.summary')
1297 self.ui.write("\n")
1305 self.ui.write("\n")
1298
1306
1299 self.showpatch(ctx, matchfn)
1307 self.showpatch(ctx, matchfn)
1300
1308
1301 def showpatch(self, ctx, matchfn):
1309 def showpatch(self, ctx, matchfn):
1302 if not matchfn:
1310 if not matchfn:
1303 matchfn = self.matchfn
1311 matchfn = self.matchfn
1304 if matchfn:
1312 if matchfn:
1305 stat = self.diffopts.get('stat')
1313 stat = self.diffopts.get('stat')
1306 diff = self.diffopts.get('patch')
1314 diff = self.diffopts.get('patch')
1307 diffopts = patch.diffallopts(self.ui, self.diffopts)
1315 diffopts = patch.diffallopts(self.ui, self.diffopts)
1308 node = ctx.node()
1316 node = ctx.node()
1309 prev = ctx.p1().node()
1317 prev = ctx.p1().node()
1310 if stat:
1318 if stat:
1311 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1319 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1312 match=matchfn, stat=True)
1320 match=matchfn, stat=True)
1313 if diff:
1321 if diff:
1314 if stat:
1322 if stat:
1315 self.ui.write("\n")
1323 self.ui.write("\n")
1316 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1324 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1317 match=matchfn, stat=False)
1325 match=matchfn, stat=False)
1318 self.ui.write("\n")
1326 self.ui.write("\n")
1319
1327
1320 class jsonchangeset(changeset_printer):
1328 class jsonchangeset(changeset_printer):
1321 '''format changeset information.'''
1329 '''format changeset information.'''
1322
1330
1323 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1331 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1324 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1332 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1325 self.cache = {}
1333 self.cache = {}
1326 self._first = True
1334 self._first = True
1327
1335
1328 def close(self):
1336 def close(self):
1329 if not self._first:
1337 if not self._first:
1330 self.ui.write("\n]\n")
1338 self.ui.write("\n]\n")
1331 else:
1339 else:
1332 self.ui.write("[]\n")
1340 self.ui.write("[]\n")
1333
1341
1334 def _show(self, ctx, copies, matchfn, props):
1342 def _show(self, ctx, copies, matchfn, props):
1335 '''show a single changeset or file revision'''
1343 '''show a single changeset or file revision'''
1336 rev = ctx.rev()
1344 rev = ctx.rev()
1337 if rev is None:
1345 if rev is None:
1338 jrev = jnode = 'null'
1346 jrev = jnode = 'null'
1339 else:
1347 else:
1340 jrev = str(rev)
1348 jrev = str(rev)
1341 jnode = '"%s"' % hex(ctx.node())
1349 jnode = '"%s"' % hex(ctx.node())
1342 j = encoding.jsonescape
1350 j = encoding.jsonescape
1343
1351
1344 if self._first:
1352 if self._first:
1345 self.ui.write("[\n {")
1353 self.ui.write("[\n {")
1346 self._first = False
1354 self._first = False
1347 else:
1355 else:
1348 self.ui.write(",\n {")
1356 self.ui.write(",\n {")
1349
1357
1350 if self.ui.quiet:
1358 if self.ui.quiet:
1351 self.ui.write(('\n "rev": %s') % jrev)
1359 self.ui.write(('\n "rev": %s') % jrev)
1352 self.ui.write((',\n "node": %s') % jnode)
1360 self.ui.write((',\n "node": %s') % jnode)
1353 self.ui.write('\n }')
1361 self.ui.write('\n }')
1354 return
1362 return
1355
1363
1356 self.ui.write(('\n "rev": %s') % jrev)
1364 self.ui.write(('\n "rev": %s') % jrev)
1357 self.ui.write((',\n "node": %s') % jnode)
1365 self.ui.write((',\n "node": %s') % jnode)
1358 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1366 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1359 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1367 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1360 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1368 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1361 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1369 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1362 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1370 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1363
1371
1364 self.ui.write((',\n "bookmarks": [%s]') %
1372 self.ui.write((',\n "bookmarks": [%s]') %
1365 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1373 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1366 self.ui.write((',\n "tags": [%s]') %
1374 self.ui.write((',\n "tags": [%s]') %
1367 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1375 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1368 self.ui.write((',\n "parents": [%s]') %
1376 self.ui.write((',\n "parents": [%s]') %
1369 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1377 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1370
1378
1371 if self.ui.debugflag:
1379 if self.ui.debugflag:
1372 if rev is None:
1380 if rev is None:
1373 jmanifestnode = 'null'
1381 jmanifestnode = 'null'
1374 else:
1382 else:
1375 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1383 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1376 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1384 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1377
1385
1378 self.ui.write((',\n "extra": {%s}') %
1386 self.ui.write((',\n "extra": {%s}') %
1379 ", ".join('"%s": "%s"' % (j(k), j(v))
1387 ", ".join('"%s": "%s"' % (j(k), j(v))
1380 for k, v in ctx.extra().items()))
1388 for k, v in ctx.extra().items()))
1381
1389
1382 files = ctx.p1().status(ctx)
1390 files = ctx.p1().status(ctx)
1383 self.ui.write((',\n "modified": [%s]') %
1391 self.ui.write((',\n "modified": [%s]') %
1384 ", ".join('"%s"' % j(f) for f in files[0]))
1392 ", ".join('"%s"' % j(f) for f in files[0]))
1385 self.ui.write((',\n "added": [%s]') %
1393 self.ui.write((',\n "added": [%s]') %
1386 ", ".join('"%s"' % j(f) for f in files[1]))
1394 ", ".join('"%s"' % j(f) for f in files[1]))
1387 self.ui.write((',\n "removed": [%s]') %
1395 self.ui.write((',\n "removed": [%s]') %
1388 ", ".join('"%s"' % j(f) for f in files[2]))
1396 ", ".join('"%s"' % j(f) for f in files[2]))
1389
1397
1390 elif self.ui.verbose:
1398 elif self.ui.verbose:
1391 self.ui.write((',\n "files": [%s]') %
1399 self.ui.write((',\n "files": [%s]') %
1392 ", ".join('"%s"' % j(f) for f in ctx.files()))
1400 ", ".join('"%s"' % j(f) for f in ctx.files()))
1393
1401
1394 if copies:
1402 if copies:
1395 self.ui.write((',\n "copies": {%s}') %
1403 self.ui.write((',\n "copies": {%s}') %
1396 ", ".join('"%s": "%s"' % (j(k), j(v))
1404 ", ".join('"%s": "%s"' % (j(k), j(v))
1397 for k, v in copies))
1405 for k, v in copies))
1398
1406
1399 matchfn = self.matchfn
1407 matchfn = self.matchfn
1400 if matchfn:
1408 if matchfn:
1401 stat = self.diffopts.get('stat')
1409 stat = self.diffopts.get('stat')
1402 diff = self.diffopts.get('patch')
1410 diff = self.diffopts.get('patch')
1403 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1411 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1404 node, prev = ctx.node(), ctx.p1().node()
1412 node, prev = ctx.node(), ctx.p1().node()
1405 if stat:
1413 if stat:
1406 self.ui.pushbuffer()
1414 self.ui.pushbuffer()
1407 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1415 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1408 match=matchfn, stat=True)
1416 match=matchfn, stat=True)
1409 self.ui.write((',\n "diffstat": "%s"')
1417 self.ui.write((',\n "diffstat": "%s"')
1410 % j(self.ui.popbuffer()))
1418 % j(self.ui.popbuffer()))
1411 if diff:
1419 if diff:
1412 self.ui.pushbuffer()
1420 self.ui.pushbuffer()
1413 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1421 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1414 match=matchfn, stat=False)
1422 match=matchfn, stat=False)
1415 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1423 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1416
1424
1417 self.ui.write("\n }")
1425 self.ui.write("\n }")
1418
1426
1419 class changeset_templater(changeset_printer):
1427 class changeset_templater(changeset_printer):
1420 '''format changeset information.'''
1428 '''format changeset information.'''
1421
1429
1422 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1430 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1423 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1431 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1424 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1432 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1425 filters = {'formatnode': formatnode}
1433 filters = {'formatnode': formatnode}
1426 defaulttempl = {
1434 defaulttempl = {
1427 'parent': '{rev}:{node|formatnode} ',
1435 'parent': '{rev}:{node|formatnode} ',
1428 'manifest': '{rev}:{node|formatnode}',
1436 'manifest': '{rev}:{node|formatnode}',
1429 'file_copy': '{name} ({source})',
1437 'file_copy': '{name} ({source})',
1430 'extra': '{key}={value|stringescape}'
1438 'extra': '{key}={value|stringescape}'
1431 }
1439 }
1432 # filecopy is preserved for compatibility reasons
1440 # filecopy is preserved for compatibility reasons
1433 defaulttempl['filecopy'] = defaulttempl['file_copy']
1441 defaulttempl['filecopy'] = defaulttempl['file_copy']
1434 assert not (tmpl and mapfile)
1442 assert not (tmpl and mapfile)
1435 if mapfile:
1443 if mapfile:
1436 self.t = templater.templater.frommapfile(mapfile, filters=filters,
1444 self.t = templater.templater.frommapfile(mapfile, filters=filters,
1437 cache=defaulttempl)
1445 cache=defaulttempl)
1438 else:
1446 else:
1439 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1447 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1440 filters=filters,
1448 filters=filters,
1441 cache=defaulttempl)
1449 cache=defaulttempl)
1442
1450
1443 self.cache = {}
1451 self.cache = {}
1444
1452
1445 # find correct templates for current mode
1453 # find correct templates for current mode
1446 tmplmodes = [
1454 tmplmodes = [
1447 (True, None),
1455 (True, None),
1448 (self.ui.verbose, 'verbose'),
1456 (self.ui.verbose, 'verbose'),
1449 (self.ui.quiet, 'quiet'),
1457 (self.ui.quiet, 'quiet'),
1450 (self.ui.debugflag, 'debug'),
1458 (self.ui.debugflag, 'debug'),
1451 ]
1459 ]
1452
1460
1453 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1461 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1454 'docheader': '', 'docfooter': ''}
1462 'docheader': '', 'docfooter': ''}
1455 for mode, postfix in tmplmodes:
1463 for mode, postfix in tmplmodes:
1456 for t in self._parts:
1464 for t in self._parts:
1457 cur = t
1465 cur = t
1458 if postfix:
1466 if postfix:
1459 cur += "_" + postfix
1467 cur += "_" + postfix
1460 if mode and cur in self.t:
1468 if mode and cur in self.t:
1461 self._parts[t] = cur
1469 self._parts[t] = cur
1462
1470
1463 if self._parts['docheader']:
1471 if self._parts['docheader']:
1464 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1472 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1465
1473
1466 def close(self):
1474 def close(self):
1467 if self._parts['docfooter']:
1475 if self._parts['docfooter']:
1468 if not self.footer:
1476 if not self.footer:
1469 self.footer = ""
1477 self.footer = ""
1470 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1478 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1471 return super(changeset_templater, self).close()
1479 return super(changeset_templater, self).close()
1472
1480
1473 def _show(self, ctx, copies, matchfn, props):
1481 def _show(self, ctx, copies, matchfn, props):
1474 '''show a single changeset or file revision'''
1482 '''show a single changeset or file revision'''
1475 props = props.copy()
1483 props = props.copy()
1476 props.update(templatekw.keywords)
1484 props.update(templatekw.keywords)
1477 props['templ'] = self.t
1485 props['templ'] = self.t
1478 props['ctx'] = ctx
1486 props['ctx'] = ctx
1479 props['repo'] = self.repo
1487 props['repo'] = self.repo
1480 props['ui'] = self.repo.ui
1488 props['ui'] = self.repo.ui
1481 props['revcache'] = {'copies': copies}
1489 props['revcache'] = {'copies': copies}
1482 props['cache'] = self.cache
1490 props['cache'] = self.cache
1483
1491
1484 # write header
1492 # write header
1485 if self._parts['header']:
1493 if self._parts['header']:
1486 h = templater.stringify(self.t(self._parts['header'], **props))
1494 h = templater.stringify(self.t(self._parts['header'], **props))
1487 if self.buffered:
1495 if self.buffered:
1488 self.header[ctx.rev()] = h
1496 self.header[ctx.rev()] = h
1489 else:
1497 else:
1490 if self.lastheader != h:
1498 if self.lastheader != h:
1491 self.lastheader = h
1499 self.lastheader = h
1492 self.ui.write(h)
1500 self.ui.write(h)
1493
1501
1494 # write changeset metadata, then patch if requested
1502 # write changeset metadata, then patch if requested
1495 key = self._parts['changeset']
1503 key = self._parts['changeset']
1496 self.ui.write(templater.stringify(self.t(key, **props)))
1504 self.ui.write(templater.stringify(self.t(key, **props)))
1497 self.showpatch(ctx, matchfn)
1505 self.showpatch(ctx, matchfn)
1498
1506
1499 if self._parts['footer']:
1507 if self._parts['footer']:
1500 if not self.footer:
1508 if not self.footer:
1501 self.footer = templater.stringify(
1509 self.footer = templater.stringify(
1502 self.t(self._parts['footer'], **props))
1510 self.t(self._parts['footer'], **props))
1503
1511
1504 def gettemplate(ui, tmpl, style):
1512 def gettemplate(ui, tmpl, style):
1505 """
1513 """
1506 Find the template matching the given template spec or style.
1514 Find the template matching the given template spec or style.
1507 """
1515 """
1508
1516
1509 # ui settings
1517 # ui settings
1510 if not tmpl and not style: # template are stronger than style
1518 if not tmpl and not style: # template are stronger than style
1511 tmpl = ui.config('ui', 'logtemplate')
1519 tmpl = ui.config('ui', 'logtemplate')
1512 if tmpl:
1520 if tmpl:
1513 return templater.unquotestring(tmpl), None
1521 return templater.unquotestring(tmpl), None
1514 else:
1522 else:
1515 style = util.expandpath(ui.config('ui', 'style', ''))
1523 style = util.expandpath(ui.config('ui', 'style', ''))
1516
1524
1517 if not tmpl and style:
1525 if not tmpl and style:
1518 mapfile = style
1526 mapfile = style
1519 if not os.path.split(mapfile)[0]:
1527 if not os.path.split(mapfile)[0]:
1520 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1528 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1521 or templater.templatepath(mapfile))
1529 or templater.templatepath(mapfile))
1522 if mapname:
1530 if mapname:
1523 mapfile = mapname
1531 mapfile = mapname
1524 return None, mapfile
1532 return None, mapfile
1525
1533
1526 if not tmpl:
1534 if not tmpl:
1527 return None, None
1535 return None, None
1528
1536
1529 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1537 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1530
1538
1531 def show_changeset(ui, repo, opts, buffered=False):
1539 def show_changeset(ui, repo, opts, buffered=False):
1532 """show one changeset using template or regular display.
1540 """show one changeset using template or regular display.
1533
1541
1534 Display format will be the first non-empty hit of:
1542 Display format will be the first non-empty hit of:
1535 1. option 'template'
1543 1. option 'template'
1536 2. option 'style'
1544 2. option 'style'
1537 3. [ui] setting 'logtemplate'
1545 3. [ui] setting 'logtemplate'
1538 4. [ui] setting 'style'
1546 4. [ui] setting 'style'
1539 If all of these values are either the unset or the empty string,
1547 If all of these values are either the unset or the empty string,
1540 regular display via changeset_printer() is done.
1548 regular display via changeset_printer() is done.
1541 """
1549 """
1542 # options
1550 # options
1543 matchfn = None
1551 matchfn = None
1544 if opts.get('patch') or opts.get('stat'):
1552 if opts.get('patch') or opts.get('stat'):
1545 matchfn = scmutil.matchall(repo)
1553 matchfn = scmutil.matchall(repo)
1546
1554
1547 if opts.get('template') == 'json':
1555 if opts.get('template') == 'json':
1548 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1556 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1549
1557
1550 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1558 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1551
1559
1552 if not tmpl and not mapfile:
1560 if not tmpl and not mapfile:
1553 return changeset_printer(ui, repo, matchfn, opts, buffered)
1561 return changeset_printer(ui, repo, matchfn, opts, buffered)
1554
1562
1555 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1563 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1556
1564
1557 def showmarker(fm, marker, index=None):
1565 def showmarker(fm, marker, index=None):
1558 """utility function to display obsolescence marker in a readable way
1566 """utility function to display obsolescence marker in a readable way
1559
1567
1560 To be used by debug function."""
1568 To be used by debug function."""
1561 if index is not None:
1569 if index is not None:
1562 fm.write('index', '%i ', index)
1570 fm.write('index', '%i ', index)
1563 fm.write('precnode', '%s ', hex(marker.precnode()))
1571 fm.write('precnode', '%s ', hex(marker.precnode()))
1564 succs = marker.succnodes()
1572 succs = marker.succnodes()
1565 fm.condwrite(succs, 'succnodes', '%s ',
1573 fm.condwrite(succs, 'succnodes', '%s ',
1566 fm.formatlist(map(hex, succs), name='node'))
1574 fm.formatlist(map(hex, succs), name='node'))
1567 fm.write('flag', '%X ', marker.flags())
1575 fm.write('flag', '%X ', marker.flags())
1568 parents = marker.parentnodes()
1576 parents = marker.parentnodes()
1569 if parents is not None:
1577 if parents is not None:
1570 fm.write('parentnodes', '{%s} ',
1578 fm.write('parentnodes', '{%s} ',
1571 fm.formatlist(map(hex, parents), name='node', sep=', '))
1579 fm.formatlist(map(hex, parents), name='node', sep=', '))
1572 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1580 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1573 meta = marker.metadata().copy()
1581 meta = marker.metadata().copy()
1574 meta.pop('date', None)
1582 meta.pop('date', None)
1575 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1583 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1576 fm.plain('\n')
1584 fm.plain('\n')
1577
1585
1578 def finddate(ui, repo, date):
1586 def finddate(ui, repo, date):
1579 """Find the tipmost changeset that matches the given date spec"""
1587 """Find the tipmost changeset that matches the given date spec"""
1580
1588
1581 df = util.matchdate(date)
1589 df = util.matchdate(date)
1582 m = scmutil.matchall(repo)
1590 m = scmutil.matchall(repo)
1583 results = {}
1591 results = {}
1584
1592
1585 def prep(ctx, fns):
1593 def prep(ctx, fns):
1586 d = ctx.date()
1594 d = ctx.date()
1587 if df(d[0]):
1595 if df(d[0]):
1588 results[ctx.rev()] = d
1596 results[ctx.rev()] = d
1589
1597
1590 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1598 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1591 rev = ctx.rev()
1599 rev = ctx.rev()
1592 if rev in results:
1600 if rev in results:
1593 ui.status(_("found revision %s from %s\n") %
1601 ui.status(_("found revision %s from %s\n") %
1594 (rev, util.datestr(results[rev])))
1602 (rev, util.datestr(results[rev])))
1595 return str(rev)
1603 return str(rev)
1596
1604
1597 raise error.Abort(_("revision matching date not found"))
1605 raise error.Abort(_("revision matching date not found"))
1598
1606
1599 def increasingwindows(windowsize=8, sizelimit=512):
1607 def increasingwindows(windowsize=8, sizelimit=512):
1600 while True:
1608 while True:
1601 yield windowsize
1609 yield windowsize
1602 if windowsize < sizelimit:
1610 if windowsize < sizelimit:
1603 windowsize *= 2
1611 windowsize *= 2
1604
1612
1605 class FileWalkError(Exception):
1613 class FileWalkError(Exception):
1606 pass
1614 pass
1607
1615
1608 def walkfilerevs(repo, match, follow, revs, fncache):
1616 def walkfilerevs(repo, match, follow, revs, fncache):
1609 '''Walks the file history for the matched files.
1617 '''Walks the file history for the matched files.
1610
1618
1611 Returns the changeset revs that are involved in the file history.
1619 Returns the changeset revs that are involved in the file history.
1612
1620
1613 Throws FileWalkError if the file history can't be walked using
1621 Throws FileWalkError if the file history can't be walked using
1614 filelogs alone.
1622 filelogs alone.
1615 '''
1623 '''
1616 wanted = set()
1624 wanted = set()
1617 copies = []
1625 copies = []
1618 minrev, maxrev = min(revs), max(revs)
1626 minrev, maxrev = min(revs), max(revs)
1619 def filerevgen(filelog, last):
1627 def filerevgen(filelog, last):
1620 """
1628 """
1621 Only files, no patterns. Check the history of each file.
1629 Only files, no patterns. Check the history of each file.
1622
1630
1623 Examines filelog entries within minrev, maxrev linkrev range
1631 Examines filelog entries within minrev, maxrev linkrev range
1624 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1632 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1625 tuples in backwards order
1633 tuples in backwards order
1626 """
1634 """
1627 cl_count = len(repo)
1635 cl_count = len(repo)
1628 revs = []
1636 revs = []
1629 for j in xrange(0, last + 1):
1637 for j in xrange(0, last + 1):
1630 linkrev = filelog.linkrev(j)
1638 linkrev = filelog.linkrev(j)
1631 if linkrev < minrev:
1639 if linkrev < minrev:
1632 continue
1640 continue
1633 # only yield rev for which we have the changelog, it can
1641 # only yield rev for which we have the changelog, it can
1634 # happen while doing "hg log" during a pull or commit
1642 # happen while doing "hg log" during a pull or commit
1635 if linkrev >= cl_count:
1643 if linkrev >= cl_count:
1636 break
1644 break
1637
1645
1638 parentlinkrevs = []
1646 parentlinkrevs = []
1639 for p in filelog.parentrevs(j):
1647 for p in filelog.parentrevs(j):
1640 if p != nullrev:
1648 if p != nullrev:
1641 parentlinkrevs.append(filelog.linkrev(p))
1649 parentlinkrevs.append(filelog.linkrev(p))
1642 n = filelog.node(j)
1650 n = filelog.node(j)
1643 revs.append((linkrev, parentlinkrevs,
1651 revs.append((linkrev, parentlinkrevs,
1644 follow and filelog.renamed(n)))
1652 follow and filelog.renamed(n)))
1645
1653
1646 return reversed(revs)
1654 return reversed(revs)
1647 def iterfiles():
1655 def iterfiles():
1648 pctx = repo['.']
1656 pctx = repo['.']
1649 for filename in match.files():
1657 for filename in match.files():
1650 if follow:
1658 if follow:
1651 if filename not in pctx:
1659 if filename not in pctx:
1652 raise error.Abort(_('cannot follow file not in parent '
1660 raise error.Abort(_('cannot follow file not in parent '
1653 'revision: "%s"') % filename)
1661 'revision: "%s"') % filename)
1654 yield filename, pctx[filename].filenode()
1662 yield filename, pctx[filename].filenode()
1655 else:
1663 else:
1656 yield filename, None
1664 yield filename, None
1657 for filename_node in copies:
1665 for filename_node in copies:
1658 yield filename_node
1666 yield filename_node
1659
1667
1660 for file_, node in iterfiles():
1668 for file_, node in iterfiles():
1661 filelog = repo.file(file_)
1669 filelog = repo.file(file_)
1662 if not len(filelog):
1670 if not len(filelog):
1663 if node is None:
1671 if node is None:
1664 # A zero count may be a directory or deleted file, so
1672 # A zero count may be a directory or deleted file, so
1665 # try to find matching entries on the slow path.
1673 # try to find matching entries on the slow path.
1666 if follow:
1674 if follow:
1667 raise error.Abort(
1675 raise error.Abort(
1668 _('cannot follow nonexistent file: "%s"') % file_)
1676 _('cannot follow nonexistent file: "%s"') % file_)
1669 raise FileWalkError("Cannot walk via filelog")
1677 raise FileWalkError("Cannot walk via filelog")
1670 else:
1678 else:
1671 continue
1679 continue
1672
1680
1673 if node is None:
1681 if node is None:
1674 last = len(filelog) - 1
1682 last = len(filelog) - 1
1675 else:
1683 else:
1676 last = filelog.rev(node)
1684 last = filelog.rev(node)
1677
1685
1678 # keep track of all ancestors of the file
1686 # keep track of all ancestors of the file
1679 ancestors = set([filelog.linkrev(last)])
1687 ancestors = set([filelog.linkrev(last)])
1680
1688
1681 # iterate from latest to oldest revision
1689 # iterate from latest to oldest revision
1682 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1690 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1683 if not follow:
1691 if not follow:
1684 if rev > maxrev:
1692 if rev > maxrev:
1685 continue
1693 continue
1686 else:
1694 else:
1687 # Note that last might not be the first interesting
1695 # Note that last might not be the first interesting
1688 # rev to us:
1696 # rev to us:
1689 # if the file has been changed after maxrev, we'll
1697 # if the file has been changed after maxrev, we'll
1690 # have linkrev(last) > maxrev, and we still need
1698 # have linkrev(last) > maxrev, and we still need
1691 # to explore the file graph
1699 # to explore the file graph
1692 if rev not in ancestors:
1700 if rev not in ancestors:
1693 continue
1701 continue
1694 # XXX insert 1327 fix here
1702 # XXX insert 1327 fix here
1695 if flparentlinkrevs:
1703 if flparentlinkrevs:
1696 ancestors.update(flparentlinkrevs)
1704 ancestors.update(flparentlinkrevs)
1697
1705
1698 fncache.setdefault(rev, []).append(file_)
1706 fncache.setdefault(rev, []).append(file_)
1699 wanted.add(rev)
1707 wanted.add(rev)
1700 if copied:
1708 if copied:
1701 copies.append(copied)
1709 copies.append(copied)
1702
1710
1703 return wanted
1711 return wanted
1704
1712
1705 class _followfilter(object):
1713 class _followfilter(object):
1706 def __init__(self, repo, onlyfirst=False):
1714 def __init__(self, repo, onlyfirst=False):
1707 self.repo = repo
1715 self.repo = repo
1708 self.startrev = nullrev
1716 self.startrev = nullrev
1709 self.roots = set()
1717 self.roots = set()
1710 self.onlyfirst = onlyfirst
1718 self.onlyfirst = onlyfirst
1711
1719
1712 def match(self, rev):
1720 def match(self, rev):
1713 def realparents(rev):
1721 def realparents(rev):
1714 if self.onlyfirst:
1722 if self.onlyfirst:
1715 return self.repo.changelog.parentrevs(rev)[0:1]
1723 return self.repo.changelog.parentrevs(rev)[0:1]
1716 else:
1724 else:
1717 return filter(lambda x: x != nullrev,
1725 return filter(lambda x: x != nullrev,
1718 self.repo.changelog.parentrevs(rev))
1726 self.repo.changelog.parentrevs(rev))
1719
1727
1720 if self.startrev == nullrev:
1728 if self.startrev == nullrev:
1721 self.startrev = rev
1729 self.startrev = rev
1722 return True
1730 return True
1723
1731
1724 if rev > self.startrev:
1732 if rev > self.startrev:
1725 # forward: all descendants
1733 # forward: all descendants
1726 if not self.roots:
1734 if not self.roots:
1727 self.roots.add(self.startrev)
1735 self.roots.add(self.startrev)
1728 for parent in realparents(rev):
1736 for parent in realparents(rev):
1729 if parent in self.roots:
1737 if parent in self.roots:
1730 self.roots.add(rev)
1738 self.roots.add(rev)
1731 return True
1739 return True
1732 else:
1740 else:
1733 # backwards: all parents
1741 # backwards: all parents
1734 if not self.roots:
1742 if not self.roots:
1735 self.roots.update(realparents(self.startrev))
1743 self.roots.update(realparents(self.startrev))
1736 if rev in self.roots:
1744 if rev in self.roots:
1737 self.roots.remove(rev)
1745 self.roots.remove(rev)
1738 self.roots.update(realparents(rev))
1746 self.roots.update(realparents(rev))
1739 return True
1747 return True
1740
1748
1741 return False
1749 return False
1742
1750
1743 def walkchangerevs(repo, match, opts, prepare):
1751 def walkchangerevs(repo, match, opts, prepare):
1744 '''Iterate over files and the revs in which they changed.
1752 '''Iterate over files and the revs in which they changed.
1745
1753
1746 Callers most commonly need to iterate backwards over the history
1754 Callers most commonly need to iterate backwards over the history
1747 in which they are interested. Doing so has awful (quadratic-looking)
1755 in which they are interested. Doing so has awful (quadratic-looking)
1748 performance, so we use iterators in a "windowed" way.
1756 performance, so we use iterators in a "windowed" way.
1749
1757
1750 We walk a window of revisions in the desired order. Within the
1758 We walk a window of revisions in the desired order. Within the
1751 window, we first walk forwards to gather data, then in the desired
1759 window, we first walk forwards to gather data, then in the desired
1752 order (usually backwards) to display it.
1760 order (usually backwards) to display it.
1753
1761
1754 This function returns an iterator yielding contexts. Before
1762 This function returns an iterator yielding contexts. Before
1755 yielding each context, the iterator will first call the prepare
1763 yielding each context, the iterator will first call the prepare
1756 function on each context in the window in forward order.'''
1764 function on each context in the window in forward order.'''
1757
1765
1758 follow = opts.get('follow') or opts.get('follow_first')
1766 follow = opts.get('follow') or opts.get('follow_first')
1759 revs = _logrevs(repo, opts)
1767 revs = _logrevs(repo, opts)
1760 if not revs:
1768 if not revs:
1761 return []
1769 return []
1762 wanted = set()
1770 wanted = set()
1763 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1771 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1764 opts.get('removed'))
1772 opts.get('removed'))
1765 fncache = {}
1773 fncache = {}
1766 change = repo.changectx
1774 change = repo.changectx
1767
1775
1768 # First step is to fill wanted, the set of revisions that we want to yield.
1776 # First step is to fill wanted, the set of revisions that we want to yield.
1769 # When it does not induce extra cost, we also fill fncache for revisions in
1777 # When it does not induce extra cost, we also fill fncache for revisions in
1770 # wanted: a cache of filenames that were changed (ctx.files()) and that
1778 # wanted: a cache of filenames that were changed (ctx.files()) and that
1771 # match the file filtering conditions.
1779 # match the file filtering conditions.
1772
1780
1773 if match.always():
1781 if match.always():
1774 # No files, no patterns. Display all revs.
1782 # No files, no patterns. Display all revs.
1775 wanted = revs
1783 wanted = revs
1776 elif not slowpath:
1784 elif not slowpath:
1777 # We only have to read through the filelog to find wanted revisions
1785 # We only have to read through the filelog to find wanted revisions
1778
1786
1779 try:
1787 try:
1780 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1788 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1781 except FileWalkError:
1789 except FileWalkError:
1782 slowpath = True
1790 slowpath = True
1783
1791
1784 # We decided to fall back to the slowpath because at least one
1792 # We decided to fall back to the slowpath because at least one
1785 # of the paths was not a file. Check to see if at least one of them
1793 # of the paths was not a file. Check to see if at least one of them
1786 # existed in history, otherwise simply return
1794 # existed in history, otherwise simply return
1787 for path in match.files():
1795 for path in match.files():
1788 if path == '.' or path in repo.store:
1796 if path == '.' or path in repo.store:
1789 break
1797 break
1790 else:
1798 else:
1791 return []
1799 return []
1792
1800
1793 if slowpath:
1801 if slowpath:
1794 # We have to read the changelog to match filenames against
1802 # We have to read the changelog to match filenames against
1795 # changed files
1803 # changed files
1796
1804
1797 if follow:
1805 if follow:
1798 raise error.Abort(_('can only follow copies/renames for explicit '
1806 raise error.Abort(_('can only follow copies/renames for explicit '
1799 'filenames'))
1807 'filenames'))
1800
1808
1801 # The slow path checks files modified in every changeset.
1809 # The slow path checks files modified in every changeset.
1802 # This is really slow on large repos, so compute the set lazily.
1810 # This is really slow on large repos, so compute the set lazily.
1803 class lazywantedset(object):
1811 class lazywantedset(object):
1804 def __init__(self):
1812 def __init__(self):
1805 self.set = set()
1813 self.set = set()
1806 self.revs = set(revs)
1814 self.revs = set(revs)
1807
1815
1808 # No need to worry about locality here because it will be accessed
1816 # No need to worry about locality here because it will be accessed
1809 # in the same order as the increasing window below.
1817 # in the same order as the increasing window below.
1810 def __contains__(self, value):
1818 def __contains__(self, value):
1811 if value in self.set:
1819 if value in self.set:
1812 return True
1820 return True
1813 elif not value in self.revs:
1821 elif not value in self.revs:
1814 return False
1822 return False
1815 else:
1823 else:
1816 self.revs.discard(value)
1824 self.revs.discard(value)
1817 ctx = change(value)
1825 ctx = change(value)
1818 matches = filter(match, ctx.files())
1826 matches = filter(match, ctx.files())
1819 if matches:
1827 if matches:
1820 fncache[value] = matches
1828 fncache[value] = matches
1821 self.set.add(value)
1829 self.set.add(value)
1822 return True
1830 return True
1823 return False
1831 return False
1824
1832
1825 def discard(self, value):
1833 def discard(self, value):
1826 self.revs.discard(value)
1834 self.revs.discard(value)
1827 self.set.discard(value)
1835 self.set.discard(value)
1828
1836
1829 wanted = lazywantedset()
1837 wanted = lazywantedset()
1830
1838
1831 # it might be worthwhile to do this in the iterator if the rev range
1839 # it might be worthwhile to do this in the iterator if the rev range
1832 # is descending and the prune args are all within that range
1840 # is descending and the prune args are all within that range
1833 for rev in opts.get('prune', ()):
1841 for rev in opts.get('prune', ()):
1834 rev = repo[rev].rev()
1842 rev = repo[rev].rev()
1835 ff = _followfilter(repo)
1843 ff = _followfilter(repo)
1836 stop = min(revs[0], revs[-1])
1844 stop = min(revs[0], revs[-1])
1837 for x in xrange(rev, stop - 1, -1):
1845 for x in xrange(rev, stop - 1, -1):
1838 if ff.match(x):
1846 if ff.match(x):
1839 wanted = wanted - [x]
1847 wanted = wanted - [x]
1840
1848
1841 # Now that wanted is correctly initialized, we can iterate over the
1849 # Now that wanted is correctly initialized, we can iterate over the
1842 # revision range, yielding only revisions in wanted.
1850 # revision range, yielding only revisions in wanted.
1843 def iterate():
1851 def iterate():
1844 if follow and match.always():
1852 if follow and match.always():
1845 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1853 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1846 def want(rev):
1854 def want(rev):
1847 return ff.match(rev) and rev in wanted
1855 return ff.match(rev) and rev in wanted
1848 else:
1856 else:
1849 def want(rev):
1857 def want(rev):
1850 return rev in wanted
1858 return rev in wanted
1851
1859
1852 it = iter(revs)
1860 it = iter(revs)
1853 stopiteration = False
1861 stopiteration = False
1854 for windowsize in increasingwindows():
1862 for windowsize in increasingwindows():
1855 nrevs = []
1863 nrevs = []
1856 for i in xrange(windowsize):
1864 for i in xrange(windowsize):
1857 rev = next(it, None)
1865 rev = next(it, None)
1858 if rev is None:
1866 if rev is None:
1859 stopiteration = True
1867 stopiteration = True
1860 break
1868 break
1861 elif want(rev):
1869 elif want(rev):
1862 nrevs.append(rev)
1870 nrevs.append(rev)
1863 for rev in sorted(nrevs):
1871 for rev in sorted(nrevs):
1864 fns = fncache.get(rev)
1872 fns = fncache.get(rev)
1865 ctx = change(rev)
1873 ctx = change(rev)
1866 if not fns:
1874 if not fns:
1867 def fns_generator():
1875 def fns_generator():
1868 for f in ctx.files():
1876 for f in ctx.files():
1869 if match(f):
1877 if match(f):
1870 yield f
1878 yield f
1871 fns = fns_generator()
1879 fns = fns_generator()
1872 prepare(ctx, fns)
1880 prepare(ctx, fns)
1873 for rev in nrevs:
1881 for rev in nrevs:
1874 yield change(rev)
1882 yield change(rev)
1875
1883
1876 if stopiteration:
1884 if stopiteration:
1877 break
1885 break
1878
1886
1879 return iterate()
1887 return iterate()
1880
1888
1881 def _makefollowlogfilematcher(repo, files, followfirst):
1889 def _makefollowlogfilematcher(repo, files, followfirst):
1882 # When displaying a revision with --patch --follow FILE, we have
1890 # When displaying a revision with --patch --follow FILE, we have
1883 # to know which file of the revision must be diffed. With
1891 # to know which file of the revision must be diffed. With
1884 # --follow, we want the names of the ancestors of FILE in the
1892 # --follow, we want the names of the ancestors of FILE in the
1885 # revision, stored in "fcache". "fcache" is populated by
1893 # revision, stored in "fcache". "fcache" is populated by
1886 # reproducing the graph traversal already done by --follow revset
1894 # reproducing the graph traversal already done by --follow revset
1887 # and relating revs to file names (which is not "correct" but
1895 # and relating revs to file names (which is not "correct" but
1888 # good enough).
1896 # good enough).
1889 fcache = {}
1897 fcache = {}
1890 fcacheready = [False]
1898 fcacheready = [False]
1891 pctx = repo['.']
1899 pctx = repo['.']
1892
1900
1893 def populate():
1901 def populate():
1894 for fn in files:
1902 for fn in files:
1895 fctx = pctx[fn]
1903 fctx = pctx[fn]
1896 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
1904 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
1897 for c in fctx.ancestors(followfirst=followfirst):
1905 for c in fctx.ancestors(followfirst=followfirst):
1898 fcache.setdefault(c.rev(), set()).add(c.path())
1906 fcache.setdefault(c.rev(), set()).add(c.path())
1899
1907
1900 def filematcher(rev):
1908 def filematcher(rev):
1901 if not fcacheready[0]:
1909 if not fcacheready[0]:
1902 # Lazy initialization
1910 # Lazy initialization
1903 fcacheready[0] = True
1911 fcacheready[0] = True
1904 populate()
1912 populate()
1905 return scmutil.matchfiles(repo, fcache.get(rev, []))
1913 return scmutil.matchfiles(repo, fcache.get(rev, []))
1906
1914
1907 return filematcher
1915 return filematcher
1908
1916
1909 def _makenofollowlogfilematcher(repo, pats, opts):
1917 def _makenofollowlogfilematcher(repo, pats, opts):
1910 '''hook for extensions to override the filematcher for non-follow cases'''
1918 '''hook for extensions to override the filematcher for non-follow cases'''
1911 return None
1919 return None
1912
1920
1913 def _makelogrevset(repo, pats, opts, revs):
1921 def _makelogrevset(repo, pats, opts, revs):
1914 """Return (expr, filematcher) where expr is a revset string built
1922 """Return (expr, filematcher) where expr is a revset string built
1915 from log options and file patterns or None. If --stat or --patch
1923 from log options and file patterns or None. If --stat or --patch
1916 are not passed filematcher is None. Otherwise it is a callable
1924 are not passed filematcher is None. Otherwise it is a callable
1917 taking a revision number and returning a match objects filtering
1925 taking a revision number and returning a match objects filtering
1918 the files to be detailed when displaying the revision.
1926 the files to be detailed when displaying the revision.
1919 """
1927 """
1920 opt2revset = {
1928 opt2revset = {
1921 'no_merges': ('not merge()', None),
1929 'no_merges': ('not merge()', None),
1922 'only_merges': ('merge()', None),
1930 'only_merges': ('merge()', None),
1923 '_ancestors': ('ancestors(%(val)s)', None),
1931 '_ancestors': ('ancestors(%(val)s)', None),
1924 '_fancestors': ('_firstancestors(%(val)s)', None),
1932 '_fancestors': ('_firstancestors(%(val)s)', None),
1925 '_descendants': ('descendants(%(val)s)', None),
1933 '_descendants': ('descendants(%(val)s)', None),
1926 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1934 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1927 '_matchfiles': ('_matchfiles(%(val)s)', None),
1935 '_matchfiles': ('_matchfiles(%(val)s)', None),
1928 'date': ('date(%(val)r)', None),
1936 'date': ('date(%(val)r)', None),
1929 'branch': ('branch(%(val)r)', ' or '),
1937 'branch': ('branch(%(val)r)', ' or '),
1930 '_patslog': ('filelog(%(val)r)', ' or '),
1938 '_patslog': ('filelog(%(val)r)', ' or '),
1931 '_patsfollow': ('follow(%(val)r)', ' or '),
1939 '_patsfollow': ('follow(%(val)r)', ' or '),
1932 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1940 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1933 'keyword': ('keyword(%(val)r)', ' or '),
1941 'keyword': ('keyword(%(val)r)', ' or '),
1934 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1942 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1935 'user': ('user(%(val)r)', ' or '),
1943 'user': ('user(%(val)r)', ' or '),
1936 }
1944 }
1937
1945
1938 opts = dict(opts)
1946 opts = dict(opts)
1939 # follow or not follow?
1947 # follow or not follow?
1940 follow = opts.get('follow') or opts.get('follow_first')
1948 follow = opts.get('follow') or opts.get('follow_first')
1941 if opts.get('follow_first'):
1949 if opts.get('follow_first'):
1942 followfirst = 1
1950 followfirst = 1
1943 else:
1951 else:
1944 followfirst = 0
1952 followfirst = 0
1945 # --follow with FILE behavior depends on revs...
1953 # --follow with FILE behavior depends on revs...
1946 it = iter(revs)
1954 it = iter(revs)
1947 startrev = next(it)
1955 startrev = next(it)
1948 followdescendants = startrev < next(it, startrev)
1956 followdescendants = startrev < next(it, startrev)
1949
1957
1950 # branch and only_branch are really aliases and must be handled at
1958 # branch and only_branch are really aliases and must be handled at
1951 # the same time
1959 # the same time
1952 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1960 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1953 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1961 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1954 # pats/include/exclude are passed to match.match() directly in
1962 # pats/include/exclude are passed to match.match() directly in
1955 # _matchfiles() revset but walkchangerevs() builds its matcher with
1963 # _matchfiles() revset but walkchangerevs() builds its matcher with
1956 # scmutil.match(). The difference is input pats are globbed on
1964 # scmutil.match(). The difference is input pats are globbed on
1957 # platforms without shell expansion (windows).
1965 # platforms without shell expansion (windows).
1958 wctx = repo[None]
1966 wctx = repo[None]
1959 match, pats = scmutil.matchandpats(wctx, pats, opts)
1967 match, pats = scmutil.matchandpats(wctx, pats, opts)
1960 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1968 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1961 opts.get('removed'))
1969 opts.get('removed'))
1962 if not slowpath:
1970 if not slowpath:
1963 for f in match.files():
1971 for f in match.files():
1964 if follow and f not in wctx:
1972 if follow and f not in wctx:
1965 # If the file exists, it may be a directory, so let it
1973 # If the file exists, it may be a directory, so let it
1966 # take the slow path.
1974 # take the slow path.
1967 if os.path.exists(repo.wjoin(f)):
1975 if os.path.exists(repo.wjoin(f)):
1968 slowpath = True
1976 slowpath = True
1969 continue
1977 continue
1970 else:
1978 else:
1971 raise error.Abort(_('cannot follow file not in parent '
1979 raise error.Abort(_('cannot follow file not in parent '
1972 'revision: "%s"') % f)
1980 'revision: "%s"') % f)
1973 filelog = repo.file(f)
1981 filelog = repo.file(f)
1974 if not filelog:
1982 if not filelog:
1975 # A zero count may be a directory or deleted file, so
1983 # A zero count may be a directory or deleted file, so
1976 # try to find matching entries on the slow path.
1984 # try to find matching entries on the slow path.
1977 if follow:
1985 if follow:
1978 raise error.Abort(
1986 raise error.Abort(
1979 _('cannot follow nonexistent file: "%s"') % f)
1987 _('cannot follow nonexistent file: "%s"') % f)
1980 slowpath = True
1988 slowpath = True
1981
1989
1982 # We decided to fall back to the slowpath because at least one
1990 # We decided to fall back to the slowpath because at least one
1983 # of the paths was not a file. Check to see if at least one of them
1991 # of the paths was not a file. Check to see if at least one of them
1984 # existed in history - in that case, we'll continue down the
1992 # existed in history - in that case, we'll continue down the
1985 # slowpath; otherwise, we can turn off the slowpath
1993 # slowpath; otherwise, we can turn off the slowpath
1986 if slowpath:
1994 if slowpath:
1987 for path in match.files():
1995 for path in match.files():
1988 if path == '.' or path in repo.store:
1996 if path == '.' or path in repo.store:
1989 break
1997 break
1990 else:
1998 else:
1991 slowpath = False
1999 slowpath = False
1992
2000
1993 fpats = ('_patsfollow', '_patsfollowfirst')
2001 fpats = ('_patsfollow', '_patsfollowfirst')
1994 fnopats = (('_ancestors', '_fancestors'),
2002 fnopats = (('_ancestors', '_fancestors'),
1995 ('_descendants', '_fdescendants'))
2003 ('_descendants', '_fdescendants'))
1996 if slowpath:
2004 if slowpath:
1997 # See walkchangerevs() slow path.
2005 # See walkchangerevs() slow path.
1998 #
2006 #
1999 # pats/include/exclude cannot be represented as separate
2007 # pats/include/exclude cannot be represented as separate
2000 # revset expressions as their filtering logic applies at file
2008 # revset expressions as their filtering logic applies at file
2001 # level. For instance "-I a -X a" matches a revision touching
2009 # level. For instance "-I a -X a" matches a revision touching
2002 # "a" and "b" while "file(a) and not file(b)" does
2010 # "a" and "b" while "file(a) and not file(b)" does
2003 # not. Besides, filesets are evaluated against the working
2011 # not. Besides, filesets are evaluated against the working
2004 # directory.
2012 # directory.
2005 matchargs = ['r:', 'd:relpath']
2013 matchargs = ['r:', 'd:relpath']
2006 for p in pats:
2014 for p in pats:
2007 matchargs.append('p:' + p)
2015 matchargs.append('p:' + p)
2008 for p in opts.get('include', []):
2016 for p in opts.get('include', []):
2009 matchargs.append('i:' + p)
2017 matchargs.append('i:' + p)
2010 for p in opts.get('exclude', []):
2018 for p in opts.get('exclude', []):
2011 matchargs.append('x:' + p)
2019 matchargs.append('x:' + p)
2012 matchargs = ','.join(('%r' % p) for p in matchargs)
2020 matchargs = ','.join(('%r' % p) for p in matchargs)
2013 opts['_matchfiles'] = matchargs
2021 opts['_matchfiles'] = matchargs
2014 if follow:
2022 if follow:
2015 opts[fnopats[0][followfirst]] = '.'
2023 opts[fnopats[0][followfirst]] = '.'
2016 else:
2024 else:
2017 if follow:
2025 if follow:
2018 if pats:
2026 if pats:
2019 # follow() revset interprets its file argument as a
2027 # follow() revset interprets its file argument as a
2020 # manifest entry, so use match.files(), not pats.
2028 # manifest entry, so use match.files(), not pats.
2021 opts[fpats[followfirst]] = list(match.files())
2029 opts[fpats[followfirst]] = list(match.files())
2022 else:
2030 else:
2023 op = fnopats[followdescendants][followfirst]
2031 op = fnopats[followdescendants][followfirst]
2024 opts[op] = 'rev(%d)' % startrev
2032 opts[op] = 'rev(%d)' % startrev
2025 else:
2033 else:
2026 opts['_patslog'] = list(pats)
2034 opts['_patslog'] = list(pats)
2027
2035
2028 filematcher = None
2036 filematcher = None
2029 if opts.get('patch') or opts.get('stat'):
2037 if opts.get('patch') or opts.get('stat'):
2030 # When following files, track renames via a special matcher.
2038 # When following files, track renames via a special matcher.
2031 # If we're forced to take the slowpath it means we're following
2039 # If we're forced to take the slowpath it means we're following
2032 # at least one pattern/directory, so don't bother with rename tracking.
2040 # at least one pattern/directory, so don't bother with rename tracking.
2033 if follow and not match.always() and not slowpath:
2041 if follow and not match.always() and not slowpath:
2034 # _makefollowlogfilematcher expects its files argument to be
2042 # _makefollowlogfilematcher expects its files argument to be
2035 # relative to the repo root, so use match.files(), not pats.
2043 # relative to the repo root, so use match.files(), not pats.
2036 filematcher = _makefollowlogfilematcher(repo, match.files(),
2044 filematcher = _makefollowlogfilematcher(repo, match.files(),
2037 followfirst)
2045 followfirst)
2038 else:
2046 else:
2039 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2047 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2040 if filematcher is None:
2048 if filematcher is None:
2041 filematcher = lambda rev: match
2049 filematcher = lambda rev: match
2042
2050
2043 expr = []
2051 expr = []
2044 for op, val in sorted(opts.iteritems()):
2052 for op, val in sorted(opts.iteritems()):
2045 if not val:
2053 if not val:
2046 continue
2054 continue
2047 if op not in opt2revset:
2055 if op not in opt2revset:
2048 continue
2056 continue
2049 revop, andor = opt2revset[op]
2057 revop, andor = opt2revset[op]
2050 if '%(val)' not in revop:
2058 if '%(val)' not in revop:
2051 expr.append(revop)
2059 expr.append(revop)
2052 else:
2060 else:
2053 if not isinstance(val, list):
2061 if not isinstance(val, list):
2054 e = revop % {'val': val}
2062 e = revop % {'val': val}
2055 else:
2063 else:
2056 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2064 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2057 expr.append(e)
2065 expr.append(e)
2058
2066
2059 if expr:
2067 if expr:
2060 expr = '(' + ' and '.join(expr) + ')'
2068 expr = '(' + ' and '.join(expr) + ')'
2061 else:
2069 else:
2062 expr = None
2070 expr = None
2063 return expr, filematcher
2071 return expr, filematcher
2064
2072
2065 def _logrevs(repo, opts):
2073 def _logrevs(repo, opts):
2066 # Default --rev value depends on --follow but --follow behavior
2074 # Default --rev value depends on --follow but --follow behavior
2067 # depends on revisions resolved from --rev...
2075 # depends on revisions resolved from --rev...
2068 follow = opts.get('follow') or opts.get('follow_first')
2076 follow = opts.get('follow') or opts.get('follow_first')
2069 if opts.get('rev'):
2077 if opts.get('rev'):
2070 revs = scmutil.revrange(repo, opts['rev'])
2078 revs = scmutil.revrange(repo, opts['rev'])
2071 elif follow and repo.dirstate.p1() == nullid:
2079 elif follow and repo.dirstate.p1() == nullid:
2072 revs = revset.baseset()
2080 revs = revset.baseset()
2073 elif follow:
2081 elif follow:
2074 revs = repo.revs('reverse(:.)')
2082 revs = repo.revs('reverse(:.)')
2075 else:
2083 else:
2076 revs = revset.spanset(repo)
2084 revs = revset.spanset(repo)
2077 revs.reverse()
2085 revs.reverse()
2078 return revs
2086 return revs
2079
2087
2080 def getgraphlogrevs(repo, pats, opts):
2088 def getgraphlogrevs(repo, pats, opts):
2081 """Return (revs, expr, filematcher) where revs is an iterable of
2089 """Return (revs, expr, filematcher) where revs is an iterable of
2082 revision numbers, expr is a revset string built from log options
2090 revision numbers, expr is a revset string built from log options
2083 and file patterns or None, and used to filter 'revs'. If --stat or
2091 and file patterns or None, and used to filter 'revs'. If --stat or
2084 --patch are not passed filematcher is None. Otherwise it is a
2092 --patch are not passed filematcher is None. Otherwise it is a
2085 callable taking a revision number and returning a match objects
2093 callable taking a revision number and returning a match objects
2086 filtering the files to be detailed when displaying the revision.
2094 filtering the files to be detailed when displaying the revision.
2087 """
2095 """
2088 limit = loglimit(opts)
2096 limit = loglimit(opts)
2089 revs = _logrevs(repo, opts)
2097 revs = _logrevs(repo, opts)
2090 if not revs:
2098 if not revs:
2091 return revset.baseset(), None, None
2099 return revset.baseset(), None, None
2092 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2100 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2093 if opts.get('rev'):
2101 if opts.get('rev'):
2094 # User-specified revs might be unsorted, but don't sort before
2102 # User-specified revs might be unsorted, but don't sort before
2095 # _makelogrevset because it might depend on the order of revs
2103 # _makelogrevset because it might depend on the order of revs
2096 if not (revs.isdescending() or revs.istopo()):
2104 if not (revs.isdescending() or revs.istopo()):
2097 revs.sort(reverse=True)
2105 revs.sort(reverse=True)
2098 if expr:
2106 if expr:
2099 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2107 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2100 revs = matcher(repo, revs)
2108 revs = matcher(repo, revs)
2101 if limit is not None:
2109 if limit is not None:
2102 limitedrevs = []
2110 limitedrevs = []
2103 for idx, rev in enumerate(revs):
2111 for idx, rev in enumerate(revs):
2104 if idx >= limit:
2112 if idx >= limit:
2105 break
2113 break
2106 limitedrevs.append(rev)
2114 limitedrevs.append(rev)
2107 revs = revset.baseset(limitedrevs)
2115 revs = revset.baseset(limitedrevs)
2108
2116
2109 return revs, expr, filematcher
2117 return revs, expr, filematcher
2110
2118
2111 def getlogrevs(repo, pats, opts):
2119 def getlogrevs(repo, pats, opts):
2112 """Return (revs, expr, filematcher) where revs is an iterable of
2120 """Return (revs, expr, filematcher) where revs is an iterable of
2113 revision numbers, expr is a revset string built from log options
2121 revision numbers, expr is a revset string built from log options
2114 and file patterns or None, and used to filter 'revs'. If --stat or
2122 and file patterns or None, and used to filter 'revs'. If --stat or
2115 --patch are not passed filematcher is None. Otherwise it is a
2123 --patch are not passed filematcher is None. Otherwise it is a
2116 callable taking a revision number and returning a match objects
2124 callable taking a revision number and returning a match objects
2117 filtering the files to be detailed when displaying the revision.
2125 filtering the files to be detailed when displaying the revision.
2118 """
2126 """
2119 limit = loglimit(opts)
2127 limit = loglimit(opts)
2120 revs = _logrevs(repo, opts)
2128 revs = _logrevs(repo, opts)
2121 if not revs:
2129 if not revs:
2122 return revset.baseset([]), None, None
2130 return revset.baseset([]), None, None
2123 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2131 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2124 if expr:
2132 if expr:
2125 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2133 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2126 revs = matcher(repo, revs)
2134 revs = matcher(repo, revs)
2127 if limit is not None:
2135 if limit is not None:
2128 limitedrevs = []
2136 limitedrevs = []
2129 for idx, r in enumerate(revs):
2137 for idx, r in enumerate(revs):
2130 if limit <= idx:
2138 if limit <= idx:
2131 break
2139 break
2132 limitedrevs.append(r)
2140 limitedrevs.append(r)
2133 revs = revset.baseset(limitedrevs)
2141 revs = revset.baseset(limitedrevs)
2134
2142
2135 return revs, expr, filematcher
2143 return revs, expr, filematcher
2136
2144
2137 def _graphnodeformatter(ui, displayer):
2145 def _graphnodeformatter(ui, displayer):
2138 spec = ui.config('ui', 'graphnodetemplate')
2146 spec = ui.config('ui', 'graphnodetemplate')
2139 if not spec:
2147 if not spec:
2140 return templatekw.showgraphnode # fast path for "{graphnode}"
2148 return templatekw.showgraphnode # fast path for "{graphnode}"
2141
2149
2142 templ = formatter.gettemplater(ui, 'graphnode', spec)
2150 templ = formatter.gettemplater(ui, 'graphnode', spec)
2143 cache = {}
2151 cache = {}
2144 if isinstance(displayer, changeset_templater):
2152 if isinstance(displayer, changeset_templater):
2145 cache = displayer.cache # reuse cache of slow templates
2153 cache = displayer.cache # reuse cache of slow templates
2146 props = templatekw.keywords.copy()
2154 props = templatekw.keywords.copy()
2147 props['templ'] = templ
2155 props['templ'] = templ
2148 props['cache'] = cache
2156 props['cache'] = cache
2149 def formatnode(repo, ctx):
2157 def formatnode(repo, ctx):
2150 props['ctx'] = ctx
2158 props['ctx'] = ctx
2151 props['repo'] = repo
2159 props['repo'] = repo
2152 props['ui'] = repo.ui
2160 props['ui'] = repo.ui
2153 props['revcache'] = {}
2161 props['revcache'] = {}
2154 return templater.stringify(templ('graphnode', **props))
2162 return templater.stringify(templ('graphnode', **props))
2155 return formatnode
2163 return formatnode
2156
2164
2157 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2165 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2158 filematcher=None):
2166 filematcher=None):
2159 formatnode = _graphnodeformatter(ui, displayer)
2167 formatnode = _graphnodeformatter(ui, displayer)
2160 state = graphmod.asciistate()
2168 state = graphmod.asciistate()
2161 styles = state['styles']
2169 styles = state['styles']
2162
2170
2163 # only set graph styling if HGPLAIN is not set.
2171 # only set graph styling if HGPLAIN is not set.
2164 if ui.plain('graph'):
2172 if ui.plain('graph'):
2165 # set all edge styles to |, the default pre-3.8 behaviour
2173 # set all edge styles to |, the default pre-3.8 behaviour
2166 styles.update(dict.fromkeys(styles, '|'))
2174 styles.update(dict.fromkeys(styles, '|'))
2167 else:
2175 else:
2168 edgetypes = {
2176 edgetypes = {
2169 'parent': graphmod.PARENT,
2177 'parent': graphmod.PARENT,
2170 'grandparent': graphmod.GRANDPARENT,
2178 'grandparent': graphmod.GRANDPARENT,
2171 'missing': graphmod.MISSINGPARENT
2179 'missing': graphmod.MISSINGPARENT
2172 }
2180 }
2173 for name, key in edgetypes.items():
2181 for name, key in edgetypes.items():
2174 # experimental config: experimental.graphstyle.*
2182 # experimental config: experimental.graphstyle.*
2175 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2183 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2176 styles[key])
2184 styles[key])
2177 if not styles[key]:
2185 if not styles[key]:
2178 styles[key] = None
2186 styles[key] = None
2179
2187
2180 # experimental config: experimental.graphshorten
2188 # experimental config: experimental.graphshorten
2181 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2189 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2182
2190
2183 for rev, type, ctx, parents in dag:
2191 for rev, type, ctx, parents in dag:
2184 char = formatnode(repo, ctx)
2192 char = formatnode(repo, ctx)
2185 copies = None
2193 copies = None
2186 if getrenamed and ctx.rev():
2194 if getrenamed and ctx.rev():
2187 copies = []
2195 copies = []
2188 for fn in ctx.files():
2196 for fn in ctx.files():
2189 rename = getrenamed(fn, ctx.rev())
2197 rename = getrenamed(fn, ctx.rev())
2190 if rename:
2198 if rename:
2191 copies.append((fn, rename[0]))
2199 copies.append((fn, rename[0]))
2192 revmatchfn = None
2200 revmatchfn = None
2193 if filematcher is not None:
2201 if filematcher is not None:
2194 revmatchfn = filematcher(ctx.rev())
2202 revmatchfn = filematcher(ctx.rev())
2195 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2203 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2196 lines = displayer.hunk.pop(rev).split('\n')
2204 lines = displayer.hunk.pop(rev).split('\n')
2197 if not lines[-1]:
2205 if not lines[-1]:
2198 del lines[-1]
2206 del lines[-1]
2199 displayer.flush(ctx)
2207 displayer.flush(ctx)
2200 edges = edgefn(type, char, lines, state, rev, parents)
2208 edges = edgefn(type, char, lines, state, rev, parents)
2201 for type, char, lines, coldata in edges:
2209 for type, char, lines, coldata in edges:
2202 graphmod.ascii(ui, state, type, char, lines, coldata)
2210 graphmod.ascii(ui, state, type, char, lines, coldata)
2203 displayer.close()
2211 displayer.close()
2204
2212
2205 def graphlog(ui, repo, *pats, **opts):
2213 def graphlog(ui, repo, *pats, **opts):
2206 # Parameters are identical to log command ones
2214 # Parameters are identical to log command ones
2207 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2215 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2208 revdag = graphmod.dagwalker(repo, revs)
2216 revdag = graphmod.dagwalker(repo, revs)
2209
2217
2210 getrenamed = None
2218 getrenamed = None
2211 if opts.get('copies'):
2219 if opts.get('copies'):
2212 endrev = None
2220 endrev = None
2213 if opts.get('rev'):
2221 if opts.get('rev'):
2214 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2222 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2215 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2223 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2216 displayer = show_changeset(ui, repo, opts, buffered=True)
2224 displayer = show_changeset(ui, repo, opts, buffered=True)
2217 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2225 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2218 filematcher)
2226 filematcher)
2219
2227
2220 def checkunsupportedgraphflags(pats, opts):
2228 def checkunsupportedgraphflags(pats, opts):
2221 for op in ["newest_first"]:
2229 for op in ["newest_first"]:
2222 if op in opts and opts[op]:
2230 if op in opts and opts[op]:
2223 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2231 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2224 % op.replace("_", "-"))
2232 % op.replace("_", "-"))
2225
2233
2226 def graphrevs(repo, nodes, opts):
2234 def graphrevs(repo, nodes, opts):
2227 limit = loglimit(opts)
2235 limit = loglimit(opts)
2228 nodes.reverse()
2236 nodes.reverse()
2229 if limit is not None:
2237 if limit is not None:
2230 nodes = nodes[:limit]
2238 nodes = nodes[:limit]
2231 return graphmod.nodes(repo, nodes)
2239 return graphmod.nodes(repo, nodes)
2232
2240
2233 def add(ui, repo, match, prefix, explicitonly, **opts):
2241 def add(ui, repo, match, prefix, explicitonly, **opts):
2234 join = lambda f: os.path.join(prefix, f)
2242 join = lambda f: os.path.join(prefix, f)
2235 bad = []
2243 bad = []
2236
2244
2237 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2245 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2238 names = []
2246 names = []
2239 wctx = repo[None]
2247 wctx = repo[None]
2240 cca = None
2248 cca = None
2241 abort, warn = scmutil.checkportabilityalert(ui)
2249 abort, warn = scmutil.checkportabilityalert(ui)
2242 if abort or warn:
2250 if abort or warn:
2243 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2251 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2244
2252
2245 badmatch = matchmod.badmatch(match, badfn)
2253 badmatch = matchmod.badmatch(match, badfn)
2246 dirstate = repo.dirstate
2254 dirstate = repo.dirstate
2247 # We don't want to just call wctx.walk here, since it would return a lot of
2255 # We don't want to just call wctx.walk here, since it would return a lot of
2248 # clean files, which we aren't interested in and takes time.
2256 # clean files, which we aren't interested in and takes time.
2249 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2257 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2250 True, False, full=False)):
2258 True, False, full=False)):
2251 exact = match.exact(f)
2259 exact = match.exact(f)
2252 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2260 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2253 if cca:
2261 if cca:
2254 cca(f)
2262 cca(f)
2255 names.append(f)
2263 names.append(f)
2256 if ui.verbose or not exact:
2264 if ui.verbose or not exact:
2257 ui.status(_('adding %s\n') % match.rel(f))
2265 ui.status(_('adding %s\n') % match.rel(f))
2258
2266
2259 for subpath in sorted(wctx.substate):
2267 for subpath in sorted(wctx.substate):
2260 sub = wctx.sub(subpath)
2268 sub = wctx.sub(subpath)
2261 try:
2269 try:
2262 submatch = matchmod.subdirmatcher(subpath, match)
2270 submatch = matchmod.subdirmatcher(subpath, match)
2263 if opts.get('subrepos'):
2271 if opts.get('subrepos'):
2264 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2272 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2265 else:
2273 else:
2266 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2274 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2267 except error.LookupError:
2275 except error.LookupError:
2268 ui.status(_("skipping missing subrepository: %s\n")
2276 ui.status(_("skipping missing subrepository: %s\n")
2269 % join(subpath))
2277 % join(subpath))
2270
2278
2271 if not opts.get('dry_run'):
2279 if not opts.get('dry_run'):
2272 rejected = wctx.add(names, prefix)
2280 rejected = wctx.add(names, prefix)
2273 bad.extend(f for f in rejected if f in match.files())
2281 bad.extend(f for f in rejected if f in match.files())
2274 return bad
2282 return bad
2275
2283
2276 def forget(ui, repo, match, prefix, explicitonly):
2284 def forget(ui, repo, match, prefix, explicitonly):
2277 join = lambda f: os.path.join(prefix, f)
2285 join = lambda f: os.path.join(prefix, f)
2278 bad = []
2286 bad = []
2279 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2287 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2280 wctx = repo[None]
2288 wctx = repo[None]
2281 forgot = []
2289 forgot = []
2282
2290
2283 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2291 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2284 forget = sorted(s[0] + s[1] + s[3] + s[6])
2292 forget = sorted(s[0] + s[1] + s[3] + s[6])
2285 if explicitonly:
2293 if explicitonly:
2286 forget = [f for f in forget if match.exact(f)]
2294 forget = [f for f in forget if match.exact(f)]
2287
2295
2288 for subpath in sorted(wctx.substate):
2296 for subpath in sorted(wctx.substate):
2289 sub = wctx.sub(subpath)
2297 sub = wctx.sub(subpath)
2290 try:
2298 try:
2291 submatch = matchmod.subdirmatcher(subpath, match)
2299 submatch = matchmod.subdirmatcher(subpath, match)
2292 subbad, subforgot = sub.forget(submatch, prefix)
2300 subbad, subforgot = sub.forget(submatch, prefix)
2293 bad.extend([subpath + '/' + f for f in subbad])
2301 bad.extend([subpath + '/' + f for f in subbad])
2294 forgot.extend([subpath + '/' + f for f in subforgot])
2302 forgot.extend([subpath + '/' + f for f in subforgot])
2295 except error.LookupError:
2303 except error.LookupError:
2296 ui.status(_("skipping missing subrepository: %s\n")
2304 ui.status(_("skipping missing subrepository: %s\n")
2297 % join(subpath))
2305 % join(subpath))
2298
2306
2299 if not explicitonly:
2307 if not explicitonly:
2300 for f in match.files():
2308 for f in match.files():
2301 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2309 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2302 if f not in forgot:
2310 if f not in forgot:
2303 if repo.wvfs.exists(f):
2311 if repo.wvfs.exists(f):
2304 # Don't complain if the exact case match wasn't given.
2312 # Don't complain if the exact case match wasn't given.
2305 # But don't do this until after checking 'forgot', so
2313 # But don't do this until after checking 'forgot', so
2306 # that subrepo files aren't normalized, and this op is
2314 # that subrepo files aren't normalized, and this op is
2307 # purely from data cached by the status walk above.
2315 # purely from data cached by the status walk above.
2308 if repo.dirstate.normalize(f) in repo.dirstate:
2316 if repo.dirstate.normalize(f) in repo.dirstate:
2309 continue
2317 continue
2310 ui.warn(_('not removing %s: '
2318 ui.warn(_('not removing %s: '
2311 'file is already untracked\n')
2319 'file is already untracked\n')
2312 % match.rel(f))
2320 % match.rel(f))
2313 bad.append(f)
2321 bad.append(f)
2314
2322
2315 for f in forget:
2323 for f in forget:
2316 if ui.verbose or not match.exact(f):
2324 if ui.verbose or not match.exact(f):
2317 ui.status(_('removing %s\n') % match.rel(f))
2325 ui.status(_('removing %s\n') % match.rel(f))
2318
2326
2319 rejected = wctx.forget(forget, prefix)
2327 rejected = wctx.forget(forget, prefix)
2320 bad.extend(f for f in rejected if f in match.files())
2328 bad.extend(f for f in rejected if f in match.files())
2321 forgot.extend(f for f in forget if f not in rejected)
2329 forgot.extend(f for f in forget if f not in rejected)
2322 return bad, forgot
2330 return bad, forgot
2323
2331
2324 def files(ui, ctx, m, fm, fmt, subrepos):
2332 def files(ui, ctx, m, fm, fmt, subrepos):
2325 rev = ctx.rev()
2333 rev = ctx.rev()
2326 ret = 1
2334 ret = 1
2327 ds = ctx.repo().dirstate
2335 ds = ctx.repo().dirstate
2328
2336
2329 for f in ctx.matches(m):
2337 for f in ctx.matches(m):
2330 if rev is None and ds[f] == 'r':
2338 if rev is None and ds[f] == 'r':
2331 continue
2339 continue
2332 fm.startitem()
2340 fm.startitem()
2333 if ui.verbose:
2341 if ui.verbose:
2334 fc = ctx[f]
2342 fc = ctx[f]
2335 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2343 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2336 fm.data(abspath=f)
2344 fm.data(abspath=f)
2337 fm.write('path', fmt, m.rel(f))
2345 fm.write('path', fmt, m.rel(f))
2338 ret = 0
2346 ret = 0
2339
2347
2340 for subpath in sorted(ctx.substate):
2348 for subpath in sorted(ctx.substate):
2341 submatch = matchmod.subdirmatcher(subpath, m)
2349 submatch = matchmod.subdirmatcher(subpath, m)
2342 if (subrepos or m.exact(subpath) or any(submatch.files())):
2350 if (subrepos or m.exact(subpath) or any(submatch.files())):
2343 sub = ctx.sub(subpath)
2351 sub = ctx.sub(subpath)
2344 try:
2352 try:
2345 recurse = m.exact(subpath) or subrepos
2353 recurse = m.exact(subpath) or subrepos
2346 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2354 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2347 ret = 0
2355 ret = 0
2348 except error.LookupError:
2356 except error.LookupError:
2349 ui.status(_("skipping missing subrepository: %s\n")
2357 ui.status(_("skipping missing subrepository: %s\n")
2350 % m.abs(subpath))
2358 % m.abs(subpath))
2351
2359
2352 return ret
2360 return ret
2353
2361
2354 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2362 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2355 join = lambda f: os.path.join(prefix, f)
2363 join = lambda f: os.path.join(prefix, f)
2356 ret = 0
2364 ret = 0
2357 s = repo.status(match=m, clean=True)
2365 s = repo.status(match=m, clean=True)
2358 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2366 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2359
2367
2360 wctx = repo[None]
2368 wctx = repo[None]
2361
2369
2362 if warnings is None:
2370 if warnings is None:
2363 warnings = []
2371 warnings = []
2364 warn = True
2372 warn = True
2365 else:
2373 else:
2366 warn = False
2374 warn = False
2367
2375
2368 subs = sorted(wctx.substate)
2376 subs = sorted(wctx.substate)
2369 total = len(subs)
2377 total = len(subs)
2370 count = 0
2378 count = 0
2371 for subpath in subs:
2379 for subpath in subs:
2372 count += 1
2380 count += 1
2373 submatch = matchmod.subdirmatcher(subpath, m)
2381 submatch = matchmod.subdirmatcher(subpath, m)
2374 if subrepos or m.exact(subpath) or any(submatch.files()):
2382 if subrepos or m.exact(subpath) or any(submatch.files()):
2375 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2383 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2376 sub = wctx.sub(subpath)
2384 sub = wctx.sub(subpath)
2377 try:
2385 try:
2378 if sub.removefiles(submatch, prefix, after, force, subrepos,
2386 if sub.removefiles(submatch, prefix, after, force, subrepos,
2379 warnings):
2387 warnings):
2380 ret = 1
2388 ret = 1
2381 except error.LookupError:
2389 except error.LookupError:
2382 warnings.append(_("skipping missing subrepository: %s\n")
2390 warnings.append(_("skipping missing subrepository: %s\n")
2383 % join(subpath))
2391 % join(subpath))
2384 ui.progress(_('searching'), None)
2392 ui.progress(_('searching'), None)
2385
2393
2386 # warn about failure to delete explicit files/dirs
2394 # warn about failure to delete explicit files/dirs
2387 deleteddirs = util.dirs(deleted)
2395 deleteddirs = util.dirs(deleted)
2388 files = m.files()
2396 files = m.files()
2389 total = len(files)
2397 total = len(files)
2390 count = 0
2398 count = 0
2391 for f in files:
2399 for f in files:
2392 def insubrepo():
2400 def insubrepo():
2393 for subpath in wctx.substate:
2401 for subpath in wctx.substate:
2394 if f.startswith(subpath + '/'):
2402 if f.startswith(subpath + '/'):
2395 return True
2403 return True
2396 return False
2404 return False
2397
2405
2398 count += 1
2406 count += 1
2399 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2407 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2400 isdir = f in deleteddirs or wctx.hasdir(f)
2408 isdir = f in deleteddirs or wctx.hasdir(f)
2401 if (f in repo.dirstate or isdir or f == '.'
2409 if (f in repo.dirstate or isdir or f == '.'
2402 or insubrepo() or f in subs):
2410 or insubrepo() or f in subs):
2403 continue
2411 continue
2404
2412
2405 if repo.wvfs.exists(f):
2413 if repo.wvfs.exists(f):
2406 if repo.wvfs.isdir(f):
2414 if repo.wvfs.isdir(f):
2407 warnings.append(_('not removing %s: no tracked files\n')
2415 warnings.append(_('not removing %s: no tracked files\n')
2408 % m.rel(f))
2416 % m.rel(f))
2409 else:
2417 else:
2410 warnings.append(_('not removing %s: file is untracked\n')
2418 warnings.append(_('not removing %s: file is untracked\n')
2411 % m.rel(f))
2419 % m.rel(f))
2412 # missing files will generate a warning elsewhere
2420 # missing files will generate a warning elsewhere
2413 ret = 1
2421 ret = 1
2414 ui.progress(_('deleting'), None)
2422 ui.progress(_('deleting'), None)
2415
2423
2416 if force:
2424 if force:
2417 list = modified + deleted + clean + added
2425 list = modified + deleted + clean + added
2418 elif after:
2426 elif after:
2419 list = deleted
2427 list = deleted
2420 remaining = modified + added + clean
2428 remaining = modified + added + clean
2421 total = len(remaining)
2429 total = len(remaining)
2422 count = 0
2430 count = 0
2423 for f in remaining:
2431 for f in remaining:
2424 count += 1
2432 count += 1
2425 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2433 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2426 warnings.append(_('not removing %s: file still exists\n')
2434 warnings.append(_('not removing %s: file still exists\n')
2427 % m.rel(f))
2435 % m.rel(f))
2428 ret = 1
2436 ret = 1
2429 ui.progress(_('skipping'), None)
2437 ui.progress(_('skipping'), None)
2430 else:
2438 else:
2431 list = deleted + clean
2439 list = deleted + clean
2432 total = len(modified) + len(added)
2440 total = len(modified) + len(added)
2433 count = 0
2441 count = 0
2434 for f in modified:
2442 for f in modified:
2435 count += 1
2443 count += 1
2436 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2444 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2437 warnings.append(_('not removing %s: file is modified (use -f'
2445 warnings.append(_('not removing %s: file is modified (use -f'
2438 ' to force removal)\n') % m.rel(f))
2446 ' to force removal)\n') % m.rel(f))
2439 ret = 1
2447 ret = 1
2440 for f in added:
2448 for f in added:
2441 count += 1
2449 count += 1
2442 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2450 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2443 warnings.append(_("not removing %s: file has been marked for add"
2451 warnings.append(_("not removing %s: file has been marked for add"
2444 " (use 'hg forget' to undo add)\n") % m.rel(f))
2452 " (use 'hg forget' to undo add)\n") % m.rel(f))
2445 ret = 1
2453 ret = 1
2446 ui.progress(_('skipping'), None)
2454 ui.progress(_('skipping'), None)
2447
2455
2448 list = sorted(list)
2456 list = sorted(list)
2449 total = len(list)
2457 total = len(list)
2450 count = 0
2458 count = 0
2451 for f in list:
2459 for f in list:
2452 count += 1
2460 count += 1
2453 if ui.verbose or not m.exact(f):
2461 if ui.verbose or not m.exact(f):
2454 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2462 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2455 ui.status(_('removing %s\n') % m.rel(f))
2463 ui.status(_('removing %s\n') % m.rel(f))
2456 ui.progress(_('deleting'), None)
2464 ui.progress(_('deleting'), None)
2457
2465
2458 with repo.wlock():
2466 with repo.wlock():
2459 if not after:
2467 if not after:
2460 for f in list:
2468 for f in list:
2461 if f in added:
2469 if f in added:
2462 continue # we never unlink added files on remove
2470 continue # we never unlink added files on remove
2463 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2471 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2464 repo[None].forget(list)
2472 repo[None].forget(list)
2465
2473
2466 if warn:
2474 if warn:
2467 for warning in warnings:
2475 for warning in warnings:
2468 ui.warn(warning)
2476 ui.warn(warning)
2469
2477
2470 return ret
2478 return ret
2471
2479
2472 def cat(ui, repo, ctx, matcher, prefix, **opts):
2480 def cat(ui, repo, ctx, matcher, prefix, **opts):
2473 err = 1
2481 err = 1
2474
2482
2475 def write(path):
2483 def write(path):
2476 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2484 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2477 pathname=os.path.join(prefix, path))
2485 pathname=os.path.join(prefix, path))
2478 data = ctx[path].data()
2486 data = ctx[path].data()
2479 if opts.get('decode'):
2487 if opts.get('decode'):
2480 data = repo.wwritedata(path, data)
2488 data = repo.wwritedata(path, data)
2481 fp.write(data)
2489 fp.write(data)
2482 fp.close()
2490 fp.close()
2483
2491
2484 # Automation often uses hg cat on single files, so special case it
2492 # Automation often uses hg cat on single files, so special case it
2485 # for performance to avoid the cost of parsing the manifest.
2493 # for performance to avoid the cost of parsing the manifest.
2486 if len(matcher.files()) == 1 and not matcher.anypats():
2494 if len(matcher.files()) == 1 and not matcher.anypats():
2487 file = matcher.files()[0]
2495 file = matcher.files()[0]
2488 mfl = repo.manifestlog
2496 mfl = repo.manifestlog
2489 mfnode = ctx.manifestnode()
2497 mfnode = ctx.manifestnode()
2490 try:
2498 try:
2491 if mfnode and mfl[mfnode].find(file)[0]:
2499 if mfnode and mfl[mfnode].find(file)[0]:
2492 write(file)
2500 write(file)
2493 return 0
2501 return 0
2494 except KeyError:
2502 except KeyError:
2495 pass
2503 pass
2496
2504
2497 for abs in ctx.walk(matcher):
2505 for abs in ctx.walk(matcher):
2498 write(abs)
2506 write(abs)
2499 err = 0
2507 err = 0
2500
2508
2501 for subpath in sorted(ctx.substate):
2509 for subpath in sorted(ctx.substate):
2502 sub = ctx.sub(subpath)
2510 sub = ctx.sub(subpath)
2503 try:
2511 try:
2504 submatch = matchmod.subdirmatcher(subpath, matcher)
2512 submatch = matchmod.subdirmatcher(subpath, matcher)
2505
2513
2506 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2514 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2507 **opts):
2515 **opts):
2508 err = 0
2516 err = 0
2509 except error.RepoLookupError:
2517 except error.RepoLookupError:
2510 ui.status(_("skipping missing subrepository: %s\n")
2518 ui.status(_("skipping missing subrepository: %s\n")
2511 % os.path.join(prefix, subpath))
2519 % os.path.join(prefix, subpath))
2512
2520
2513 return err
2521 return err
2514
2522
2515 def commit(ui, repo, commitfunc, pats, opts):
2523 def commit(ui, repo, commitfunc, pats, opts):
2516 '''commit the specified files or all outstanding changes'''
2524 '''commit the specified files or all outstanding changes'''
2517 date = opts.get('date')
2525 date = opts.get('date')
2518 if date:
2526 if date:
2519 opts['date'] = util.parsedate(date)
2527 opts['date'] = util.parsedate(date)
2520 message = logmessage(ui, opts)
2528 message = logmessage(ui, opts)
2521 matcher = scmutil.match(repo[None], pats, opts)
2529 matcher = scmutil.match(repo[None], pats, opts)
2522
2530
2523 # extract addremove carefully -- this function can be called from a command
2531 # extract addremove carefully -- this function can be called from a command
2524 # that doesn't support addremove
2532 # that doesn't support addremove
2525 if opts.get('addremove'):
2533 if opts.get('addremove'):
2526 if scmutil.addremove(repo, matcher, "", opts) != 0:
2534 if scmutil.addremove(repo, matcher, "", opts) != 0:
2527 raise error.Abort(
2535 raise error.Abort(
2528 _("failed to mark all new/missing files as added/removed"))
2536 _("failed to mark all new/missing files as added/removed"))
2529
2537
2530 return commitfunc(ui, repo, message, matcher, opts)
2538 return commitfunc(ui, repo, message, matcher, opts)
2531
2539
2532 def samefile(f, ctx1, ctx2):
2540 def samefile(f, ctx1, ctx2):
2533 if f in ctx1.manifest():
2541 if f in ctx1.manifest():
2534 a = ctx1.filectx(f)
2542 a = ctx1.filectx(f)
2535 if f in ctx2.manifest():
2543 if f in ctx2.manifest():
2536 b = ctx2.filectx(f)
2544 b = ctx2.filectx(f)
2537 return (not a.cmp(b)
2545 return (not a.cmp(b)
2538 and a.flags() == b.flags())
2546 and a.flags() == b.flags())
2539 else:
2547 else:
2540 return False
2548 return False
2541 else:
2549 else:
2542 return f not in ctx2.manifest()
2550 return f not in ctx2.manifest()
2543
2551
2544 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2552 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2545 # avoid cycle context -> subrepo -> cmdutil
2553 # avoid cycle context -> subrepo -> cmdutil
2546 from . import context
2554 from . import context
2547
2555
2548 # amend will reuse the existing user if not specified, but the obsolete
2556 # amend will reuse the existing user if not specified, but the obsolete
2549 # marker creation requires that the current user's name is specified.
2557 # marker creation requires that the current user's name is specified.
2550 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2558 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2551 ui.username() # raise exception if username not set
2559 ui.username() # raise exception if username not set
2552
2560
2553 ui.note(_('amending changeset %s\n') % old)
2561 ui.note(_('amending changeset %s\n') % old)
2554 base = old.p1()
2562 base = old.p1()
2555 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2563 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2556
2564
2557 wlock = lock = newid = None
2565 wlock = lock = newid = None
2558 try:
2566 try:
2559 wlock = repo.wlock()
2567 wlock = repo.wlock()
2560 lock = repo.lock()
2568 lock = repo.lock()
2561 with repo.transaction('amend') as tr:
2569 with repo.transaction('amend') as tr:
2562 # See if we got a message from -m or -l, if not, open the editor
2570 # See if we got a message from -m or -l, if not, open the editor
2563 # with the message of the changeset to amend
2571 # with the message of the changeset to amend
2564 message = logmessage(ui, opts)
2572 message = logmessage(ui, opts)
2565 # ensure logfile does not conflict with later enforcement of the
2573 # ensure logfile does not conflict with later enforcement of the
2566 # message. potential logfile content has been processed by
2574 # message. potential logfile content has been processed by
2567 # `logmessage` anyway.
2575 # `logmessage` anyway.
2568 opts.pop('logfile')
2576 opts.pop('logfile')
2569 # First, do a regular commit to record all changes in the working
2577 # First, do a regular commit to record all changes in the working
2570 # directory (if there are any)
2578 # directory (if there are any)
2571 ui.callhooks = False
2579 ui.callhooks = False
2572 activebookmark = repo._bookmarks.active
2580 activebookmark = repo._bookmarks.active
2573 try:
2581 try:
2574 repo._bookmarks.active = None
2582 repo._bookmarks.active = None
2575 opts['message'] = 'temporary amend commit for %s' % old
2583 opts['message'] = 'temporary amend commit for %s' % old
2576 node = commit(ui, repo, commitfunc, pats, opts)
2584 node = commit(ui, repo, commitfunc, pats, opts)
2577 finally:
2585 finally:
2578 repo._bookmarks.active = activebookmark
2586 repo._bookmarks.active = activebookmark
2579 repo._bookmarks.recordchange(tr)
2587 repo._bookmarks.recordchange(tr)
2580 ui.callhooks = True
2588 ui.callhooks = True
2581 ctx = repo[node]
2589 ctx = repo[node]
2582
2590
2583 # Participating changesets:
2591 # Participating changesets:
2584 #
2592 #
2585 # node/ctx o - new (intermediate) commit that contains changes
2593 # node/ctx o - new (intermediate) commit that contains changes
2586 # | from working dir to go into amending commit
2594 # | from working dir to go into amending commit
2587 # | (or a workingctx if there were no changes)
2595 # | (or a workingctx if there were no changes)
2588 # |
2596 # |
2589 # old o - changeset to amend
2597 # old o - changeset to amend
2590 # |
2598 # |
2591 # base o - parent of amending changeset
2599 # base o - parent of amending changeset
2592
2600
2593 # Update extra dict from amended commit (e.g. to preserve graft
2601 # Update extra dict from amended commit (e.g. to preserve graft
2594 # source)
2602 # source)
2595 extra.update(old.extra())
2603 extra.update(old.extra())
2596
2604
2597 # Also update it from the intermediate commit or from the wctx
2605 # Also update it from the intermediate commit or from the wctx
2598 extra.update(ctx.extra())
2606 extra.update(ctx.extra())
2599
2607
2600 if len(old.parents()) > 1:
2608 if len(old.parents()) > 1:
2601 # ctx.files() isn't reliable for merges, so fall back to the
2609 # ctx.files() isn't reliable for merges, so fall back to the
2602 # slower repo.status() method
2610 # slower repo.status() method
2603 files = set([fn for st in repo.status(base, old)[:3]
2611 files = set([fn for st in repo.status(base, old)[:3]
2604 for fn in st])
2612 for fn in st])
2605 else:
2613 else:
2606 files = set(old.files())
2614 files = set(old.files())
2607
2615
2608 # Second, we use either the commit we just did, or if there were no
2616 # Second, we use either the commit we just did, or if there were no
2609 # changes the parent of the working directory as the version of the
2617 # changes the parent of the working directory as the version of the
2610 # files in the final amend commit
2618 # files in the final amend commit
2611 if node:
2619 if node:
2612 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2620 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2613
2621
2614 user = ctx.user()
2622 user = ctx.user()
2615 date = ctx.date()
2623 date = ctx.date()
2616 # Recompute copies (avoid recording a -> b -> a)
2624 # Recompute copies (avoid recording a -> b -> a)
2617 copied = copies.pathcopies(base, ctx)
2625 copied = copies.pathcopies(base, ctx)
2618 if old.p2:
2626 if old.p2:
2619 copied.update(copies.pathcopies(old.p2(), ctx))
2627 copied.update(copies.pathcopies(old.p2(), ctx))
2620
2628
2621 # Prune files which were reverted by the updates: if old
2629 # Prune files which were reverted by the updates: if old
2622 # introduced file X and our intermediate commit, node,
2630 # introduced file X and our intermediate commit, node,
2623 # renamed that file, then those two files are the same and
2631 # renamed that file, then those two files are the same and
2624 # we can discard X from our list of files. Likewise if X
2632 # we can discard X from our list of files. Likewise if X
2625 # was deleted, it's no longer relevant
2633 # was deleted, it's no longer relevant
2626 files.update(ctx.files())
2634 files.update(ctx.files())
2627 files = [f for f in files if not samefile(f, ctx, base)]
2635 files = [f for f in files if not samefile(f, ctx, base)]
2628
2636
2629 def filectxfn(repo, ctx_, path):
2637 def filectxfn(repo, ctx_, path):
2630 try:
2638 try:
2631 fctx = ctx[path]
2639 fctx = ctx[path]
2632 flags = fctx.flags()
2640 flags = fctx.flags()
2633 mctx = context.memfilectx(repo,
2641 mctx = context.memfilectx(repo,
2634 fctx.path(), fctx.data(),
2642 fctx.path(), fctx.data(),
2635 islink='l' in flags,
2643 islink='l' in flags,
2636 isexec='x' in flags,
2644 isexec='x' in flags,
2637 copied=copied.get(path))
2645 copied=copied.get(path))
2638 return mctx
2646 return mctx
2639 except KeyError:
2647 except KeyError:
2640 return None
2648 return None
2641 else:
2649 else:
2642 ui.note(_('copying changeset %s to %s\n') % (old, base))
2650 ui.note(_('copying changeset %s to %s\n') % (old, base))
2643
2651
2644 # Use version of files as in the old cset
2652 # Use version of files as in the old cset
2645 def filectxfn(repo, ctx_, path):
2653 def filectxfn(repo, ctx_, path):
2646 try:
2654 try:
2647 return old.filectx(path)
2655 return old.filectx(path)
2648 except KeyError:
2656 except KeyError:
2649 return None
2657 return None
2650
2658
2651 user = opts.get('user') or old.user()
2659 user = opts.get('user') or old.user()
2652 date = opts.get('date') or old.date()
2660 date = opts.get('date') or old.date()
2653 editform = mergeeditform(old, 'commit.amend')
2661 editform = mergeeditform(old, 'commit.amend')
2654 editor = getcommiteditor(editform=editform, **opts)
2662 editor = getcommiteditor(editform=editform, **opts)
2655 if not message:
2663 if not message:
2656 editor = getcommiteditor(edit=True, editform=editform)
2664 editor = getcommiteditor(edit=True, editform=editform)
2657 message = old.description()
2665 message = old.description()
2658
2666
2659 pureextra = extra.copy()
2667 pureextra = extra.copy()
2660 extra['amend_source'] = old.hex()
2668 extra['amend_source'] = old.hex()
2661
2669
2662 new = context.memctx(repo,
2670 new = context.memctx(repo,
2663 parents=[base.node(), old.p2().node()],
2671 parents=[base.node(), old.p2().node()],
2664 text=message,
2672 text=message,
2665 files=files,
2673 files=files,
2666 filectxfn=filectxfn,
2674 filectxfn=filectxfn,
2667 user=user,
2675 user=user,
2668 date=date,
2676 date=date,
2669 extra=extra,
2677 extra=extra,
2670 editor=editor)
2678 editor=editor)
2671
2679
2672 newdesc = changelog.stripdesc(new.description())
2680 newdesc = changelog.stripdesc(new.description())
2673 if ((not node)
2681 if ((not node)
2674 and newdesc == old.description()
2682 and newdesc == old.description()
2675 and user == old.user()
2683 and user == old.user()
2676 and date == old.date()
2684 and date == old.date()
2677 and pureextra == old.extra()):
2685 and pureextra == old.extra()):
2678 # nothing changed. continuing here would create a new node
2686 # nothing changed. continuing here would create a new node
2679 # anyway because of the amend_source noise.
2687 # anyway because of the amend_source noise.
2680 #
2688 #
2681 # This not what we expect from amend.
2689 # This not what we expect from amend.
2682 return old.node()
2690 return old.node()
2683
2691
2684 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2692 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2685 try:
2693 try:
2686 if opts.get('secret'):
2694 if opts.get('secret'):
2687 commitphase = 'secret'
2695 commitphase = 'secret'
2688 else:
2696 else:
2689 commitphase = old.phase()
2697 commitphase = old.phase()
2690 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2698 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2691 newid = repo.commitctx(new)
2699 newid = repo.commitctx(new)
2692 finally:
2700 finally:
2693 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2701 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2694 if newid != old.node():
2702 if newid != old.node():
2695 # Reroute the working copy parent to the new changeset
2703 # Reroute the working copy parent to the new changeset
2696 repo.setparents(newid, nullid)
2704 repo.setparents(newid, nullid)
2697
2705
2698 # Move bookmarks from old parent to amend commit
2706 # Move bookmarks from old parent to amend commit
2699 bms = repo.nodebookmarks(old.node())
2707 bms = repo.nodebookmarks(old.node())
2700 if bms:
2708 if bms:
2701 marks = repo._bookmarks
2709 marks = repo._bookmarks
2702 for bm in bms:
2710 for bm in bms:
2703 ui.debug('moving bookmarks %r from %s to %s\n' %
2711 ui.debug('moving bookmarks %r from %s to %s\n' %
2704 (marks, old.hex(), hex(newid)))
2712 (marks, old.hex(), hex(newid)))
2705 marks[bm] = newid
2713 marks[bm] = newid
2706 marks.recordchange(tr)
2714 marks.recordchange(tr)
2707 #commit the whole amend process
2715 #commit the whole amend process
2708 if createmarkers:
2716 if createmarkers:
2709 # mark the new changeset as successor of the rewritten one
2717 # mark the new changeset as successor of the rewritten one
2710 new = repo[newid]
2718 new = repo[newid]
2711 obs = [(old, (new,))]
2719 obs = [(old, (new,))]
2712 if node:
2720 if node:
2713 obs.append((ctx, ()))
2721 obs.append((ctx, ()))
2714
2722
2715 obsolete.createmarkers(repo, obs)
2723 obsolete.createmarkers(repo, obs)
2716 if not createmarkers and newid != old.node():
2724 if not createmarkers and newid != old.node():
2717 # Strip the intermediate commit (if there was one) and the amended
2725 # Strip the intermediate commit (if there was one) and the amended
2718 # commit
2726 # commit
2719 if node:
2727 if node:
2720 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2728 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2721 ui.note(_('stripping amended changeset %s\n') % old)
2729 ui.note(_('stripping amended changeset %s\n') % old)
2722 repair.strip(ui, repo, old.node(), topic='amend-backup')
2730 repair.strip(ui, repo, old.node(), topic='amend-backup')
2723 finally:
2731 finally:
2724 lockmod.release(lock, wlock)
2732 lockmod.release(lock, wlock)
2725 return newid
2733 return newid
2726
2734
2727 def commiteditor(repo, ctx, subs, editform=''):
2735 def commiteditor(repo, ctx, subs, editform=''):
2728 if ctx.description():
2736 if ctx.description():
2729 return ctx.description()
2737 return ctx.description()
2730 return commitforceeditor(repo, ctx, subs, editform=editform,
2738 return commitforceeditor(repo, ctx, subs, editform=editform,
2731 unchangedmessagedetection=True)
2739 unchangedmessagedetection=True)
2732
2740
2733 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2741 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2734 editform='', unchangedmessagedetection=False):
2742 editform='', unchangedmessagedetection=False):
2735 if not extramsg:
2743 if not extramsg:
2736 extramsg = _("Leave message empty to abort commit.")
2744 extramsg = _("Leave message empty to abort commit.")
2737
2745
2738 forms = [e for e in editform.split('.') if e]
2746 forms = [e for e in editform.split('.') if e]
2739 forms.insert(0, 'changeset')
2747 forms.insert(0, 'changeset')
2740 templatetext = None
2748 templatetext = None
2741 while forms:
2749 while forms:
2742 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2750 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2743 if tmpl:
2751 if tmpl:
2744 templatetext = committext = buildcommittemplate(
2752 templatetext = committext = buildcommittemplate(
2745 repo, ctx, subs, extramsg, tmpl)
2753 repo, ctx, subs, extramsg, tmpl)
2746 break
2754 break
2747 forms.pop()
2755 forms.pop()
2748 else:
2756 else:
2749 committext = buildcommittext(repo, ctx, subs, extramsg)
2757 committext = buildcommittext(repo, ctx, subs, extramsg)
2750
2758
2751 # run editor in the repository root
2759 # run editor in the repository root
2752 olddir = pycompat.getcwd()
2760 olddir = pycompat.getcwd()
2753 os.chdir(repo.root)
2761 os.chdir(repo.root)
2754
2762
2755 # make in-memory changes visible to external process
2763 # make in-memory changes visible to external process
2756 tr = repo.currenttransaction()
2764 tr = repo.currenttransaction()
2757 repo.dirstate.write(tr)
2765 repo.dirstate.write(tr)
2758 pending = tr and tr.writepending() and repo.root
2766 pending = tr and tr.writepending() and repo.root
2759
2767
2760 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2768 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2761 editform=editform, pending=pending)
2769 editform=editform, pending=pending)
2762 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2770 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2763 os.chdir(olddir)
2771 os.chdir(olddir)
2764
2772
2765 if finishdesc:
2773 if finishdesc:
2766 text = finishdesc(text)
2774 text = finishdesc(text)
2767 if not text.strip():
2775 if not text.strip():
2768 raise error.Abort(_("empty commit message"))
2776 raise error.Abort(_("empty commit message"))
2769 if unchangedmessagedetection and editortext == templatetext:
2777 if unchangedmessagedetection and editortext == templatetext:
2770 raise error.Abort(_("commit message unchanged"))
2778 raise error.Abort(_("commit message unchanged"))
2771
2779
2772 return text
2780 return text
2773
2781
2774 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2782 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2775 ui = repo.ui
2783 ui = repo.ui
2776 tmpl, mapfile = gettemplate(ui, tmpl, None)
2784 tmpl, mapfile = gettemplate(ui, tmpl, None)
2777
2785
2778 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2786 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2779
2787
2780 for k, v in repo.ui.configitems('committemplate'):
2788 for k, v in repo.ui.configitems('committemplate'):
2781 if k != 'changeset':
2789 if k != 'changeset':
2782 t.t.cache[k] = v
2790 t.t.cache[k] = v
2783
2791
2784 if not extramsg:
2792 if not extramsg:
2785 extramsg = '' # ensure that extramsg is string
2793 extramsg = '' # ensure that extramsg is string
2786
2794
2787 ui.pushbuffer()
2795 ui.pushbuffer()
2788 t.show(ctx, extramsg=extramsg)
2796 t.show(ctx, extramsg=extramsg)
2789 return ui.popbuffer()
2797 return ui.popbuffer()
2790
2798
2791 def hgprefix(msg):
2799 def hgprefix(msg):
2792 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2800 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2793
2801
2794 def buildcommittext(repo, ctx, subs, extramsg):
2802 def buildcommittext(repo, ctx, subs, extramsg):
2795 edittext = []
2803 edittext = []
2796 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2804 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2797 if ctx.description():
2805 if ctx.description():
2798 edittext.append(ctx.description())
2806 edittext.append(ctx.description())
2799 edittext.append("")
2807 edittext.append("")
2800 edittext.append("") # Empty line between message and comments.
2808 edittext.append("") # Empty line between message and comments.
2801 edittext.append(hgprefix(_("Enter commit message."
2809 edittext.append(hgprefix(_("Enter commit message."
2802 " Lines beginning with 'HG:' are removed.")))
2810 " Lines beginning with 'HG:' are removed.")))
2803 edittext.append(hgprefix(extramsg))
2811 edittext.append(hgprefix(extramsg))
2804 edittext.append("HG: --")
2812 edittext.append("HG: --")
2805 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2813 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2806 if ctx.p2():
2814 if ctx.p2():
2807 edittext.append(hgprefix(_("branch merge")))
2815 edittext.append(hgprefix(_("branch merge")))
2808 if ctx.branch():
2816 if ctx.branch():
2809 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2817 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2810 if bookmarks.isactivewdirparent(repo):
2818 if bookmarks.isactivewdirparent(repo):
2811 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2819 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2812 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2820 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2813 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2821 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2814 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2822 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2815 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2823 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2816 if not added and not modified and not removed:
2824 if not added and not modified and not removed:
2817 edittext.append(hgprefix(_("no files changed")))
2825 edittext.append(hgprefix(_("no files changed")))
2818 edittext.append("")
2826 edittext.append("")
2819
2827
2820 return "\n".join(edittext)
2828 return "\n".join(edittext)
2821
2829
2822 def commitstatus(repo, node, branch, bheads=None, opts=None):
2830 def commitstatus(repo, node, branch, bheads=None, opts=None):
2823 if opts is None:
2831 if opts is None:
2824 opts = {}
2832 opts = {}
2825 ctx = repo[node]
2833 ctx = repo[node]
2826 parents = ctx.parents()
2834 parents = ctx.parents()
2827
2835
2828 if (not opts.get('amend') and bheads and node not in bheads and not
2836 if (not opts.get('amend') and bheads and node not in bheads and not
2829 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2837 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2830 repo.ui.status(_('created new head\n'))
2838 repo.ui.status(_('created new head\n'))
2831 # The message is not printed for initial roots. For the other
2839 # The message is not printed for initial roots. For the other
2832 # changesets, it is printed in the following situations:
2840 # changesets, it is printed in the following situations:
2833 #
2841 #
2834 # Par column: for the 2 parents with ...
2842 # Par column: for the 2 parents with ...
2835 # N: null or no parent
2843 # N: null or no parent
2836 # B: parent is on another named branch
2844 # B: parent is on another named branch
2837 # C: parent is a regular non head changeset
2845 # C: parent is a regular non head changeset
2838 # H: parent was a branch head of the current branch
2846 # H: parent was a branch head of the current branch
2839 # Msg column: whether we print "created new head" message
2847 # Msg column: whether we print "created new head" message
2840 # In the following, it is assumed that there already exists some
2848 # In the following, it is assumed that there already exists some
2841 # initial branch heads of the current branch, otherwise nothing is
2849 # initial branch heads of the current branch, otherwise nothing is
2842 # printed anyway.
2850 # printed anyway.
2843 #
2851 #
2844 # Par Msg Comment
2852 # Par Msg Comment
2845 # N N y additional topo root
2853 # N N y additional topo root
2846 #
2854 #
2847 # B N y additional branch root
2855 # B N y additional branch root
2848 # C N y additional topo head
2856 # C N y additional topo head
2849 # H N n usual case
2857 # H N n usual case
2850 #
2858 #
2851 # B B y weird additional branch root
2859 # B B y weird additional branch root
2852 # C B y branch merge
2860 # C B y branch merge
2853 # H B n merge with named branch
2861 # H B n merge with named branch
2854 #
2862 #
2855 # C C y additional head from merge
2863 # C C y additional head from merge
2856 # C H n merge with a head
2864 # C H n merge with a head
2857 #
2865 #
2858 # H H n head merge: head count decreases
2866 # H H n head merge: head count decreases
2859
2867
2860 if not opts.get('close_branch'):
2868 if not opts.get('close_branch'):
2861 for r in parents:
2869 for r in parents:
2862 if r.closesbranch() and r.branch() == branch:
2870 if r.closesbranch() and r.branch() == branch:
2863 repo.ui.status(_('reopening closed branch head %d\n') % r)
2871 repo.ui.status(_('reopening closed branch head %d\n') % r)
2864
2872
2865 if repo.ui.debugflag:
2873 if repo.ui.debugflag:
2866 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2874 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2867 elif repo.ui.verbose:
2875 elif repo.ui.verbose:
2868 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2876 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2869
2877
2870 def postcommitstatus(repo, pats, opts):
2878 def postcommitstatus(repo, pats, opts):
2871 return repo.status(match=scmutil.match(repo[None], pats, opts))
2879 return repo.status(match=scmutil.match(repo[None], pats, opts))
2872
2880
2873 def revert(ui, repo, ctx, parents, *pats, **opts):
2881 def revert(ui, repo, ctx, parents, *pats, **opts):
2874 parent, p2 = parents
2882 parent, p2 = parents
2875 node = ctx.node()
2883 node = ctx.node()
2876
2884
2877 mf = ctx.manifest()
2885 mf = ctx.manifest()
2878 if node == p2:
2886 if node == p2:
2879 parent = p2
2887 parent = p2
2880
2888
2881 # need all matching names in dirstate and manifest of target rev,
2889 # need all matching names in dirstate and manifest of target rev,
2882 # so have to walk both. do not print errors if files exist in one
2890 # so have to walk both. do not print errors if files exist in one
2883 # but not other. in both cases, filesets should be evaluated against
2891 # but not other. in both cases, filesets should be evaluated against
2884 # workingctx to get consistent result (issue4497). this means 'set:**'
2892 # workingctx to get consistent result (issue4497). this means 'set:**'
2885 # cannot be used to select missing files from target rev.
2893 # cannot be used to select missing files from target rev.
2886
2894
2887 # `names` is a mapping for all elements in working copy and target revision
2895 # `names` is a mapping for all elements in working copy and target revision
2888 # The mapping is in the form:
2896 # The mapping is in the form:
2889 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2897 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2890 names = {}
2898 names = {}
2891
2899
2892 with repo.wlock():
2900 with repo.wlock():
2893 ## filling of the `names` mapping
2901 ## filling of the `names` mapping
2894 # walk dirstate to fill `names`
2902 # walk dirstate to fill `names`
2895
2903
2896 interactive = opts.get('interactive', False)
2904 interactive = opts.get('interactive', False)
2897 wctx = repo[None]
2905 wctx = repo[None]
2898 m = scmutil.match(wctx, pats, opts)
2906 m = scmutil.match(wctx, pats, opts)
2899
2907
2900 # we'll need this later
2908 # we'll need this later
2901 targetsubs = sorted(s for s in wctx.substate if m(s))
2909 targetsubs = sorted(s for s in wctx.substate if m(s))
2902
2910
2903 if not m.always():
2911 if not m.always():
2904 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2912 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2905 names[abs] = m.rel(abs), m.exact(abs)
2913 names[abs] = m.rel(abs), m.exact(abs)
2906
2914
2907 # walk target manifest to fill `names`
2915 # walk target manifest to fill `names`
2908
2916
2909 def badfn(path, msg):
2917 def badfn(path, msg):
2910 if path in names:
2918 if path in names:
2911 return
2919 return
2912 if path in ctx.substate:
2920 if path in ctx.substate:
2913 return
2921 return
2914 path_ = path + '/'
2922 path_ = path + '/'
2915 for f in names:
2923 for f in names:
2916 if f.startswith(path_):
2924 if f.startswith(path_):
2917 return
2925 return
2918 ui.warn("%s: %s\n" % (m.rel(path), msg))
2926 ui.warn("%s: %s\n" % (m.rel(path), msg))
2919
2927
2920 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2928 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2921 if abs not in names:
2929 if abs not in names:
2922 names[abs] = m.rel(abs), m.exact(abs)
2930 names[abs] = m.rel(abs), m.exact(abs)
2923
2931
2924 # Find status of all file in `names`.
2932 # Find status of all file in `names`.
2925 m = scmutil.matchfiles(repo, names)
2933 m = scmutil.matchfiles(repo, names)
2926
2934
2927 changes = repo.status(node1=node, match=m,
2935 changes = repo.status(node1=node, match=m,
2928 unknown=True, ignored=True, clean=True)
2936 unknown=True, ignored=True, clean=True)
2929 else:
2937 else:
2930 changes = repo.status(node1=node, match=m)
2938 changes = repo.status(node1=node, match=m)
2931 for kind in changes:
2939 for kind in changes:
2932 for abs in kind:
2940 for abs in kind:
2933 names[abs] = m.rel(abs), m.exact(abs)
2941 names[abs] = m.rel(abs), m.exact(abs)
2934
2942
2935 m = scmutil.matchfiles(repo, names)
2943 m = scmutil.matchfiles(repo, names)
2936
2944
2937 modified = set(changes.modified)
2945 modified = set(changes.modified)
2938 added = set(changes.added)
2946 added = set(changes.added)
2939 removed = set(changes.removed)
2947 removed = set(changes.removed)
2940 _deleted = set(changes.deleted)
2948 _deleted = set(changes.deleted)
2941 unknown = set(changes.unknown)
2949 unknown = set(changes.unknown)
2942 unknown.update(changes.ignored)
2950 unknown.update(changes.ignored)
2943 clean = set(changes.clean)
2951 clean = set(changes.clean)
2944 modadded = set()
2952 modadded = set()
2945
2953
2946 # split between files known in target manifest and the others
2954 # split between files known in target manifest and the others
2947 smf = set(mf)
2955 smf = set(mf)
2948
2956
2949 # determine the exact nature of the deleted changesets
2957 # determine the exact nature of the deleted changesets
2950 deladded = _deleted - smf
2958 deladded = _deleted - smf
2951 deleted = _deleted - deladded
2959 deleted = _deleted - deladded
2952
2960
2953 # We need to account for the state of the file in the dirstate,
2961 # We need to account for the state of the file in the dirstate,
2954 # even when we revert against something else than parent. This will
2962 # even when we revert against something else than parent. This will
2955 # slightly alter the behavior of revert (doing back up or not, delete
2963 # slightly alter the behavior of revert (doing back up or not, delete
2956 # or just forget etc).
2964 # or just forget etc).
2957 if parent == node:
2965 if parent == node:
2958 dsmodified = modified
2966 dsmodified = modified
2959 dsadded = added
2967 dsadded = added
2960 dsremoved = removed
2968 dsremoved = removed
2961 # store all local modifications, useful later for rename detection
2969 # store all local modifications, useful later for rename detection
2962 localchanges = dsmodified | dsadded
2970 localchanges = dsmodified | dsadded
2963 modified, added, removed = set(), set(), set()
2971 modified, added, removed = set(), set(), set()
2964 else:
2972 else:
2965 changes = repo.status(node1=parent, match=m)
2973 changes = repo.status(node1=parent, match=m)
2966 dsmodified = set(changes.modified)
2974 dsmodified = set(changes.modified)
2967 dsadded = set(changes.added)
2975 dsadded = set(changes.added)
2968 dsremoved = set(changes.removed)
2976 dsremoved = set(changes.removed)
2969 # store all local modifications, useful later for rename detection
2977 # store all local modifications, useful later for rename detection
2970 localchanges = dsmodified | dsadded
2978 localchanges = dsmodified | dsadded
2971
2979
2972 # only take into account for removes between wc and target
2980 # only take into account for removes between wc and target
2973 clean |= dsremoved - removed
2981 clean |= dsremoved - removed
2974 dsremoved &= removed
2982 dsremoved &= removed
2975 # distinct between dirstate remove and other
2983 # distinct between dirstate remove and other
2976 removed -= dsremoved
2984 removed -= dsremoved
2977
2985
2978 modadded = added & dsmodified
2986 modadded = added & dsmodified
2979 added -= modadded
2987 added -= modadded
2980
2988
2981 # tell newly modified apart.
2989 # tell newly modified apart.
2982 dsmodified &= modified
2990 dsmodified &= modified
2983 dsmodified |= modified & dsadded # dirstate added may need backup
2991 dsmodified |= modified & dsadded # dirstate added may need backup
2984 modified -= dsmodified
2992 modified -= dsmodified
2985
2993
2986 # We need to wait for some post-processing to update this set
2994 # We need to wait for some post-processing to update this set
2987 # before making the distinction. The dirstate will be used for
2995 # before making the distinction. The dirstate will be used for
2988 # that purpose.
2996 # that purpose.
2989 dsadded = added
2997 dsadded = added
2990
2998
2991 # in case of merge, files that are actually added can be reported as
2999 # in case of merge, files that are actually added can be reported as
2992 # modified, we need to post process the result
3000 # modified, we need to post process the result
2993 if p2 != nullid:
3001 if p2 != nullid:
2994 mergeadd = dsmodified - smf
3002 mergeadd = dsmodified - smf
2995 dsadded |= mergeadd
3003 dsadded |= mergeadd
2996 dsmodified -= mergeadd
3004 dsmodified -= mergeadd
2997
3005
2998 # if f is a rename, update `names` to also revert the source
3006 # if f is a rename, update `names` to also revert the source
2999 cwd = repo.getcwd()
3007 cwd = repo.getcwd()
3000 for f in localchanges:
3008 for f in localchanges:
3001 src = repo.dirstate.copied(f)
3009 src = repo.dirstate.copied(f)
3002 # XXX should we check for rename down to target node?
3010 # XXX should we check for rename down to target node?
3003 if src and src not in names and repo.dirstate[src] == 'r':
3011 if src and src not in names and repo.dirstate[src] == 'r':
3004 dsremoved.add(src)
3012 dsremoved.add(src)
3005 names[src] = (repo.pathto(src, cwd), True)
3013 names[src] = (repo.pathto(src, cwd), True)
3006
3014
3007 # distinguish between file to forget and the other
3015 # distinguish between file to forget and the other
3008 added = set()
3016 added = set()
3009 for abs in dsadded:
3017 for abs in dsadded:
3010 if repo.dirstate[abs] != 'a':
3018 if repo.dirstate[abs] != 'a':
3011 added.add(abs)
3019 added.add(abs)
3012 dsadded -= added
3020 dsadded -= added
3013
3021
3014 for abs in deladded:
3022 for abs in deladded:
3015 if repo.dirstate[abs] == 'a':
3023 if repo.dirstate[abs] == 'a':
3016 dsadded.add(abs)
3024 dsadded.add(abs)
3017 deladded -= dsadded
3025 deladded -= dsadded
3018
3026
3019 # For files marked as removed, we check if an unknown file is present at
3027 # For files marked as removed, we check if an unknown file is present at
3020 # the same path. If a such file exists it may need to be backed up.
3028 # the same path. If a such file exists it may need to be backed up.
3021 # Making the distinction at this stage helps have simpler backup
3029 # Making the distinction at this stage helps have simpler backup
3022 # logic.
3030 # logic.
3023 removunk = set()
3031 removunk = set()
3024 for abs in removed:
3032 for abs in removed:
3025 target = repo.wjoin(abs)
3033 target = repo.wjoin(abs)
3026 if os.path.lexists(target):
3034 if os.path.lexists(target):
3027 removunk.add(abs)
3035 removunk.add(abs)
3028 removed -= removunk
3036 removed -= removunk
3029
3037
3030 dsremovunk = set()
3038 dsremovunk = set()
3031 for abs in dsremoved:
3039 for abs in dsremoved:
3032 target = repo.wjoin(abs)
3040 target = repo.wjoin(abs)
3033 if os.path.lexists(target):
3041 if os.path.lexists(target):
3034 dsremovunk.add(abs)
3042 dsremovunk.add(abs)
3035 dsremoved -= dsremovunk
3043 dsremoved -= dsremovunk
3036
3044
3037 # action to be actually performed by revert
3045 # action to be actually performed by revert
3038 # (<list of file>, message>) tuple
3046 # (<list of file>, message>) tuple
3039 actions = {'revert': ([], _('reverting %s\n')),
3047 actions = {'revert': ([], _('reverting %s\n')),
3040 'add': ([], _('adding %s\n')),
3048 'add': ([], _('adding %s\n')),
3041 'remove': ([], _('removing %s\n')),
3049 'remove': ([], _('removing %s\n')),
3042 'drop': ([], _('removing %s\n')),
3050 'drop': ([], _('removing %s\n')),
3043 'forget': ([], _('forgetting %s\n')),
3051 'forget': ([], _('forgetting %s\n')),
3044 'undelete': ([], _('undeleting %s\n')),
3052 'undelete': ([], _('undeleting %s\n')),
3045 'noop': (None, _('no changes needed to %s\n')),
3053 'noop': (None, _('no changes needed to %s\n')),
3046 'unknown': (None, _('file not managed: %s\n')),
3054 'unknown': (None, _('file not managed: %s\n')),
3047 }
3055 }
3048
3056
3049 # "constant" that convey the backup strategy.
3057 # "constant" that convey the backup strategy.
3050 # All set to `discard` if `no-backup` is set do avoid checking
3058 # All set to `discard` if `no-backup` is set do avoid checking
3051 # no_backup lower in the code.
3059 # no_backup lower in the code.
3052 # These values are ordered for comparison purposes
3060 # These values are ordered for comparison purposes
3053 backupinteractive = 3 # do backup if interactively modified
3061 backupinteractive = 3 # do backup if interactively modified
3054 backup = 2 # unconditionally do backup
3062 backup = 2 # unconditionally do backup
3055 check = 1 # check if the existing file differs from target
3063 check = 1 # check if the existing file differs from target
3056 discard = 0 # never do backup
3064 discard = 0 # never do backup
3057 if opts.get('no_backup'):
3065 if opts.get('no_backup'):
3058 backupinteractive = backup = check = discard
3066 backupinteractive = backup = check = discard
3059 if interactive:
3067 if interactive:
3060 dsmodifiedbackup = backupinteractive
3068 dsmodifiedbackup = backupinteractive
3061 else:
3069 else:
3062 dsmodifiedbackup = backup
3070 dsmodifiedbackup = backup
3063 tobackup = set()
3071 tobackup = set()
3064
3072
3065 backupanddel = actions['remove']
3073 backupanddel = actions['remove']
3066 if not opts.get('no_backup'):
3074 if not opts.get('no_backup'):
3067 backupanddel = actions['drop']
3075 backupanddel = actions['drop']
3068
3076
3069 disptable = (
3077 disptable = (
3070 # dispatch table:
3078 # dispatch table:
3071 # file state
3079 # file state
3072 # action
3080 # action
3073 # make backup
3081 # make backup
3074
3082
3075 ## Sets that results that will change file on disk
3083 ## Sets that results that will change file on disk
3076 # Modified compared to target, no local change
3084 # Modified compared to target, no local change
3077 (modified, actions['revert'], discard),
3085 (modified, actions['revert'], discard),
3078 # Modified compared to target, but local file is deleted
3086 # Modified compared to target, but local file is deleted
3079 (deleted, actions['revert'], discard),
3087 (deleted, actions['revert'], discard),
3080 # Modified compared to target, local change
3088 # Modified compared to target, local change
3081 (dsmodified, actions['revert'], dsmodifiedbackup),
3089 (dsmodified, actions['revert'], dsmodifiedbackup),
3082 # Added since target
3090 # Added since target
3083 (added, actions['remove'], discard),
3091 (added, actions['remove'], discard),
3084 # Added in working directory
3092 # Added in working directory
3085 (dsadded, actions['forget'], discard),
3093 (dsadded, actions['forget'], discard),
3086 # Added since target, have local modification
3094 # Added since target, have local modification
3087 (modadded, backupanddel, backup),
3095 (modadded, backupanddel, backup),
3088 # Added since target but file is missing in working directory
3096 # Added since target but file is missing in working directory
3089 (deladded, actions['drop'], discard),
3097 (deladded, actions['drop'], discard),
3090 # Removed since target, before working copy parent
3098 # Removed since target, before working copy parent
3091 (removed, actions['add'], discard),
3099 (removed, actions['add'], discard),
3092 # Same as `removed` but an unknown file exists at the same path
3100 # Same as `removed` but an unknown file exists at the same path
3093 (removunk, actions['add'], check),
3101 (removunk, actions['add'], check),
3094 # Removed since targe, marked as such in working copy parent
3102 # Removed since targe, marked as such in working copy parent
3095 (dsremoved, actions['undelete'], discard),
3103 (dsremoved, actions['undelete'], discard),
3096 # Same as `dsremoved` but an unknown file exists at the same path
3104 # Same as `dsremoved` but an unknown file exists at the same path
3097 (dsremovunk, actions['undelete'], check),
3105 (dsremovunk, actions['undelete'], check),
3098 ## the following sets does not result in any file changes
3106 ## the following sets does not result in any file changes
3099 # File with no modification
3107 # File with no modification
3100 (clean, actions['noop'], discard),
3108 (clean, actions['noop'], discard),
3101 # Existing file, not tracked anywhere
3109 # Existing file, not tracked anywhere
3102 (unknown, actions['unknown'], discard),
3110 (unknown, actions['unknown'], discard),
3103 )
3111 )
3104
3112
3105 for abs, (rel, exact) in sorted(names.items()):
3113 for abs, (rel, exact) in sorted(names.items()):
3106 # target file to be touch on disk (relative to cwd)
3114 # target file to be touch on disk (relative to cwd)
3107 target = repo.wjoin(abs)
3115 target = repo.wjoin(abs)
3108 # search the entry in the dispatch table.
3116 # search the entry in the dispatch table.
3109 # if the file is in any of these sets, it was touched in the working
3117 # if the file is in any of these sets, it was touched in the working
3110 # directory parent and we are sure it needs to be reverted.
3118 # directory parent and we are sure it needs to be reverted.
3111 for table, (xlist, msg), dobackup in disptable:
3119 for table, (xlist, msg), dobackup in disptable:
3112 if abs not in table:
3120 if abs not in table:
3113 continue
3121 continue
3114 if xlist is not None:
3122 if xlist is not None:
3115 xlist.append(abs)
3123 xlist.append(abs)
3116 if dobackup:
3124 if dobackup:
3117 # If in interactive mode, don't automatically create
3125 # If in interactive mode, don't automatically create
3118 # .orig files (issue4793)
3126 # .orig files (issue4793)
3119 if dobackup == backupinteractive:
3127 if dobackup == backupinteractive:
3120 tobackup.add(abs)
3128 tobackup.add(abs)
3121 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3129 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3122 bakname = scmutil.origpath(ui, repo, rel)
3130 bakname = scmutil.origpath(ui, repo, rel)
3123 ui.note(_('saving current version of %s as %s\n') %
3131 ui.note(_('saving current version of %s as %s\n') %
3124 (rel, bakname))
3132 (rel, bakname))
3125 if not opts.get('dry_run'):
3133 if not opts.get('dry_run'):
3126 if interactive:
3134 if interactive:
3127 util.copyfile(target, bakname)
3135 util.copyfile(target, bakname)
3128 else:
3136 else:
3129 util.rename(target, bakname)
3137 util.rename(target, bakname)
3130 if ui.verbose or not exact:
3138 if ui.verbose or not exact:
3131 if not isinstance(msg, basestring):
3139 if not isinstance(msg, basestring):
3132 msg = msg(abs)
3140 msg = msg(abs)
3133 ui.status(msg % rel)
3141 ui.status(msg % rel)
3134 elif exact:
3142 elif exact:
3135 ui.warn(msg % rel)
3143 ui.warn(msg % rel)
3136 break
3144 break
3137
3145
3138 if not opts.get('dry_run'):
3146 if not opts.get('dry_run'):
3139 needdata = ('revert', 'add', 'undelete')
3147 needdata = ('revert', 'add', 'undelete')
3140 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3148 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3141 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3149 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3142
3150
3143 if targetsubs:
3151 if targetsubs:
3144 # Revert the subrepos on the revert list
3152 # Revert the subrepos on the revert list
3145 for sub in targetsubs:
3153 for sub in targetsubs:
3146 try:
3154 try:
3147 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3155 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3148 except KeyError:
3156 except KeyError:
3149 raise error.Abort("subrepository '%s' does not exist in %s!"
3157 raise error.Abort("subrepository '%s' does not exist in %s!"
3150 % (sub, short(ctx.node())))
3158 % (sub, short(ctx.node())))
3151
3159
3152 def _revertprefetch(repo, ctx, *files):
3160 def _revertprefetch(repo, ctx, *files):
3153 """Let extension changing the storage layer prefetch content"""
3161 """Let extension changing the storage layer prefetch content"""
3154 pass
3162 pass
3155
3163
3156 def _performrevert(repo, parents, ctx, actions, interactive=False,
3164 def _performrevert(repo, parents, ctx, actions, interactive=False,
3157 tobackup=None):
3165 tobackup=None):
3158 """function that actually perform all the actions computed for revert
3166 """function that actually perform all the actions computed for revert
3159
3167
3160 This is an independent function to let extension to plug in and react to
3168 This is an independent function to let extension to plug in and react to
3161 the imminent revert.
3169 the imminent revert.
3162
3170
3163 Make sure you have the working directory locked when calling this function.
3171 Make sure you have the working directory locked when calling this function.
3164 """
3172 """
3165 parent, p2 = parents
3173 parent, p2 = parents
3166 node = ctx.node()
3174 node = ctx.node()
3167 excluded_files = []
3175 excluded_files = []
3168 matcher_opts = {"exclude": excluded_files}
3176 matcher_opts = {"exclude": excluded_files}
3169
3177
3170 def checkout(f):
3178 def checkout(f):
3171 fc = ctx[f]
3179 fc = ctx[f]
3172 repo.wwrite(f, fc.data(), fc.flags())
3180 repo.wwrite(f, fc.data(), fc.flags())
3173
3181
3174 def doremove(f):
3182 def doremove(f):
3175 try:
3183 try:
3176 util.unlinkpath(repo.wjoin(f))
3184 util.unlinkpath(repo.wjoin(f))
3177 except OSError:
3185 except OSError:
3178 pass
3186 pass
3179 repo.dirstate.remove(f)
3187 repo.dirstate.remove(f)
3180
3188
3181 audit_path = pathutil.pathauditor(repo.root)
3189 audit_path = pathutil.pathauditor(repo.root)
3182 for f in actions['forget'][0]:
3190 for f in actions['forget'][0]:
3183 if interactive:
3191 if interactive:
3184 choice = repo.ui.promptchoice(
3192 choice = repo.ui.promptchoice(
3185 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3193 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3186 if choice == 0:
3194 if choice == 0:
3187 repo.dirstate.drop(f)
3195 repo.dirstate.drop(f)
3188 else:
3196 else:
3189 excluded_files.append(repo.wjoin(f))
3197 excluded_files.append(repo.wjoin(f))
3190 else:
3198 else:
3191 repo.dirstate.drop(f)
3199 repo.dirstate.drop(f)
3192 for f in actions['remove'][0]:
3200 for f in actions['remove'][0]:
3193 audit_path(f)
3201 audit_path(f)
3194 if interactive:
3202 if interactive:
3195 choice = repo.ui.promptchoice(
3203 choice = repo.ui.promptchoice(
3196 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3204 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3197 if choice == 0:
3205 if choice == 0:
3198 doremove(f)
3206 doremove(f)
3199 else:
3207 else:
3200 excluded_files.append(repo.wjoin(f))
3208 excluded_files.append(repo.wjoin(f))
3201 else:
3209 else:
3202 doremove(f)
3210 doremove(f)
3203 for f in actions['drop'][0]:
3211 for f in actions['drop'][0]:
3204 audit_path(f)
3212 audit_path(f)
3205 repo.dirstate.remove(f)
3213 repo.dirstate.remove(f)
3206
3214
3207 normal = None
3215 normal = None
3208 if node == parent:
3216 if node == parent:
3209 # We're reverting to our parent. If possible, we'd like status
3217 # We're reverting to our parent. If possible, we'd like status
3210 # to report the file as clean. We have to use normallookup for
3218 # to report the file as clean. We have to use normallookup for
3211 # merges to avoid losing information about merged/dirty files.
3219 # merges to avoid losing information about merged/dirty files.
3212 if p2 != nullid:
3220 if p2 != nullid:
3213 normal = repo.dirstate.normallookup
3221 normal = repo.dirstate.normallookup
3214 else:
3222 else:
3215 normal = repo.dirstate.normal
3223 normal = repo.dirstate.normal
3216
3224
3217 newlyaddedandmodifiedfiles = set()
3225 newlyaddedandmodifiedfiles = set()
3218 if interactive:
3226 if interactive:
3219 # Prompt the user for changes to revert
3227 # Prompt the user for changes to revert
3220 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3228 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3221 m = scmutil.match(ctx, torevert, matcher_opts)
3229 m = scmutil.match(ctx, torevert, matcher_opts)
3222 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3230 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3223 diffopts.nodates = True
3231 diffopts.nodates = True
3224 diffopts.git = True
3232 diffopts.git = True
3225 reversehunks = repo.ui.configbool('experimental',
3233 reversehunks = repo.ui.configbool('experimental',
3226 'revertalternateinteractivemode',
3234 'revertalternateinteractivemode',
3227 True)
3235 True)
3228 if reversehunks:
3236 if reversehunks:
3229 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3237 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3230 else:
3238 else:
3231 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3239 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3232 originalchunks = patch.parsepatch(diff)
3240 originalchunks = patch.parsepatch(diff)
3233 operation = 'discard' if node == parent else 'revert'
3241 operation = 'discard' if node == parent else 'revert'
3234
3242
3235 try:
3243 try:
3236
3244
3237 chunks, opts = recordfilter(repo.ui, originalchunks,
3245 chunks, opts = recordfilter(repo.ui, originalchunks,
3238 operation=operation)
3246 operation=operation)
3239 if reversehunks:
3247 if reversehunks:
3240 chunks = patch.reversehunks(chunks)
3248 chunks = patch.reversehunks(chunks)
3241
3249
3242 except patch.PatchError as err:
3250 except patch.PatchError as err:
3243 raise error.Abort(_('error parsing patch: %s') % err)
3251 raise error.Abort(_('error parsing patch: %s') % err)
3244
3252
3245 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3253 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3246 if tobackup is None:
3254 if tobackup is None:
3247 tobackup = set()
3255 tobackup = set()
3248 # Apply changes
3256 # Apply changes
3249 fp = stringio()
3257 fp = stringio()
3250 for c in chunks:
3258 for c in chunks:
3251 # Create a backup file only if this hunk should be backed up
3259 # Create a backup file only if this hunk should be backed up
3252 if ishunk(c) and c.header.filename() in tobackup:
3260 if ishunk(c) and c.header.filename() in tobackup:
3253 abs = c.header.filename()
3261 abs = c.header.filename()
3254 target = repo.wjoin(abs)
3262 target = repo.wjoin(abs)
3255 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3263 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3256 util.copyfile(target, bakname)
3264 util.copyfile(target, bakname)
3257 tobackup.remove(abs)
3265 tobackup.remove(abs)
3258 c.write(fp)
3266 c.write(fp)
3259 dopatch = fp.tell()
3267 dopatch = fp.tell()
3260 fp.seek(0)
3268 fp.seek(0)
3261 if dopatch:
3269 if dopatch:
3262 try:
3270 try:
3263 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3271 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3264 except patch.PatchError as err:
3272 except patch.PatchError as err:
3265 raise error.Abort(str(err))
3273 raise error.Abort(str(err))
3266 del fp
3274 del fp
3267 else:
3275 else:
3268 for f in actions['revert'][0]:
3276 for f in actions['revert'][0]:
3269 checkout(f)
3277 checkout(f)
3270 if normal:
3278 if normal:
3271 normal(f)
3279 normal(f)
3272
3280
3273 for f in actions['add'][0]:
3281 for f in actions['add'][0]:
3274 # Don't checkout modified files, they are already created by the diff
3282 # Don't checkout modified files, they are already created by the diff
3275 if f not in newlyaddedandmodifiedfiles:
3283 if f not in newlyaddedandmodifiedfiles:
3276 checkout(f)
3284 checkout(f)
3277 repo.dirstate.add(f)
3285 repo.dirstate.add(f)
3278
3286
3279 normal = repo.dirstate.normallookup
3287 normal = repo.dirstate.normallookup
3280 if node == parent and p2 == nullid:
3288 if node == parent and p2 == nullid:
3281 normal = repo.dirstate.normal
3289 normal = repo.dirstate.normal
3282 for f in actions['undelete'][0]:
3290 for f in actions['undelete'][0]:
3283 checkout(f)
3291 checkout(f)
3284 normal(f)
3292 normal(f)
3285
3293
3286 copied = copies.pathcopies(repo[parent], ctx)
3294 copied = copies.pathcopies(repo[parent], ctx)
3287
3295
3288 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3296 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3289 if f in copied:
3297 if f in copied:
3290 repo.dirstate.copy(copied[f], f)
3298 repo.dirstate.copy(copied[f], f)
3291
3299
3292 def command(table):
3300 def command(table):
3293 """Returns a function object to be used as a decorator for making commands.
3301 """Returns a function object to be used as a decorator for making commands.
3294
3302
3295 This function receives a command table as its argument. The table should
3303 This function receives a command table as its argument. The table should
3296 be a dict.
3304 be a dict.
3297
3305
3298 The returned function can be used as a decorator for adding commands
3306 The returned function can be used as a decorator for adding commands
3299 to that command table. This function accepts multiple arguments to define
3307 to that command table. This function accepts multiple arguments to define
3300 a command.
3308 a command.
3301
3309
3302 The first argument is the command name.
3310 The first argument is the command name.
3303
3311
3304 The options argument is an iterable of tuples defining command arguments.
3312 The options argument is an iterable of tuples defining command arguments.
3305 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3313 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3306
3314
3307 The synopsis argument defines a short, one line summary of how to use the
3315 The synopsis argument defines a short, one line summary of how to use the
3308 command. This shows up in the help output.
3316 command. This shows up in the help output.
3309
3317
3310 The norepo argument defines whether the command does not require a
3318 The norepo argument defines whether the command does not require a
3311 local repository. Most commands operate against a repository, thus the
3319 local repository. Most commands operate against a repository, thus the
3312 default is False.
3320 default is False.
3313
3321
3314 The optionalrepo argument defines whether the command optionally requires
3322 The optionalrepo argument defines whether the command optionally requires
3315 a local repository.
3323 a local repository.
3316
3324
3317 The inferrepo argument defines whether to try to find a repository from the
3325 The inferrepo argument defines whether to try to find a repository from the
3318 command line arguments. If True, arguments will be examined for potential
3326 command line arguments. If True, arguments will be examined for potential
3319 repository locations. See ``findrepo()``. If a repository is found, it
3327 repository locations. See ``findrepo()``. If a repository is found, it
3320 will be used.
3328 will be used.
3321 """
3329 """
3322 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3330 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3323 inferrepo=False):
3331 inferrepo=False):
3324 def decorator(func):
3332 def decorator(func):
3325 func.norepo = norepo
3333 func.norepo = norepo
3326 func.optionalrepo = optionalrepo
3334 func.optionalrepo = optionalrepo
3327 func.inferrepo = inferrepo
3335 func.inferrepo = inferrepo
3328 if synopsis:
3336 if synopsis:
3329 table[name] = func, list(options), synopsis
3337 table[name] = func, list(options), synopsis
3330 else:
3338 else:
3331 table[name] = func, list(options)
3339 table[name] = func, list(options)
3332 return func
3340 return func
3333 return decorator
3341 return decorator
3334
3342
3335 return cmd
3343 return cmd
3336
3344
3337 def checkunresolved(ms):
3345 def checkunresolved(ms):
3338 ms._repo.ui.deprecwarn('checkunresolved moved from cmdutil to mergeutil',
3346 ms._repo.ui.deprecwarn('checkunresolved moved from cmdutil to mergeutil',
3339 '4.1')
3347 '4.1')
3340 return mergeutil.checkunresolved(ms)
3348 return mergeutil.checkunresolved(ms)
3341
3349
3342 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3350 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3343 # commands.outgoing. "missing" is "missing" of the result of
3351 # commands.outgoing. "missing" is "missing" of the result of
3344 # "findcommonoutgoing()"
3352 # "findcommonoutgoing()"
3345 outgoinghooks = util.hooks()
3353 outgoinghooks = util.hooks()
3346
3354
3347 # a list of (ui, repo) functions called by commands.summary
3355 # a list of (ui, repo) functions called by commands.summary
3348 summaryhooks = util.hooks()
3356 summaryhooks = util.hooks()
3349
3357
3350 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3358 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3351 #
3359 #
3352 # functions should return tuple of booleans below, if 'changes' is None:
3360 # functions should return tuple of booleans below, if 'changes' is None:
3353 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3361 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3354 #
3362 #
3355 # otherwise, 'changes' is a tuple of tuples below:
3363 # otherwise, 'changes' is a tuple of tuples below:
3356 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3364 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3357 # - (desturl, destbranch, destpeer, outgoing)
3365 # - (desturl, destbranch, destpeer, outgoing)
3358 summaryremotehooks = util.hooks()
3366 summaryremotehooks = util.hooks()
3359
3367
3360 # A list of state files kept by multistep operations like graft.
3368 # A list of state files kept by multistep operations like graft.
3361 # Since graft cannot be aborted, it is considered 'clearable' by update.
3369 # Since graft cannot be aborted, it is considered 'clearable' by update.
3362 # note: bisect is intentionally excluded
3370 # note: bisect is intentionally excluded
3363 # (state file, clearable, allowcommit, error, hint)
3371 # (state file, clearable, allowcommit, error, hint)
3364 unfinishedstates = [
3372 unfinishedstates = [
3365 ('graftstate', True, False, _('graft in progress'),
3373 ('graftstate', True, False, _('graft in progress'),
3366 _("use 'hg graft --continue' or 'hg update' to abort")),
3374 _("use 'hg graft --continue' or 'hg update' to abort")),
3367 ('updatestate', True, False, _('last update was interrupted'),
3375 ('updatestate', True, False, _('last update was interrupted'),
3368 _("use 'hg update' to get a consistent checkout"))
3376 _("use 'hg update' to get a consistent checkout"))
3369 ]
3377 ]
3370
3378
3371 def checkunfinished(repo, commit=False):
3379 def checkunfinished(repo, commit=False):
3372 '''Look for an unfinished multistep operation, like graft, and abort
3380 '''Look for an unfinished multistep operation, like graft, and abort
3373 if found. It's probably good to check this right before
3381 if found. It's probably good to check this right before
3374 bailifchanged().
3382 bailifchanged().
3375 '''
3383 '''
3376 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3384 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3377 if commit and allowcommit:
3385 if commit and allowcommit:
3378 continue
3386 continue
3379 if repo.vfs.exists(f):
3387 if repo.vfs.exists(f):
3380 raise error.Abort(msg, hint=hint)
3388 raise error.Abort(msg, hint=hint)
3381
3389
3382 def clearunfinished(repo):
3390 def clearunfinished(repo):
3383 '''Check for unfinished operations (as above), and clear the ones
3391 '''Check for unfinished operations (as above), and clear the ones
3384 that are clearable.
3392 that are clearable.
3385 '''
3393 '''
3386 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3394 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3387 if not clearable and repo.vfs.exists(f):
3395 if not clearable and repo.vfs.exists(f):
3388 raise error.Abort(msg, hint=hint)
3396 raise error.Abort(msg, hint=hint)
3389 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3397 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3390 if clearable and repo.vfs.exists(f):
3398 if clearable and repo.vfs.exists(f):
3391 util.unlink(repo.join(f))
3399 util.unlink(repo.join(f))
3392
3400
3393 afterresolvedstates = [
3401 afterresolvedstates = [
3394 ('graftstate',
3402 ('graftstate',
3395 _('hg graft --continue')),
3403 _('hg graft --continue')),
3396 ]
3404 ]
3397
3405
3398 def howtocontinue(repo):
3406 def howtocontinue(repo):
3399 '''Check for an unfinished operation and return the command to finish
3407 '''Check for an unfinished operation and return the command to finish
3400 it.
3408 it.
3401
3409
3402 afterresolvedstates tuples define a .hg/{file} and the corresponding
3410 afterresolvedstates tuples define a .hg/{file} and the corresponding
3403 command needed to finish it.
3411 command needed to finish it.
3404
3412
3405 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3413 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3406 a boolean.
3414 a boolean.
3407 '''
3415 '''
3408 contmsg = _("continue: %s")
3416 contmsg = _("continue: %s")
3409 for f, msg in afterresolvedstates:
3417 for f, msg in afterresolvedstates:
3410 if repo.vfs.exists(f):
3418 if repo.vfs.exists(f):
3411 return contmsg % msg, True
3419 return contmsg % msg, True
3412 workingctx = repo[None]
3420 workingctx = repo[None]
3413 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3421 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3414 for s in workingctx.substate)
3422 for s in workingctx.substate)
3415 if dirty:
3423 if dirty:
3416 return contmsg % _("hg commit"), False
3424 return contmsg % _("hg commit"), False
3417 return None, None
3425 return None, None
3418
3426
3419 def checkafterresolved(repo):
3427 def checkafterresolved(repo):
3420 '''Inform the user about the next action after completing hg resolve
3428 '''Inform the user about the next action after completing hg resolve
3421
3429
3422 If there's a matching afterresolvedstates, howtocontinue will yield
3430 If there's a matching afterresolvedstates, howtocontinue will yield
3423 repo.ui.warn as the reporter.
3431 repo.ui.warn as the reporter.
3424
3432
3425 Otherwise, it will yield repo.ui.note.
3433 Otherwise, it will yield repo.ui.note.
3426 '''
3434 '''
3427 msg, warning = howtocontinue(repo)
3435 msg, warning = howtocontinue(repo)
3428 if msg is not None:
3436 if msg is not None:
3429 if warning:
3437 if warning:
3430 repo.ui.warn("%s\n" % msg)
3438 repo.ui.warn("%s\n" % msg)
3431 else:
3439 else:
3432 repo.ui.note("%s\n" % msg)
3440 repo.ui.note("%s\n" % msg)
3433
3441
3434 def wrongtooltocontinue(repo, task):
3442 def wrongtooltocontinue(repo, task):
3435 '''Raise an abort suggesting how to properly continue if there is an
3443 '''Raise an abort suggesting how to properly continue if there is an
3436 active task.
3444 active task.
3437
3445
3438 Uses howtocontinue() to find the active task.
3446 Uses howtocontinue() to find the active task.
3439
3447
3440 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3448 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3441 a hint.
3449 a hint.
3442 '''
3450 '''
3443 after = howtocontinue(repo)
3451 after = howtocontinue(repo)
3444 hint = None
3452 hint = None
3445 if after[1]:
3453 if after[1]:
3446 hint = after[0]
3454 hint = after[0]
3447 raise error.Abort(_('no %s in progress') % task, hint=hint)
3455 raise error.Abort(_('no %s in progress') % task, hint=hint)
3448
3456
3449 class dirstateguard(dirstateguardmod.dirstateguard):
3457 class dirstateguard(dirstateguardmod.dirstateguard):
3450 def __init__(self, repo, name):
3458 def __init__(self, repo, name):
3451 dirstateguardmod.dirstateguard.__init__(self, repo, name)
3459 dirstateguardmod.dirstateguard.__init__(self, repo, name)
3452 repo.ui.deprecwarn(
3460 repo.ui.deprecwarn(
3453 'dirstateguard has moved from cmdutil to dirstateguard',
3461 'dirstateguard has moved from cmdutil to dirstateguard',
3454 '4.1')
3462 '4.1')
@@ -1,1179 +1,1180 b''
1 $ cat << EOF >> $HGRCPATH
1 $ cat << EOF >> $HGRCPATH
2 > [format]
2 > [format]
3 > usegeneraldelta=yes
3 > usegeneraldelta=yes
4 > EOF
4 > EOF
5
5
6 $ hg init
6 $ hg init
7
7
8 Setup:
8 Setup:
9
9
10 $ echo a >> a
10 $ echo a >> a
11 $ hg ci -Am 'base'
11 $ hg ci -Am 'base'
12 adding a
12 adding a
13
13
14 Refuse to amend public csets:
14 Refuse to amend public csets:
15
15
16 $ hg phase -r . -p
16 $ hg phase -r . -p
17 $ hg ci --amend
17 $ hg ci --amend
18 abort: cannot amend public changesets
18 abort: cannot amend public changesets
19 [255]
19 [255]
20 $ hg phase -r . -f -d
20 $ hg phase -r . -f -d
21
21
22 $ echo a >> a
22 $ echo a >> a
23 $ hg ci -Am 'base1'
23 $ hg ci -Am 'base1'
24
24
25 Nothing to amend:
25 Nothing to amend:
26
26
27 $ hg ci --amend -m 'base1'
27 $ hg ci --amend -m 'base1'
28 nothing changed
28 nothing changed
29 [1]
29 [1]
30
30
31 $ cat >> $HGRCPATH <<EOF
31 $ cat >> $HGRCPATH <<EOF
32 > [hooks]
32 > [hooks]
33 > pretxncommit.foo = sh -c "echo \\"pretxncommit \$HG_NODE\\"; hg id -r \$HG_NODE"
33 > pretxncommit.foo = sh -c "echo \\"pretxncommit \$HG_NODE\\"; hg id -r \$HG_NODE"
34 > EOF
34 > EOF
35
35
36 Amending changeset with changes in working dir:
36 Amending changeset with changes in working dir:
37 (and check that --message does not trigger an editor)
37 (and check that --message does not trigger an editor)
38
38
39 $ echo a >> a
39 $ echo a >> a
40 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -m 'amend base1'
40 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -m 'amend base1'
41 pretxncommit 43f1ba15f28a50abf0aae529cf8a16bfced7b149
41 pretxncommit 43f1ba15f28a50abf0aae529cf8a16bfced7b149
42 43f1ba15f28a tip
42 43f1ba15f28a tip
43 saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-f1bf3ab8-amend-backup.hg (glob)
43 saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-f1bf3ab8-amend-backup.hg (glob)
44 $ echo 'pretxncommit.foo = ' >> $HGRCPATH
44 $ echo 'pretxncommit.foo = ' >> $HGRCPATH
45 $ hg diff -c .
45 $ hg diff -c .
46 diff -r ad120869acf0 -r 43f1ba15f28a a
46 diff -r ad120869acf0 -r 43f1ba15f28a a
47 --- a/a Thu Jan 01 00:00:00 1970 +0000
47 --- a/a Thu Jan 01 00:00:00 1970 +0000
48 +++ b/a Thu Jan 01 00:00:00 1970 +0000
48 +++ b/a Thu Jan 01 00:00:00 1970 +0000
49 @@ -1,1 +1,3 @@
49 @@ -1,1 +1,3 @@
50 a
50 a
51 +a
51 +a
52 +a
52 +a
53 $ hg log
53 $ hg log
54 changeset: 1:43f1ba15f28a
54 changeset: 1:43f1ba15f28a
55 tag: tip
55 tag: tip
56 user: test
56 user: test
57 date: Thu Jan 01 00:00:00 1970 +0000
57 date: Thu Jan 01 00:00:00 1970 +0000
58 summary: amend base1
58 summary: amend base1
59
59
60 changeset: 0:ad120869acf0
60 changeset: 0:ad120869acf0
61 user: test
61 user: test
62 date: Thu Jan 01 00:00:00 1970 +0000
62 date: Thu Jan 01 00:00:00 1970 +0000
63 summary: base
63 summary: base
64
64
65
65
66 Check proper abort for empty message
66 Check proper abort for empty message
67
67
68 $ cat > editor.sh << '__EOF__'
68 $ cat > editor.sh << '__EOF__'
69 > #!/bin/sh
69 > #!/bin/sh
70 > echo "" > "$1"
70 > echo "" > "$1"
71 > __EOF__
71 > __EOF__
72 $ echo b > b
72 $ echo b > b
73 $ hg add b
73 $ hg add b
74 $ hg summary
74 $ hg summary
75 parent: 1:43f1ba15f28a tip
75 parent: 1:43f1ba15f28a tip
76 amend base1
76 amend base1
77 branch: default
77 branch: default
78 commit: 1 added, 1 unknown
78 commit: 1 added, 1 unknown
79 update: (current)
79 update: (current)
80 phases: 2 draft
80 phases: 2 draft
81 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
81 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
82 transaction abort!
82 transaction abort!
83 rollback completed
83 rollback completed
84 abort: empty commit message
84 abort: empty commit message
85 [255]
85 [255]
86 $ hg summary
86 $ hg summary
87 parent: 1:43f1ba15f28a tip
87 parent: 1:43f1ba15f28a tip
88 amend base1
88 amend base1
89 branch: default
89 branch: default
90 commit: 1 added, 1 unknown
90 commit: 1 added, 1 unknown
91 update: (current)
91 update: (current)
92 phases: 2 draft
92 phases: 2 draft
93
93
94 Add new file:
94 Add new file:
95 $ hg ci --amend -m 'amend base1 new file'
95 $ hg ci --amend -m 'amend base1 new file'
96 saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-7a3b3496-amend-backup.hg (glob)
96 saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-7a3b3496-amend-backup.hg (glob)
97
97
98 Remove file that was added in amended commit:
98 Remove file that was added in amended commit:
99 (and test logfile option)
99 (and test logfile option)
100 (and test that logfile option do not trigger an editor)
100 (and test that logfile option do not trigger an editor)
101
101
102 $ hg rm b
102 $ hg rm b
103 $ echo 'amend base1 remove new file' > ../logfile
103 $ echo 'amend base1 remove new file' > ../logfile
104 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg ci --amend --logfile ../logfile
104 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg ci --amend --logfile ../logfile
105 saved backup bundle to $TESTTMP/.hg/strip-backup/b8e3cb2b3882-0b55739a-amend-backup.hg (glob)
105 saved backup bundle to $TESTTMP/.hg/strip-backup/b8e3cb2b3882-0b55739a-amend-backup.hg (glob)
106
106
107 $ hg cat b
107 $ hg cat b
108 b: no such file in rev 74609c7f506e
108 b: no such file in rev 74609c7f506e
109 [1]
109 [1]
110
110
111 No changes, just a different message:
111 No changes, just a different message:
112
112
113 $ hg ci -v --amend -m 'no changes, new message'
113 $ hg ci -v --amend -m 'no changes, new message'
114 amending changeset 74609c7f506e
114 amending changeset 74609c7f506e
115 copying changeset 74609c7f506e to ad120869acf0
115 copying changeset 74609c7f506e to ad120869acf0
116 committing files:
116 committing files:
117 a
117 a
118 committing manifest
118 committing manifest
119 committing changelog
119 committing changelog
120 stripping amended changeset 74609c7f506e
120 stripping amended changeset 74609c7f506e
121 1 changesets found
121 1 changesets found
122 uncompressed size of bundle content:
122 uncompressed size of bundle content:
123 254 (changelog)
123 254 (changelog)
124 163 (manifests)
124 163 (manifests)
125 129 a
125 129 a
126 saved backup bundle to $TESTTMP/.hg/strip-backup/74609c7f506e-1bfde511-amend-backup.hg (glob)
126 saved backup bundle to $TESTTMP/.hg/strip-backup/74609c7f506e-1bfde511-amend-backup.hg (glob)
127 1 changesets found
127 1 changesets found
128 uncompressed size of bundle content:
128 uncompressed size of bundle content:
129 250 (changelog)
129 250 (changelog)
130 163 (manifests)
130 163 (manifests)
131 129 a
131 129 a
132 adding branch
132 adding branch
133 adding changesets
133 adding changesets
134 adding manifests
134 adding manifests
135 adding file changes
135 adding file changes
136 added 1 changesets with 1 changes to 1 files
136 added 1 changesets with 1 changes to 1 files
137 committed changeset 1:1cd866679df8
137 committed changeset 1:1cd866679df8
138 $ hg diff -c .
138 $ hg diff -c .
139 diff -r ad120869acf0 -r 1cd866679df8 a
139 diff -r ad120869acf0 -r 1cd866679df8 a
140 --- a/a Thu Jan 01 00:00:00 1970 +0000
140 --- a/a Thu Jan 01 00:00:00 1970 +0000
141 +++ b/a Thu Jan 01 00:00:00 1970 +0000
141 +++ b/a Thu Jan 01 00:00:00 1970 +0000
142 @@ -1,1 +1,3 @@
142 @@ -1,1 +1,3 @@
143 a
143 a
144 +a
144 +a
145 +a
145 +a
146 $ hg log
146 $ hg log
147 changeset: 1:1cd866679df8
147 changeset: 1:1cd866679df8
148 tag: tip
148 tag: tip
149 user: test
149 user: test
150 date: Thu Jan 01 00:00:00 1970 +0000
150 date: Thu Jan 01 00:00:00 1970 +0000
151 summary: no changes, new message
151 summary: no changes, new message
152
152
153 changeset: 0:ad120869acf0
153 changeset: 0:ad120869acf0
154 user: test
154 user: test
155 date: Thu Jan 01 00:00:00 1970 +0000
155 date: Thu Jan 01 00:00:00 1970 +0000
156 summary: base
156 summary: base
157
157
158
158
159 Disable default date on commit so when -d isn't given, the old date is preserved:
159 Disable default date on commit so when -d isn't given, the old date is preserved:
160
160
161 $ echo '[defaults]' >> $HGRCPATH
161 $ echo '[defaults]' >> $HGRCPATH
162 $ echo 'commit=' >> $HGRCPATH
162 $ echo 'commit=' >> $HGRCPATH
163
163
164 Test -u/-d:
164 Test -u/-d:
165
165
166 $ cat > .hg/checkeditform.sh <<EOF
166 $ cat > .hg/checkeditform.sh <<EOF
167 > env | grep HGEDITFORM
167 > env | grep HGEDITFORM
168 > true
168 > true
169 > EOF
169 > EOF
170 $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -u foo -d '1 0'
170 $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -u foo -d '1 0'
171 HGEDITFORM=commit.amend.normal
171 HGEDITFORM=commit.amend.normal
172 saved backup bundle to $TESTTMP/.hg/strip-backup/1cd866679df8-5f5bcb85-amend-backup.hg (glob)
172 saved backup bundle to $TESTTMP/.hg/strip-backup/1cd866679df8-5f5bcb85-amend-backup.hg (glob)
173 $ echo a >> a
173 $ echo a >> a
174 $ hg ci --amend -u foo -d '1 0'
174 $ hg ci --amend -u foo -d '1 0'
175 saved backup bundle to $TESTTMP/.hg/strip-backup/780e6f23e03d-83b10a27-amend-backup.hg (glob)
175 saved backup bundle to $TESTTMP/.hg/strip-backup/780e6f23e03d-83b10a27-amend-backup.hg (glob)
176 $ hg log -r .
176 $ hg log -r .
177 changeset: 1:5f357c7560ab
177 changeset: 1:5f357c7560ab
178 tag: tip
178 tag: tip
179 user: foo
179 user: foo
180 date: Thu Jan 01 00:00:01 1970 +0000
180 date: Thu Jan 01 00:00:01 1970 +0000
181 summary: no changes, new message
181 summary: no changes, new message
182
182
183
183
184 Open editor with old commit message if a message isn't given otherwise:
184 Open editor with old commit message if a message isn't given otherwise:
185
185
186 $ cat > editor.sh << '__EOF__'
186 $ cat > editor.sh << '__EOF__'
187 > #!/bin/sh
187 > #!/bin/sh
188 > cat $1
188 > cat $1
189 > echo "another precious commit message" > "$1"
189 > echo "another precious commit message" > "$1"
190 > __EOF__
190 > __EOF__
191
191
192 at first, test saving last-message.txt
192 at first, test saving last-message.txt
193
193
194 $ cat > .hg/hgrc << '__EOF__'
194 $ cat > .hg/hgrc << '__EOF__'
195 > [hooks]
195 > [hooks]
196 > pretxncommit.test-saving-last-message = false
196 > pretxncommit.test-saving-last-message = false
197 > __EOF__
197 > __EOF__
198
198
199 $ rm -f .hg/last-message.txt
199 $ rm -f .hg/last-message.txt
200 $ hg commit --amend -v -m "message given from command line"
200 $ hg commit --amend -v -m "message given from command line"
201 amending changeset 5f357c7560ab
201 amending changeset 5f357c7560ab
202 copying changeset 5f357c7560ab to ad120869acf0
202 copying changeset 5f357c7560ab to ad120869acf0
203 committing files:
203 committing files:
204 a
204 a
205 committing manifest
205 committing manifest
206 committing changelog
206 committing changelog
207 running hook pretxncommit.test-saving-last-message: false
207 running hook pretxncommit.test-saving-last-message: false
208 transaction abort!
208 transaction abort!
209 rollback completed
209 rollback completed
210 abort: pretxncommit.test-saving-last-message hook exited with status 1
210 abort: pretxncommit.test-saving-last-message hook exited with status 1
211 [255]
211 [255]
212 $ cat .hg/last-message.txt
212 $ cat .hg/last-message.txt
213 message given from command line (no-eol)
213 message given from command line (no-eol)
214
214
215 $ rm -f .hg/last-message.txt
215 $ rm -f .hg/last-message.txt
216 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
216 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
217 amending changeset 5f357c7560ab
217 amending changeset 5f357c7560ab
218 copying changeset 5f357c7560ab to ad120869acf0
218 copying changeset 5f357c7560ab to ad120869acf0
219 no changes, new message
219 no changes, new message
220
220
221
221
222 HG: Enter commit message. Lines beginning with 'HG:' are removed.
222 HG: Enter commit message. Lines beginning with 'HG:' are removed.
223 HG: Leave message empty to abort commit.
223 HG: Leave message empty to abort commit.
224 HG: --
224 HG: --
225 HG: user: foo
225 HG: user: foo
226 HG: branch 'default'
226 HG: branch 'default'
227 HG: changed a
227 HG: changed a
228 committing files:
228 committing files:
229 a
229 a
230 committing manifest
230 committing manifest
231 committing changelog
231 committing changelog
232 running hook pretxncommit.test-saving-last-message: false
232 running hook pretxncommit.test-saving-last-message: false
233 transaction abort!
233 transaction abort!
234 rollback completed
234 rollback completed
235 abort: pretxncommit.test-saving-last-message hook exited with status 1
235 abort: pretxncommit.test-saving-last-message hook exited with status 1
236 [255]
236 [255]
237
237
238 $ cat .hg/last-message.txt
238 $ cat .hg/last-message.txt
239 another precious commit message
239 another precious commit message
240
240
241 $ cat > .hg/hgrc << '__EOF__'
241 $ cat > .hg/hgrc << '__EOF__'
242 > [hooks]
242 > [hooks]
243 > pretxncommit.test-saving-last-message =
243 > pretxncommit.test-saving-last-message =
244 > __EOF__
244 > __EOF__
245
245
246 then, test editing custom commit message
246 then, test editing custom commit message
247
247
248 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
248 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
249 amending changeset 5f357c7560ab
249 amending changeset 5f357c7560ab
250 copying changeset 5f357c7560ab to ad120869acf0
250 copying changeset 5f357c7560ab to ad120869acf0
251 no changes, new message
251 no changes, new message
252
252
253
253
254 HG: Enter commit message. Lines beginning with 'HG:' are removed.
254 HG: Enter commit message. Lines beginning with 'HG:' are removed.
255 HG: Leave message empty to abort commit.
255 HG: Leave message empty to abort commit.
256 HG: --
256 HG: --
257 HG: user: foo
257 HG: user: foo
258 HG: branch 'default'
258 HG: branch 'default'
259 HG: changed a
259 HG: changed a
260 committing files:
260 committing files:
261 a
261 a
262 committing manifest
262 committing manifest
263 committing changelog
263 committing changelog
264 stripping amended changeset 5f357c7560ab
264 stripping amended changeset 5f357c7560ab
265 1 changesets found
265 1 changesets found
266 uncompressed size of bundle content:
266 uncompressed size of bundle content:
267 249 (changelog)
267 249 (changelog)
268 163 (manifests)
268 163 (manifests)
269 131 a
269 131 a
270 saved backup bundle to $TESTTMP/.hg/strip-backup/5f357c7560ab-e7c84ade-amend-backup.hg (glob)
270 saved backup bundle to $TESTTMP/.hg/strip-backup/5f357c7560ab-e7c84ade-amend-backup.hg (glob)
271 1 changesets found
271 1 changesets found
272 uncompressed size of bundle content:
272 uncompressed size of bundle content:
273 257 (changelog)
273 257 (changelog)
274 163 (manifests)
274 163 (manifests)
275 131 a
275 131 a
276 adding branch
276 adding branch
277 adding changesets
277 adding changesets
278 adding manifests
278 adding manifests
279 adding file changes
279 adding file changes
280 added 1 changesets with 1 changes to 1 files
280 added 1 changesets with 1 changes to 1 files
281 committed changeset 1:7ab3bf440b54
281 committed changeset 1:7ab3bf440b54
282
282
283 Same, but with changes in working dir (different code path):
283 Same, but with changes in working dir (different code path):
284
284
285 $ echo a >> a
285 $ echo a >> a
286 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
286 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
287 amending changeset 7ab3bf440b54
287 amending changeset 7ab3bf440b54
288 committing files:
288 committing files:
289 a
289 a
290 committing manifest
290 committing manifest
291 committing changelog
291 committing changelog
292 copying changeset a0ea9b1a4c8c to ad120869acf0
292 copying changeset a0ea9b1a4c8c to ad120869acf0
293 another precious commit message
293 another precious commit message
294
294
295
295
296 HG: Enter commit message. Lines beginning with 'HG:' are removed.
296 HG: Enter commit message. Lines beginning with 'HG:' are removed.
297 HG: Leave message empty to abort commit.
297 HG: Leave message empty to abort commit.
298 HG: --
298 HG: --
299 HG: user: foo
299 HG: user: foo
300 HG: branch 'default'
300 HG: branch 'default'
301 HG: changed a
301 HG: changed a
302 committing files:
302 committing files:
303 a
303 a
304 committing manifest
304 committing manifest
305 committing changelog
305 committing changelog
306 stripping intermediate changeset a0ea9b1a4c8c
306 stripping intermediate changeset a0ea9b1a4c8c
307 stripping amended changeset 7ab3bf440b54
307 stripping amended changeset 7ab3bf440b54
308 2 changesets found
308 2 changesets found
309 uncompressed size of bundle content:
309 uncompressed size of bundle content:
310 464 (changelog)
310 464 (changelog)
311 322 (manifests)
311 322 (manifests)
312 249 a
312 249 a
313 saved backup bundle to $TESTTMP/.hg/strip-backup/7ab3bf440b54-8e3b5088-amend-backup.hg (glob)
313 saved backup bundle to $TESTTMP/.hg/strip-backup/7ab3bf440b54-8e3b5088-amend-backup.hg (glob)
314 1 changesets found
314 1 changesets found
315 uncompressed size of bundle content:
315 uncompressed size of bundle content:
316 257 (changelog)
316 257 (changelog)
317 163 (manifests)
317 163 (manifests)
318 133 a
318 133 a
319 adding branch
319 adding branch
320 adding changesets
320 adding changesets
321 adding manifests
321 adding manifests
322 adding file changes
322 adding file changes
323 added 1 changesets with 1 changes to 1 files
323 added 1 changesets with 1 changes to 1 files
324 committed changeset 1:ea22a388757c
324 committed changeset 1:ea22a388757c
325
325
326 $ rm editor.sh
326 $ rm editor.sh
327 $ hg log -r .
327 $ hg log -r .
328 changeset: 1:ea22a388757c
328 changeset: 1:ea22a388757c
329 tag: tip
329 tag: tip
330 user: foo
330 user: foo
331 date: Thu Jan 01 00:00:01 1970 +0000
331 date: Thu Jan 01 00:00:01 1970 +0000
332 summary: another precious commit message
332 summary: another precious commit message
333
333
334
334
335 Moving bookmarks, preserve active bookmark:
335 Moving bookmarks, preserve active bookmark:
336
336
337 $ hg book book1
337 $ hg book book1
338 $ hg book book2
338 $ hg book book2
339 $ hg ci --amend -m 'move bookmarks'
339 $ hg ci --amend -m 'move bookmarks'
340 saved backup bundle to $TESTTMP/.hg/strip-backup/ea22a388757c-e51094db-amend-backup.hg (glob)
340 saved backup bundle to $TESTTMP/.hg/strip-backup/ea22a388757c-e51094db-amend-backup.hg (glob)
341 $ hg book
341 $ hg book
342 book1 1:6cec5aa930e2
342 book1 1:6cec5aa930e2
343 * book2 1:6cec5aa930e2
343 * book2 1:6cec5aa930e2
344 $ echo a >> a
344 $ echo a >> a
345 $ hg ci --amend -m 'move bookmarks'
345 $ hg ci --amend -m 'move bookmarks'
346 saved backup bundle to $TESTTMP/.hg/strip-backup/6cec5aa930e2-e9b06de4-amend-backup.hg (glob)
346 saved backup bundle to $TESTTMP/.hg/strip-backup/6cec5aa930e2-e9b06de4-amend-backup.hg (glob)
347 $ hg book
347 $ hg book
348 book1 1:48bb6e53a15f
348 book1 1:48bb6e53a15f
349 * book2 1:48bb6e53a15f
349 * book2 1:48bb6e53a15f
350
350
351 abort does not loose bookmarks
351 abort does not loose bookmarks
352
352
353 $ cat > editor.sh << '__EOF__'
353 $ cat > editor.sh << '__EOF__'
354 > #!/bin/sh
354 > #!/bin/sh
355 > echo "" > "$1"
355 > echo "" > "$1"
356 > __EOF__
356 > __EOF__
357 $ echo a >> a
357 $ echo a >> a
358 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
358 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
359 transaction abort!
359 transaction abort!
360 rollback completed
360 rollback completed
361 abort: empty commit message
361 abort: empty commit message
362 [255]
362 [255]
363 $ hg book
363 $ hg book
364 book1 1:48bb6e53a15f
364 book1 1:48bb6e53a15f
365 * book2 1:48bb6e53a15f
365 * book2 1:48bb6e53a15f
366 $ hg revert -Caq
366 $ hg revert -Caq
367 $ rm editor.sh
367 $ rm editor.sh
368
368
369 $ echo '[defaults]' >> $HGRCPATH
369 $ echo '[defaults]' >> $HGRCPATH
370 $ echo "commit=-d '0 0'" >> $HGRCPATH
370 $ echo "commit=-d '0 0'" >> $HGRCPATH
371
371
372 Moving branches:
372 Moving branches:
373
373
374 $ hg branch foo
374 $ hg branch foo
375 marked working directory as branch foo
375 marked working directory as branch foo
376 (branches are permanent and global, did you want a bookmark?)
376 (branches are permanent and global, did you want a bookmark?)
377 $ echo a >> a
377 $ echo a >> a
378 $ hg ci -m 'branch foo'
378 $ hg ci -m 'branch foo'
379 $ hg branch default -f
379 $ hg branch default -f
380 marked working directory as branch default
380 marked working directory as branch default
381 $ hg ci --amend -m 'back to default'
381 $ hg ci --amend -m 'back to default'
382 saved backup bundle to $TESTTMP/.hg/strip-backup/8ac881fbf49d-fd962fef-amend-backup.hg (glob)
382 saved backup bundle to $TESTTMP/.hg/strip-backup/8ac881fbf49d-fd962fef-amend-backup.hg (glob)
383 $ hg branches
383 $ hg branches
384 default 2:ce12b0b57d46
384 default 2:ce12b0b57d46
385
385
386 Close branch:
386 Close branch:
387
387
388 $ hg up -q 0
388 $ hg up -q 0
389 $ echo b >> b
389 $ echo b >> b
390 $ hg branch foo
390 $ hg branch foo
391 marked working directory as branch foo
391 marked working directory as branch foo
392 (branches are permanent and global, did you want a bookmark?)
392 (branches are permanent and global, did you want a bookmark?)
393 $ hg ci -Am 'fork'
393 $ hg ci -Am 'fork'
394 adding b
394 adding b
395 $ echo b >> b
395 $ echo b >> b
396 $ hg ci -mb
396 $ hg ci -mb
397 $ hg ci --amend --close-branch -m 'closing branch foo'
397 $ hg ci --amend --close-branch -m 'closing branch foo'
398 saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-6701c392-amend-backup.hg (glob)
398 saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-6701c392-amend-backup.hg (glob)
399
399
400 Same thing, different code path:
400 Same thing, different code path:
401
401
402 $ echo b >> b
402 $ echo b >> b
403 $ hg ci -m 'reopen branch'
403 $ hg ci -m 'reopen branch'
404 reopening closed branch head 4
404 reopening closed branch head 4
405 $ echo b >> b
405 $ echo b >> b
406 $ hg ci --amend --close-branch
406 $ hg ci --amend --close-branch
407 saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-49c0c55d-amend-backup.hg (glob)
407 saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-49c0c55d-amend-backup.hg (glob)
408 $ hg branches
408 $ hg branches
409 default 2:ce12b0b57d46
409 default 2:ce12b0b57d46
410
410
411 Refuse to amend during a merge:
411 Refuse to amend during a merge:
412
412
413 $ hg up -q default
413 $ hg up -q default
414 $ hg merge foo
414 $ hg merge foo
415 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
415 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
416 (branch merge, don't forget to commit)
416 (branch merge, don't forget to commit)
417 $ hg ci --amend
417 $ hg ci --amend
418 abort: cannot amend while merging
418 abort: cannot amend while merging
419 [255]
419 [255]
420 $ hg ci -m 'merge'
420 $ hg ci -m 'merge'
421
421
422 Follow copies/renames:
422 Follow copies/renames:
423
423
424 $ hg mv b c
424 $ hg mv b c
425 $ hg ci -m 'b -> c'
425 $ hg ci -m 'b -> c'
426 $ hg mv c d
426 $ hg mv c d
427 $ hg ci --amend -m 'b -> d'
427 $ hg ci --amend -m 'b -> d'
428 saved backup bundle to $TESTTMP/.hg/strip-backup/b8c6eac7f12e-adaaa8b1-amend-backup.hg (glob)
428 saved backup bundle to $TESTTMP/.hg/strip-backup/b8c6eac7f12e-adaaa8b1-amend-backup.hg (glob)
429 $ hg st --rev '.^' --copies d
429 $ hg st --rev '.^' --copies d
430 A d
430 A d
431 b
431 b
432 $ hg cp d e
432 $ hg cp d e
433 $ hg ci -m 'e = d'
433 $ hg ci -m 'e = d'
434 $ hg cp e f
434 $ hg cp e f
435 $ hg ci --amend -m 'f = d'
435 $ hg ci --amend -m 'f = d'
436 saved backup bundle to $TESTTMP/.hg/strip-backup/7f9761d65613-d37aa788-amend-backup.hg (glob)
436 saved backup bundle to $TESTTMP/.hg/strip-backup/7f9761d65613-d37aa788-amend-backup.hg (glob)
437 $ hg st --rev '.^' --copies f
437 $ hg st --rev '.^' --copies f
438 A f
438 A f
439 d
439 d
440
440
441 $ mv f f.orig
441 $ mv f f.orig
442 $ hg rm -A f
442 $ hg rm -A f
443 $ hg ci -m removef
443 $ hg ci -m removef
444 $ hg cp a f
444 $ hg cp a f
445 $ mv f.orig f
445 $ mv f.orig f
446 $ hg ci --amend -m replacef
446 $ hg ci --amend -m replacef
447 saved backup bundle to $TESTTMP/.hg/strip-backup/9e8c5f7e3d95-90259f67-amend-backup.hg (glob)
447 saved backup bundle to $TESTTMP/.hg/strip-backup/9e8c5f7e3d95-90259f67-amend-backup.hg (glob)
448 $ hg st --change . --copies
448 $ hg st --change . --copies
449 $ hg log -r . --template "{file_copies}\n"
449 $ hg log -r . --template "{file_copies}\n"
450
450
451
451
452 Move added file (issue3410):
452 Move added file (issue3410):
453
453
454 $ echo g >> g
454 $ echo g >> g
455 $ hg ci -Am g
455 $ hg ci -Am g
456 adding g
456 adding g
457 $ hg mv g h
457 $ hg mv g h
458 $ hg ci --amend
458 $ hg ci --amend
459 saved backup bundle to $TESTTMP/.hg/strip-backup/24aa8eacce2b-7059e0f1-amend-backup.hg (glob)
459 saved backup bundle to $TESTTMP/.hg/strip-backup/24aa8eacce2b-7059e0f1-amend-backup.hg (glob)
460 $ hg st --change . --copies h
460 $ hg st --change . --copies h
461 A h
461 A h
462 $ hg log -r . --template "{file_copies}\n"
462 $ hg log -r . --template "{file_copies}\n"
463
463
464
464
465 Can't rollback an amend:
465 Can't rollback an amend:
466
466
467 $ hg rollback
467 $ hg rollback
468 no rollback information available
468 no rollback information available
469 [1]
469 [1]
470
470
471 Preserve extra dict (issue3430):
471 Preserve extra dict (issue3430):
472
472
473 $ hg branch a
473 $ hg branch a
474 marked working directory as branch a
474 marked working directory as branch a
475 (branches are permanent and global, did you want a bookmark?)
475 (branches are permanent and global, did you want a bookmark?)
476 $ echo a >> a
476 $ echo a >> a
477 $ hg ci -ma
477 $ hg ci -ma
478 $ hg ci --amend -m "a'"
478 $ hg ci --amend -m "a'"
479 saved backup bundle to $TESTTMP/.hg/strip-backup/3837aa2a2fdb-2be01fd1-amend-backup.hg (glob)
479 saved backup bundle to $TESTTMP/.hg/strip-backup/3837aa2a2fdb-2be01fd1-amend-backup.hg (glob)
480 $ hg log -r . --template "{branch}\n"
480 $ hg log -r . --template "{branch}\n"
481 a
481 a
482 $ hg ci --amend -m "a''"
482 $ hg ci --amend -m "a''"
483 saved backup bundle to $TESTTMP/.hg/strip-backup/c05c06be7514-ed28c4cd-amend-backup.hg (glob)
483 saved backup bundle to $TESTTMP/.hg/strip-backup/c05c06be7514-ed28c4cd-amend-backup.hg (glob)
484 $ hg log -r . --template "{branch}\n"
484 $ hg log -r . --template "{branch}\n"
485 a
485 a
486
486
487 Also preserve other entries in the dict that are in the old commit,
487 Also preserve other entries in the dict that are in the old commit,
488 first graft something so there's an additional entry:
488 first graft something so there's an additional entry:
489
489
490 $ hg up 0 -q
490 $ hg up 0 -q
491 $ echo z > z
491 $ echo z > z
492 $ hg ci -Am 'fork'
492 $ hg ci -Am 'fork'
493 adding z
493 adding z
494 created new head
494 created new head
495 $ hg up 11
495 $ hg up 11
496 5 files updated, 0 files merged, 1 files removed, 0 files unresolved
496 5 files updated, 0 files merged, 1 files removed, 0 files unresolved
497 $ hg graft 12
497 $ hg graft 12
498 grafting 12:2647734878ef "fork" (tip)
498 grafting 12:2647734878ef "fork" (tip)
499 $ hg ci --amend -m 'graft amend'
499 $ hg ci --amend -m 'graft amend'
500 saved backup bundle to $TESTTMP/.hg/strip-backup/bd010aea3f39-eedb103b-amend-backup.hg (glob)
500 saved backup bundle to $TESTTMP/.hg/strip-backup/bd010aea3f39-eedb103b-amend-backup.hg (glob)
501 $ hg log -r . --debug | grep extra
501 $ hg log -r . --debug | grep extra
502 extra: amend_source=bd010aea3f39f3fb2a2f884b9ccb0471cd77398e
502 extra: amend_source=bd010aea3f39f3fb2a2f884b9ccb0471cd77398e
503 extra: branch=a
503 extra: branch=a
504 extra: source=2647734878ef0236dda712fae9c1651cf694ea8a
504 extra: source=2647734878ef0236dda712fae9c1651cf694ea8a
505
505
506 Preserve phase
506 Preserve phase
507
507
508 $ hg phase '.^::.'
508 $ hg phase '.^::.'
509 11: draft
509 11: draft
510 13: draft
510 13: draft
511 $ hg phase --secret --force .
511 $ hg phase --secret --force .
512 $ hg phase '.^::.'
512 $ hg phase '.^::.'
513 11: draft
513 11: draft
514 13: secret
514 13: secret
515 $ hg commit --amend -m 'amend for phase' -q
515 $ hg commit --amend -m 'amend for phase' -q
516 $ hg phase '.^::.'
516 $ hg phase '.^::.'
517 11: draft
517 11: draft
518 13: secret
518 13: secret
519
519
520 Test amend with obsolete
520 Test amend with obsolete
521 ---------------------------
521 ---------------------------
522
522
523 Enable obsolete
523 Enable obsolete
524
524
525 $ cat >> $HGRCPATH << EOF
525 $ cat >> $HGRCPATH << EOF
526 > [experimental]
526 > [experimental]
527 > evolution=createmarkers,allowunstable
527 > evolution=createmarkers,allowunstable
528 > EOF
528 > EOF
529
529
530 Amend with no files changes
530 Amend with no files changes
531
531
532 $ hg id -n
532 $ hg id -n
533 13
533 13
534 $ hg ci --amend -m 'babar'
534 $ hg ci --amend -m 'babar'
535 $ hg id -n
535 $ hg id -n
536 14
536 14
537 $ hg log -Gl 3 --style=compact
537 $ hg log -Gl 3 --style=compact
538 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
538 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
539 | babar
539 | babar
540 |
540 |
541 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
541 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
542 | | fork
542 | | fork
543 | ~
543 | ~
544 o 11 3334b7925910 1970-01-01 00:00 +0000 test
544 o 11 3334b7925910 1970-01-01 00:00 +0000 test
545 | a''
545 | a''
546 ~
546 ~
547 $ hg log -Gl 4 --hidden --style=compact
547 $ hg log -Gl 4 --hidden --style=compact
548 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
548 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
549 | babar
549 | babar
550 |
550 |
551 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
551 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
552 |/ amend for phase
552 |/ amend for phase
553 |
553 |
554 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
554 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
555 | | fork
555 | | fork
556 | ~
556 | ~
557 o 11 3334b7925910 1970-01-01 00:00 +0000 test
557 o 11 3334b7925910 1970-01-01 00:00 +0000 test
558 | a''
558 | a''
559 ~
559 ~
560
560
561 Amend with files changes
561 Amend with files changes
562
562
563 (note: the extra commit over 15 is a temporary junk I would be happy to get
563 (note: the extra commit over 15 is a temporary junk I would be happy to get
564 ride of)
564 ride of)
565
565
566 $ echo 'babar' >> a
566 $ echo 'babar' >> a
567 $ hg commit --amend
567 $ hg commit --amend
568 $ hg log -Gl 6 --hidden --style=compact
568 $ hg log -Gl 6 --hidden --style=compact
569 @ 16[tip]:11 9f9e9bccf56c 1970-01-01 00:00 +0000 test
569 @ 16[tip]:11 9f9e9bccf56c 1970-01-01 00:00 +0000 test
570 | babar
570 | babar
571 |
571 |
572 | x 15 90fef497c56f 1970-01-01 00:00 +0000 test
572 | x 15 90fef497c56f 1970-01-01 00:00 +0000 test
573 | | temporary amend commit for b650e6ee8614
573 | | temporary amend commit for b650e6ee8614
574 | |
574 | |
575 | x 14:11 b650e6ee8614 1970-01-01 00:00 +0000 test
575 | x 14:11 b650e6ee8614 1970-01-01 00:00 +0000 test
576 |/ babar
576 |/ babar
577 |
577 |
578 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
578 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
579 |/ amend for phase
579 |/ amend for phase
580 |
580 |
581 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
581 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
582 | | fork
582 | | fork
583 | ~
583 | ~
584 o 11 3334b7925910 1970-01-01 00:00 +0000 test
584 o 11 3334b7925910 1970-01-01 00:00 +0000 test
585 | a''
585 | a''
586 ~
586 ~
587
587
588
588
589 Test that amend does not make it easy to create obsolescence cycle
589 Test that amend does not make it easy to create obsolescence cycle
590 ---------------------------------------------------------------------
590 ---------------------------------------------------------------------
591
591
592 $ hg id -r 14 --hidden
592 $ hg id -r 14 --hidden
593 b650e6ee8614 (a)
593 b650e6ee8614 (a)
594 $ hg revert -ar 14 --hidden
594 $ hg revert -ar 14 --hidden
595 reverting a
595 reverting a
596 $ hg commit --amend
596 $ hg commit --amend
597 $ hg id
597 $ hg id
598 b99e5df575f7 (a) tip
598 b99e5df575f7 (a) tip
599
599
600 Test that rewriting leaving instability behind is allowed
600 Test that rewriting leaving instability behind is allowed
601 ---------------------------------------------------------------------
601 ---------------------------------------------------------------------
602
602
603 $ hg up '.^'
603 $ hg up '.^'
604 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
604 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
605 $ echo 'b' >> a
605 $ echo 'b' >> a
606 $ hg log --style compact -r 'children(.)'
606 $ hg log --style compact -r 'children(.)'
607 18[tip]:11 b99e5df575f7 1970-01-01 00:00 +0000 test
607 18[tip]:11 b99e5df575f7 1970-01-01 00:00 +0000 test
608 babar
608 babar
609
609
610 $ hg commit --amend
610 $ hg commit --amend
611 $ hg log -r 'unstable()'
611 $ hg log -r 'unstable()'
612 changeset: 18:b99e5df575f7
612 changeset: 18:b99e5df575f7
613 branch: a
613 branch: a
614 parent: 11:3334b7925910
614 parent: 11:3334b7925910
615 user: test
615 user: test
616 date: Thu Jan 01 00:00:00 1970 +0000
616 date: Thu Jan 01 00:00:00 1970 +0000
617 trouble: unstable
617 summary: babar
618 summary: babar
618
619
619
620
620 Amend a merge changeset (with renames and conflicts from the second parent):
621 Amend a merge changeset (with renames and conflicts from the second parent):
621
622
622 $ hg up -q default
623 $ hg up -q default
623 $ hg branch -q bar
624 $ hg branch -q bar
624 $ hg cp a aa
625 $ hg cp a aa
625 $ hg mv z zz
626 $ hg mv z zz
626 $ echo cc > cc
627 $ echo cc > cc
627 $ hg add cc
628 $ hg add cc
628 $ hg ci -m aazzcc
629 $ hg ci -m aazzcc
629 $ hg up -q default
630 $ hg up -q default
630 $ echo a >> a
631 $ echo a >> a
631 $ echo dd > cc
632 $ echo dd > cc
632 $ hg add cc
633 $ hg add cc
633 $ hg ci -m aa
634 $ hg ci -m aa
634 $ hg merge -q bar
635 $ hg merge -q bar
635 warning: conflicts while merging cc! (edit, then use 'hg resolve --mark')
636 warning: conflicts while merging cc! (edit, then use 'hg resolve --mark')
636 [1]
637 [1]
637 $ hg resolve -m cc
638 $ hg resolve -m cc
638 (no more unresolved files)
639 (no more unresolved files)
639 $ hg ci -m 'merge bar'
640 $ hg ci -m 'merge bar'
640 $ hg log --config diff.git=1 -pr .
641 $ hg log --config diff.git=1 -pr .
641 changeset: 23:163cfd7219f7
642 changeset: 23:163cfd7219f7
642 tag: tip
643 tag: tip
643 parent: 22:30d96aeaf27b
644 parent: 22:30d96aeaf27b
644 parent: 21:1aa437659d19
645 parent: 21:1aa437659d19
645 user: test
646 user: test
646 date: Thu Jan 01 00:00:00 1970 +0000
647 date: Thu Jan 01 00:00:00 1970 +0000
647 summary: merge bar
648 summary: merge bar
648
649
649 diff --git a/a b/aa
650 diff --git a/a b/aa
650 copy from a
651 copy from a
651 copy to aa
652 copy to aa
652 diff --git a/cc b/cc
653 diff --git a/cc b/cc
653 --- a/cc
654 --- a/cc
654 +++ b/cc
655 +++ b/cc
655 @@ -1,1 +1,5 @@
656 @@ -1,1 +1,5 @@
656 +<<<<<<< working copy: 30d96aeaf27b - test: aa
657 +<<<<<<< working copy: 30d96aeaf27b - test: aa
657 dd
658 dd
658 +=======
659 +=======
659 +cc
660 +cc
660 +>>>>>>> merge rev: 1aa437659d19 bar - test: aazzcc
661 +>>>>>>> merge rev: 1aa437659d19 bar - test: aazzcc
661 diff --git a/z b/zz
662 diff --git a/z b/zz
662 rename from z
663 rename from z
663 rename to zz
664 rename to zz
664
665
665 $ hg debugrename aa
666 $ hg debugrename aa
666 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
667 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
667 $ hg debugrename zz
668 $ hg debugrename zz
668 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
669 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
669 $ hg debugrename cc
670 $ hg debugrename cc
670 cc not renamed
671 cc not renamed
671 $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -m 'merge bar (amend message)' --edit
672 $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -m 'merge bar (amend message)' --edit
672 HGEDITFORM=commit.amend.merge
673 HGEDITFORM=commit.amend.merge
673 $ hg log --config diff.git=1 -pr .
674 $ hg log --config diff.git=1 -pr .
674 changeset: 24:bca52d4ed186
675 changeset: 24:bca52d4ed186
675 tag: tip
676 tag: tip
676 parent: 22:30d96aeaf27b
677 parent: 22:30d96aeaf27b
677 parent: 21:1aa437659d19
678 parent: 21:1aa437659d19
678 user: test
679 user: test
679 date: Thu Jan 01 00:00:00 1970 +0000
680 date: Thu Jan 01 00:00:00 1970 +0000
680 summary: merge bar (amend message)
681 summary: merge bar (amend message)
681
682
682 diff --git a/a b/aa
683 diff --git a/a b/aa
683 copy from a
684 copy from a
684 copy to aa
685 copy to aa
685 diff --git a/cc b/cc
686 diff --git a/cc b/cc
686 --- a/cc
687 --- a/cc
687 +++ b/cc
688 +++ b/cc
688 @@ -1,1 +1,5 @@
689 @@ -1,1 +1,5 @@
689 +<<<<<<< working copy: 30d96aeaf27b - test: aa
690 +<<<<<<< working copy: 30d96aeaf27b - test: aa
690 dd
691 dd
691 +=======
692 +=======
692 +cc
693 +cc
693 +>>>>>>> merge rev: 1aa437659d19 bar - test: aazzcc
694 +>>>>>>> merge rev: 1aa437659d19 bar - test: aazzcc
694 diff --git a/z b/zz
695 diff --git a/z b/zz
695 rename from z
696 rename from z
696 rename to zz
697 rename to zz
697
698
698 $ hg debugrename aa
699 $ hg debugrename aa
699 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
700 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
700 $ hg debugrename zz
701 $ hg debugrename zz
701 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
702 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
702 $ hg debugrename cc
703 $ hg debugrename cc
703 cc not renamed
704 cc not renamed
704 $ hg mv zz z
705 $ hg mv zz z
705 $ hg ci --amend -m 'merge bar (undo rename)'
706 $ hg ci --amend -m 'merge bar (undo rename)'
706 $ hg log --config diff.git=1 -pr .
707 $ hg log --config diff.git=1 -pr .
707 changeset: 26:12594a98ca3f
708 changeset: 26:12594a98ca3f
708 tag: tip
709 tag: tip
709 parent: 22:30d96aeaf27b
710 parent: 22:30d96aeaf27b
710 parent: 21:1aa437659d19
711 parent: 21:1aa437659d19
711 user: test
712 user: test
712 date: Thu Jan 01 00:00:00 1970 +0000
713 date: Thu Jan 01 00:00:00 1970 +0000
713 summary: merge bar (undo rename)
714 summary: merge bar (undo rename)
714
715
715 diff --git a/a b/aa
716 diff --git a/a b/aa
716 copy from a
717 copy from a
717 copy to aa
718 copy to aa
718 diff --git a/cc b/cc
719 diff --git a/cc b/cc
719 --- a/cc
720 --- a/cc
720 +++ b/cc
721 +++ b/cc
721 @@ -1,1 +1,5 @@
722 @@ -1,1 +1,5 @@
722 +<<<<<<< working copy: 30d96aeaf27b - test: aa
723 +<<<<<<< working copy: 30d96aeaf27b - test: aa
723 dd
724 dd
724 +=======
725 +=======
725 +cc
726 +cc
726 +>>>>>>> merge rev: 1aa437659d19 bar - test: aazzcc
727 +>>>>>>> merge rev: 1aa437659d19 bar - test: aazzcc
727
728
728 $ hg debugrename z
729 $ hg debugrename z
729 z not renamed
730 z not renamed
730
731
731 Amend a merge changeset (with renames during the merge):
732 Amend a merge changeset (with renames during the merge):
732
733
733 $ hg up -q bar
734 $ hg up -q bar
734 $ echo x > x
735 $ echo x > x
735 $ hg add x
736 $ hg add x
736 $ hg ci -m x
737 $ hg ci -m x
737 $ hg up -q default
738 $ hg up -q default
738 $ hg merge -q bar
739 $ hg merge -q bar
739 $ hg mv aa aaa
740 $ hg mv aa aaa
740 $ echo aa >> aaa
741 $ echo aa >> aaa
741 $ hg ci -m 'merge bar again'
742 $ hg ci -m 'merge bar again'
742 $ hg log --config diff.git=1 -pr .
743 $ hg log --config diff.git=1 -pr .
743 changeset: 28:dffde028b388
744 changeset: 28:dffde028b388
744 tag: tip
745 tag: tip
745 parent: 26:12594a98ca3f
746 parent: 26:12594a98ca3f
746 parent: 27:4c94d5bc65f5
747 parent: 27:4c94d5bc65f5
747 user: test
748 user: test
748 date: Thu Jan 01 00:00:00 1970 +0000
749 date: Thu Jan 01 00:00:00 1970 +0000
749 summary: merge bar again
750 summary: merge bar again
750
751
751 diff --git a/aa b/aa
752 diff --git a/aa b/aa
752 deleted file mode 100644
753 deleted file mode 100644
753 --- a/aa
754 --- a/aa
754 +++ /dev/null
755 +++ /dev/null
755 @@ -1,2 +0,0 @@
756 @@ -1,2 +0,0 @@
756 -a
757 -a
757 -a
758 -a
758 diff --git a/aaa b/aaa
759 diff --git a/aaa b/aaa
759 new file mode 100644
760 new file mode 100644
760 --- /dev/null
761 --- /dev/null
761 +++ b/aaa
762 +++ b/aaa
762 @@ -0,0 +1,3 @@
763 @@ -0,0 +1,3 @@
763 +a
764 +a
764 +a
765 +a
765 +aa
766 +aa
766 diff --git a/x b/x
767 diff --git a/x b/x
767 new file mode 100644
768 new file mode 100644
768 --- /dev/null
769 --- /dev/null
769 +++ b/x
770 +++ b/x
770 @@ -0,0 +1,1 @@
771 @@ -0,0 +1,1 @@
771 +x
772 +x
772
773
773 $ hg debugrename aaa
774 $ hg debugrename aaa
774 aaa renamed from aa:37d9b5d994eab34eda9c16b195ace52c7b129980
775 aaa renamed from aa:37d9b5d994eab34eda9c16b195ace52c7b129980
775 $ hg mv aaa aa
776 $ hg mv aaa aa
776 $ hg ci --amend -m 'merge bar again (undo rename)'
777 $ hg ci --amend -m 'merge bar again (undo rename)'
777 $ hg log --config diff.git=1 -pr .
778 $ hg log --config diff.git=1 -pr .
778 changeset: 30:18e3ba160489
779 changeset: 30:18e3ba160489
779 tag: tip
780 tag: tip
780 parent: 26:12594a98ca3f
781 parent: 26:12594a98ca3f
781 parent: 27:4c94d5bc65f5
782 parent: 27:4c94d5bc65f5
782 user: test
783 user: test
783 date: Thu Jan 01 00:00:00 1970 +0000
784 date: Thu Jan 01 00:00:00 1970 +0000
784 summary: merge bar again (undo rename)
785 summary: merge bar again (undo rename)
785
786
786 diff --git a/aa b/aa
787 diff --git a/aa b/aa
787 --- a/aa
788 --- a/aa
788 +++ b/aa
789 +++ b/aa
789 @@ -1,2 +1,3 @@
790 @@ -1,2 +1,3 @@
790 a
791 a
791 a
792 a
792 +aa
793 +aa
793 diff --git a/x b/x
794 diff --git a/x b/x
794 new file mode 100644
795 new file mode 100644
795 --- /dev/null
796 --- /dev/null
796 +++ b/x
797 +++ b/x
797 @@ -0,0 +1,1 @@
798 @@ -0,0 +1,1 @@
798 +x
799 +x
799
800
800 $ hg debugrename aa
801 $ hg debugrename aa
801 aa not renamed
802 aa not renamed
802 $ hg debugrename -r '.^' aa
803 $ hg debugrename -r '.^' aa
803 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
804 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
804
805
805 Amend a merge changeset (with manifest-level conflicts):
806 Amend a merge changeset (with manifest-level conflicts):
806
807
807 $ hg up -q bar
808 $ hg up -q bar
808 $ hg rm aa
809 $ hg rm aa
809 $ hg ci -m 'rm aa'
810 $ hg ci -m 'rm aa'
810 $ hg up -q default
811 $ hg up -q default
811 $ echo aa >> aa
812 $ echo aa >> aa
812 $ hg ci -m aa
813 $ hg ci -m aa
813 $ hg merge -q bar --config ui.interactive=True << EOF
814 $ hg merge -q bar --config ui.interactive=True << EOF
814 > c
815 > c
815 > EOF
816 > EOF
816 local [working copy] changed aa which other [merge rev] deleted
817 local [working copy] changed aa which other [merge rev] deleted
817 use (c)hanged version, (d)elete, or leave (u)nresolved? c
818 use (c)hanged version, (d)elete, or leave (u)nresolved? c
818 $ hg ci -m 'merge bar (with conflicts)'
819 $ hg ci -m 'merge bar (with conflicts)'
819 $ hg log --config diff.git=1 -pr .
820 $ hg log --config diff.git=1 -pr .
820 changeset: 33:b4c3035e2544
821 changeset: 33:b4c3035e2544
821 tag: tip
822 tag: tip
822 parent: 32:4b216ca5ba97
823 parent: 32:4b216ca5ba97
823 parent: 31:67db8847a540
824 parent: 31:67db8847a540
824 user: test
825 user: test
825 date: Thu Jan 01 00:00:00 1970 +0000
826 date: Thu Jan 01 00:00:00 1970 +0000
826 summary: merge bar (with conflicts)
827 summary: merge bar (with conflicts)
827
828
828
829
829 $ hg rm aa
830 $ hg rm aa
830 $ hg ci --amend -m 'merge bar (with conflicts, amended)'
831 $ hg ci --amend -m 'merge bar (with conflicts, amended)'
831 $ hg log --config diff.git=1 -pr .
832 $ hg log --config diff.git=1 -pr .
832 changeset: 35:1205ed810051
833 changeset: 35:1205ed810051
833 tag: tip
834 tag: tip
834 parent: 32:4b216ca5ba97
835 parent: 32:4b216ca5ba97
835 parent: 31:67db8847a540
836 parent: 31:67db8847a540
836 user: test
837 user: test
837 date: Thu Jan 01 00:00:00 1970 +0000
838 date: Thu Jan 01 00:00:00 1970 +0000
838 summary: merge bar (with conflicts, amended)
839 summary: merge bar (with conflicts, amended)
839
840
840 diff --git a/aa b/aa
841 diff --git a/aa b/aa
841 deleted file mode 100644
842 deleted file mode 100644
842 --- a/aa
843 --- a/aa
843 +++ /dev/null
844 +++ /dev/null
844 @@ -1,4 +0,0 @@
845 @@ -1,4 +0,0 @@
845 -a
846 -a
846 -a
847 -a
847 -aa
848 -aa
848 -aa
849 -aa
849
850
850 Issue 3445: amending with --close-branch a commit that created a new head should fail
851 Issue 3445: amending with --close-branch a commit that created a new head should fail
851 This shouldn't be possible:
852 This shouldn't be possible:
852
853
853 $ hg up -q default
854 $ hg up -q default
854 $ hg branch closewithamend
855 $ hg branch closewithamend
855 marked working directory as branch closewithamend
856 marked working directory as branch closewithamend
856 $ echo foo > foo
857 $ echo foo > foo
857 $ hg add foo
858 $ hg add foo
858 $ hg ci -m..
859 $ hg ci -m..
859 $ hg ci --amend --close-branch -m 'closing'
860 $ hg ci --amend --close-branch -m 'closing'
860 abort: can only close branch heads
861 abort: can only close branch heads
861 [255]
862 [255]
862
863
863 This silliness fails:
864 This silliness fails:
864
865
865 $ hg branch silliness
866 $ hg branch silliness
866 marked working directory as branch silliness
867 marked working directory as branch silliness
867 $ echo b >> b
868 $ echo b >> b
868 $ hg ci --close-branch -m'open and close'
869 $ hg ci --close-branch -m'open and close'
869 abort: can only close branch heads
870 abort: can only close branch heads
870 [255]
871 [255]
871
872
872 Test that amend with --secret creates new secret changeset forcibly
873 Test that amend with --secret creates new secret changeset forcibly
873 ---------------------------------------------------------------------
874 ---------------------------------------------------------------------
874
875
875 $ hg phase '.^::.'
876 $ hg phase '.^::.'
876 35: draft
877 35: draft
877 36: draft
878 36: draft
878 $ hg commit --amend --secret -m 'amend as secret' -q
879 $ hg commit --amend --secret -m 'amend as secret' -q
879 $ hg phase '.^::.'
880 $ hg phase '.^::.'
880 35: draft
881 35: draft
881 38: secret
882 38: secret
882
883
883 Test that amend with --edit invokes editor forcibly
884 Test that amend with --edit invokes editor forcibly
884 ---------------------------------------------------
885 ---------------------------------------------------
885
886
886 $ hg parents --template "{desc}\n"
887 $ hg parents --template "{desc}\n"
887 amend as secret
888 amend as secret
888 $ HGEDITOR=cat hg commit --amend -m "editor should be suppressed"
889 $ HGEDITOR=cat hg commit --amend -m "editor should be suppressed"
889 $ hg parents --template "{desc}\n"
890 $ hg parents --template "{desc}\n"
890 editor should be suppressed
891 editor should be suppressed
891
892
892 $ hg status --rev '.^1::.'
893 $ hg status --rev '.^1::.'
893 A foo
894 A foo
894 $ HGEDITOR=cat hg commit --amend -m "editor should be invoked" --edit
895 $ HGEDITOR=cat hg commit --amend -m "editor should be invoked" --edit
895 editor should be invoked
896 editor should be invoked
896
897
897
898
898 HG: Enter commit message. Lines beginning with 'HG:' are removed.
899 HG: Enter commit message. Lines beginning with 'HG:' are removed.
899 HG: Leave message empty to abort commit.
900 HG: Leave message empty to abort commit.
900 HG: --
901 HG: --
901 HG: user: test
902 HG: user: test
902 HG: branch 'silliness'
903 HG: branch 'silliness'
903 HG: added foo
904 HG: added foo
904 $ hg parents --template "{desc}\n"
905 $ hg parents --template "{desc}\n"
905 editor should be invoked
906 editor should be invoked
906
907
907 Test that "diff()" in committemplate works correctly for amending
908 Test that "diff()" in committemplate works correctly for amending
908 -----------------------------------------------------------------
909 -----------------------------------------------------------------
909
910
910 $ cat >> .hg/hgrc <<EOF
911 $ cat >> .hg/hgrc <<EOF
911 > [committemplate]
912 > [committemplate]
912 > changeset.commit.amend = {desc}\n
913 > changeset.commit.amend = {desc}\n
913 > HG: M: {file_mods}
914 > HG: M: {file_mods}
914 > HG: A: {file_adds}
915 > HG: A: {file_adds}
915 > HG: R: {file_dels}
916 > HG: R: {file_dels}
916 > {splitlines(diff()) % 'HG: {line}\n'}
917 > {splitlines(diff()) % 'HG: {line}\n'}
917 > EOF
918 > EOF
918
919
919 $ hg parents --template "M: {file_mods}\nA: {file_adds}\nR: {file_dels}\n"
920 $ hg parents --template "M: {file_mods}\nA: {file_adds}\nR: {file_dels}\n"
920 M:
921 M:
921 A: foo
922 A: foo
922 R:
923 R:
923 $ hg status -amr
924 $ hg status -amr
924 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of foo"
925 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of foo"
925 expecting diff of foo
926 expecting diff of foo
926
927
927 HG: M:
928 HG: M:
928 HG: A: foo
929 HG: A: foo
929 HG: R:
930 HG: R:
930 HG: diff -r 1205ed810051 foo
931 HG: diff -r 1205ed810051 foo
931 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
932 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
932 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
933 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
933 HG: @@ -0,0 +1,1 @@
934 HG: @@ -0,0 +1,1 @@
934 HG: +foo
935 HG: +foo
935
936
936 $ echo y > y
937 $ echo y > y
937 $ hg add y
938 $ hg add y
938 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of foo and y"
939 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of foo and y"
939 expecting diff of foo and y
940 expecting diff of foo and y
940
941
941 HG: M:
942 HG: M:
942 HG: A: foo y
943 HG: A: foo y
943 HG: R:
944 HG: R:
944 HG: diff -r 1205ed810051 foo
945 HG: diff -r 1205ed810051 foo
945 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
946 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
946 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
947 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
947 HG: @@ -0,0 +1,1 @@
948 HG: @@ -0,0 +1,1 @@
948 HG: +foo
949 HG: +foo
949 HG: diff -r 1205ed810051 y
950 HG: diff -r 1205ed810051 y
950 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
951 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
951 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
952 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
952 HG: @@ -0,0 +1,1 @@
953 HG: @@ -0,0 +1,1 @@
953 HG: +y
954 HG: +y
954
955
955 $ hg rm a
956 $ hg rm a
956 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of a, foo and y"
957 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of a, foo and y"
957 expecting diff of a, foo and y
958 expecting diff of a, foo and y
958
959
959 HG: M:
960 HG: M:
960 HG: A: foo y
961 HG: A: foo y
961 HG: R: a
962 HG: R: a
962 HG: diff -r 1205ed810051 a
963 HG: diff -r 1205ed810051 a
963 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
964 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
964 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
965 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
965 HG: @@ -1,2 +0,0 @@
966 HG: @@ -1,2 +0,0 @@
966 HG: -a
967 HG: -a
967 HG: -a
968 HG: -a
968 HG: diff -r 1205ed810051 foo
969 HG: diff -r 1205ed810051 foo
969 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
970 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
970 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
971 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
971 HG: @@ -0,0 +1,1 @@
972 HG: @@ -0,0 +1,1 @@
972 HG: +foo
973 HG: +foo
973 HG: diff -r 1205ed810051 y
974 HG: diff -r 1205ed810051 y
974 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
975 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
975 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
976 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
976 HG: @@ -0,0 +1,1 @@
977 HG: @@ -0,0 +1,1 @@
977 HG: +y
978 HG: +y
978
979
979 $ hg rm x
980 $ hg rm x
980 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of a, foo, x and y"
981 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of a, foo, x and y"
981 expecting diff of a, foo, x and y
982 expecting diff of a, foo, x and y
982
983
983 HG: M:
984 HG: M:
984 HG: A: foo y
985 HG: A: foo y
985 HG: R: a x
986 HG: R: a x
986 HG: diff -r 1205ed810051 a
987 HG: diff -r 1205ed810051 a
987 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
988 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
988 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
989 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
989 HG: @@ -1,2 +0,0 @@
990 HG: @@ -1,2 +0,0 @@
990 HG: -a
991 HG: -a
991 HG: -a
992 HG: -a
992 HG: diff -r 1205ed810051 foo
993 HG: diff -r 1205ed810051 foo
993 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
994 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
994 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
995 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
995 HG: @@ -0,0 +1,1 @@
996 HG: @@ -0,0 +1,1 @@
996 HG: +foo
997 HG: +foo
997 HG: diff -r 1205ed810051 x
998 HG: diff -r 1205ed810051 x
998 HG: --- a/x Thu Jan 01 00:00:00 1970 +0000
999 HG: --- a/x Thu Jan 01 00:00:00 1970 +0000
999 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1000 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1000 HG: @@ -1,1 +0,0 @@
1001 HG: @@ -1,1 +0,0 @@
1001 HG: -x
1002 HG: -x
1002 HG: diff -r 1205ed810051 y
1003 HG: diff -r 1205ed810051 y
1003 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1004 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1004 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
1005 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
1005 HG: @@ -0,0 +1,1 @@
1006 HG: @@ -0,0 +1,1 @@
1006 HG: +y
1007 HG: +y
1007
1008
1008 $ echo cccc >> cc
1009 $ echo cccc >> cc
1009 $ hg status -amr
1010 $ hg status -amr
1010 M cc
1011 M cc
1011 $ HGEDITOR=cat hg commit --amend -e -m "cc should be excluded" -X cc
1012 $ HGEDITOR=cat hg commit --amend -e -m "cc should be excluded" -X cc
1012 cc should be excluded
1013 cc should be excluded
1013
1014
1014 HG: M:
1015 HG: M:
1015 HG: A: foo y
1016 HG: A: foo y
1016 HG: R: a x
1017 HG: R: a x
1017 HG: diff -r 1205ed810051 a
1018 HG: diff -r 1205ed810051 a
1018 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
1019 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
1019 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1020 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1020 HG: @@ -1,2 +0,0 @@
1021 HG: @@ -1,2 +0,0 @@
1021 HG: -a
1022 HG: -a
1022 HG: -a
1023 HG: -a
1023 HG: diff -r 1205ed810051 foo
1024 HG: diff -r 1205ed810051 foo
1024 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1025 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1025 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
1026 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
1026 HG: @@ -0,0 +1,1 @@
1027 HG: @@ -0,0 +1,1 @@
1027 HG: +foo
1028 HG: +foo
1028 HG: diff -r 1205ed810051 x
1029 HG: diff -r 1205ed810051 x
1029 HG: --- a/x Thu Jan 01 00:00:00 1970 +0000
1030 HG: --- a/x Thu Jan 01 00:00:00 1970 +0000
1030 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1031 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1031 HG: @@ -1,1 +0,0 @@
1032 HG: @@ -1,1 +0,0 @@
1032 HG: -x
1033 HG: -x
1033 HG: diff -r 1205ed810051 y
1034 HG: diff -r 1205ed810051 y
1034 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1035 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1035 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
1036 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
1036 HG: @@ -0,0 +1,1 @@
1037 HG: @@ -0,0 +1,1 @@
1037 HG: +y
1038 HG: +y
1038
1039
1039 Check for issue4405
1040 Check for issue4405
1040 -------------------
1041 -------------------
1041
1042
1042 Setup the repo with a file that gets moved in a second commit.
1043 Setup the repo with a file that gets moved in a second commit.
1043 $ hg init repo
1044 $ hg init repo
1044 $ cd repo
1045 $ cd repo
1045 $ touch a0
1046 $ touch a0
1046 $ hg add a0
1047 $ hg add a0
1047 $ hg commit -m a0
1048 $ hg commit -m a0
1048 $ hg mv a0 a1
1049 $ hg mv a0 a1
1049 $ hg commit -m a1
1050 $ hg commit -m a1
1050 $ hg up -q 0
1051 $ hg up -q 0
1051 $ hg log -G --template '{rev} {desc}'
1052 $ hg log -G --template '{rev} {desc}'
1052 o 1 a1
1053 o 1 a1
1053 |
1054 |
1054 @ 0 a0
1055 @ 0 a0
1055
1056
1056
1057
1057 Now we branch the repro, but re-use the file contents, so we have a divergence
1058 Now we branch the repro, but re-use the file contents, so we have a divergence
1058 in the file revlog topology and the changelog topology.
1059 in the file revlog topology and the changelog topology.
1059 $ hg revert --rev 1 --all
1060 $ hg revert --rev 1 --all
1060 removing a0
1061 removing a0
1061 adding a1
1062 adding a1
1062 $ hg ci -qm 'a1-amend'
1063 $ hg ci -qm 'a1-amend'
1063 $ hg log -G --template '{rev} {desc}'
1064 $ hg log -G --template '{rev} {desc}'
1064 @ 2 a1-amend
1065 @ 2 a1-amend
1065 |
1066 |
1066 | o 1 a1
1067 | o 1 a1
1067 |/
1068 |/
1068 o 0 a0
1069 o 0 a0
1069
1070
1070
1071
1071 The way mercurial does amends is to create a temporary commit (rev 3) and then
1072 The way mercurial does amends is to create a temporary commit (rev 3) and then
1072 fold the new and old commits together into another commit (rev 4). During this
1073 fold the new and old commits together into another commit (rev 4). During this
1073 process, _findlimit is called to check how far back to look for the transitive
1074 process, _findlimit is called to check how far back to look for the transitive
1074 closure of file copy information, but due to the divergence of the filelog
1075 closure of file copy information, but due to the divergence of the filelog
1075 and changelog graph topologies, before _findlimit was fixed, it returned a rev
1076 and changelog graph topologies, before _findlimit was fixed, it returned a rev
1076 which was not far enough back in this case.
1077 which was not far enough back in this case.
1077 $ hg mv a1 a2
1078 $ hg mv a1 a2
1078 $ hg status --copies --rev 0
1079 $ hg status --copies --rev 0
1079 A a2
1080 A a2
1080 a0
1081 a0
1081 R a0
1082 R a0
1082 $ hg ci --amend -q
1083 $ hg ci --amend -q
1083 $ hg log -G --template '{rev} {desc}'
1084 $ hg log -G --template '{rev} {desc}'
1084 @ 4 a1-amend
1085 @ 4 a1-amend
1085 |
1086 |
1086 | o 1 a1
1087 | o 1 a1
1087 |/
1088 |/
1088 o 0 a0
1089 o 0 a0
1089
1090
1090
1091
1091 Before the fix, the copy information was lost.
1092 Before the fix, the copy information was lost.
1092 $ hg status --copies --rev 0
1093 $ hg status --copies --rev 0
1093 A a2
1094 A a2
1094 a0
1095 a0
1095 R a0
1096 R a0
1096 $ cd ..
1097 $ cd ..
1097
1098
1098 Check that amend properly preserve rename from directory rename (issue-4516)
1099 Check that amend properly preserve rename from directory rename (issue-4516)
1099
1100
1100 If a parent of the merge renames a full directory, any files added to the old
1101 If a parent of the merge renames a full directory, any files added to the old
1101 directory in the other parent will be renamed to the new directory. For some
1102 directory in the other parent will be renamed to the new directory. For some
1102 reason, the rename metadata was when amending such merge. This test ensure we
1103 reason, the rename metadata was when amending such merge. This test ensure we
1103 do not regress. We have a dedicated repo because it needs a setup with renamed
1104 do not regress. We have a dedicated repo because it needs a setup with renamed
1104 directory)
1105 directory)
1105
1106
1106 $ hg init issue4516
1107 $ hg init issue4516
1107 $ cd issue4516
1108 $ cd issue4516
1108 $ mkdir olddirname
1109 $ mkdir olddirname
1109 $ echo line1 > olddirname/commonfile.py
1110 $ echo line1 > olddirname/commonfile.py
1110 $ hg add olddirname/commonfile.py
1111 $ hg add olddirname/commonfile.py
1111 $ hg ci -m first
1112 $ hg ci -m first
1112
1113
1113 $ hg branch newdirname
1114 $ hg branch newdirname
1114 marked working directory as branch newdirname
1115 marked working directory as branch newdirname
1115 (branches are permanent and global, did you want a bookmark?)
1116 (branches are permanent and global, did you want a bookmark?)
1116 $ hg mv olddirname newdirname
1117 $ hg mv olddirname newdirname
1117 moving olddirname/commonfile.py to newdirname/commonfile.py (glob)
1118 moving olddirname/commonfile.py to newdirname/commonfile.py (glob)
1118 $ hg ci -m rename
1119 $ hg ci -m rename
1119
1120
1120 $ hg update default
1121 $ hg update default
1121 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1122 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1122 $ echo line1 > olddirname/newfile.py
1123 $ echo line1 > olddirname/newfile.py
1123 $ hg add olddirname/newfile.py
1124 $ hg add olddirname/newfile.py
1124 $ hg ci -m log
1125 $ hg ci -m log
1125
1126
1126 $ hg up newdirname
1127 $ hg up newdirname
1127 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
1128 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
1128 $ # create newdirname/newfile.py
1129 $ # create newdirname/newfile.py
1129 $ hg merge default
1130 $ hg merge default
1130 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1131 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1131 (branch merge, don't forget to commit)
1132 (branch merge, don't forget to commit)
1132 $ hg ci -m add
1133 $ hg ci -m add
1133 $
1134 $
1134 $ hg debugrename newdirname/newfile.py
1135 $ hg debugrename newdirname/newfile.py
1135 newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob)
1136 newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob)
1136 $ hg status -C --change .
1137 $ hg status -C --change .
1137 A newdirname/newfile.py
1138 A newdirname/newfile.py
1138 $ hg status -C --rev 1
1139 $ hg status -C --rev 1
1139 A newdirname/newfile.py
1140 A newdirname/newfile.py
1140 $ hg status -C --rev 2
1141 $ hg status -C --rev 2
1141 A newdirname/commonfile.py
1142 A newdirname/commonfile.py
1142 olddirname/commonfile.py
1143 olddirname/commonfile.py
1143 A newdirname/newfile.py
1144 A newdirname/newfile.py
1144 olddirname/newfile.py
1145 olddirname/newfile.py
1145 R olddirname/commonfile.py
1146 R olddirname/commonfile.py
1146 R olddirname/newfile.py
1147 R olddirname/newfile.py
1147 $ hg debugindex newdirname/newfile.py
1148 $ hg debugindex newdirname/newfile.py
1148 rev offset length delta linkrev nodeid p1 p2
1149 rev offset length delta linkrev nodeid p1 p2
1149 0 0 88 -1 3 34a4d536c0c0 000000000000 000000000000
1150 0 0 88 -1 3 34a4d536c0c0 000000000000 000000000000
1150
1151
1151 $ echo a >> newdirname/commonfile.py
1152 $ echo a >> newdirname/commonfile.py
1152 $ hg ci --amend -m bug
1153 $ hg ci --amend -m bug
1153 $ hg debugrename newdirname/newfile.py
1154 $ hg debugrename newdirname/newfile.py
1154 newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob)
1155 newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob)
1155 $ hg debugindex newdirname/newfile.py
1156 $ hg debugindex newdirname/newfile.py
1156 rev offset length delta linkrev nodeid p1 p2
1157 rev offset length delta linkrev nodeid p1 p2
1157 0 0 88 -1 3 34a4d536c0c0 000000000000 000000000000
1158 0 0 88 -1 3 34a4d536c0c0 000000000000 000000000000
1158
1159
1159 #if execbit
1160 #if execbit
1160
1161
1161 Test if amend preserves executable bit changes
1162 Test if amend preserves executable bit changes
1162 $ chmod +x newdirname/commonfile.py
1163 $ chmod +x newdirname/commonfile.py
1163 $ hg ci -m chmod
1164 $ hg ci -m chmod
1164 $ hg ci --amend -m "chmod amended"
1165 $ hg ci --amend -m "chmod amended"
1165 $ hg ci --amend -m "chmod amended second time"
1166 $ hg ci --amend -m "chmod amended second time"
1166 $ hg log -p --git -r .
1167 $ hg log -p --git -r .
1167 changeset: 8:b1326f52dddf
1168 changeset: 8:b1326f52dddf
1168 branch: newdirname
1169 branch: newdirname
1169 tag: tip
1170 tag: tip
1170 parent: 5:7fd235f7cb2f
1171 parent: 5:7fd235f7cb2f
1171 user: test
1172 user: test
1172 date: Thu Jan 01 00:00:00 1970 +0000
1173 date: Thu Jan 01 00:00:00 1970 +0000
1173 summary: chmod amended second time
1174 summary: chmod amended second time
1174
1175
1175 diff --git a/newdirname/commonfile.py b/newdirname/commonfile.py
1176 diff --git a/newdirname/commonfile.py b/newdirname/commonfile.py
1176 old mode 100644
1177 old mode 100644
1177 new mode 100755
1178 new mode 100755
1178
1179
1179 #endif
1180 #endif
General Comments 0
You need to be logged in to leave comments. Login now