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