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