##// END OF EJS Templates
cmdutil: make in-memory changes visible to external editor (issue4378)...
FUJIWARA Katsunori -
r26750:9f9ec4ab default
parent child Browse files
Show More
@@ -1,3377 +1,3382 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, bin, nullid, nullrev, short
8 from node import hex, bin, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile, cStringIO, 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, commiting, shelving, etc.
66 what kind of filtering they are doing: reverting, commiting, 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 dictionnary of the changeset, please mutate it,
844 # - extra: the future extra dictionnary 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
2719 # make in-memory changes visible to external process
2720 tr = repo.currenttransaction()
2721 repo.dirstate.write(tr)
2722 pending = tr and tr.writepending() and repo.root
2723
2718 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2724 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2719 editform=editform)
2725 editform=editform, pending=pending)
2720
2721 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2726 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2722 os.chdir(olddir)
2727 os.chdir(olddir)
2723
2728
2724 if finishdesc:
2729 if finishdesc:
2725 text = finishdesc(text)
2730 text = finishdesc(text)
2726 if not text.strip():
2731 if not text.strip():
2727 raise error.Abort(_("empty commit message"))
2732 raise error.Abort(_("empty commit message"))
2728 if unchangedmessagedetection and editortext == templatetext:
2733 if unchangedmessagedetection and editortext == templatetext:
2729 raise error.Abort(_("commit message unchanged"))
2734 raise error.Abort(_("commit message unchanged"))
2730
2735
2731 return text
2736 return text
2732
2737
2733 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2738 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2734 ui = repo.ui
2739 ui = repo.ui
2735 tmpl, mapfile = gettemplate(ui, tmpl, None)
2740 tmpl, mapfile = gettemplate(ui, tmpl, None)
2736
2741
2737 try:
2742 try:
2738 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2743 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2739 except SyntaxError as inst:
2744 except SyntaxError as inst:
2740 raise error.Abort(inst.args[0])
2745 raise error.Abort(inst.args[0])
2741
2746
2742 for k, v in repo.ui.configitems('committemplate'):
2747 for k, v in repo.ui.configitems('committemplate'):
2743 if k != 'changeset':
2748 if k != 'changeset':
2744 t.t.cache[k] = v
2749 t.t.cache[k] = v
2745
2750
2746 if not extramsg:
2751 if not extramsg:
2747 extramsg = '' # ensure that extramsg is string
2752 extramsg = '' # ensure that extramsg is string
2748
2753
2749 ui.pushbuffer()
2754 ui.pushbuffer()
2750 t.show(ctx, extramsg=extramsg)
2755 t.show(ctx, extramsg=extramsg)
2751 return ui.popbuffer()
2756 return ui.popbuffer()
2752
2757
2753 def hgprefix(msg):
2758 def hgprefix(msg):
2754 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])
2755
2760
2756 def buildcommittext(repo, ctx, subs, extramsg):
2761 def buildcommittext(repo, ctx, subs, extramsg):
2757 edittext = []
2762 edittext = []
2758 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2763 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2759 if ctx.description():
2764 if ctx.description():
2760 edittext.append(ctx.description())
2765 edittext.append(ctx.description())
2761 edittext.append("")
2766 edittext.append("")
2762 edittext.append("") # Empty line between message and comments.
2767 edittext.append("") # Empty line between message and comments.
2763 edittext.append(hgprefix(_("Enter commit message."
2768 edittext.append(hgprefix(_("Enter commit message."
2764 " Lines beginning with 'HG:' are removed.")))
2769 " Lines beginning with 'HG:' are removed.")))
2765 edittext.append(hgprefix(extramsg))
2770 edittext.append(hgprefix(extramsg))
2766 edittext.append("HG: --")
2771 edittext.append("HG: --")
2767 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2772 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2768 if ctx.p2():
2773 if ctx.p2():
2769 edittext.append(hgprefix(_("branch merge")))
2774 edittext.append(hgprefix(_("branch merge")))
2770 if ctx.branch():
2775 if ctx.branch():
2771 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2776 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2772 if bookmarks.isactivewdirparent(repo):
2777 if bookmarks.isactivewdirparent(repo):
2773 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2778 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2774 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2779 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2775 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2780 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2776 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2781 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2777 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2782 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2778 if not added and not modified and not removed:
2783 if not added and not modified and not removed:
2779 edittext.append(hgprefix(_("no files changed")))
2784 edittext.append(hgprefix(_("no files changed")))
2780 edittext.append("")
2785 edittext.append("")
2781
2786
2782 return "\n".join(edittext)
2787 return "\n".join(edittext)
2783
2788
2784 def commitstatus(repo, node, branch, bheads=None, opts=None):
2789 def commitstatus(repo, node, branch, bheads=None, opts=None):
2785 if opts is None:
2790 if opts is None:
2786 opts = {}
2791 opts = {}
2787 ctx = repo[node]
2792 ctx = repo[node]
2788 parents = ctx.parents()
2793 parents = ctx.parents()
2789
2794
2790 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
2791 [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]):
2792 repo.ui.status(_('created new head\n'))
2797 repo.ui.status(_('created new head\n'))
2793 # The message is not printed for initial roots. For the other
2798 # The message is not printed for initial roots. For the other
2794 # changesets, it is printed in the following situations:
2799 # changesets, it is printed in the following situations:
2795 #
2800 #
2796 # Par column: for the 2 parents with ...
2801 # Par column: for the 2 parents with ...
2797 # N: null or no parent
2802 # N: null or no parent
2798 # B: parent is on another named branch
2803 # B: parent is on another named branch
2799 # C: parent is a regular non head changeset
2804 # C: parent is a regular non head changeset
2800 # H: parent was a branch head of the current branch
2805 # H: parent was a branch head of the current branch
2801 # Msg column: whether we print "created new head" message
2806 # Msg column: whether we print "created new head" message
2802 # In the following, it is assumed that there already exists some
2807 # In the following, it is assumed that there already exists some
2803 # initial branch heads of the current branch, otherwise nothing is
2808 # initial branch heads of the current branch, otherwise nothing is
2804 # printed anyway.
2809 # printed anyway.
2805 #
2810 #
2806 # Par Msg Comment
2811 # Par Msg Comment
2807 # N N y additional topo root
2812 # N N y additional topo root
2808 #
2813 #
2809 # B N y additional branch root
2814 # B N y additional branch root
2810 # C N y additional topo head
2815 # C N y additional topo head
2811 # H N n usual case
2816 # H N n usual case
2812 #
2817 #
2813 # B B y weird additional branch root
2818 # B B y weird additional branch root
2814 # C B y branch merge
2819 # C B y branch merge
2815 # H B n merge with named branch
2820 # H B n merge with named branch
2816 #
2821 #
2817 # C C y additional head from merge
2822 # C C y additional head from merge
2818 # C H n merge with a head
2823 # C H n merge with a head
2819 #
2824 #
2820 # H H n head merge: head count decreases
2825 # H H n head merge: head count decreases
2821
2826
2822 if not opts.get('close_branch'):
2827 if not opts.get('close_branch'):
2823 for r in parents:
2828 for r in parents:
2824 if r.closesbranch() and r.branch() == branch:
2829 if r.closesbranch() and r.branch() == branch:
2825 repo.ui.status(_('reopening closed branch head %d\n') % r)
2830 repo.ui.status(_('reopening closed branch head %d\n') % r)
2826
2831
2827 if repo.ui.debugflag:
2832 if repo.ui.debugflag:
2828 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()))
2829 elif repo.ui.verbose:
2834 elif repo.ui.verbose:
2830 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2835 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2831
2836
2832 def revert(ui, repo, ctx, parents, *pats, **opts):
2837 def revert(ui, repo, ctx, parents, *pats, **opts):
2833 parent, p2 = parents
2838 parent, p2 = parents
2834 node = ctx.node()
2839 node = ctx.node()
2835
2840
2836 mf = ctx.manifest()
2841 mf = ctx.manifest()
2837 if node == p2:
2842 if node == p2:
2838 parent = p2
2843 parent = p2
2839 if node == parent:
2844 if node == parent:
2840 pmf = mf
2845 pmf = mf
2841 else:
2846 else:
2842 pmf = None
2847 pmf = None
2843
2848
2844 # need all matching names in dirstate and manifest of target rev,
2849 # need all matching names in dirstate and manifest of target rev,
2845 # 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
2846 # but not other. in both cases, filesets should be evaluated against
2851 # but not other. in both cases, filesets should be evaluated against
2847 # workingctx to get consistent result (issue4497). this means 'set:**'
2852 # workingctx to get consistent result (issue4497). this means 'set:**'
2848 # cannot be used to select missing files from target rev.
2853 # cannot be used to select missing files from target rev.
2849
2854
2850 # `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
2851 # The mapping is in the form:
2856 # The mapping is in the form:
2852 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2857 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2853 names = {}
2858 names = {}
2854
2859
2855 wlock = repo.wlock()
2860 wlock = repo.wlock()
2856 try:
2861 try:
2857 ## filling of the `names` mapping
2862 ## filling of the `names` mapping
2858 # walk dirstate to fill `names`
2863 # walk dirstate to fill `names`
2859
2864
2860 interactive = opts.get('interactive', False)
2865 interactive = opts.get('interactive', False)
2861 wctx = repo[None]
2866 wctx = repo[None]
2862 m = scmutil.match(wctx, pats, opts)
2867 m = scmutil.match(wctx, pats, opts)
2863
2868
2864 # we'll need this later
2869 # we'll need this later
2865 targetsubs = sorted(s for s in wctx.substate if m(s))
2870 targetsubs = sorted(s for s in wctx.substate if m(s))
2866
2871
2867 if not m.always():
2872 if not m.always():
2868 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)):
2869 names[abs] = m.rel(abs), m.exact(abs)
2874 names[abs] = m.rel(abs), m.exact(abs)
2870
2875
2871 # walk target manifest to fill `names`
2876 # walk target manifest to fill `names`
2872
2877
2873 def badfn(path, msg):
2878 def badfn(path, msg):
2874 if path in names:
2879 if path in names:
2875 return
2880 return
2876 if path in ctx.substate:
2881 if path in ctx.substate:
2877 return
2882 return
2878 path_ = path + '/'
2883 path_ = path + '/'
2879 for f in names:
2884 for f in names:
2880 if f.startswith(path_):
2885 if f.startswith(path_):
2881 return
2886 return
2882 ui.warn("%s: %s\n" % (m.rel(path), msg))
2887 ui.warn("%s: %s\n" % (m.rel(path), msg))
2883
2888
2884 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2889 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2885 if abs not in names:
2890 if abs not in names:
2886 names[abs] = m.rel(abs), m.exact(abs)
2891 names[abs] = m.rel(abs), m.exact(abs)
2887
2892
2888 # Find status of all file in `names`.
2893 # Find status of all file in `names`.
2889 m = scmutil.matchfiles(repo, names)
2894 m = scmutil.matchfiles(repo, names)
2890
2895
2891 changes = repo.status(node1=node, match=m,
2896 changes = repo.status(node1=node, match=m,
2892 unknown=True, ignored=True, clean=True)
2897 unknown=True, ignored=True, clean=True)
2893 else:
2898 else:
2894 changes = repo.status(node1=node, match=m)
2899 changes = repo.status(node1=node, match=m)
2895 for kind in changes:
2900 for kind in changes:
2896 for abs in kind:
2901 for abs in kind:
2897 names[abs] = m.rel(abs), m.exact(abs)
2902 names[abs] = m.rel(abs), m.exact(abs)
2898
2903
2899 m = scmutil.matchfiles(repo, names)
2904 m = scmutil.matchfiles(repo, names)
2900
2905
2901 modified = set(changes.modified)
2906 modified = set(changes.modified)
2902 added = set(changes.added)
2907 added = set(changes.added)
2903 removed = set(changes.removed)
2908 removed = set(changes.removed)
2904 _deleted = set(changes.deleted)
2909 _deleted = set(changes.deleted)
2905 unknown = set(changes.unknown)
2910 unknown = set(changes.unknown)
2906 unknown.update(changes.ignored)
2911 unknown.update(changes.ignored)
2907 clean = set(changes.clean)
2912 clean = set(changes.clean)
2908 modadded = set()
2913 modadded = set()
2909
2914
2910 # split between files known in target manifest and the others
2915 # split between files known in target manifest and the others
2911 smf = set(mf)
2916 smf = set(mf)
2912
2917
2913 # determine the exact nature of the deleted changesets
2918 # determine the exact nature of the deleted changesets
2914 deladded = _deleted - smf
2919 deladded = _deleted - smf
2915 deleted = _deleted - deladded
2920 deleted = _deleted - deladded
2916
2921
2917 # 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,
2918 # even when we revert against something else than parent. This will
2923 # even when we revert against something else than parent. This will
2919 # 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
2920 # or just forget etc).
2925 # or just forget etc).
2921 if parent == node:
2926 if parent == node:
2922 dsmodified = modified
2927 dsmodified = modified
2923 dsadded = added
2928 dsadded = added
2924 dsremoved = removed
2929 dsremoved = removed
2925 # store all local modifications, useful later for rename detection
2930 # store all local modifications, useful later for rename detection
2926 localchanges = dsmodified | dsadded
2931 localchanges = dsmodified | dsadded
2927 modified, added, removed = set(), set(), set()
2932 modified, added, removed = set(), set(), set()
2928 else:
2933 else:
2929 changes = repo.status(node1=parent, match=m)
2934 changes = repo.status(node1=parent, match=m)
2930 dsmodified = set(changes.modified)
2935 dsmodified = set(changes.modified)
2931 dsadded = set(changes.added)
2936 dsadded = set(changes.added)
2932 dsremoved = set(changes.removed)
2937 dsremoved = set(changes.removed)
2933 # store all local modifications, useful later for rename detection
2938 # store all local modifications, useful later for rename detection
2934 localchanges = dsmodified | dsadded
2939 localchanges = dsmodified | dsadded
2935
2940
2936 # only take into account for removes between wc and target
2941 # only take into account for removes between wc and target
2937 clean |= dsremoved - removed
2942 clean |= dsremoved - removed
2938 dsremoved &= removed
2943 dsremoved &= removed
2939 # distinct between dirstate remove and other
2944 # distinct between dirstate remove and other
2940 removed -= dsremoved
2945 removed -= dsremoved
2941
2946
2942 modadded = added & dsmodified
2947 modadded = added & dsmodified
2943 added -= modadded
2948 added -= modadded
2944
2949
2945 # tell newly modified apart.
2950 # tell newly modified apart.
2946 dsmodified &= modified
2951 dsmodified &= modified
2947 dsmodified |= modified & dsadded # dirstate added may needs backup
2952 dsmodified |= modified & dsadded # dirstate added may needs backup
2948 modified -= dsmodified
2953 modified -= dsmodified
2949
2954
2950 # 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
2951 # before making the distinction. The dirstate will be used for
2956 # before making the distinction. The dirstate will be used for
2952 # that purpose.
2957 # that purpose.
2953 dsadded = added
2958 dsadded = added
2954
2959
2955 # 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
2956 # modified, we need to post process the result
2961 # modified, we need to post process the result
2957 if p2 != nullid:
2962 if p2 != nullid:
2958 if pmf is None:
2963 if pmf is None:
2959 # only need parent manifest in the merge case,
2964 # only need parent manifest in the merge case,
2960 # so do not read by default
2965 # so do not read by default
2961 pmf = repo[parent].manifest()
2966 pmf = repo[parent].manifest()
2962 mergeadd = dsmodified - set(pmf)
2967 mergeadd = dsmodified - set(pmf)
2963 dsadded |= mergeadd
2968 dsadded |= mergeadd
2964 dsmodified -= mergeadd
2969 dsmodified -= mergeadd
2965
2970
2966 # 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
2967 cwd = repo.getcwd()
2972 cwd = repo.getcwd()
2968 for f in localchanges:
2973 for f in localchanges:
2969 src = repo.dirstate.copied(f)
2974 src = repo.dirstate.copied(f)
2970 # XXX should we check for rename down to target node?
2975 # XXX should we check for rename down to target node?
2971 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':
2972 dsremoved.add(src)
2977 dsremoved.add(src)
2973 names[src] = (repo.pathto(src, cwd), True)
2978 names[src] = (repo.pathto(src, cwd), True)
2974
2979
2975 # distinguish between file to forget and the other
2980 # distinguish between file to forget and the other
2976 added = set()
2981 added = set()
2977 for abs in dsadded:
2982 for abs in dsadded:
2978 if repo.dirstate[abs] != 'a':
2983 if repo.dirstate[abs] != 'a':
2979 added.add(abs)
2984 added.add(abs)
2980 dsadded -= added
2985 dsadded -= added
2981
2986
2982 for abs in deladded:
2987 for abs in deladded:
2983 if repo.dirstate[abs] == 'a':
2988 if repo.dirstate[abs] == 'a':
2984 dsadded.add(abs)
2989 dsadded.add(abs)
2985 deladded -= dsadded
2990 deladded -= dsadded
2986
2991
2987 # 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
2988 # 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.
2989 # Making the distinction at this stage helps have simpler backup
2994 # Making the distinction at this stage helps have simpler backup
2990 # logic.
2995 # logic.
2991 removunk = set()
2996 removunk = set()
2992 for abs in removed:
2997 for abs in removed:
2993 target = repo.wjoin(abs)
2998 target = repo.wjoin(abs)
2994 if os.path.lexists(target):
2999 if os.path.lexists(target):
2995 removunk.add(abs)
3000 removunk.add(abs)
2996 removed -= removunk
3001 removed -= removunk
2997
3002
2998 dsremovunk = set()
3003 dsremovunk = set()
2999 for abs in dsremoved:
3004 for abs in dsremoved:
3000 target = repo.wjoin(abs)
3005 target = repo.wjoin(abs)
3001 if os.path.lexists(target):
3006 if os.path.lexists(target):
3002 dsremovunk.add(abs)
3007 dsremovunk.add(abs)
3003 dsremoved -= dsremovunk
3008 dsremoved -= dsremovunk
3004
3009
3005 # action to be actually performed by revert
3010 # action to be actually performed by revert
3006 # (<list of file>, message>) tuple
3011 # (<list of file>, message>) tuple
3007 actions = {'revert': ([], _('reverting %s\n')),
3012 actions = {'revert': ([], _('reverting %s\n')),
3008 'add': ([], _('adding %s\n')),
3013 'add': ([], _('adding %s\n')),
3009 'remove': ([], _('removing %s\n')),
3014 'remove': ([], _('removing %s\n')),
3010 'drop': ([], _('removing %s\n')),
3015 'drop': ([], _('removing %s\n')),
3011 'forget': ([], _('forgetting %s\n')),
3016 'forget': ([], _('forgetting %s\n')),
3012 'undelete': ([], _('undeleting %s\n')),
3017 'undelete': ([], _('undeleting %s\n')),
3013 'noop': (None, _('no changes needed to %s\n')),
3018 'noop': (None, _('no changes needed to %s\n')),
3014 'unknown': (None, _('file not managed: %s\n')),
3019 'unknown': (None, _('file not managed: %s\n')),
3015 }
3020 }
3016
3021
3017 # "constant" that convey the backup strategy.
3022 # "constant" that convey the backup strategy.
3018 # 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
3019 # no_backup lower in the code.
3024 # no_backup lower in the code.
3020 # These values are ordered for comparison purposes
3025 # These values are ordered for comparison purposes
3021 backup = 2 # unconditionally do backup
3026 backup = 2 # unconditionally do backup
3022 check = 1 # check if the existing file differs from target
3027 check = 1 # check if the existing file differs from target
3023 discard = 0 # never do backup
3028 discard = 0 # never do backup
3024 if opts.get('no_backup'):
3029 if opts.get('no_backup'):
3025 backup = check = discard
3030 backup = check = discard
3026
3031
3027 backupanddel = actions['remove']
3032 backupanddel = actions['remove']
3028 if not opts.get('no_backup'):
3033 if not opts.get('no_backup'):
3029 backupanddel = actions['drop']
3034 backupanddel = actions['drop']
3030
3035
3031 disptable = (
3036 disptable = (
3032 # dispatch table:
3037 # dispatch table:
3033 # file state
3038 # file state
3034 # action
3039 # action
3035 # make backup
3040 # make backup
3036
3041
3037 ## Sets that results that will change file on disk
3042 ## Sets that results that will change file on disk
3038 # Modified compared to target, no local change
3043 # Modified compared to target, no local change
3039 (modified, actions['revert'], discard),
3044 (modified, actions['revert'], discard),
3040 # Modified compared to target, but local file is deleted
3045 # Modified compared to target, but local file is deleted
3041 (deleted, actions['revert'], discard),
3046 (deleted, actions['revert'], discard),
3042 # Modified compared to target, local change
3047 # Modified compared to target, local change
3043 (dsmodified, actions['revert'], backup),
3048 (dsmodified, actions['revert'], backup),
3044 # Added since target
3049 # Added since target
3045 (added, actions['remove'], discard),
3050 (added, actions['remove'], discard),
3046 # Added in working directory
3051 # Added in working directory
3047 (dsadded, actions['forget'], discard),
3052 (dsadded, actions['forget'], discard),
3048 # Added since target, have local modification
3053 # Added since target, have local modification
3049 (modadded, backupanddel, backup),
3054 (modadded, backupanddel, backup),
3050 # Added since target but file is missing in working directory
3055 # Added since target but file is missing in working directory
3051 (deladded, actions['drop'], discard),
3056 (deladded, actions['drop'], discard),
3052 # Removed since target, before working copy parent
3057 # Removed since target, before working copy parent
3053 (removed, actions['add'], discard),
3058 (removed, actions['add'], discard),
3054 # 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
3055 (removunk, actions['add'], check),
3060 (removunk, actions['add'], check),
3056 # Removed since targe, marked as such in working copy parent
3061 # Removed since targe, marked as such in working copy parent
3057 (dsremoved, actions['undelete'], discard),
3062 (dsremoved, actions['undelete'], discard),
3058 # 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
3059 (dsremovunk, actions['undelete'], check),
3064 (dsremovunk, actions['undelete'], check),
3060 ## the following sets does not result in any file changes
3065 ## the following sets does not result in any file changes
3061 # File with no modification
3066 # File with no modification
3062 (clean, actions['noop'], discard),
3067 (clean, actions['noop'], discard),
3063 # Existing file, not tracked anywhere
3068 # Existing file, not tracked anywhere
3064 (unknown, actions['unknown'], discard),
3069 (unknown, actions['unknown'], discard),
3065 )
3070 )
3066
3071
3067 for abs, (rel, exact) in sorted(names.items()):
3072 for abs, (rel, exact) in sorted(names.items()):
3068 # target file to be touch on disk (relative to cwd)
3073 # target file to be touch on disk (relative to cwd)
3069 target = repo.wjoin(abs)
3074 target = repo.wjoin(abs)
3070 # search the entry in the dispatch table.
3075 # search the entry in the dispatch table.
3071 # 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
3072 # directory parent and we are sure it needs to be reverted.
3077 # directory parent and we are sure it needs to be reverted.
3073 for table, (xlist, msg), dobackup in disptable:
3078 for table, (xlist, msg), dobackup in disptable:
3074 if abs not in table:
3079 if abs not in table:
3075 continue
3080 continue
3076 if xlist is not None:
3081 if xlist is not None:
3077 xlist.append(abs)
3082 xlist.append(abs)
3078 if dobackup and (backup <= dobackup
3083 if dobackup and (backup <= dobackup
3079 or wctx[abs].cmp(ctx[abs])):
3084 or wctx[abs].cmp(ctx[abs])):
3080 bakname = "%s.orig" % rel
3085 bakname = "%s.orig" % rel
3081 ui.note(_('saving current version of %s as %s\n') %
3086 ui.note(_('saving current version of %s as %s\n') %
3082 (rel, bakname))
3087 (rel, bakname))
3083 if not opts.get('dry_run'):
3088 if not opts.get('dry_run'):
3084 if interactive:
3089 if interactive:
3085 util.copyfile(target, bakname)
3090 util.copyfile(target, bakname)
3086 else:
3091 else:
3087 util.rename(target, bakname)
3092 util.rename(target, bakname)
3088 if ui.verbose or not exact:
3093 if ui.verbose or not exact:
3089 if not isinstance(msg, basestring):
3094 if not isinstance(msg, basestring):
3090 msg = msg(abs)
3095 msg = msg(abs)
3091 ui.status(msg % rel)
3096 ui.status(msg % rel)
3092 elif exact:
3097 elif exact:
3093 ui.warn(msg % rel)
3098 ui.warn(msg % rel)
3094 break
3099 break
3095
3100
3096 if not opts.get('dry_run'):
3101 if not opts.get('dry_run'):
3097 needdata = ('revert', 'add', 'undelete')
3102 needdata = ('revert', 'add', 'undelete')
3098 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3103 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3099 _performrevert(repo, parents, ctx, actions, interactive)
3104 _performrevert(repo, parents, ctx, actions, interactive)
3100
3105
3101 if targetsubs:
3106 if targetsubs:
3102 # Revert the subrepos on the revert list
3107 # Revert the subrepos on the revert list
3103 for sub in targetsubs:
3108 for sub in targetsubs:
3104 try:
3109 try:
3105 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3110 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3106 except KeyError:
3111 except KeyError:
3107 raise error.Abort("subrepository '%s' does not exist in %s!"
3112 raise error.Abort("subrepository '%s' does not exist in %s!"
3108 % (sub, short(ctx.node())))
3113 % (sub, short(ctx.node())))
3109 finally:
3114 finally:
3110 wlock.release()
3115 wlock.release()
3111
3116
3112 def _revertprefetch(repo, ctx, *files):
3117 def _revertprefetch(repo, ctx, *files):
3113 """Let extension changing the storage layer prefetch content"""
3118 """Let extension changing the storage layer prefetch content"""
3114 pass
3119 pass
3115
3120
3116 def _performrevert(repo, parents, ctx, actions, interactive=False):
3121 def _performrevert(repo, parents, ctx, actions, interactive=False):
3117 """function that actually perform all the actions computed for revert
3122 """function that actually perform all the actions computed for revert
3118
3123
3119 This is an independent function to let extension to plug in and react to
3124 This is an independent function to let extension to plug in and react to
3120 the imminent revert.
3125 the imminent revert.
3121
3126
3122 Make sure you have the working directory locked when calling this function.
3127 Make sure you have the working directory locked when calling this function.
3123 """
3128 """
3124 parent, p2 = parents
3129 parent, p2 = parents
3125 node = ctx.node()
3130 node = ctx.node()
3126 def checkout(f):
3131 def checkout(f):
3127 fc = ctx[f]
3132 fc = ctx[f]
3128 repo.wwrite(f, fc.data(), fc.flags())
3133 repo.wwrite(f, fc.data(), fc.flags())
3129
3134
3130 audit_path = pathutil.pathauditor(repo.root)
3135 audit_path = pathutil.pathauditor(repo.root)
3131 for f in actions['forget'][0]:
3136 for f in actions['forget'][0]:
3132 repo.dirstate.drop(f)
3137 repo.dirstate.drop(f)
3133 for f in actions['remove'][0]:
3138 for f in actions['remove'][0]:
3134 audit_path(f)
3139 audit_path(f)
3135 try:
3140 try:
3136 util.unlinkpath(repo.wjoin(f))
3141 util.unlinkpath(repo.wjoin(f))
3137 except OSError:
3142 except OSError:
3138 pass
3143 pass
3139 repo.dirstate.remove(f)
3144 repo.dirstate.remove(f)
3140 for f in actions['drop'][0]:
3145 for f in actions['drop'][0]:
3141 audit_path(f)
3146 audit_path(f)
3142 repo.dirstate.remove(f)
3147 repo.dirstate.remove(f)
3143
3148
3144 normal = None
3149 normal = None
3145 if node == parent:
3150 if node == parent:
3146 # We're reverting to our parent. If possible, we'd like status
3151 # We're reverting to our parent. If possible, we'd like status
3147 # to report the file as clean. We have to use normallookup for
3152 # to report the file as clean. We have to use normallookup for
3148 # merges to avoid losing information about merged/dirty files.
3153 # merges to avoid losing information about merged/dirty files.
3149 if p2 != nullid:
3154 if p2 != nullid:
3150 normal = repo.dirstate.normallookup
3155 normal = repo.dirstate.normallookup
3151 else:
3156 else:
3152 normal = repo.dirstate.normal
3157 normal = repo.dirstate.normal
3153
3158
3154 newlyaddedandmodifiedfiles = set()
3159 newlyaddedandmodifiedfiles = set()
3155 if interactive:
3160 if interactive:
3156 # Prompt the user for changes to revert
3161 # Prompt the user for changes to revert
3157 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3162 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3158 m = scmutil.match(ctx, torevert, {})
3163 m = scmutil.match(ctx, torevert, {})
3159 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3164 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3160 diffopts.nodates = True
3165 diffopts.nodates = True
3161 diffopts.git = True
3166 diffopts.git = True
3162 reversehunks = repo.ui.configbool('experimental',
3167 reversehunks = repo.ui.configbool('experimental',
3163 'revertalternateinteractivemode',
3168 'revertalternateinteractivemode',
3164 True)
3169 True)
3165 if reversehunks:
3170 if reversehunks:
3166 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3171 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3167 else:
3172 else:
3168 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3173 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3169 originalchunks = patch.parsepatch(diff)
3174 originalchunks = patch.parsepatch(diff)
3170
3175
3171 try:
3176 try:
3172
3177
3173 chunks = recordfilter(repo.ui, originalchunks)
3178 chunks = recordfilter(repo.ui, originalchunks)
3174 if reversehunks:
3179 if reversehunks:
3175 chunks = patch.reversehunks(chunks)
3180 chunks = patch.reversehunks(chunks)
3176
3181
3177 except patch.PatchError as err:
3182 except patch.PatchError as err:
3178 raise error.Abort(_('error parsing patch: %s') % err)
3183 raise error.Abort(_('error parsing patch: %s') % err)
3179
3184
3180 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3185 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3181 # Apply changes
3186 # Apply changes
3182 fp = cStringIO.StringIO()
3187 fp = cStringIO.StringIO()
3183 for c in chunks:
3188 for c in chunks:
3184 c.write(fp)
3189 c.write(fp)
3185 dopatch = fp.tell()
3190 dopatch = fp.tell()
3186 fp.seek(0)
3191 fp.seek(0)
3187 if dopatch:
3192 if dopatch:
3188 try:
3193 try:
3189 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3194 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3190 except patch.PatchError as err:
3195 except patch.PatchError as err:
3191 raise error.Abort(str(err))
3196 raise error.Abort(str(err))
3192 del fp
3197 del fp
3193 else:
3198 else:
3194 for f in actions['revert'][0]:
3199 for f in actions['revert'][0]:
3195 checkout(f)
3200 checkout(f)
3196 if normal:
3201 if normal:
3197 normal(f)
3202 normal(f)
3198
3203
3199 for f in actions['add'][0]:
3204 for f in actions['add'][0]:
3200 # Don't checkout modified files, they are already created by the diff
3205 # Don't checkout modified files, they are already created by the diff
3201 if f not in newlyaddedandmodifiedfiles:
3206 if f not in newlyaddedandmodifiedfiles:
3202 checkout(f)
3207 checkout(f)
3203 repo.dirstate.add(f)
3208 repo.dirstate.add(f)
3204
3209
3205 normal = repo.dirstate.normallookup
3210 normal = repo.dirstate.normallookup
3206 if node == parent and p2 == nullid:
3211 if node == parent and p2 == nullid:
3207 normal = repo.dirstate.normal
3212 normal = repo.dirstate.normal
3208 for f in actions['undelete'][0]:
3213 for f in actions['undelete'][0]:
3209 checkout(f)
3214 checkout(f)
3210 normal(f)
3215 normal(f)
3211
3216
3212 copied = copies.pathcopies(repo[parent], ctx)
3217 copied = copies.pathcopies(repo[parent], ctx)
3213
3218
3214 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3219 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3215 if f in copied:
3220 if f in copied:
3216 repo.dirstate.copy(copied[f], f)
3221 repo.dirstate.copy(copied[f], f)
3217
3222
3218 def command(table):
3223 def command(table):
3219 """Returns a function object to be used as a decorator for making commands.
3224 """Returns a function object to be used as a decorator for making commands.
3220
3225
3221 This function receives a command table as its argument. The table should
3226 This function receives a command table as its argument. The table should
3222 be a dict.
3227 be a dict.
3223
3228
3224 The returned function can be used as a decorator for adding commands
3229 The returned function can be used as a decorator for adding commands
3225 to that command table. This function accepts multiple arguments to define
3230 to that command table. This function accepts multiple arguments to define
3226 a command.
3231 a command.
3227
3232
3228 The first argument is the command name.
3233 The first argument is the command name.
3229
3234
3230 The options argument is an iterable of tuples defining command arguments.
3235 The options argument is an iterable of tuples defining command arguments.
3231 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3236 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3232
3237
3233 The synopsis argument defines a short, one line summary of how to use the
3238 The synopsis argument defines a short, one line summary of how to use the
3234 command. This shows up in the help output.
3239 command. This shows up in the help output.
3235
3240
3236 The norepo argument defines whether the command does not require a
3241 The norepo argument defines whether the command does not require a
3237 local repository. Most commands operate against a repository, thus the
3242 local repository. Most commands operate against a repository, thus the
3238 default is False.
3243 default is False.
3239
3244
3240 The optionalrepo argument defines whether the command optionally requires
3245 The optionalrepo argument defines whether the command optionally requires
3241 a local repository.
3246 a local repository.
3242
3247
3243 The inferrepo argument defines whether to try to find a repository from the
3248 The inferrepo argument defines whether to try to find a repository from the
3244 command line arguments. If True, arguments will be examined for potential
3249 command line arguments. If True, arguments will be examined for potential
3245 repository locations. See ``findrepo()``. If a repository is found, it
3250 repository locations. See ``findrepo()``. If a repository is found, it
3246 will be used.
3251 will be used.
3247 """
3252 """
3248 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3253 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3249 inferrepo=False):
3254 inferrepo=False):
3250 def decorator(func):
3255 def decorator(func):
3251 if synopsis:
3256 if synopsis:
3252 table[name] = func, list(options), synopsis
3257 table[name] = func, list(options), synopsis
3253 else:
3258 else:
3254 table[name] = func, list(options)
3259 table[name] = func, list(options)
3255
3260
3256 if norepo:
3261 if norepo:
3257 # Avoid import cycle.
3262 # Avoid import cycle.
3258 import commands
3263 import commands
3259 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3264 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3260
3265
3261 if optionalrepo:
3266 if optionalrepo:
3262 import commands
3267 import commands
3263 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3268 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3264
3269
3265 if inferrepo:
3270 if inferrepo:
3266 import commands
3271 import commands
3267 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3272 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3268
3273
3269 return func
3274 return func
3270 return decorator
3275 return decorator
3271
3276
3272 return cmd
3277 return cmd
3273
3278
3274 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3279 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3275 # commands.outgoing. "missing" is "missing" of the result of
3280 # commands.outgoing. "missing" is "missing" of the result of
3276 # "findcommonoutgoing()"
3281 # "findcommonoutgoing()"
3277 outgoinghooks = util.hooks()
3282 outgoinghooks = util.hooks()
3278
3283
3279 # a list of (ui, repo) functions called by commands.summary
3284 # a list of (ui, repo) functions called by commands.summary
3280 summaryhooks = util.hooks()
3285 summaryhooks = util.hooks()
3281
3286
3282 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3287 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3283 #
3288 #
3284 # functions should return tuple of booleans below, if 'changes' is None:
3289 # functions should return tuple of booleans below, if 'changes' is None:
3285 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3290 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3286 #
3291 #
3287 # otherwise, 'changes' is a tuple of tuples below:
3292 # otherwise, 'changes' is a tuple of tuples below:
3288 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3293 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3289 # - (desturl, destbranch, destpeer, outgoing)
3294 # - (desturl, destbranch, destpeer, outgoing)
3290 summaryremotehooks = util.hooks()
3295 summaryremotehooks = util.hooks()
3291
3296
3292 # A list of state files kept by multistep operations like graft.
3297 # A list of state files kept by multistep operations like graft.
3293 # Since graft cannot be aborted, it is considered 'clearable' by update.
3298 # Since graft cannot be aborted, it is considered 'clearable' by update.
3294 # note: bisect is intentionally excluded
3299 # note: bisect is intentionally excluded
3295 # (state file, clearable, allowcommit, error, hint)
3300 # (state file, clearable, allowcommit, error, hint)
3296 unfinishedstates = [
3301 unfinishedstates = [
3297 ('graftstate', True, False, _('graft in progress'),
3302 ('graftstate', True, False, _('graft in progress'),
3298 _("use 'hg graft --continue' or 'hg update' to abort")),
3303 _("use 'hg graft --continue' or 'hg update' to abort")),
3299 ('updatestate', True, False, _('last update was interrupted'),
3304 ('updatestate', True, False, _('last update was interrupted'),
3300 _("use 'hg update' to get a consistent checkout"))
3305 _("use 'hg update' to get a consistent checkout"))
3301 ]
3306 ]
3302
3307
3303 def checkunfinished(repo, commit=False):
3308 def checkunfinished(repo, commit=False):
3304 '''Look for an unfinished multistep operation, like graft, and abort
3309 '''Look for an unfinished multistep operation, like graft, and abort
3305 if found. It's probably good to check this right before
3310 if found. It's probably good to check this right before
3306 bailifchanged().
3311 bailifchanged().
3307 '''
3312 '''
3308 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3313 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3309 if commit and allowcommit:
3314 if commit and allowcommit:
3310 continue
3315 continue
3311 if repo.vfs.exists(f):
3316 if repo.vfs.exists(f):
3312 raise error.Abort(msg, hint=hint)
3317 raise error.Abort(msg, hint=hint)
3313
3318
3314 def clearunfinished(repo):
3319 def clearunfinished(repo):
3315 '''Check for unfinished operations (as above), and clear the ones
3320 '''Check for unfinished operations (as above), and clear the ones
3316 that are clearable.
3321 that are clearable.
3317 '''
3322 '''
3318 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3323 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3319 if not clearable and repo.vfs.exists(f):
3324 if not clearable and repo.vfs.exists(f):
3320 raise error.Abort(msg, hint=hint)
3325 raise error.Abort(msg, hint=hint)
3321 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3326 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3322 if clearable and repo.vfs.exists(f):
3327 if clearable and repo.vfs.exists(f):
3323 util.unlink(repo.join(f))
3328 util.unlink(repo.join(f))
3324
3329
3325 class dirstateguard(object):
3330 class dirstateguard(object):
3326 '''Restore dirstate at unexpected failure.
3331 '''Restore dirstate at unexpected failure.
3327
3332
3328 At the construction, this class does:
3333 At the construction, this class does:
3329
3334
3330 - write current ``repo.dirstate`` out, and
3335 - write current ``repo.dirstate`` out, and
3331 - save ``.hg/dirstate`` into the backup file
3336 - save ``.hg/dirstate`` into the backup file
3332
3337
3333 This restores ``.hg/dirstate`` from backup file, if ``release()``
3338 This restores ``.hg/dirstate`` from backup file, if ``release()``
3334 is invoked before ``close()``.
3339 is invoked before ``close()``.
3335
3340
3336 This just removes the backup file at ``close()`` before ``release()``.
3341 This just removes the backup file at ``close()`` before ``release()``.
3337 '''
3342 '''
3338
3343
3339 def __init__(self, repo, name):
3344 def __init__(self, repo, name):
3340 self._repo = repo
3345 self._repo = repo
3341 self._suffix = '.backup.%s.%d' % (name, id(self))
3346 self._suffix = '.backup.%s.%d' % (name, id(self))
3342 repo.dirstate._savebackup(repo.currenttransaction(), self._suffix)
3347 repo.dirstate._savebackup(repo.currenttransaction(), self._suffix)
3343 self._active = True
3348 self._active = True
3344 self._closed = False
3349 self._closed = False
3345
3350
3346 def __del__(self):
3351 def __del__(self):
3347 if self._active: # still active
3352 if self._active: # still active
3348 # this may occur, even if this class is used correctly:
3353 # this may occur, even if this class is used correctly:
3349 # for example, releasing other resources like transaction
3354 # for example, releasing other resources like transaction
3350 # may raise exception before ``dirstateguard.release`` in
3355 # may raise exception before ``dirstateguard.release`` in
3351 # ``release(tr, ....)``.
3356 # ``release(tr, ....)``.
3352 self._abort()
3357 self._abort()
3353
3358
3354 def close(self):
3359 def close(self):
3355 if not self._active: # already inactivated
3360 if not self._active: # already inactivated
3356 msg = (_("can't close already inactivated backup: dirstate%s")
3361 msg = (_("can't close already inactivated backup: dirstate%s")
3357 % self._suffix)
3362 % self._suffix)
3358 raise error.Abort(msg)
3363 raise error.Abort(msg)
3359
3364
3360 self._repo.dirstate._clearbackup(self._repo.currenttransaction(),
3365 self._repo.dirstate._clearbackup(self._repo.currenttransaction(),
3361 self._suffix)
3366 self._suffix)
3362 self._active = False
3367 self._active = False
3363 self._closed = True
3368 self._closed = True
3364
3369
3365 def _abort(self):
3370 def _abort(self):
3366 self._repo.dirstate._restorebackup(self._repo.currenttransaction(),
3371 self._repo.dirstate._restorebackup(self._repo.currenttransaction(),
3367 self._suffix)
3372 self._suffix)
3368 self._active = False
3373 self._active = False
3369
3374
3370 def release(self):
3375 def release(self):
3371 if not self._closed:
3376 if not self._closed:
3372 if not self._active: # already inactivated
3377 if not self._active: # already inactivated
3373 msg = (_("can't release already inactivated backup:"
3378 msg = (_("can't release already inactivated backup:"
3374 " dirstate%s")
3379 " dirstate%s")
3375 % self._suffix)
3380 % self._suffix)
3376 raise error.Abort(msg)
3381 raise error.Abort(msg)
3377 self._abort()
3382 self._abort()
@@ -1,1124 +1,1127 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import getpass
11 import getpass
12 import inspect
12 import inspect
13 import os
13 import os
14 import socket
14 import socket
15 import sys
15 import sys
16 import tempfile
16 import tempfile
17 import traceback
17 import traceback
18
18
19 from .i18n import _
19 from .i18n import _
20 from .node import hex
20 from .node import hex
21
21
22 from . import (
22 from . import (
23 config,
23 config,
24 error,
24 error,
25 formatter,
25 formatter,
26 progress,
26 progress,
27 scmutil,
27 scmutil,
28 util,
28 util,
29 )
29 )
30
30
31 samplehgrcs = {
31 samplehgrcs = {
32 'user':
32 'user':
33 """# example user config (see "hg help config" for more info)
33 """# example user config (see "hg help config" for more info)
34 [ui]
34 [ui]
35 # name and email, e.g.
35 # name and email, e.g.
36 # username = Jane Doe <jdoe@example.com>
36 # username = Jane Doe <jdoe@example.com>
37 username =
37 username =
38
38
39 [extensions]
39 [extensions]
40 # uncomment these lines to enable some popular extensions
40 # uncomment these lines to enable some popular extensions
41 # (see "hg help extensions" for more info)
41 # (see "hg help extensions" for more info)
42 #
42 #
43 # pager =
43 # pager =
44 # progress =
44 # progress =
45 # color =""",
45 # color =""",
46
46
47 'cloned':
47 'cloned':
48 """# example repository config (see "hg help config" for more info)
48 """# example repository config (see "hg help config" for more info)
49 [paths]
49 [paths]
50 default = %s
50 default = %s
51
51
52 # path aliases to other clones of this repo in URLs or filesystem paths
52 # path aliases to other clones of this repo in URLs or filesystem paths
53 # (see "hg help config.paths" for more info)
53 # (see "hg help config.paths" for more info)
54 #
54 #
55 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
55 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
56 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
56 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
57 # my-clone = /home/jdoe/jdoes-clone
57 # my-clone = /home/jdoe/jdoes-clone
58
58
59 [ui]
59 [ui]
60 # name and email (local to this repository, optional), e.g.
60 # name and email (local to this repository, optional), e.g.
61 # username = Jane Doe <jdoe@example.com>
61 # username = Jane Doe <jdoe@example.com>
62 """,
62 """,
63
63
64 'local':
64 'local':
65 """# example repository config (see "hg help config" for more info)
65 """# example repository config (see "hg help config" for more info)
66 [paths]
66 [paths]
67 # path aliases to other clones of this repo in URLs or filesystem paths
67 # path aliases to other clones of this repo in URLs or filesystem paths
68 # (see "hg help config.paths" for more info)
68 # (see "hg help config.paths" for more info)
69 #
69 #
70 # default = http://example.com/hg/example-repo
70 # default = http://example.com/hg/example-repo
71 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
71 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
72 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
72 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
73 # my-clone = /home/jdoe/jdoes-clone
73 # my-clone = /home/jdoe/jdoes-clone
74
74
75 [ui]
75 [ui]
76 # name and email (local to this repository, optional), e.g.
76 # name and email (local to this repository, optional), e.g.
77 # username = Jane Doe <jdoe@example.com>
77 # username = Jane Doe <jdoe@example.com>
78 """,
78 """,
79
79
80 'global':
80 'global':
81 """# example system-wide hg config (see "hg help config" for more info)
81 """# example system-wide hg config (see "hg help config" for more info)
82
82
83 [extensions]
83 [extensions]
84 # uncomment these lines to enable some popular extensions
84 # uncomment these lines to enable some popular extensions
85 # (see "hg help extensions" for more info)
85 # (see "hg help extensions" for more info)
86 #
86 #
87 # blackbox =
87 # blackbox =
88 # progress =
88 # progress =
89 # color =
89 # color =
90 # pager =""",
90 # pager =""",
91 }
91 }
92
92
93 class ui(object):
93 class ui(object):
94 def __init__(self, src=None):
94 def __init__(self, src=None):
95 # _buffers: used for temporary capture of output
95 # _buffers: used for temporary capture of output
96 self._buffers = []
96 self._buffers = []
97 # _bufferstates:
97 # _bufferstates:
98 # should the temporary capture include stderr and subprocess output
98 # should the temporary capture include stderr and subprocess output
99 self._bufferstates = []
99 self._bufferstates = []
100 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
100 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
101 self._reportuntrusted = True
101 self._reportuntrusted = True
102 self._ocfg = config.config() # overlay
102 self._ocfg = config.config() # overlay
103 self._tcfg = config.config() # trusted
103 self._tcfg = config.config() # trusted
104 self._ucfg = config.config() # untrusted
104 self._ucfg = config.config() # untrusted
105 self._trustusers = set()
105 self._trustusers = set()
106 self._trustgroups = set()
106 self._trustgroups = set()
107 self.callhooks = True
107 self.callhooks = True
108
108
109 if src:
109 if src:
110 self.fout = src.fout
110 self.fout = src.fout
111 self.ferr = src.ferr
111 self.ferr = src.ferr
112 self.fin = src.fin
112 self.fin = src.fin
113
113
114 self._tcfg = src._tcfg.copy()
114 self._tcfg = src._tcfg.copy()
115 self._ucfg = src._ucfg.copy()
115 self._ucfg = src._ucfg.copy()
116 self._ocfg = src._ocfg.copy()
116 self._ocfg = src._ocfg.copy()
117 self._trustusers = src._trustusers.copy()
117 self._trustusers = src._trustusers.copy()
118 self._trustgroups = src._trustgroups.copy()
118 self._trustgroups = src._trustgroups.copy()
119 self.environ = src.environ
119 self.environ = src.environ
120 self.callhooks = src.callhooks
120 self.callhooks = src.callhooks
121 self.fixconfig()
121 self.fixconfig()
122 else:
122 else:
123 self.fout = sys.stdout
123 self.fout = sys.stdout
124 self.ferr = sys.stderr
124 self.ferr = sys.stderr
125 self.fin = sys.stdin
125 self.fin = sys.stdin
126
126
127 # shared read-only environment
127 # shared read-only environment
128 self.environ = os.environ
128 self.environ = os.environ
129 # we always trust global config files
129 # we always trust global config files
130 for f in scmutil.rcpath():
130 for f in scmutil.rcpath():
131 self.readconfig(f, trust=True)
131 self.readconfig(f, trust=True)
132
132
133 def copy(self):
133 def copy(self):
134 return self.__class__(self)
134 return self.__class__(self)
135
135
136 def formatter(self, topic, opts):
136 def formatter(self, topic, opts):
137 return formatter.formatter(self, topic, opts)
137 return formatter.formatter(self, topic, opts)
138
138
139 def _trusted(self, fp, f):
139 def _trusted(self, fp, f):
140 st = util.fstat(fp)
140 st = util.fstat(fp)
141 if util.isowner(st):
141 if util.isowner(st):
142 return True
142 return True
143
143
144 tusers, tgroups = self._trustusers, self._trustgroups
144 tusers, tgroups = self._trustusers, self._trustgroups
145 if '*' in tusers or '*' in tgroups:
145 if '*' in tusers or '*' in tgroups:
146 return True
146 return True
147
147
148 user = util.username(st.st_uid)
148 user = util.username(st.st_uid)
149 group = util.groupname(st.st_gid)
149 group = util.groupname(st.st_gid)
150 if user in tusers or group in tgroups or user == util.username():
150 if user in tusers or group in tgroups or user == util.username():
151 return True
151 return True
152
152
153 if self._reportuntrusted:
153 if self._reportuntrusted:
154 self.warn(_('not trusting file %s from untrusted '
154 self.warn(_('not trusting file %s from untrusted '
155 'user %s, group %s\n') % (f, user, group))
155 'user %s, group %s\n') % (f, user, group))
156 return False
156 return False
157
157
158 def readconfig(self, filename, root=None, trust=False,
158 def readconfig(self, filename, root=None, trust=False,
159 sections=None, remap=None):
159 sections=None, remap=None):
160 try:
160 try:
161 fp = open(filename)
161 fp = open(filename)
162 except IOError:
162 except IOError:
163 if not sections: # ignore unless we were looking for something
163 if not sections: # ignore unless we were looking for something
164 return
164 return
165 raise
165 raise
166
166
167 cfg = config.config()
167 cfg = config.config()
168 trusted = sections or trust or self._trusted(fp, filename)
168 trusted = sections or trust or self._trusted(fp, filename)
169
169
170 try:
170 try:
171 cfg.read(filename, fp, sections=sections, remap=remap)
171 cfg.read(filename, fp, sections=sections, remap=remap)
172 fp.close()
172 fp.close()
173 except error.ConfigError as inst:
173 except error.ConfigError as inst:
174 if trusted:
174 if trusted:
175 raise
175 raise
176 self.warn(_("ignored: %s\n") % str(inst))
176 self.warn(_("ignored: %s\n") % str(inst))
177
177
178 if self.plain():
178 if self.plain():
179 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
179 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
180 'logtemplate', 'statuscopies', 'style',
180 'logtemplate', 'statuscopies', 'style',
181 'traceback', 'verbose'):
181 'traceback', 'verbose'):
182 if k in cfg['ui']:
182 if k in cfg['ui']:
183 del cfg['ui'][k]
183 del cfg['ui'][k]
184 for k, v in cfg.items('defaults'):
184 for k, v in cfg.items('defaults'):
185 del cfg['defaults'][k]
185 del cfg['defaults'][k]
186 # Don't remove aliases from the configuration if in the exceptionlist
186 # Don't remove aliases from the configuration if in the exceptionlist
187 if self.plain('alias'):
187 if self.plain('alias'):
188 for k, v in cfg.items('alias'):
188 for k, v in cfg.items('alias'):
189 del cfg['alias'][k]
189 del cfg['alias'][k]
190 if self.plain('revsetalias'):
190 if self.plain('revsetalias'):
191 for k, v in cfg.items('revsetalias'):
191 for k, v in cfg.items('revsetalias'):
192 del cfg['revsetalias'][k]
192 del cfg['revsetalias'][k]
193
193
194 if trusted:
194 if trusted:
195 self._tcfg.update(cfg)
195 self._tcfg.update(cfg)
196 self._tcfg.update(self._ocfg)
196 self._tcfg.update(self._ocfg)
197 self._ucfg.update(cfg)
197 self._ucfg.update(cfg)
198 self._ucfg.update(self._ocfg)
198 self._ucfg.update(self._ocfg)
199
199
200 if root is None:
200 if root is None:
201 root = os.path.expanduser('~')
201 root = os.path.expanduser('~')
202 self.fixconfig(root=root)
202 self.fixconfig(root=root)
203
203
204 def fixconfig(self, root=None, section=None):
204 def fixconfig(self, root=None, section=None):
205 if section in (None, 'paths'):
205 if section in (None, 'paths'):
206 # expand vars and ~
206 # expand vars and ~
207 # translate paths relative to root (or home) into absolute paths
207 # translate paths relative to root (or home) into absolute paths
208 root = root or os.getcwd()
208 root = root or os.getcwd()
209 for c in self._tcfg, self._ucfg, self._ocfg:
209 for c in self._tcfg, self._ucfg, self._ocfg:
210 for n, p in c.items('paths'):
210 for n, p in c.items('paths'):
211 if not p:
211 if not p:
212 continue
212 continue
213 if '%%' in p:
213 if '%%' in p:
214 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
214 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
215 % (n, p, self.configsource('paths', n)))
215 % (n, p, self.configsource('paths', n)))
216 p = p.replace('%%', '%')
216 p = p.replace('%%', '%')
217 p = util.expandpath(p)
217 p = util.expandpath(p)
218 if not util.hasscheme(p) and not os.path.isabs(p):
218 if not util.hasscheme(p) and not os.path.isabs(p):
219 p = os.path.normpath(os.path.join(root, p))
219 p = os.path.normpath(os.path.join(root, p))
220 c.set("paths", n, p)
220 c.set("paths", n, p)
221
221
222 if section in (None, 'ui'):
222 if section in (None, 'ui'):
223 # update ui options
223 # update ui options
224 self.debugflag = self.configbool('ui', 'debug')
224 self.debugflag = self.configbool('ui', 'debug')
225 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
225 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
226 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
226 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
227 if self.verbose and self.quiet:
227 if self.verbose and self.quiet:
228 self.quiet = self.verbose = False
228 self.quiet = self.verbose = False
229 self._reportuntrusted = self.debugflag or self.configbool("ui",
229 self._reportuntrusted = self.debugflag or self.configbool("ui",
230 "report_untrusted", True)
230 "report_untrusted", True)
231 self.tracebackflag = self.configbool('ui', 'traceback', False)
231 self.tracebackflag = self.configbool('ui', 'traceback', False)
232
232
233 if section in (None, 'trusted'):
233 if section in (None, 'trusted'):
234 # update trust information
234 # update trust information
235 self._trustusers.update(self.configlist('trusted', 'users'))
235 self._trustusers.update(self.configlist('trusted', 'users'))
236 self._trustgroups.update(self.configlist('trusted', 'groups'))
236 self._trustgroups.update(self.configlist('trusted', 'groups'))
237
237
238 def backupconfig(self, section, item):
238 def backupconfig(self, section, item):
239 return (self._ocfg.backup(section, item),
239 return (self._ocfg.backup(section, item),
240 self._tcfg.backup(section, item),
240 self._tcfg.backup(section, item),
241 self._ucfg.backup(section, item),)
241 self._ucfg.backup(section, item),)
242 def restoreconfig(self, data):
242 def restoreconfig(self, data):
243 self._ocfg.restore(data[0])
243 self._ocfg.restore(data[0])
244 self._tcfg.restore(data[1])
244 self._tcfg.restore(data[1])
245 self._ucfg.restore(data[2])
245 self._ucfg.restore(data[2])
246
246
247 def setconfig(self, section, name, value, source=''):
247 def setconfig(self, section, name, value, source=''):
248 for cfg in (self._ocfg, self._tcfg, self._ucfg):
248 for cfg in (self._ocfg, self._tcfg, self._ucfg):
249 cfg.set(section, name, value, source)
249 cfg.set(section, name, value, source)
250 self.fixconfig(section=section)
250 self.fixconfig(section=section)
251
251
252 def _data(self, untrusted):
252 def _data(self, untrusted):
253 return untrusted and self._ucfg or self._tcfg
253 return untrusted and self._ucfg or self._tcfg
254
254
255 def configsource(self, section, name, untrusted=False):
255 def configsource(self, section, name, untrusted=False):
256 return self._data(untrusted).source(section, name) or 'none'
256 return self._data(untrusted).source(section, name) or 'none'
257
257
258 def config(self, section, name, default=None, untrusted=False):
258 def config(self, section, name, default=None, untrusted=False):
259 if isinstance(name, list):
259 if isinstance(name, list):
260 alternates = name
260 alternates = name
261 else:
261 else:
262 alternates = [name]
262 alternates = [name]
263
263
264 for n in alternates:
264 for n in alternates:
265 value = self._data(untrusted).get(section, n, None)
265 value = self._data(untrusted).get(section, n, None)
266 if value is not None:
266 if value is not None:
267 name = n
267 name = n
268 break
268 break
269 else:
269 else:
270 value = default
270 value = default
271
271
272 if self.debugflag and not untrusted and self._reportuntrusted:
272 if self.debugflag and not untrusted and self._reportuntrusted:
273 for n in alternates:
273 for n in alternates:
274 uvalue = self._ucfg.get(section, n)
274 uvalue = self._ucfg.get(section, n)
275 if uvalue is not None and uvalue != value:
275 if uvalue is not None and uvalue != value:
276 self.debug("ignoring untrusted configuration option "
276 self.debug("ignoring untrusted configuration option "
277 "%s.%s = %s\n" % (section, n, uvalue))
277 "%s.%s = %s\n" % (section, n, uvalue))
278 return value
278 return value
279
279
280 def configpath(self, section, name, default=None, untrusted=False):
280 def configpath(self, section, name, default=None, untrusted=False):
281 'get a path config item, expanded relative to repo root or config file'
281 'get a path config item, expanded relative to repo root or config file'
282 v = self.config(section, name, default, untrusted)
282 v = self.config(section, name, default, untrusted)
283 if v is None:
283 if v is None:
284 return None
284 return None
285 if not os.path.isabs(v) or "://" not in v:
285 if not os.path.isabs(v) or "://" not in v:
286 src = self.configsource(section, name, untrusted)
286 src = self.configsource(section, name, untrusted)
287 if ':' in src:
287 if ':' in src:
288 base = os.path.dirname(src.rsplit(':')[0])
288 base = os.path.dirname(src.rsplit(':')[0])
289 v = os.path.join(base, os.path.expanduser(v))
289 v = os.path.join(base, os.path.expanduser(v))
290 return v
290 return v
291
291
292 def configbool(self, section, name, default=False, untrusted=False):
292 def configbool(self, section, name, default=False, untrusted=False):
293 """parse a configuration element as a boolean
293 """parse a configuration element as a boolean
294
294
295 >>> u = ui(); s = 'foo'
295 >>> u = ui(); s = 'foo'
296 >>> u.setconfig(s, 'true', 'yes')
296 >>> u.setconfig(s, 'true', 'yes')
297 >>> u.configbool(s, 'true')
297 >>> u.configbool(s, 'true')
298 True
298 True
299 >>> u.setconfig(s, 'false', 'no')
299 >>> u.setconfig(s, 'false', 'no')
300 >>> u.configbool(s, 'false')
300 >>> u.configbool(s, 'false')
301 False
301 False
302 >>> u.configbool(s, 'unknown')
302 >>> u.configbool(s, 'unknown')
303 False
303 False
304 >>> u.configbool(s, 'unknown', True)
304 >>> u.configbool(s, 'unknown', True)
305 True
305 True
306 >>> u.setconfig(s, 'invalid', 'somevalue')
306 >>> u.setconfig(s, 'invalid', 'somevalue')
307 >>> u.configbool(s, 'invalid')
307 >>> u.configbool(s, 'invalid')
308 Traceback (most recent call last):
308 Traceback (most recent call last):
309 ...
309 ...
310 ConfigError: foo.invalid is not a boolean ('somevalue')
310 ConfigError: foo.invalid is not a boolean ('somevalue')
311 """
311 """
312
312
313 v = self.config(section, name, None, untrusted)
313 v = self.config(section, name, None, untrusted)
314 if v is None:
314 if v is None:
315 return default
315 return default
316 if isinstance(v, bool):
316 if isinstance(v, bool):
317 return v
317 return v
318 b = util.parsebool(v)
318 b = util.parsebool(v)
319 if b is None:
319 if b is None:
320 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
320 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
321 % (section, name, v))
321 % (section, name, v))
322 return b
322 return b
323
323
324 def configint(self, section, name, default=None, untrusted=False):
324 def configint(self, section, name, default=None, untrusted=False):
325 """parse a configuration element as an integer
325 """parse a configuration element as an integer
326
326
327 >>> u = ui(); s = 'foo'
327 >>> u = ui(); s = 'foo'
328 >>> u.setconfig(s, 'int1', '42')
328 >>> u.setconfig(s, 'int1', '42')
329 >>> u.configint(s, 'int1')
329 >>> u.configint(s, 'int1')
330 42
330 42
331 >>> u.setconfig(s, 'int2', '-42')
331 >>> u.setconfig(s, 'int2', '-42')
332 >>> u.configint(s, 'int2')
332 >>> u.configint(s, 'int2')
333 -42
333 -42
334 >>> u.configint(s, 'unknown', 7)
334 >>> u.configint(s, 'unknown', 7)
335 7
335 7
336 >>> u.setconfig(s, 'invalid', 'somevalue')
336 >>> u.setconfig(s, 'invalid', 'somevalue')
337 >>> u.configint(s, 'invalid')
337 >>> u.configint(s, 'invalid')
338 Traceback (most recent call last):
338 Traceback (most recent call last):
339 ...
339 ...
340 ConfigError: foo.invalid is not an integer ('somevalue')
340 ConfigError: foo.invalid is not an integer ('somevalue')
341 """
341 """
342
342
343 v = self.config(section, name, None, untrusted)
343 v = self.config(section, name, None, untrusted)
344 if v is None:
344 if v is None:
345 return default
345 return default
346 try:
346 try:
347 return int(v)
347 return int(v)
348 except ValueError:
348 except ValueError:
349 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
349 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
350 % (section, name, v))
350 % (section, name, v))
351
351
352 def configbytes(self, section, name, default=0, untrusted=False):
352 def configbytes(self, section, name, default=0, untrusted=False):
353 """parse a configuration element as a quantity in bytes
353 """parse a configuration element as a quantity in bytes
354
354
355 Units can be specified as b (bytes), k or kb (kilobytes), m or
355 Units can be specified as b (bytes), k or kb (kilobytes), m or
356 mb (megabytes), g or gb (gigabytes).
356 mb (megabytes), g or gb (gigabytes).
357
357
358 >>> u = ui(); s = 'foo'
358 >>> u = ui(); s = 'foo'
359 >>> u.setconfig(s, 'val1', '42')
359 >>> u.setconfig(s, 'val1', '42')
360 >>> u.configbytes(s, 'val1')
360 >>> u.configbytes(s, 'val1')
361 42
361 42
362 >>> u.setconfig(s, 'val2', '42.5 kb')
362 >>> u.setconfig(s, 'val2', '42.5 kb')
363 >>> u.configbytes(s, 'val2')
363 >>> u.configbytes(s, 'val2')
364 43520
364 43520
365 >>> u.configbytes(s, 'unknown', '7 MB')
365 >>> u.configbytes(s, 'unknown', '7 MB')
366 7340032
366 7340032
367 >>> u.setconfig(s, 'invalid', 'somevalue')
367 >>> u.setconfig(s, 'invalid', 'somevalue')
368 >>> u.configbytes(s, 'invalid')
368 >>> u.configbytes(s, 'invalid')
369 Traceback (most recent call last):
369 Traceback (most recent call last):
370 ...
370 ...
371 ConfigError: foo.invalid is not a byte quantity ('somevalue')
371 ConfigError: foo.invalid is not a byte quantity ('somevalue')
372 """
372 """
373
373
374 value = self.config(section, name)
374 value = self.config(section, name)
375 if value is None:
375 if value is None:
376 if not isinstance(default, str):
376 if not isinstance(default, str):
377 return default
377 return default
378 value = default
378 value = default
379 try:
379 try:
380 return util.sizetoint(value)
380 return util.sizetoint(value)
381 except error.ParseError:
381 except error.ParseError:
382 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
382 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
383 % (section, name, value))
383 % (section, name, value))
384
384
385 def configlist(self, section, name, default=None, untrusted=False):
385 def configlist(self, section, name, default=None, untrusted=False):
386 """parse a configuration element as a list of comma/space separated
386 """parse a configuration element as a list of comma/space separated
387 strings
387 strings
388
388
389 >>> u = ui(); s = 'foo'
389 >>> u = ui(); s = 'foo'
390 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
390 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
391 >>> u.configlist(s, 'list1')
391 >>> u.configlist(s, 'list1')
392 ['this', 'is', 'a small', 'test']
392 ['this', 'is', 'a small', 'test']
393 """
393 """
394
394
395 def _parse_plain(parts, s, offset):
395 def _parse_plain(parts, s, offset):
396 whitespace = False
396 whitespace = False
397 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
397 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
398 whitespace = True
398 whitespace = True
399 offset += 1
399 offset += 1
400 if offset >= len(s):
400 if offset >= len(s):
401 return None, parts, offset
401 return None, parts, offset
402 if whitespace:
402 if whitespace:
403 parts.append('')
403 parts.append('')
404 if s[offset] == '"' and not parts[-1]:
404 if s[offset] == '"' and not parts[-1]:
405 return _parse_quote, parts, offset + 1
405 return _parse_quote, parts, offset + 1
406 elif s[offset] == '"' and parts[-1][-1] == '\\':
406 elif s[offset] == '"' and parts[-1][-1] == '\\':
407 parts[-1] = parts[-1][:-1] + s[offset]
407 parts[-1] = parts[-1][:-1] + s[offset]
408 return _parse_plain, parts, offset + 1
408 return _parse_plain, parts, offset + 1
409 parts[-1] += s[offset]
409 parts[-1] += s[offset]
410 return _parse_plain, parts, offset + 1
410 return _parse_plain, parts, offset + 1
411
411
412 def _parse_quote(parts, s, offset):
412 def _parse_quote(parts, s, offset):
413 if offset < len(s) and s[offset] == '"': # ""
413 if offset < len(s) and s[offset] == '"': # ""
414 parts.append('')
414 parts.append('')
415 offset += 1
415 offset += 1
416 while offset < len(s) and (s[offset].isspace() or
416 while offset < len(s) and (s[offset].isspace() or
417 s[offset] == ','):
417 s[offset] == ','):
418 offset += 1
418 offset += 1
419 return _parse_plain, parts, offset
419 return _parse_plain, parts, offset
420
420
421 while offset < len(s) and s[offset] != '"':
421 while offset < len(s) and s[offset] != '"':
422 if (s[offset] == '\\' and offset + 1 < len(s)
422 if (s[offset] == '\\' and offset + 1 < len(s)
423 and s[offset + 1] == '"'):
423 and s[offset + 1] == '"'):
424 offset += 1
424 offset += 1
425 parts[-1] += '"'
425 parts[-1] += '"'
426 else:
426 else:
427 parts[-1] += s[offset]
427 parts[-1] += s[offset]
428 offset += 1
428 offset += 1
429
429
430 if offset >= len(s):
430 if offset >= len(s):
431 real_parts = _configlist(parts[-1])
431 real_parts = _configlist(parts[-1])
432 if not real_parts:
432 if not real_parts:
433 parts[-1] = '"'
433 parts[-1] = '"'
434 else:
434 else:
435 real_parts[0] = '"' + real_parts[0]
435 real_parts[0] = '"' + real_parts[0]
436 parts = parts[:-1]
436 parts = parts[:-1]
437 parts.extend(real_parts)
437 parts.extend(real_parts)
438 return None, parts, offset
438 return None, parts, offset
439
439
440 offset += 1
440 offset += 1
441 while offset < len(s) and s[offset] in [' ', ',']:
441 while offset < len(s) and s[offset] in [' ', ',']:
442 offset += 1
442 offset += 1
443
443
444 if offset < len(s):
444 if offset < len(s):
445 if offset + 1 == len(s) and s[offset] == '"':
445 if offset + 1 == len(s) and s[offset] == '"':
446 parts[-1] += '"'
446 parts[-1] += '"'
447 offset += 1
447 offset += 1
448 else:
448 else:
449 parts.append('')
449 parts.append('')
450 else:
450 else:
451 return None, parts, offset
451 return None, parts, offset
452
452
453 return _parse_plain, parts, offset
453 return _parse_plain, parts, offset
454
454
455 def _configlist(s):
455 def _configlist(s):
456 s = s.rstrip(' ,')
456 s = s.rstrip(' ,')
457 if not s:
457 if not s:
458 return []
458 return []
459 parser, parts, offset = _parse_plain, [''], 0
459 parser, parts, offset = _parse_plain, [''], 0
460 while parser:
460 while parser:
461 parser, parts, offset = parser(parts, s, offset)
461 parser, parts, offset = parser(parts, s, offset)
462 return parts
462 return parts
463
463
464 result = self.config(section, name, untrusted=untrusted)
464 result = self.config(section, name, untrusted=untrusted)
465 if result is None:
465 if result is None:
466 result = default or []
466 result = default or []
467 if isinstance(result, basestring):
467 if isinstance(result, basestring):
468 result = _configlist(result.lstrip(' ,\n'))
468 result = _configlist(result.lstrip(' ,\n'))
469 if result is None:
469 if result is None:
470 result = default or []
470 result = default or []
471 return result
471 return result
472
472
473 def has_section(self, section, untrusted=False):
473 def has_section(self, section, untrusted=False):
474 '''tell whether section exists in config.'''
474 '''tell whether section exists in config.'''
475 return section in self._data(untrusted)
475 return section in self._data(untrusted)
476
476
477 def configitems(self, section, untrusted=False):
477 def configitems(self, section, untrusted=False):
478 items = self._data(untrusted).items(section)
478 items = self._data(untrusted).items(section)
479 if self.debugflag and not untrusted and self._reportuntrusted:
479 if self.debugflag and not untrusted and self._reportuntrusted:
480 for k, v in self._ucfg.items(section):
480 for k, v in self._ucfg.items(section):
481 if self._tcfg.get(section, k) != v:
481 if self._tcfg.get(section, k) != v:
482 self.debug("ignoring untrusted configuration option "
482 self.debug("ignoring untrusted configuration option "
483 "%s.%s = %s\n" % (section, k, v))
483 "%s.%s = %s\n" % (section, k, v))
484 return items
484 return items
485
485
486 def walkconfig(self, untrusted=False):
486 def walkconfig(self, untrusted=False):
487 cfg = self._data(untrusted)
487 cfg = self._data(untrusted)
488 for section in cfg.sections():
488 for section in cfg.sections():
489 for name, value in self.configitems(section, untrusted):
489 for name, value in self.configitems(section, untrusted):
490 yield section, name, value
490 yield section, name, value
491
491
492 def plain(self, feature=None):
492 def plain(self, feature=None):
493 '''is plain mode active?
493 '''is plain mode active?
494
494
495 Plain mode means that all configuration variables which affect
495 Plain mode means that all configuration variables which affect
496 the behavior and output of Mercurial should be
496 the behavior and output of Mercurial should be
497 ignored. Additionally, the output should be stable,
497 ignored. Additionally, the output should be stable,
498 reproducible and suitable for use in scripts or applications.
498 reproducible and suitable for use in scripts or applications.
499
499
500 The only way to trigger plain mode is by setting either the
500 The only way to trigger plain mode is by setting either the
501 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
501 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
502
502
503 The return value can either be
503 The return value can either be
504 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
504 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
505 - True otherwise
505 - True otherwise
506 '''
506 '''
507 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
507 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
508 return False
508 return False
509 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
509 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
510 if feature and exceptions:
510 if feature and exceptions:
511 return feature not in exceptions
511 return feature not in exceptions
512 return True
512 return True
513
513
514 def username(self):
514 def username(self):
515 """Return default username to be used in commits.
515 """Return default username to be used in commits.
516
516
517 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
517 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
518 and stop searching if one of these is set.
518 and stop searching if one of these is set.
519 If not found and ui.askusername is True, ask the user, else use
519 If not found and ui.askusername is True, ask the user, else use
520 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
520 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
521 """
521 """
522 user = os.environ.get("HGUSER")
522 user = os.environ.get("HGUSER")
523 if user is None:
523 if user is None:
524 user = self.config("ui", ["username", "user"])
524 user = self.config("ui", ["username", "user"])
525 if user is not None:
525 if user is not None:
526 user = os.path.expandvars(user)
526 user = os.path.expandvars(user)
527 if user is None:
527 if user is None:
528 user = os.environ.get("EMAIL")
528 user = os.environ.get("EMAIL")
529 if user is None and self.configbool("ui", "askusername"):
529 if user is None and self.configbool("ui", "askusername"):
530 user = self.prompt(_("enter a commit username:"), default=None)
530 user = self.prompt(_("enter a commit username:"), default=None)
531 if user is None and not self.interactive():
531 if user is None and not self.interactive():
532 try:
532 try:
533 user = '%s@%s' % (util.getuser(), socket.getfqdn())
533 user = '%s@%s' % (util.getuser(), socket.getfqdn())
534 self.warn(_("no username found, using '%s' instead\n") % user)
534 self.warn(_("no username found, using '%s' instead\n") % user)
535 except KeyError:
535 except KeyError:
536 pass
536 pass
537 if not user:
537 if not user:
538 raise error.Abort(_('no username supplied'),
538 raise error.Abort(_('no username supplied'),
539 hint=_('use "hg config --edit" '
539 hint=_('use "hg config --edit" '
540 'to set your username'))
540 'to set your username'))
541 if "\n" in user:
541 if "\n" in user:
542 raise error.Abort(_("username %s contains a newline\n")
542 raise error.Abort(_("username %s contains a newline\n")
543 % repr(user))
543 % repr(user))
544 return user
544 return user
545
545
546 def shortuser(self, user):
546 def shortuser(self, user):
547 """Return a short representation of a user name or email address."""
547 """Return a short representation of a user name or email address."""
548 if not self.verbose:
548 if not self.verbose:
549 user = util.shortuser(user)
549 user = util.shortuser(user)
550 return user
550 return user
551
551
552 def expandpath(self, loc, default=None):
552 def expandpath(self, loc, default=None):
553 """Return repository location relative to cwd or from [paths]"""
553 """Return repository location relative to cwd or from [paths]"""
554 try:
554 try:
555 p = self.paths.getpath(loc)
555 p = self.paths.getpath(loc)
556 if p:
556 if p:
557 return p.rawloc
557 return p.rawloc
558 except error.RepoError:
558 except error.RepoError:
559 pass
559 pass
560
560
561 if default:
561 if default:
562 try:
562 try:
563 p = self.paths.getpath(default)
563 p = self.paths.getpath(default)
564 if p:
564 if p:
565 return p.rawloc
565 return p.rawloc
566 except error.RepoError:
566 except error.RepoError:
567 pass
567 pass
568
568
569 return loc
569 return loc
570
570
571 @util.propertycache
571 @util.propertycache
572 def paths(self):
572 def paths(self):
573 return paths(self)
573 return paths(self)
574
574
575 def pushbuffer(self, error=False, subproc=False):
575 def pushbuffer(self, error=False, subproc=False):
576 """install a buffer to capture standard output of the ui object
576 """install a buffer to capture standard output of the ui object
577
577
578 If error is True, the error output will be captured too.
578 If error is True, the error output will be captured too.
579
579
580 If subproc is True, output from subprocesses (typically hooks) will be
580 If subproc is True, output from subprocesses (typically hooks) will be
581 captured too."""
581 captured too."""
582 self._buffers.append([])
582 self._buffers.append([])
583 self._bufferstates.append((error, subproc))
583 self._bufferstates.append((error, subproc))
584
584
585 def popbuffer(self, labeled=False):
585 def popbuffer(self, labeled=False):
586 '''pop the last buffer and return the buffered output
586 '''pop the last buffer and return the buffered output
587
587
588 If labeled is True, any labels associated with buffered
588 If labeled is True, any labels associated with buffered
589 output will be handled. By default, this has no effect
589 output will be handled. By default, this has no effect
590 on the output returned, but extensions and GUI tools may
590 on the output returned, but extensions and GUI tools may
591 handle this argument and returned styled output. If output
591 handle this argument and returned styled output. If output
592 is being buffered so it can be captured and parsed or
592 is being buffered so it can be captured and parsed or
593 processed, labeled should not be set to True.
593 processed, labeled should not be set to True.
594 '''
594 '''
595 self._bufferstates.pop()
595 self._bufferstates.pop()
596 return "".join(self._buffers.pop())
596 return "".join(self._buffers.pop())
597
597
598 def write(self, *args, **opts):
598 def write(self, *args, **opts):
599 '''write args to output
599 '''write args to output
600
600
601 By default, this method simply writes to the buffer or stdout,
601 By default, this method simply writes to the buffer or stdout,
602 but extensions or GUI tools may override this method,
602 but extensions or GUI tools may override this method,
603 write_err(), popbuffer(), and label() to style output from
603 write_err(), popbuffer(), and label() to style output from
604 various parts of hg.
604 various parts of hg.
605
605
606 An optional keyword argument, "label", can be passed in.
606 An optional keyword argument, "label", can be passed in.
607 This should be a string containing label names separated by
607 This should be a string containing label names separated by
608 space. Label names take the form of "topic.type". For example,
608 space. Label names take the form of "topic.type". For example,
609 ui.debug() issues a label of "ui.debug".
609 ui.debug() issues a label of "ui.debug".
610
610
611 When labeling output for a specific command, a label of
611 When labeling output for a specific command, a label of
612 "cmdname.type" is recommended. For example, status issues
612 "cmdname.type" is recommended. For example, status issues
613 a label of "status.modified" for modified files.
613 a label of "status.modified" for modified files.
614 '''
614 '''
615 self._progclear()
615 self._progclear()
616 if self._buffers:
616 if self._buffers:
617 self._buffers[-1].extend([str(a) for a in args])
617 self._buffers[-1].extend([str(a) for a in args])
618 else:
618 else:
619 for a in args:
619 for a in args:
620 self.fout.write(str(a))
620 self.fout.write(str(a))
621
621
622 def write_err(self, *args, **opts):
622 def write_err(self, *args, **opts):
623 self._progclear()
623 self._progclear()
624 try:
624 try:
625 if self._bufferstates and self._bufferstates[-1][0]:
625 if self._bufferstates and self._bufferstates[-1][0]:
626 return self.write(*args, **opts)
626 return self.write(*args, **opts)
627 if not getattr(self.fout, 'closed', False):
627 if not getattr(self.fout, 'closed', False):
628 self.fout.flush()
628 self.fout.flush()
629 for a in args:
629 for a in args:
630 self.ferr.write(str(a))
630 self.ferr.write(str(a))
631 # stderr may be buffered under win32 when redirected to files,
631 # stderr may be buffered under win32 when redirected to files,
632 # including stdout.
632 # including stdout.
633 if not getattr(self.ferr, 'closed', False):
633 if not getattr(self.ferr, 'closed', False):
634 self.ferr.flush()
634 self.ferr.flush()
635 except IOError as inst:
635 except IOError as inst:
636 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
636 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
637 raise
637 raise
638
638
639 def flush(self):
639 def flush(self):
640 try: self.fout.flush()
640 try: self.fout.flush()
641 except (IOError, ValueError): pass
641 except (IOError, ValueError): pass
642 try: self.ferr.flush()
642 try: self.ferr.flush()
643 except (IOError, ValueError): pass
643 except (IOError, ValueError): pass
644
644
645 def _isatty(self, fh):
645 def _isatty(self, fh):
646 if self.configbool('ui', 'nontty', False):
646 if self.configbool('ui', 'nontty', False):
647 return False
647 return False
648 return util.isatty(fh)
648 return util.isatty(fh)
649
649
650 def interactive(self):
650 def interactive(self):
651 '''is interactive input allowed?
651 '''is interactive input allowed?
652
652
653 An interactive session is a session where input can be reasonably read
653 An interactive session is a session where input can be reasonably read
654 from `sys.stdin'. If this function returns false, any attempt to read
654 from `sys.stdin'. If this function returns false, any attempt to read
655 from stdin should fail with an error, unless a sensible default has been
655 from stdin should fail with an error, unless a sensible default has been
656 specified.
656 specified.
657
657
658 Interactiveness is triggered by the value of the `ui.interactive'
658 Interactiveness is triggered by the value of the `ui.interactive'
659 configuration variable or - if it is unset - when `sys.stdin' points
659 configuration variable or - if it is unset - when `sys.stdin' points
660 to a terminal device.
660 to a terminal device.
661
661
662 This function refers to input only; for output, see `ui.formatted()'.
662 This function refers to input only; for output, see `ui.formatted()'.
663 '''
663 '''
664 i = self.configbool("ui", "interactive", None)
664 i = self.configbool("ui", "interactive", None)
665 if i is None:
665 if i is None:
666 # some environments replace stdin without implementing isatty
666 # some environments replace stdin without implementing isatty
667 # usually those are non-interactive
667 # usually those are non-interactive
668 return self._isatty(self.fin)
668 return self._isatty(self.fin)
669
669
670 return i
670 return i
671
671
672 def termwidth(self):
672 def termwidth(self):
673 '''how wide is the terminal in columns?
673 '''how wide is the terminal in columns?
674 '''
674 '''
675 if 'COLUMNS' in os.environ:
675 if 'COLUMNS' in os.environ:
676 try:
676 try:
677 return int(os.environ['COLUMNS'])
677 return int(os.environ['COLUMNS'])
678 except ValueError:
678 except ValueError:
679 pass
679 pass
680 return util.termwidth()
680 return util.termwidth()
681
681
682 def formatted(self):
682 def formatted(self):
683 '''should formatted output be used?
683 '''should formatted output be used?
684
684
685 It is often desirable to format the output to suite the output medium.
685 It is often desirable to format the output to suite the output medium.
686 Examples of this are truncating long lines or colorizing messages.
686 Examples of this are truncating long lines or colorizing messages.
687 However, this is not often not desirable when piping output into other
687 However, this is not often not desirable when piping output into other
688 utilities, e.g. `grep'.
688 utilities, e.g. `grep'.
689
689
690 Formatted output is triggered by the value of the `ui.formatted'
690 Formatted output is triggered by the value of the `ui.formatted'
691 configuration variable or - if it is unset - when `sys.stdout' points
691 configuration variable or - if it is unset - when `sys.stdout' points
692 to a terminal device. Please note that `ui.formatted' should be
692 to a terminal device. Please note that `ui.formatted' should be
693 considered an implementation detail; it is not intended for use outside
693 considered an implementation detail; it is not intended for use outside
694 Mercurial or its extensions.
694 Mercurial or its extensions.
695
695
696 This function refers to output only; for input, see `ui.interactive()'.
696 This function refers to output only; for input, see `ui.interactive()'.
697 This function always returns false when in plain mode, see `ui.plain()'.
697 This function always returns false when in plain mode, see `ui.plain()'.
698 '''
698 '''
699 if self.plain():
699 if self.plain():
700 return False
700 return False
701
701
702 i = self.configbool("ui", "formatted", None)
702 i = self.configbool("ui", "formatted", None)
703 if i is None:
703 if i is None:
704 # some environments replace stdout without implementing isatty
704 # some environments replace stdout without implementing isatty
705 # usually those are non-interactive
705 # usually those are non-interactive
706 return self._isatty(self.fout)
706 return self._isatty(self.fout)
707
707
708 return i
708 return i
709
709
710 def _readline(self, prompt=''):
710 def _readline(self, prompt=''):
711 if self._isatty(self.fin):
711 if self._isatty(self.fin):
712 try:
712 try:
713 # magically add command line editing support, where
713 # magically add command line editing support, where
714 # available
714 # available
715 import readline
715 import readline
716 # force demandimport to really load the module
716 # force demandimport to really load the module
717 readline.read_history_file
717 readline.read_history_file
718 # windows sometimes raises something other than ImportError
718 # windows sometimes raises something other than ImportError
719 except Exception:
719 except Exception:
720 pass
720 pass
721
721
722 # call write() so output goes through subclassed implementation
722 # call write() so output goes through subclassed implementation
723 # e.g. color extension on Windows
723 # e.g. color extension on Windows
724 self.write(prompt)
724 self.write(prompt)
725
725
726 # instead of trying to emulate raw_input, swap (self.fin,
726 # instead of trying to emulate raw_input, swap (self.fin,
727 # self.fout) with (sys.stdin, sys.stdout)
727 # self.fout) with (sys.stdin, sys.stdout)
728 oldin = sys.stdin
728 oldin = sys.stdin
729 oldout = sys.stdout
729 oldout = sys.stdout
730 sys.stdin = self.fin
730 sys.stdin = self.fin
731 sys.stdout = self.fout
731 sys.stdout = self.fout
732 # prompt ' ' must exist; otherwise readline may delete entire line
732 # prompt ' ' must exist; otherwise readline may delete entire line
733 # - http://bugs.python.org/issue12833
733 # - http://bugs.python.org/issue12833
734 line = raw_input(' ')
734 line = raw_input(' ')
735 sys.stdin = oldin
735 sys.stdin = oldin
736 sys.stdout = oldout
736 sys.stdout = oldout
737
737
738 # When stdin is in binary mode on Windows, it can cause
738 # When stdin is in binary mode on Windows, it can cause
739 # raw_input() to emit an extra trailing carriage return
739 # raw_input() to emit an extra trailing carriage return
740 if os.linesep == '\r\n' and line and line[-1] == '\r':
740 if os.linesep == '\r\n' and line and line[-1] == '\r':
741 line = line[:-1]
741 line = line[:-1]
742 return line
742 return line
743
743
744 def prompt(self, msg, default="y"):
744 def prompt(self, msg, default="y"):
745 """Prompt user with msg, read response.
745 """Prompt user with msg, read response.
746 If ui is not interactive, the default is returned.
746 If ui is not interactive, the default is returned.
747 """
747 """
748 if not self.interactive():
748 if not self.interactive():
749 self.write(msg, ' ', default, "\n")
749 self.write(msg, ' ', default, "\n")
750 return default
750 return default
751 try:
751 try:
752 r = self._readline(self.label(msg, 'ui.prompt'))
752 r = self._readline(self.label(msg, 'ui.prompt'))
753 if not r:
753 if not r:
754 r = default
754 r = default
755 if self.configbool('ui', 'promptecho'):
755 if self.configbool('ui', 'promptecho'):
756 self.write(r, "\n")
756 self.write(r, "\n")
757 return r
757 return r
758 except EOFError:
758 except EOFError:
759 raise error.Abort(_('response expected'))
759 raise error.Abort(_('response expected'))
760
760
761 @staticmethod
761 @staticmethod
762 def extractchoices(prompt):
762 def extractchoices(prompt):
763 """Extract prompt message and list of choices from specified prompt.
763 """Extract prompt message and list of choices from specified prompt.
764
764
765 This returns tuple "(message, choices)", and "choices" is the
765 This returns tuple "(message, choices)", and "choices" is the
766 list of tuple "(response character, text without &)".
766 list of tuple "(response character, text without &)".
767 """
767 """
768 parts = prompt.split('$$')
768 parts = prompt.split('$$')
769 msg = parts[0].rstrip(' ')
769 msg = parts[0].rstrip(' ')
770 choices = [p.strip(' ') for p in parts[1:]]
770 choices = [p.strip(' ') for p in parts[1:]]
771 return (msg,
771 return (msg,
772 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
772 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
773 for s in choices])
773 for s in choices])
774
774
775 def promptchoice(self, prompt, default=0):
775 def promptchoice(self, prompt, default=0):
776 """Prompt user with a message, read response, and ensure it matches
776 """Prompt user with a message, read response, and ensure it matches
777 one of the provided choices. The prompt is formatted as follows:
777 one of the provided choices. The prompt is formatted as follows:
778
778
779 "would you like fries with that (Yn)? $$ &Yes $$ &No"
779 "would you like fries with that (Yn)? $$ &Yes $$ &No"
780
780
781 The index of the choice is returned. Responses are case
781 The index of the choice is returned. Responses are case
782 insensitive. If ui is not interactive, the default is
782 insensitive. If ui is not interactive, the default is
783 returned.
783 returned.
784 """
784 """
785
785
786 msg, choices = self.extractchoices(prompt)
786 msg, choices = self.extractchoices(prompt)
787 resps = [r for r, t in choices]
787 resps = [r for r, t in choices]
788 while True:
788 while True:
789 r = self.prompt(msg, resps[default])
789 r = self.prompt(msg, resps[default])
790 if r.lower() in resps:
790 if r.lower() in resps:
791 return resps.index(r.lower())
791 return resps.index(r.lower())
792 self.write(_("unrecognized response\n"))
792 self.write(_("unrecognized response\n"))
793
793
794 def getpass(self, prompt=None, default=None):
794 def getpass(self, prompt=None, default=None):
795 if not self.interactive():
795 if not self.interactive():
796 return default
796 return default
797 try:
797 try:
798 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
798 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
799 # disable getpass() only if explicitly specified. it's still valid
799 # disable getpass() only if explicitly specified. it's still valid
800 # to interact with tty even if fin is not a tty.
800 # to interact with tty even if fin is not a tty.
801 if self.configbool('ui', 'nontty'):
801 if self.configbool('ui', 'nontty'):
802 return self.fin.readline().rstrip('\n')
802 return self.fin.readline().rstrip('\n')
803 else:
803 else:
804 return getpass.getpass('')
804 return getpass.getpass('')
805 except EOFError:
805 except EOFError:
806 raise error.Abort(_('response expected'))
806 raise error.Abort(_('response expected'))
807 def status(self, *msg, **opts):
807 def status(self, *msg, **opts):
808 '''write status message to output (if ui.quiet is False)
808 '''write status message to output (if ui.quiet is False)
809
809
810 This adds an output label of "ui.status".
810 This adds an output label of "ui.status".
811 '''
811 '''
812 if not self.quiet:
812 if not self.quiet:
813 opts['label'] = opts.get('label', '') + ' ui.status'
813 opts['label'] = opts.get('label', '') + ' ui.status'
814 self.write(*msg, **opts)
814 self.write(*msg, **opts)
815 def warn(self, *msg, **opts):
815 def warn(self, *msg, **opts):
816 '''write warning message to output (stderr)
816 '''write warning message to output (stderr)
817
817
818 This adds an output label of "ui.warning".
818 This adds an output label of "ui.warning".
819 '''
819 '''
820 opts['label'] = opts.get('label', '') + ' ui.warning'
820 opts['label'] = opts.get('label', '') + ' ui.warning'
821 self.write_err(*msg, **opts)
821 self.write_err(*msg, **opts)
822 def note(self, *msg, **opts):
822 def note(self, *msg, **opts):
823 '''write note to output (if ui.verbose is True)
823 '''write note to output (if ui.verbose is True)
824
824
825 This adds an output label of "ui.note".
825 This adds an output label of "ui.note".
826 '''
826 '''
827 if self.verbose:
827 if self.verbose:
828 opts['label'] = opts.get('label', '') + ' ui.note'
828 opts['label'] = opts.get('label', '') + ' ui.note'
829 self.write(*msg, **opts)
829 self.write(*msg, **opts)
830 def debug(self, *msg, **opts):
830 def debug(self, *msg, **opts):
831 '''write debug message to output (if ui.debugflag is True)
831 '''write debug message to output (if ui.debugflag is True)
832
832
833 This adds an output label of "ui.debug".
833 This adds an output label of "ui.debug".
834 '''
834 '''
835 if self.debugflag:
835 if self.debugflag:
836 opts['label'] = opts.get('label', '') + ' ui.debug'
836 opts['label'] = opts.get('label', '') + ' ui.debug'
837 self.write(*msg, **opts)
837 self.write(*msg, **opts)
838 def edit(self, text, user, extra=None, editform=None):
838
839 def edit(self, text, user, extra=None, editform=None, pending=None):
839 if extra is None:
840 if extra is None:
840 extra = {}
841 extra = {}
841 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
842 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
842 text=True)
843 text=True)
843 try:
844 try:
844 f = os.fdopen(fd, "w")
845 f = os.fdopen(fd, "w")
845 f.write(text)
846 f.write(text)
846 f.close()
847 f.close()
847
848
848 environ = {'HGUSER': user}
849 environ = {'HGUSER': user}
849 if 'transplant_source' in extra:
850 if 'transplant_source' in extra:
850 environ.update({'HGREVISION': hex(extra['transplant_source'])})
851 environ.update({'HGREVISION': hex(extra['transplant_source'])})
851 for label in ('intermediate-source', 'source', 'rebase_source'):
852 for label in ('intermediate-source', 'source', 'rebase_source'):
852 if label in extra:
853 if label in extra:
853 environ.update({'HGREVISION': extra[label]})
854 environ.update({'HGREVISION': extra[label]})
854 break
855 break
855 if editform:
856 if editform:
856 environ.update({'HGEDITFORM': editform})
857 environ.update({'HGEDITFORM': editform})
858 if pending:
859 environ.update({'HG_PENDING': pending})
857
860
858 editor = self.geteditor()
861 editor = self.geteditor()
859
862
860 self.system("%s \"%s\"" % (editor, name),
863 self.system("%s \"%s\"" % (editor, name),
861 environ=environ,
864 environ=environ,
862 onerr=error.Abort, errprefix=_("edit failed"))
865 onerr=error.Abort, errprefix=_("edit failed"))
863
866
864 f = open(name)
867 f = open(name)
865 t = f.read()
868 t = f.read()
866 f.close()
869 f.close()
867 finally:
870 finally:
868 os.unlink(name)
871 os.unlink(name)
869
872
870 return t
873 return t
871
874
872 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
875 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
873 '''execute shell command with appropriate output stream. command
876 '''execute shell command with appropriate output stream. command
874 output will be redirected if fout is not stdout.
877 output will be redirected if fout is not stdout.
875 '''
878 '''
876 out = self.fout
879 out = self.fout
877 if any(s[1] for s in self._bufferstates):
880 if any(s[1] for s in self._bufferstates):
878 out = self
881 out = self
879 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
882 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
880 errprefix=errprefix, out=out)
883 errprefix=errprefix, out=out)
881
884
882 def traceback(self, exc=None, force=False):
885 def traceback(self, exc=None, force=False):
883 '''print exception traceback if traceback printing enabled or forced.
886 '''print exception traceback if traceback printing enabled or forced.
884 only to call in exception handler. returns true if traceback
887 only to call in exception handler. returns true if traceback
885 printed.'''
888 printed.'''
886 if self.tracebackflag or force:
889 if self.tracebackflag or force:
887 if exc is None:
890 if exc is None:
888 exc = sys.exc_info()
891 exc = sys.exc_info()
889 cause = getattr(exc[1], 'cause', None)
892 cause = getattr(exc[1], 'cause', None)
890
893
891 if cause is not None:
894 if cause is not None:
892 causetb = traceback.format_tb(cause[2])
895 causetb = traceback.format_tb(cause[2])
893 exctb = traceback.format_tb(exc[2])
896 exctb = traceback.format_tb(exc[2])
894 exconly = traceback.format_exception_only(cause[0], cause[1])
897 exconly = traceback.format_exception_only(cause[0], cause[1])
895
898
896 # exclude frame where 'exc' was chained and rethrown from exctb
899 # exclude frame where 'exc' was chained and rethrown from exctb
897 self.write_err('Traceback (most recent call last):\n',
900 self.write_err('Traceback (most recent call last):\n',
898 ''.join(exctb[:-1]),
901 ''.join(exctb[:-1]),
899 ''.join(causetb),
902 ''.join(causetb),
900 ''.join(exconly))
903 ''.join(exconly))
901 else:
904 else:
902 output = traceback.format_exception(exc[0], exc[1], exc[2])
905 output = traceback.format_exception(exc[0], exc[1], exc[2])
903 self.write_err(''.join(output))
906 self.write_err(''.join(output))
904 return self.tracebackflag or force
907 return self.tracebackflag or force
905
908
906 def geteditor(self):
909 def geteditor(self):
907 '''return editor to use'''
910 '''return editor to use'''
908 if sys.platform == 'plan9':
911 if sys.platform == 'plan9':
909 # vi is the MIPS instruction simulator on Plan 9. We
912 # vi is the MIPS instruction simulator on Plan 9. We
910 # instead default to E to plumb commit messages to
913 # instead default to E to plumb commit messages to
911 # avoid confusion.
914 # avoid confusion.
912 editor = 'E'
915 editor = 'E'
913 else:
916 else:
914 editor = 'vi'
917 editor = 'vi'
915 return (os.environ.get("HGEDITOR") or
918 return (os.environ.get("HGEDITOR") or
916 self.config("ui", "editor") or
919 self.config("ui", "editor") or
917 os.environ.get("VISUAL") or
920 os.environ.get("VISUAL") or
918 os.environ.get("EDITOR", editor))
921 os.environ.get("EDITOR", editor))
919
922
920 @util.propertycache
923 @util.propertycache
921 def _progbar(self):
924 def _progbar(self):
922 """setup the progbar singleton to the ui object"""
925 """setup the progbar singleton to the ui object"""
923 if (self.quiet or self.debugflag
926 if (self.quiet or self.debugflag
924 or self.configbool('progress', 'disable', False)
927 or self.configbool('progress', 'disable', False)
925 or not progress.shouldprint(self)):
928 or not progress.shouldprint(self)):
926 return None
929 return None
927 return getprogbar(self)
930 return getprogbar(self)
928
931
929 def _progclear(self):
932 def _progclear(self):
930 """clear progress bar output if any. use it before any output"""
933 """clear progress bar output if any. use it before any output"""
931 if '_progbar' not in vars(self): # nothing loadef yet
934 if '_progbar' not in vars(self): # nothing loadef yet
932 return
935 return
933 if self._progbar is not None and self._progbar.printed:
936 if self._progbar is not None and self._progbar.printed:
934 self._progbar.clear()
937 self._progbar.clear()
935
938
936 def progress(self, topic, pos, item="", unit="", total=None):
939 def progress(self, topic, pos, item="", unit="", total=None):
937 '''show a progress message
940 '''show a progress message
938
941
939 With stock hg, this is simply a debug message that is hidden
942 With stock hg, this is simply a debug message that is hidden
940 by default, but with extensions or GUI tools it may be
943 by default, but with extensions or GUI tools it may be
941 visible. 'topic' is the current operation, 'item' is a
944 visible. 'topic' is the current operation, 'item' is a
942 non-numeric marker of the current position (i.e. the currently
945 non-numeric marker of the current position (i.e. the currently
943 in-process file), 'pos' is the current numeric position (i.e.
946 in-process file), 'pos' is the current numeric position (i.e.
944 revision, bytes, etc.), unit is a corresponding unit label,
947 revision, bytes, etc.), unit is a corresponding unit label,
945 and total is the highest expected pos.
948 and total is the highest expected pos.
946
949
947 Multiple nested topics may be active at a time.
950 Multiple nested topics may be active at a time.
948
951
949 All topics should be marked closed by setting pos to None at
952 All topics should be marked closed by setting pos to None at
950 termination.
953 termination.
951 '''
954 '''
952 if self._progbar is not None:
955 if self._progbar is not None:
953 self._progbar.progress(topic, pos, item=item, unit=unit,
956 self._progbar.progress(topic, pos, item=item, unit=unit,
954 total=total)
957 total=total)
955 if pos is None or not self.configbool('progress', 'debug'):
958 if pos is None or not self.configbool('progress', 'debug'):
956 return
959 return
957
960
958 if unit:
961 if unit:
959 unit = ' ' + unit
962 unit = ' ' + unit
960 if item:
963 if item:
961 item = ' ' + item
964 item = ' ' + item
962
965
963 if total:
966 if total:
964 pct = 100.0 * pos / total
967 pct = 100.0 * pos / total
965 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
968 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
966 % (topic, item, pos, total, unit, pct))
969 % (topic, item, pos, total, unit, pct))
967 else:
970 else:
968 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
971 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
969
972
970 def log(self, service, *msg, **opts):
973 def log(self, service, *msg, **opts):
971 '''hook for logging facility extensions
974 '''hook for logging facility extensions
972
975
973 service should be a readily-identifiable subsystem, which will
976 service should be a readily-identifiable subsystem, which will
974 allow filtering.
977 allow filtering.
975
978
976 *msg should be a newline-terminated format string to log, and
979 *msg should be a newline-terminated format string to log, and
977 then any values to %-format into that format string.
980 then any values to %-format into that format string.
978
981
979 **opts currently has no defined meanings.
982 **opts currently has no defined meanings.
980 '''
983 '''
981
984
982 def label(self, msg, label):
985 def label(self, msg, label):
983 '''style msg based on supplied label
986 '''style msg based on supplied label
984
987
985 Like ui.write(), this just returns msg unchanged, but extensions
988 Like ui.write(), this just returns msg unchanged, but extensions
986 and GUI tools can override it to allow styling output without
989 and GUI tools can override it to allow styling output without
987 writing it.
990 writing it.
988
991
989 ui.write(s, 'label') is equivalent to
992 ui.write(s, 'label') is equivalent to
990 ui.write(ui.label(s, 'label')).
993 ui.write(ui.label(s, 'label')).
991 '''
994 '''
992 return msg
995 return msg
993
996
994 def develwarn(self, msg):
997 def develwarn(self, msg):
995 """issue a developer warning message"""
998 """issue a developer warning message"""
996 msg = 'devel-warn: ' + msg
999 msg = 'devel-warn: ' + msg
997 if self.tracebackflag:
1000 if self.tracebackflag:
998 util.debugstacktrace(msg, 2, self.ferr, self.fout)
1001 util.debugstacktrace(msg, 2, self.ferr, self.fout)
999 else:
1002 else:
1000 curframe = inspect.currentframe()
1003 curframe = inspect.currentframe()
1001 calframe = inspect.getouterframes(curframe, 2)
1004 calframe = inspect.getouterframes(curframe, 2)
1002 self.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4]))
1005 self.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4]))
1003
1006
1004 class paths(dict):
1007 class paths(dict):
1005 """Represents a collection of paths and their configs.
1008 """Represents a collection of paths and their configs.
1006
1009
1007 Data is initially derived from ui instances and the config files they have
1010 Data is initially derived from ui instances and the config files they have
1008 loaded.
1011 loaded.
1009 """
1012 """
1010 def __init__(self, ui):
1013 def __init__(self, ui):
1011 dict.__init__(self)
1014 dict.__init__(self)
1012
1015
1013 for name, loc in ui.configitems('paths'):
1016 for name, loc in ui.configitems('paths'):
1014 # No location is the same as not existing.
1017 # No location is the same as not existing.
1015 if not loc:
1018 if not loc:
1016 continue
1019 continue
1017
1020
1018 # TODO ignore default-push once all consumers stop referencing it
1021 # TODO ignore default-push once all consumers stop referencing it
1019 # since it is handled specifically below.
1022 # since it is handled specifically below.
1020
1023
1021 self[name] = path(name, rawloc=loc)
1024 self[name] = path(name, rawloc=loc)
1022
1025
1023 # Handle default-push, which is a one-off that defines the push URL for
1026 # Handle default-push, which is a one-off that defines the push URL for
1024 # the "default" path.
1027 # the "default" path.
1025 defaultpush = ui.config('paths', 'default-push')
1028 defaultpush = ui.config('paths', 'default-push')
1026 if defaultpush and 'default' in self:
1029 if defaultpush and 'default' in self:
1027 self['default']._pushloc = defaultpush
1030 self['default']._pushloc = defaultpush
1028
1031
1029 def getpath(self, name, default=None):
1032 def getpath(self, name, default=None):
1030 """Return a ``path`` from a string, falling back to a default.
1033 """Return a ``path`` from a string, falling back to a default.
1031
1034
1032 ``name`` can be a named path or locations. Locations are filesystem
1035 ``name`` can be a named path or locations. Locations are filesystem
1033 paths or URIs.
1036 paths or URIs.
1034
1037
1035 Returns None if ``name`` is not a registered path, a URI, or a local
1038 Returns None if ``name`` is not a registered path, a URI, or a local
1036 path to a repo.
1039 path to a repo.
1037 """
1040 """
1038 # Only fall back to default if no path was requested.
1041 # Only fall back to default if no path was requested.
1039 if name is None:
1042 if name is None:
1040 if default:
1043 if default:
1041 try:
1044 try:
1042 return self[default]
1045 return self[default]
1043 except KeyError:
1046 except KeyError:
1044 return None
1047 return None
1045 else:
1048 else:
1046 return None
1049 return None
1047
1050
1048 # Most likely empty string.
1051 # Most likely empty string.
1049 # This may need to raise in the future.
1052 # This may need to raise in the future.
1050 if not name:
1053 if not name:
1051 return None
1054 return None
1052
1055
1053 try:
1056 try:
1054 return self[name]
1057 return self[name]
1055 except KeyError:
1058 except KeyError:
1056 # Try to resolve as a local path or URI.
1059 # Try to resolve as a local path or URI.
1057 try:
1060 try:
1058 return path(None, rawloc=name)
1061 return path(None, rawloc=name)
1059 except ValueError:
1062 except ValueError:
1060 raise error.RepoError(_('repository %s does not exist') %
1063 raise error.RepoError(_('repository %s does not exist') %
1061 name)
1064 name)
1062
1065
1063 assert False
1066 assert False
1064
1067
1065 class path(object):
1068 class path(object):
1066 """Represents an individual path and its configuration."""
1069 """Represents an individual path and its configuration."""
1067
1070
1068 def __init__(self, name, rawloc=None, pushloc=None):
1071 def __init__(self, name, rawloc=None, pushloc=None):
1069 """Construct a path from its config options.
1072 """Construct a path from its config options.
1070
1073
1071 ``name`` is the symbolic name of the path.
1074 ``name`` is the symbolic name of the path.
1072 ``rawloc`` is the raw location, as defined in the config.
1075 ``rawloc`` is the raw location, as defined in the config.
1073 ``pushloc`` is the raw locations pushes should be made to.
1076 ``pushloc`` is the raw locations pushes should be made to.
1074
1077
1075 If ``name`` is not defined, we require that the location be a) a local
1078 If ``name`` is not defined, we require that the location be a) a local
1076 filesystem path with a .hg directory or b) a URL. If not,
1079 filesystem path with a .hg directory or b) a URL. If not,
1077 ``ValueError`` is raised.
1080 ``ValueError`` is raised.
1078 """
1081 """
1079 if not rawloc:
1082 if not rawloc:
1080 raise ValueError('rawloc must be defined')
1083 raise ValueError('rawloc must be defined')
1081
1084
1082 # Locations may define branches via syntax <base>#<branch>.
1085 # Locations may define branches via syntax <base>#<branch>.
1083 u = util.url(rawloc)
1086 u = util.url(rawloc)
1084 branch = None
1087 branch = None
1085 if u.fragment:
1088 if u.fragment:
1086 branch = u.fragment
1089 branch = u.fragment
1087 u.fragment = None
1090 u.fragment = None
1088
1091
1089 self.url = u
1092 self.url = u
1090 self.branch = branch
1093 self.branch = branch
1091
1094
1092 self.name = name
1095 self.name = name
1093 self.rawloc = rawloc
1096 self.rawloc = rawloc
1094 self.loc = str(u)
1097 self.loc = str(u)
1095 self._pushloc = pushloc
1098 self._pushloc = pushloc
1096
1099
1097 # When given a raw location but not a symbolic name, validate the
1100 # When given a raw location but not a symbolic name, validate the
1098 # location is valid.
1101 # location is valid.
1099 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1102 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1100 raise ValueError('location is not a URL or path to a local '
1103 raise ValueError('location is not a URL or path to a local '
1101 'repo: %s' % rawloc)
1104 'repo: %s' % rawloc)
1102
1105
1103 def _isvalidlocalpath(self, path):
1106 def _isvalidlocalpath(self, path):
1104 """Returns True if the given path is a potentially valid repository.
1107 """Returns True if the given path is a potentially valid repository.
1105 This is its own function so that extensions can change the definition of
1108 This is its own function so that extensions can change the definition of
1106 'valid' in this case (like when pulling from a git repo into a hg
1109 'valid' in this case (like when pulling from a git repo into a hg
1107 one)."""
1110 one)."""
1108 return os.path.isdir(os.path.join(path, '.hg'))
1111 return os.path.isdir(os.path.join(path, '.hg'))
1109
1112
1110 @property
1113 @property
1111 def pushloc(self):
1114 def pushloc(self):
1112 return self._pushloc or self.loc
1115 return self._pushloc or self.loc
1113
1116
1114 # we instantiate one globally shared progress bar to avoid
1117 # we instantiate one globally shared progress bar to avoid
1115 # competing progress bars when multiple UI objects get created
1118 # competing progress bars when multiple UI objects get created
1116 _progresssingleton = None
1119 _progresssingleton = None
1117
1120
1118 def getprogbar(ui):
1121 def getprogbar(ui):
1119 global _progresssingleton
1122 global _progresssingleton
1120 if _progresssingleton is None:
1123 if _progresssingleton is None:
1121 # passing 'ui' object to the singleton is fishy,
1124 # passing 'ui' object to the singleton is fishy,
1122 # this is how the extension used to work but feel free to rework it.
1125 # this is how the extension used to work but feel free to rework it.
1123 _progresssingleton = progress.progbar(ui)
1126 _progresssingleton = progress.progbar(ui)
1124 return _progresssingleton
1127 return _progresssingleton
@@ -1,1151 +1,1151 b''
1 $ hg init
1 $ hg init
2
2
3 Setup:
3 Setup:
4
4
5 $ echo a >> a
5 $ echo a >> a
6 $ hg ci -Am 'base'
6 $ hg ci -Am 'base'
7 adding a
7 adding a
8
8
9 Refuse to amend public csets:
9 Refuse to amend public csets:
10
10
11 $ hg phase -r . -p
11 $ hg phase -r . -p
12 $ hg ci --amend
12 $ hg ci --amend
13 abort: cannot amend public changesets
13 abort: cannot amend public changesets
14 [255]
14 [255]
15 $ hg phase -r . -f -d
15 $ hg phase -r . -f -d
16
16
17 $ echo a >> a
17 $ echo a >> a
18 $ hg ci -Am 'base1'
18 $ hg ci -Am 'base1'
19
19
20 Nothing to amend:
20 Nothing to amend:
21
21
22 $ hg ci --amend
22 $ hg ci --amend -m 'base1'
23 nothing changed
23 nothing changed
24 [1]
24 [1]
25
25
26 $ cat >> $HGRCPATH <<EOF
26 $ cat >> $HGRCPATH <<EOF
27 > [hooks]
27 > [hooks]
28 > pretxncommit.foo = sh -c "echo \\"pretxncommit \$HG_NODE\\"; hg id -r \$HG_NODE"
28 > pretxncommit.foo = sh -c "echo \\"pretxncommit \$HG_NODE\\"; hg id -r \$HG_NODE"
29 > EOF
29 > EOF
30
30
31 Amending changeset with changes in working dir:
31 Amending changeset with changes in working dir:
32 (and check that --message does not trigger an editor)
32 (and check that --message does not trigger an editor)
33
33
34 $ echo a >> a
34 $ echo a >> a
35 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -m 'amend base1'
35 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -m 'amend base1'
36 pretxncommit 43f1ba15f28a50abf0aae529cf8a16bfced7b149
36 pretxncommit 43f1ba15f28a50abf0aae529cf8a16bfced7b149
37 43f1ba15f28a tip
37 43f1ba15f28a tip
38 saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-f1bf3ab8-amend-backup.hg (glob)
38 saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-f1bf3ab8-amend-backup.hg (glob)
39 $ echo 'pretxncommit.foo = ' >> $HGRCPATH
39 $ echo 'pretxncommit.foo = ' >> $HGRCPATH
40 $ hg diff -c .
40 $ hg diff -c .
41 diff -r ad120869acf0 -r 43f1ba15f28a a
41 diff -r ad120869acf0 -r 43f1ba15f28a a
42 --- a/a Thu Jan 01 00:00:00 1970 +0000
42 --- a/a Thu Jan 01 00:00:00 1970 +0000
43 +++ b/a Thu Jan 01 00:00:00 1970 +0000
43 +++ b/a Thu Jan 01 00:00:00 1970 +0000
44 @@ -1,1 +1,3 @@
44 @@ -1,1 +1,3 @@
45 a
45 a
46 +a
46 +a
47 +a
47 +a
48 $ hg log
48 $ hg log
49 changeset: 1:43f1ba15f28a
49 changeset: 1:43f1ba15f28a
50 tag: tip
50 tag: tip
51 user: test
51 user: test
52 date: Thu Jan 01 00:00:00 1970 +0000
52 date: Thu Jan 01 00:00:00 1970 +0000
53 summary: amend base1
53 summary: amend base1
54
54
55 changeset: 0:ad120869acf0
55 changeset: 0:ad120869acf0
56 user: test
56 user: test
57 date: Thu Jan 01 00:00:00 1970 +0000
57 date: Thu Jan 01 00:00:00 1970 +0000
58 summary: base
58 summary: base
59
59
60
60
61 Check proper abort for empty message
61 Check proper abort for empty message
62
62
63 $ cat > editor.sh << '__EOF__'
63 $ cat > editor.sh << '__EOF__'
64 > #!/bin/sh
64 > #!/bin/sh
65 > echo "" > "$1"
65 > echo "" > "$1"
66 > __EOF__
66 > __EOF__
67 $ echo b > b
67 $ echo b > b
68 $ hg add b
68 $ hg add b
69 $ hg summary
69 $ hg summary
70 parent: 1:43f1ba15f28a tip
70 parent: 1:43f1ba15f28a tip
71 amend base1
71 amend base1
72 branch: default
72 branch: default
73 commit: 1 added, 1 unknown
73 commit: 1 added, 1 unknown
74 update: (current)
74 update: (current)
75 phases: 2 draft
75 phases: 2 draft
76 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
76 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
77 transaction abort!
77 transaction abort!
78 rollback completed
78 rollback completed
79 abort: empty commit message
79 abort: empty commit message
80 [255]
80 [255]
81 $ hg summary
81 $ hg summary
82 parent: 1:43f1ba15f28a tip
82 parent: 1:43f1ba15f28a tip
83 amend base1
83 amend base1
84 branch: default
84 branch: default
85 commit: 1 added, 1 unknown
85 commit: 1 added, 1 unknown
86 update: (current)
86 update: (current)
87 phases: 2 draft
87 phases: 2 draft
88
88
89 Add new file:
89 Add new file:
90 $ hg ci --amend -m 'amend base1 new file'
90 $ hg ci --amend -m 'amend base1 new file'
91 saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-7a3b3496-amend-backup.hg (glob)
91 saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-7a3b3496-amend-backup.hg (glob)
92
92
93 Remove file that was added in amended commit:
93 Remove file that was added in amended commit:
94 (and test logfile option)
94 (and test logfile option)
95 (and test that logfile option do not trigger an editor)
95 (and test that logfile option do not trigger an editor)
96
96
97 $ hg rm b
97 $ hg rm b
98 $ echo 'amend base1 remove new file' > ../logfile
98 $ echo 'amend base1 remove new file' > ../logfile
99 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg ci --amend --logfile ../logfile
99 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg ci --amend --logfile ../logfile
100 saved backup bundle to $TESTTMP/.hg/strip-backup/b8e3cb2b3882-0b55739a-amend-backup.hg (glob)
100 saved backup bundle to $TESTTMP/.hg/strip-backup/b8e3cb2b3882-0b55739a-amend-backup.hg (glob)
101
101
102 $ hg cat b
102 $ hg cat b
103 b: no such file in rev 74609c7f506e
103 b: no such file in rev 74609c7f506e
104 [1]
104 [1]
105
105
106 No changes, just a different message:
106 No changes, just a different message:
107
107
108 $ hg ci -v --amend -m 'no changes, new message'
108 $ hg ci -v --amend -m 'no changes, new message'
109 amending changeset 74609c7f506e
109 amending changeset 74609c7f506e
110 copying changeset 74609c7f506e to ad120869acf0
110 copying changeset 74609c7f506e to ad120869acf0
111 committing files:
111 committing files:
112 a
112 a
113 committing manifest
113 committing manifest
114 committing changelog
114 committing changelog
115 stripping amended changeset 74609c7f506e
115 stripping amended changeset 74609c7f506e
116 1 changesets found
116 1 changesets found
117 uncompressed size of bundle content:
117 uncompressed size of bundle content:
118 250 (changelog)
118 250 (changelog)
119 143 (manifests)
119 143 (manifests)
120 109 a
120 109 a
121 saved backup bundle to $TESTTMP/.hg/strip-backup/74609c7f506e-1bfde511-amend-backup.hg (glob)
121 saved backup bundle to $TESTTMP/.hg/strip-backup/74609c7f506e-1bfde511-amend-backup.hg (glob)
122 1 changesets found
122 1 changesets found
123 uncompressed size of bundle content:
123 uncompressed size of bundle content:
124 246 (changelog)
124 246 (changelog)
125 143 (manifests)
125 143 (manifests)
126 109 a
126 109 a
127 adding branch
127 adding branch
128 adding changesets
128 adding changesets
129 adding manifests
129 adding manifests
130 adding file changes
130 adding file changes
131 added 1 changesets with 1 changes to 1 files
131 added 1 changesets with 1 changes to 1 files
132 committed changeset 1:1cd866679df8
132 committed changeset 1:1cd866679df8
133 $ hg diff -c .
133 $ hg diff -c .
134 diff -r ad120869acf0 -r 1cd866679df8 a
134 diff -r ad120869acf0 -r 1cd866679df8 a
135 --- a/a Thu Jan 01 00:00:00 1970 +0000
135 --- a/a Thu Jan 01 00:00:00 1970 +0000
136 +++ b/a Thu Jan 01 00:00:00 1970 +0000
136 +++ b/a Thu Jan 01 00:00:00 1970 +0000
137 @@ -1,1 +1,3 @@
137 @@ -1,1 +1,3 @@
138 a
138 a
139 +a
139 +a
140 +a
140 +a
141 $ hg log
141 $ hg log
142 changeset: 1:1cd866679df8
142 changeset: 1:1cd866679df8
143 tag: tip
143 tag: tip
144 user: test
144 user: test
145 date: Thu Jan 01 00:00:00 1970 +0000
145 date: Thu Jan 01 00:00:00 1970 +0000
146 summary: no changes, new message
146 summary: no changes, new message
147
147
148 changeset: 0:ad120869acf0
148 changeset: 0:ad120869acf0
149 user: test
149 user: test
150 date: Thu Jan 01 00:00:00 1970 +0000
150 date: Thu Jan 01 00:00:00 1970 +0000
151 summary: base
151 summary: base
152
152
153
153
154 Disable default date on commit so when -d isn't given, the old date is preserved:
154 Disable default date on commit so when -d isn't given, the old date is preserved:
155
155
156 $ echo '[defaults]' >> $HGRCPATH
156 $ echo '[defaults]' >> $HGRCPATH
157 $ echo 'commit=' >> $HGRCPATH
157 $ echo 'commit=' >> $HGRCPATH
158
158
159 Test -u/-d:
159 Test -u/-d:
160
160
161 $ cat > .hg/checkeditform.sh <<EOF
161 $ cat > .hg/checkeditform.sh <<EOF
162 > env | grep HGEDITFORM
162 > env | grep HGEDITFORM
163 > true
163 > true
164 > EOF
164 > EOF
165 $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -u foo -d '1 0'
165 $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -u foo -d '1 0'
166 HGEDITFORM=commit.amend.normal
166 HGEDITFORM=commit.amend.normal
167 saved backup bundle to $TESTTMP/.hg/strip-backup/1cd866679df8-5f5bcb85-amend-backup.hg (glob)
167 saved backup bundle to $TESTTMP/.hg/strip-backup/1cd866679df8-5f5bcb85-amend-backup.hg (glob)
168 $ echo a >> a
168 $ echo a >> a
169 $ hg ci --amend -u foo -d '1 0'
169 $ hg ci --amend -u foo -d '1 0'
170 saved backup bundle to $TESTTMP/.hg/strip-backup/780e6f23e03d-83b10a27-amend-backup.hg (glob)
170 saved backup bundle to $TESTTMP/.hg/strip-backup/780e6f23e03d-83b10a27-amend-backup.hg (glob)
171 $ hg log -r .
171 $ hg log -r .
172 changeset: 1:5f357c7560ab
172 changeset: 1:5f357c7560ab
173 tag: tip
173 tag: tip
174 user: foo
174 user: foo
175 date: Thu Jan 01 00:00:01 1970 +0000
175 date: Thu Jan 01 00:00:01 1970 +0000
176 summary: no changes, new message
176 summary: no changes, new message
177
177
178
178
179 Open editor with old commit message if a message isn't given otherwise:
179 Open editor with old commit message if a message isn't given otherwise:
180
180
181 $ cat > editor.sh << '__EOF__'
181 $ cat > editor.sh << '__EOF__'
182 > #!/bin/sh
182 > #!/bin/sh
183 > cat $1
183 > cat $1
184 > echo "another precious commit message" > "$1"
184 > echo "another precious commit message" > "$1"
185 > __EOF__
185 > __EOF__
186
186
187 at first, test saving last-message.txt
187 at first, test saving last-message.txt
188
188
189 $ cat > .hg/hgrc << '__EOF__'
189 $ cat > .hg/hgrc << '__EOF__'
190 > [hooks]
190 > [hooks]
191 > pretxncommit.test-saving-last-message = false
191 > pretxncommit.test-saving-last-message = false
192 > __EOF__
192 > __EOF__
193
193
194 $ rm -f .hg/last-message.txt
194 $ rm -f .hg/last-message.txt
195 $ hg commit --amend -v -m "message given from command line"
195 $ hg commit --amend -v -m "message given from command line"
196 amending changeset 5f357c7560ab
196 amending changeset 5f357c7560ab
197 copying changeset 5f357c7560ab to ad120869acf0
197 copying changeset 5f357c7560ab to ad120869acf0
198 committing files:
198 committing files:
199 a
199 a
200 committing manifest
200 committing manifest
201 committing changelog
201 committing changelog
202 running hook pretxncommit.test-saving-last-message: false
202 running hook pretxncommit.test-saving-last-message: false
203 transaction abort!
203 transaction abort!
204 rollback completed
204 rollback completed
205 abort: pretxncommit.test-saving-last-message hook exited with status 1
205 abort: pretxncommit.test-saving-last-message hook exited with status 1
206 [255]
206 [255]
207 $ cat .hg/last-message.txt
207 $ cat .hg/last-message.txt
208 message given from command line (no-eol)
208 message given from command line (no-eol)
209
209
210 $ rm -f .hg/last-message.txt
210 $ rm -f .hg/last-message.txt
211 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
211 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
212 amending changeset 5f357c7560ab
212 amending changeset 5f357c7560ab
213 copying changeset 5f357c7560ab to ad120869acf0
213 copying changeset 5f357c7560ab to ad120869acf0
214 no changes, new message
214 no changes, new message
215
215
216
216
217 HG: Enter commit message. Lines beginning with 'HG:' are removed.
217 HG: Enter commit message. Lines beginning with 'HG:' are removed.
218 HG: Leave message empty to abort commit.
218 HG: Leave message empty to abort commit.
219 HG: --
219 HG: --
220 HG: user: foo
220 HG: user: foo
221 HG: branch 'default'
221 HG: branch 'default'
222 HG: changed a
222 HG: changed a
223 committing files:
223 committing files:
224 a
224 a
225 committing manifest
225 committing manifest
226 committing changelog
226 committing changelog
227 running hook pretxncommit.test-saving-last-message: false
227 running hook pretxncommit.test-saving-last-message: false
228 transaction abort!
228 transaction abort!
229 rollback completed
229 rollback completed
230 abort: pretxncommit.test-saving-last-message hook exited with status 1
230 abort: pretxncommit.test-saving-last-message hook exited with status 1
231 [255]
231 [255]
232
232
233 $ cat .hg/last-message.txt
233 $ cat .hg/last-message.txt
234 another precious commit message
234 another precious commit message
235
235
236 $ cat > .hg/hgrc << '__EOF__'
236 $ cat > .hg/hgrc << '__EOF__'
237 > [hooks]
237 > [hooks]
238 > pretxncommit.test-saving-last-message =
238 > pretxncommit.test-saving-last-message =
239 > __EOF__
239 > __EOF__
240
240
241 then, test editing custom commit message
241 then, test editing custom commit message
242
242
243 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
243 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
244 amending changeset 5f357c7560ab
244 amending changeset 5f357c7560ab
245 copying changeset 5f357c7560ab to ad120869acf0
245 copying changeset 5f357c7560ab to ad120869acf0
246 no changes, new message
246 no changes, new message
247
247
248
248
249 HG: Enter commit message. Lines beginning with 'HG:' are removed.
249 HG: Enter commit message. Lines beginning with 'HG:' are removed.
250 HG: Leave message empty to abort commit.
250 HG: Leave message empty to abort commit.
251 HG: --
251 HG: --
252 HG: user: foo
252 HG: user: foo
253 HG: branch 'default'
253 HG: branch 'default'
254 HG: changed a
254 HG: changed a
255 committing files:
255 committing files:
256 a
256 a
257 committing manifest
257 committing manifest
258 committing changelog
258 committing changelog
259 stripping amended changeset 5f357c7560ab
259 stripping amended changeset 5f357c7560ab
260 1 changesets found
260 1 changesets found
261 uncompressed size of bundle content:
261 uncompressed size of bundle content:
262 238 (changelog)
262 238 (changelog)
263 143 (manifests)
263 143 (manifests)
264 111 a
264 111 a
265 saved backup bundle to $TESTTMP/.hg/strip-backup/5f357c7560ab-e7c84ade-amend-backup.hg (glob)
265 saved backup bundle to $TESTTMP/.hg/strip-backup/5f357c7560ab-e7c84ade-amend-backup.hg (glob)
266 1 changesets found
266 1 changesets found
267 uncompressed size of bundle content:
267 uncompressed size of bundle content:
268 246 (changelog)
268 246 (changelog)
269 143 (manifests)
269 143 (manifests)
270 111 a
270 111 a
271 adding branch
271 adding branch
272 adding changesets
272 adding changesets
273 adding manifests
273 adding manifests
274 adding file changes
274 adding file changes
275 added 1 changesets with 1 changes to 1 files
275 added 1 changesets with 1 changes to 1 files
276 committed changeset 1:7ab3bf440b54
276 committed changeset 1:7ab3bf440b54
277
277
278 Same, but with changes in working dir (different code path):
278 Same, but with changes in working dir (different code path):
279
279
280 $ echo a >> a
280 $ echo a >> a
281 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
281 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
282 amending changeset 7ab3bf440b54
282 amending changeset 7ab3bf440b54
283 committing files:
283 committing files:
284 a
284 a
285 committing manifest
285 committing manifest
286 committing changelog
286 committing changelog
287 copying changeset a0ea9b1a4c8c to ad120869acf0
287 copying changeset a0ea9b1a4c8c to ad120869acf0
288 another precious commit message
288 another precious commit message
289
289
290
290
291 HG: Enter commit message. Lines beginning with 'HG:' are removed.
291 HG: Enter commit message. Lines beginning with 'HG:' are removed.
292 HG: Leave message empty to abort commit.
292 HG: Leave message empty to abort commit.
293 HG: --
293 HG: --
294 HG: user: foo
294 HG: user: foo
295 HG: branch 'default'
295 HG: branch 'default'
296 HG: changed a
296 HG: changed a
297 committing files:
297 committing files:
298 a
298 a
299 committing manifest
299 committing manifest
300 committing changelog
300 committing changelog
301 stripping intermediate changeset a0ea9b1a4c8c
301 stripping intermediate changeset a0ea9b1a4c8c
302 stripping amended changeset 7ab3bf440b54
302 stripping amended changeset 7ab3bf440b54
303 2 changesets found
303 2 changesets found
304 uncompressed size of bundle content:
304 uncompressed size of bundle content:
305 450 (changelog)
305 450 (changelog)
306 282 (manifests)
306 282 (manifests)
307 209 a
307 209 a
308 saved backup bundle to $TESTTMP/.hg/strip-backup/7ab3bf440b54-8e3b5088-amend-backup.hg (glob)
308 saved backup bundle to $TESTTMP/.hg/strip-backup/7ab3bf440b54-8e3b5088-amend-backup.hg (glob)
309 1 changesets found
309 1 changesets found
310 uncompressed size of bundle content:
310 uncompressed size of bundle content:
311 246 (changelog)
311 246 (changelog)
312 143 (manifests)
312 143 (manifests)
313 113 a
313 113 a
314 adding branch
314 adding branch
315 adding changesets
315 adding changesets
316 adding manifests
316 adding manifests
317 adding file changes
317 adding file changes
318 added 1 changesets with 1 changes to 1 files
318 added 1 changesets with 1 changes to 1 files
319 committed changeset 1:ea22a388757c
319 committed changeset 1:ea22a388757c
320
320
321 $ rm editor.sh
321 $ rm editor.sh
322 $ hg log -r .
322 $ hg log -r .
323 changeset: 1:ea22a388757c
323 changeset: 1:ea22a388757c
324 tag: tip
324 tag: tip
325 user: foo
325 user: foo
326 date: Thu Jan 01 00:00:01 1970 +0000
326 date: Thu Jan 01 00:00:01 1970 +0000
327 summary: another precious commit message
327 summary: another precious commit message
328
328
329
329
330 Moving bookmarks, preserve active bookmark:
330 Moving bookmarks, preserve active bookmark:
331
331
332 $ hg book book1
332 $ hg book book1
333 $ hg book book2
333 $ hg book book2
334 $ hg ci --amend -m 'move bookmarks'
334 $ hg ci --amend -m 'move bookmarks'
335 saved backup bundle to $TESTTMP/.hg/strip-backup/ea22a388757c-e51094db-amend-backup.hg (glob)
335 saved backup bundle to $TESTTMP/.hg/strip-backup/ea22a388757c-e51094db-amend-backup.hg (glob)
336 $ hg book
336 $ hg book
337 book1 1:6cec5aa930e2
337 book1 1:6cec5aa930e2
338 * book2 1:6cec5aa930e2
338 * book2 1:6cec5aa930e2
339 $ echo a >> a
339 $ echo a >> a
340 $ hg ci --amend -m 'move bookmarks'
340 $ hg ci --amend -m 'move bookmarks'
341 saved backup bundle to $TESTTMP/.hg/strip-backup/6cec5aa930e2-e9b06de4-amend-backup.hg (glob)
341 saved backup bundle to $TESTTMP/.hg/strip-backup/6cec5aa930e2-e9b06de4-amend-backup.hg (glob)
342 $ hg book
342 $ hg book
343 book1 1:48bb6e53a15f
343 book1 1:48bb6e53a15f
344 * book2 1:48bb6e53a15f
344 * book2 1:48bb6e53a15f
345
345
346 abort does not loose bookmarks
346 abort does not loose bookmarks
347
347
348 $ cat > editor.sh << '__EOF__'
348 $ cat > editor.sh << '__EOF__'
349 > #!/bin/sh
349 > #!/bin/sh
350 > echo "" > "$1"
350 > echo "" > "$1"
351 > __EOF__
351 > __EOF__
352 $ echo a >> a
352 $ echo a >> a
353 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
353 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
354 transaction abort!
354 transaction abort!
355 rollback completed
355 rollback completed
356 abort: empty commit message
356 abort: empty commit message
357 [255]
357 [255]
358 $ hg book
358 $ hg book
359 book1 1:48bb6e53a15f
359 book1 1:48bb6e53a15f
360 * book2 1:48bb6e53a15f
360 * book2 1:48bb6e53a15f
361 $ hg revert -Caq
361 $ hg revert -Caq
362 $ rm editor.sh
362 $ rm editor.sh
363
363
364 $ echo '[defaults]' >> $HGRCPATH
364 $ echo '[defaults]' >> $HGRCPATH
365 $ echo "commit=-d '0 0'" >> $HGRCPATH
365 $ echo "commit=-d '0 0'" >> $HGRCPATH
366
366
367 Moving branches:
367 Moving branches:
368
368
369 $ hg branch foo
369 $ hg branch foo
370 marked working directory as branch foo
370 marked working directory as branch foo
371 (branches are permanent and global, did you want a bookmark?)
371 (branches are permanent and global, did you want a bookmark?)
372 $ echo a >> a
372 $ echo a >> a
373 $ hg ci -m 'branch foo'
373 $ hg ci -m 'branch foo'
374 $ hg branch default -f
374 $ hg branch default -f
375 marked working directory as branch default
375 marked working directory as branch default
376 $ hg ci --amend -m 'back to default'
376 $ hg ci --amend -m 'back to default'
377 saved backup bundle to $TESTTMP/.hg/strip-backup/8ac881fbf49d-fd962fef-amend-backup.hg (glob)
377 saved backup bundle to $TESTTMP/.hg/strip-backup/8ac881fbf49d-fd962fef-amend-backup.hg (glob)
378 $ hg branches
378 $ hg branches
379 default 2:ce12b0b57d46
379 default 2:ce12b0b57d46
380
380
381 Close branch:
381 Close branch:
382
382
383 $ hg up -q 0
383 $ hg up -q 0
384 $ echo b >> b
384 $ echo b >> b
385 $ hg branch foo
385 $ hg branch foo
386 marked working directory as branch foo
386 marked working directory as branch foo
387 (branches are permanent and global, did you want a bookmark?)
387 (branches are permanent and global, did you want a bookmark?)
388 $ hg ci -Am 'fork'
388 $ hg ci -Am 'fork'
389 adding b
389 adding b
390 $ echo b >> b
390 $ echo b >> b
391 $ hg ci -mb
391 $ hg ci -mb
392 $ hg ci --amend --close-branch -m 'closing branch foo'
392 $ hg ci --amend --close-branch -m 'closing branch foo'
393 saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-6701c392-amend-backup.hg (glob)
393 saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-6701c392-amend-backup.hg (glob)
394
394
395 Same thing, different code path:
395 Same thing, different code path:
396
396
397 $ echo b >> b
397 $ echo b >> b
398 $ hg ci -m 'reopen branch'
398 $ hg ci -m 'reopen branch'
399 reopening closed branch head 4
399 reopening closed branch head 4
400 $ echo b >> b
400 $ echo b >> b
401 $ hg ci --amend --close-branch
401 $ hg ci --amend --close-branch
402 saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-49c0c55d-amend-backup.hg (glob)
402 saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-49c0c55d-amend-backup.hg (glob)
403 $ hg branches
403 $ hg branches
404 default 2:ce12b0b57d46
404 default 2:ce12b0b57d46
405
405
406 Refuse to amend during a merge:
406 Refuse to amend during a merge:
407
407
408 $ hg up -q default
408 $ hg up -q default
409 $ hg merge foo
409 $ hg merge foo
410 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
410 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
411 (branch merge, don't forget to commit)
411 (branch merge, don't forget to commit)
412 $ hg ci --amend
412 $ hg ci --amend
413 abort: cannot amend while merging
413 abort: cannot amend while merging
414 [255]
414 [255]
415 $ hg ci -m 'merge'
415 $ hg ci -m 'merge'
416
416
417 Follow copies/renames:
417 Follow copies/renames:
418
418
419 $ hg mv b c
419 $ hg mv b c
420 $ hg ci -m 'b -> c'
420 $ hg ci -m 'b -> c'
421 $ hg mv c d
421 $ hg mv c d
422 $ hg ci --amend -m 'b -> d'
422 $ hg ci --amend -m 'b -> d'
423 saved backup bundle to $TESTTMP/.hg/strip-backup/b8c6eac7f12e-adaaa8b1-amend-backup.hg (glob)
423 saved backup bundle to $TESTTMP/.hg/strip-backup/b8c6eac7f12e-adaaa8b1-amend-backup.hg (glob)
424 $ hg st --rev '.^' --copies d
424 $ hg st --rev '.^' --copies d
425 A d
425 A d
426 b
426 b
427 $ hg cp d e
427 $ hg cp d e
428 $ hg ci -m 'e = d'
428 $ hg ci -m 'e = d'
429 $ hg cp e f
429 $ hg cp e f
430 $ hg ci --amend -m 'f = d'
430 $ hg ci --amend -m 'f = d'
431 saved backup bundle to $TESTTMP/.hg/strip-backup/7f9761d65613-d37aa788-amend-backup.hg (glob)
431 saved backup bundle to $TESTTMP/.hg/strip-backup/7f9761d65613-d37aa788-amend-backup.hg (glob)
432 $ hg st --rev '.^' --copies f
432 $ hg st --rev '.^' --copies f
433 A f
433 A f
434 d
434 d
435
435
436 $ mv f f.orig
436 $ mv f f.orig
437 $ hg rm -A f
437 $ hg rm -A f
438 $ hg ci -m removef
438 $ hg ci -m removef
439 $ hg cp a f
439 $ hg cp a f
440 $ mv f.orig f
440 $ mv f.orig f
441 $ hg ci --amend -m replacef
441 $ hg ci --amend -m replacef
442 saved backup bundle to $TESTTMP/.hg/strip-backup/9e8c5f7e3d95-90259f67-amend-backup.hg (glob)
442 saved backup bundle to $TESTTMP/.hg/strip-backup/9e8c5f7e3d95-90259f67-amend-backup.hg (glob)
443 $ hg st --change . --copies
443 $ hg st --change . --copies
444 $ hg log -r . --template "{file_copies}\n"
444 $ hg log -r . --template "{file_copies}\n"
445
445
446
446
447 Move added file (issue3410):
447 Move added file (issue3410):
448
448
449 $ echo g >> g
449 $ echo g >> g
450 $ hg ci -Am g
450 $ hg ci -Am g
451 adding g
451 adding g
452 $ hg mv g h
452 $ hg mv g h
453 $ hg ci --amend
453 $ hg ci --amend
454 saved backup bundle to $TESTTMP/.hg/strip-backup/24aa8eacce2b-7059e0f1-amend-backup.hg (glob)
454 saved backup bundle to $TESTTMP/.hg/strip-backup/24aa8eacce2b-7059e0f1-amend-backup.hg (glob)
455 $ hg st --change . --copies h
455 $ hg st --change . --copies h
456 A h
456 A h
457 $ hg log -r . --template "{file_copies}\n"
457 $ hg log -r . --template "{file_copies}\n"
458
458
459
459
460 Can't rollback an amend:
460 Can't rollback an amend:
461
461
462 $ hg rollback
462 $ hg rollback
463 no rollback information available
463 no rollback information available
464 [1]
464 [1]
465
465
466 Preserve extra dict (issue3430):
466 Preserve extra dict (issue3430):
467
467
468 $ hg branch a
468 $ hg branch a
469 marked working directory as branch a
469 marked working directory as branch a
470 (branches are permanent and global, did you want a bookmark?)
470 (branches are permanent and global, did you want a bookmark?)
471 $ echo a >> a
471 $ echo a >> a
472 $ hg ci -ma
472 $ hg ci -ma
473 $ hg ci --amend -m "a'"
473 $ hg ci --amend -m "a'"
474 saved backup bundle to $TESTTMP/.hg/strip-backup/3837aa2a2fdb-2be01fd1-amend-backup.hg (glob)
474 saved backup bundle to $TESTTMP/.hg/strip-backup/3837aa2a2fdb-2be01fd1-amend-backup.hg (glob)
475 $ hg log -r . --template "{branch}\n"
475 $ hg log -r . --template "{branch}\n"
476 a
476 a
477 $ hg ci --amend -m "a''"
477 $ hg ci --amend -m "a''"
478 saved backup bundle to $TESTTMP/.hg/strip-backup/c05c06be7514-ed28c4cd-amend-backup.hg (glob)
478 saved backup bundle to $TESTTMP/.hg/strip-backup/c05c06be7514-ed28c4cd-amend-backup.hg (glob)
479 $ hg log -r . --template "{branch}\n"
479 $ hg log -r . --template "{branch}\n"
480 a
480 a
481
481
482 Also preserve other entries in the dict that are in the old commit,
482 Also preserve other entries in the dict that are in the old commit,
483 first graft something so there's an additional entry:
483 first graft something so there's an additional entry:
484
484
485 $ hg up 0 -q
485 $ hg up 0 -q
486 $ echo z > z
486 $ echo z > z
487 $ hg ci -Am 'fork'
487 $ hg ci -Am 'fork'
488 adding z
488 adding z
489 created new head
489 created new head
490 $ hg up 11
490 $ hg up 11
491 5 files updated, 0 files merged, 1 files removed, 0 files unresolved
491 5 files updated, 0 files merged, 1 files removed, 0 files unresolved
492 $ hg graft 12
492 $ hg graft 12
493 grafting 12:2647734878ef "fork" (tip)
493 grafting 12:2647734878ef "fork" (tip)
494 $ hg ci --amend -m 'graft amend'
494 $ hg ci --amend -m 'graft amend'
495 saved backup bundle to $TESTTMP/.hg/strip-backup/bd010aea3f39-eedb103b-amend-backup.hg (glob)
495 saved backup bundle to $TESTTMP/.hg/strip-backup/bd010aea3f39-eedb103b-amend-backup.hg (glob)
496 $ hg log -r . --debug | grep extra
496 $ hg log -r . --debug | grep extra
497 extra: amend_source=bd010aea3f39f3fb2a2f884b9ccb0471cd77398e
497 extra: amend_source=bd010aea3f39f3fb2a2f884b9ccb0471cd77398e
498 extra: branch=a
498 extra: branch=a
499 extra: source=2647734878ef0236dda712fae9c1651cf694ea8a
499 extra: source=2647734878ef0236dda712fae9c1651cf694ea8a
500
500
501 Preserve phase
501 Preserve phase
502
502
503 $ hg phase '.^::.'
503 $ hg phase '.^::.'
504 11: draft
504 11: draft
505 13: draft
505 13: draft
506 $ hg phase --secret --force .
506 $ hg phase --secret --force .
507 $ hg phase '.^::.'
507 $ hg phase '.^::.'
508 11: draft
508 11: draft
509 13: secret
509 13: secret
510 $ hg commit --amend -m 'amend for phase' -q
510 $ hg commit --amend -m 'amend for phase' -q
511 $ hg phase '.^::.'
511 $ hg phase '.^::.'
512 11: draft
512 11: draft
513 13: secret
513 13: secret
514
514
515 Test amend with obsolete
515 Test amend with obsolete
516 ---------------------------
516 ---------------------------
517
517
518 Enable obsolete
518 Enable obsolete
519
519
520 $ cat >> $HGRCPATH << EOF
520 $ cat >> $HGRCPATH << EOF
521 > [experimental]
521 > [experimental]
522 > evolution=createmarkers,allowunstable
522 > evolution=createmarkers,allowunstable
523 > EOF
523 > EOF
524
524
525 Amend with no files changes
525 Amend with no files changes
526
526
527 $ hg id -n
527 $ hg id -n
528 13
528 13
529 $ hg ci --amend -m 'babar'
529 $ hg ci --amend -m 'babar'
530 $ hg id -n
530 $ hg id -n
531 14
531 14
532 $ hg log -Gl 3 --style=compact
532 $ hg log -Gl 3 --style=compact
533 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
533 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
534 | babar
534 | babar
535 |
535 |
536 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
536 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
537 | | fork
537 | | fork
538 | |
538 | |
539 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
539 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
540 | | a''
540 | | a''
541 | |
541 | |
542 $ hg log -Gl 4 --hidden --style=compact
542 $ hg log -Gl 4 --hidden --style=compact
543 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
543 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
544 | babar
544 | babar
545 |
545 |
546 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
546 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
547 |/ amend for phase
547 |/ amend for phase
548 |
548 |
549 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
549 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
550 | | fork
550 | | fork
551 | |
551 | |
552 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
552 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
553 | | a''
553 | | a''
554 | |
554 | |
555
555
556 Amend with files changes
556 Amend with files changes
557
557
558 (note: the extra commit over 15 is a temporary junk I would be happy to get
558 (note: the extra commit over 15 is a temporary junk I would be happy to get
559 ride of)
559 ride of)
560
560
561 $ echo 'babar' >> a
561 $ echo 'babar' >> a
562 $ hg commit --amend
562 $ hg commit --amend
563 $ hg log -Gl 6 --hidden --style=compact
563 $ hg log -Gl 6 --hidden --style=compact
564 @ 16[tip]:11 9f9e9bccf56c 1970-01-01 00:00 +0000 test
564 @ 16[tip]:11 9f9e9bccf56c 1970-01-01 00:00 +0000 test
565 | babar
565 | babar
566 |
566 |
567 | x 15 90fef497c56f 1970-01-01 00:00 +0000 test
567 | x 15 90fef497c56f 1970-01-01 00:00 +0000 test
568 | | temporary amend commit for b650e6ee8614
568 | | temporary amend commit for b650e6ee8614
569 | |
569 | |
570 | x 14:11 b650e6ee8614 1970-01-01 00:00 +0000 test
570 | x 14:11 b650e6ee8614 1970-01-01 00:00 +0000 test
571 |/ babar
571 |/ babar
572 |
572 |
573 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
573 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
574 |/ amend for phase
574 |/ amend for phase
575 |
575 |
576 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
576 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
577 | | fork
577 | | fork
578 | |
578 | |
579 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
579 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
580 | | a''
580 | | a''
581 | |
581 | |
582
582
583
583
584 Test that amend does not make it easy to create obsolescence cycle
584 Test that amend does not make it easy to create obsolescence cycle
585 ---------------------------------------------------------------------
585 ---------------------------------------------------------------------
586
586
587 $ hg id -r 14 --hidden
587 $ hg id -r 14 --hidden
588 b650e6ee8614 (a)
588 b650e6ee8614 (a)
589 $ hg revert -ar 14 --hidden
589 $ hg revert -ar 14 --hidden
590 reverting a
590 reverting a
591 $ hg commit --amend
591 $ hg commit --amend
592 $ hg id
592 $ hg id
593 b99e5df575f7 (a) tip
593 b99e5df575f7 (a) tip
594
594
595 Test that rewriting leaving instability behind is allowed
595 Test that rewriting leaving instability behind is allowed
596 ---------------------------------------------------------------------
596 ---------------------------------------------------------------------
597
597
598 $ hg up '.^'
598 $ hg up '.^'
599 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
599 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
600 $ echo 'b' >> a
600 $ echo 'b' >> a
601 $ hg log --style compact -r 'children(.)'
601 $ hg log --style compact -r 'children(.)'
602 18[tip]:11 b99e5df575f7 1970-01-01 00:00 +0000 test
602 18[tip]:11 b99e5df575f7 1970-01-01 00:00 +0000 test
603 babar
603 babar
604
604
605 $ hg commit --amend
605 $ hg commit --amend
606 $ hg log -r 'unstable()'
606 $ hg log -r 'unstable()'
607 changeset: 18:b99e5df575f7
607 changeset: 18:b99e5df575f7
608 branch: a
608 branch: a
609 parent: 11:3334b7925910
609 parent: 11:3334b7925910
610 user: test
610 user: test
611 date: Thu Jan 01 00:00:00 1970 +0000
611 date: Thu Jan 01 00:00:00 1970 +0000
612 summary: babar
612 summary: babar
613
613
614
614
615 Amend a merge changeset (with renames and conflicts from the second parent):
615 Amend a merge changeset (with renames and conflicts from the second parent):
616
616
617 $ hg up -q default
617 $ hg up -q default
618 $ hg branch -q bar
618 $ hg branch -q bar
619 $ hg cp a aa
619 $ hg cp a aa
620 $ hg mv z zz
620 $ hg mv z zz
621 $ echo cc > cc
621 $ echo cc > cc
622 $ hg add cc
622 $ hg add cc
623 $ hg ci -m aazzcc
623 $ hg ci -m aazzcc
624 $ hg up -q default
624 $ hg up -q default
625 $ echo a >> a
625 $ echo a >> a
626 $ echo dd > cc
626 $ echo dd > cc
627 $ hg add cc
627 $ hg add cc
628 $ hg ci -m aa
628 $ hg ci -m aa
629 $ hg merge -q bar
629 $ hg merge -q bar
630 warning: conflicts while merging cc! (edit, then use 'hg resolve --mark')
630 warning: conflicts while merging cc! (edit, then use 'hg resolve --mark')
631 [1]
631 [1]
632 $ hg resolve -m cc
632 $ hg resolve -m cc
633 (no more unresolved files)
633 (no more unresolved files)
634 $ hg ci -m 'merge bar'
634 $ hg ci -m 'merge bar'
635 $ hg log --config diff.git=1 -pr .
635 $ hg log --config diff.git=1 -pr .
636 changeset: 23:93cd4445f720
636 changeset: 23:93cd4445f720
637 tag: tip
637 tag: tip
638 parent: 22:30d96aeaf27b
638 parent: 22:30d96aeaf27b
639 parent: 21:1aa437659d19
639 parent: 21:1aa437659d19
640 user: test
640 user: test
641 date: Thu Jan 01 00:00:00 1970 +0000
641 date: Thu Jan 01 00:00:00 1970 +0000
642 summary: merge bar
642 summary: merge bar
643
643
644 diff --git a/a b/aa
644 diff --git a/a b/aa
645 copy from a
645 copy from a
646 copy to aa
646 copy to aa
647 diff --git a/cc b/cc
647 diff --git a/cc b/cc
648 --- a/cc
648 --- a/cc
649 +++ b/cc
649 +++ b/cc
650 @@ -1,1 +1,5 @@
650 @@ -1,1 +1,5 @@
651 +<<<<<<< local: 30d96aeaf27b - test: aa
651 +<<<<<<< local: 30d96aeaf27b - test: aa
652 dd
652 dd
653 +=======
653 +=======
654 +cc
654 +cc
655 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
655 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
656 diff --git a/z b/zz
656 diff --git a/z b/zz
657 rename from z
657 rename from z
658 rename to zz
658 rename to zz
659
659
660 $ hg debugrename aa
660 $ hg debugrename aa
661 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
661 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
662 $ hg debugrename zz
662 $ hg debugrename zz
663 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
663 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
664 $ hg debugrename cc
664 $ hg debugrename cc
665 cc not renamed
665 cc not renamed
666 $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -m 'merge bar (amend message)' --edit
666 $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -m 'merge bar (amend message)' --edit
667 HGEDITFORM=commit.amend.merge
667 HGEDITFORM=commit.amend.merge
668 $ hg log --config diff.git=1 -pr .
668 $ hg log --config diff.git=1 -pr .
669 changeset: 24:832b50f2c271
669 changeset: 24:832b50f2c271
670 tag: tip
670 tag: tip
671 parent: 22:30d96aeaf27b
671 parent: 22:30d96aeaf27b
672 parent: 21:1aa437659d19
672 parent: 21:1aa437659d19
673 user: test
673 user: test
674 date: Thu Jan 01 00:00:00 1970 +0000
674 date: Thu Jan 01 00:00:00 1970 +0000
675 summary: merge bar (amend message)
675 summary: merge bar (amend message)
676
676
677 diff --git a/a b/aa
677 diff --git a/a b/aa
678 copy from a
678 copy from a
679 copy to aa
679 copy to aa
680 diff --git a/cc b/cc
680 diff --git a/cc b/cc
681 --- a/cc
681 --- a/cc
682 +++ b/cc
682 +++ b/cc
683 @@ -1,1 +1,5 @@
683 @@ -1,1 +1,5 @@
684 +<<<<<<< local: 30d96aeaf27b - test: aa
684 +<<<<<<< local: 30d96aeaf27b - test: aa
685 dd
685 dd
686 +=======
686 +=======
687 +cc
687 +cc
688 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
688 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
689 diff --git a/z b/zz
689 diff --git a/z b/zz
690 rename from z
690 rename from z
691 rename to zz
691 rename to zz
692
692
693 $ hg debugrename aa
693 $ hg debugrename aa
694 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
694 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
695 $ hg debugrename zz
695 $ hg debugrename zz
696 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
696 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
697 $ hg debugrename cc
697 $ hg debugrename cc
698 cc not renamed
698 cc not renamed
699 $ hg mv zz z
699 $ hg mv zz z
700 $ hg ci --amend -m 'merge bar (undo rename)'
700 $ hg ci --amend -m 'merge bar (undo rename)'
701 $ hg log --config diff.git=1 -pr .
701 $ hg log --config diff.git=1 -pr .
702 changeset: 26:bdafc5c72f74
702 changeset: 26:bdafc5c72f74
703 tag: tip
703 tag: tip
704 parent: 22:30d96aeaf27b
704 parent: 22:30d96aeaf27b
705 parent: 21:1aa437659d19
705 parent: 21:1aa437659d19
706 user: test
706 user: test
707 date: Thu Jan 01 00:00:00 1970 +0000
707 date: Thu Jan 01 00:00:00 1970 +0000
708 summary: merge bar (undo rename)
708 summary: merge bar (undo rename)
709
709
710 diff --git a/a b/aa
710 diff --git a/a b/aa
711 copy from a
711 copy from a
712 copy to aa
712 copy to aa
713 diff --git a/cc b/cc
713 diff --git a/cc b/cc
714 --- a/cc
714 --- a/cc
715 +++ b/cc
715 +++ b/cc
716 @@ -1,1 +1,5 @@
716 @@ -1,1 +1,5 @@
717 +<<<<<<< local: 30d96aeaf27b - test: aa
717 +<<<<<<< local: 30d96aeaf27b - test: aa
718 dd
718 dd
719 +=======
719 +=======
720 +cc
720 +cc
721 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
721 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
722
722
723 $ hg debugrename z
723 $ hg debugrename z
724 z not renamed
724 z not renamed
725
725
726 Amend a merge changeset (with renames during the merge):
726 Amend a merge changeset (with renames during the merge):
727
727
728 $ hg up -q bar
728 $ hg up -q bar
729 $ echo x > x
729 $ echo x > x
730 $ hg add x
730 $ hg add x
731 $ hg ci -m x
731 $ hg ci -m x
732 $ hg up -q default
732 $ hg up -q default
733 $ hg merge -q bar
733 $ hg merge -q bar
734 $ hg mv aa aaa
734 $ hg mv aa aaa
735 $ echo aa >> aaa
735 $ echo aa >> aaa
736 $ hg ci -m 'merge bar again'
736 $ hg ci -m 'merge bar again'
737 $ hg log --config diff.git=1 -pr .
737 $ hg log --config diff.git=1 -pr .
738 changeset: 28:32f19415b634
738 changeset: 28:32f19415b634
739 tag: tip
739 tag: tip
740 parent: 26:bdafc5c72f74
740 parent: 26:bdafc5c72f74
741 parent: 27:4c94d5bc65f5
741 parent: 27:4c94d5bc65f5
742 user: test
742 user: test
743 date: Thu Jan 01 00:00:00 1970 +0000
743 date: Thu Jan 01 00:00:00 1970 +0000
744 summary: merge bar again
744 summary: merge bar again
745
745
746 diff --git a/aa b/aa
746 diff --git a/aa b/aa
747 deleted file mode 100644
747 deleted file mode 100644
748 --- a/aa
748 --- a/aa
749 +++ /dev/null
749 +++ /dev/null
750 @@ -1,2 +0,0 @@
750 @@ -1,2 +0,0 @@
751 -a
751 -a
752 -a
752 -a
753 diff --git a/aaa b/aaa
753 diff --git a/aaa b/aaa
754 new file mode 100644
754 new file mode 100644
755 --- /dev/null
755 --- /dev/null
756 +++ b/aaa
756 +++ b/aaa
757 @@ -0,0 +1,3 @@
757 @@ -0,0 +1,3 @@
758 +a
758 +a
759 +a
759 +a
760 +aa
760 +aa
761 diff --git a/x b/x
761 diff --git a/x b/x
762 new file mode 100644
762 new file mode 100644
763 --- /dev/null
763 --- /dev/null
764 +++ b/x
764 +++ b/x
765 @@ -0,0 +1,1 @@
765 @@ -0,0 +1,1 @@
766 +x
766 +x
767
767
768 $ hg debugrename aaa
768 $ hg debugrename aaa
769 aaa renamed from aa:37d9b5d994eab34eda9c16b195ace52c7b129980
769 aaa renamed from aa:37d9b5d994eab34eda9c16b195ace52c7b129980
770 $ hg mv aaa aa
770 $ hg mv aaa aa
771 $ hg ci --amend -m 'merge bar again (undo rename)'
771 $ hg ci --amend -m 'merge bar again (undo rename)'
772 $ hg log --config diff.git=1 -pr .
772 $ hg log --config diff.git=1 -pr .
773 changeset: 30:1e2a06b3d312
773 changeset: 30:1e2a06b3d312
774 tag: tip
774 tag: tip
775 parent: 26:bdafc5c72f74
775 parent: 26:bdafc5c72f74
776 parent: 27:4c94d5bc65f5
776 parent: 27:4c94d5bc65f5
777 user: test
777 user: test
778 date: Thu Jan 01 00:00:00 1970 +0000
778 date: Thu Jan 01 00:00:00 1970 +0000
779 summary: merge bar again (undo rename)
779 summary: merge bar again (undo rename)
780
780
781 diff --git a/aa b/aa
781 diff --git a/aa b/aa
782 --- a/aa
782 --- a/aa
783 +++ b/aa
783 +++ b/aa
784 @@ -1,2 +1,3 @@
784 @@ -1,2 +1,3 @@
785 a
785 a
786 a
786 a
787 +aa
787 +aa
788 diff --git a/x b/x
788 diff --git a/x b/x
789 new file mode 100644
789 new file mode 100644
790 --- /dev/null
790 --- /dev/null
791 +++ b/x
791 +++ b/x
792 @@ -0,0 +1,1 @@
792 @@ -0,0 +1,1 @@
793 +x
793 +x
794
794
795 $ hg debugrename aa
795 $ hg debugrename aa
796 aa not renamed
796 aa not renamed
797 $ hg debugrename -r '.^' aa
797 $ hg debugrename -r '.^' aa
798 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
798 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
799
799
800 Amend a merge changeset (with manifest-level conflicts):
800 Amend a merge changeset (with manifest-level conflicts):
801
801
802 $ hg up -q bar
802 $ hg up -q bar
803 $ hg rm aa
803 $ hg rm aa
804 $ hg ci -m 'rm aa'
804 $ hg ci -m 'rm aa'
805 $ hg up -q default
805 $ hg up -q default
806 $ echo aa >> aa
806 $ echo aa >> aa
807 $ hg ci -m aa
807 $ hg ci -m aa
808 $ hg merge -q bar
808 $ hg merge -q bar
809 local changed aa which remote deleted
809 local changed aa which remote deleted
810 use (c)hanged version or (d)elete? c
810 use (c)hanged version or (d)elete? c
811 $ hg ci -m 'merge bar (with conflicts)'
811 $ hg ci -m 'merge bar (with conflicts)'
812 $ hg log --config diff.git=1 -pr .
812 $ hg log --config diff.git=1 -pr .
813 changeset: 33:97a298b0c59f
813 changeset: 33:97a298b0c59f
814 tag: tip
814 tag: tip
815 parent: 32:3d78ce4226b8
815 parent: 32:3d78ce4226b8
816 parent: 31:67db8847a540
816 parent: 31:67db8847a540
817 user: test
817 user: test
818 date: Thu Jan 01 00:00:00 1970 +0000
818 date: Thu Jan 01 00:00:00 1970 +0000
819 summary: merge bar (with conflicts)
819 summary: merge bar (with conflicts)
820
820
821
821
822 $ hg rm aa
822 $ hg rm aa
823 $ hg ci --amend -m 'merge bar (with conflicts, amended)'
823 $ hg ci --amend -m 'merge bar (with conflicts, amended)'
824 $ hg log --config diff.git=1 -pr .
824 $ hg log --config diff.git=1 -pr .
825 changeset: 35:6de0c1bde1c8
825 changeset: 35:6de0c1bde1c8
826 tag: tip
826 tag: tip
827 parent: 32:3d78ce4226b8
827 parent: 32:3d78ce4226b8
828 parent: 31:67db8847a540
828 parent: 31:67db8847a540
829 user: test
829 user: test
830 date: Thu Jan 01 00:00:00 1970 +0000
830 date: Thu Jan 01 00:00:00 1970 +0000
831 summary: merge bar (with conflicts, amended)
831 summary: merge bar (with conflicts, amended)
832
832
833 diff --git a/aa b/aa
833 diff --git a/aa b/aa
834 deleted file mode 100644
834 deleted file mode 100644
835 --- a/aa
835 --- a/aa
836 +++ /dev/null
836 +++ /dev/null
837 @@ -1,4 +0,0 @@
837 @@ -1,4 +0,0 @@
838 -a
838 -a
839 -a
839 -a
840 -aa
840 -aa
841 -aa
841 -aa
842
842
843 Issue 3445: amending with --close-branch a commit that created a new head should fail
843 Issue 3445: amending with --close-branch a commit that created a new head should fail
844 This shouldn't be possible:
844 This shouldn't be possible:
845
845
846 $ hg up -q default
846 $ hg up -q default
847 $ hg branch closewithamend
847 $ hg branch closewithamend
848 marked working directory as branch closewithamend
848 marked working directory as branch closewithamend
849 $ echo foo > foo
849 $ echo foo > foo
850 $ hg add foo
850 $ hg add foo
851 $ hg ci -m..
851 $ hg ci -m..
852 $ hg ci --amend --close-branch -m 'closing'
852 $ hg ci --amend --close-branch -m 'closing'
853 abort: can only close branch heads
853 abort: can only close branch heads
854 [255]
854 [255]
855
855
856 This silliness fails:
856 This silliness fails:
857
857
858 $ hg branch silliness
858 $ hg branch silliness
859 marked working directory as branch silliness
859 marked working directory as branch silliness
860 $ echo b >> b
860 $ echo b >> b
861 $ hg ci --close-branch -m'open and close'
861 $ hg ci --close-branch -m'open and close'
862 abort: can only close branch heads
862 abort: can only close branch heads
863 [255]
863 [255]
864
864
865 Test that amend with --secret creates new secret changeset forcibly
865 Test that amend with --secret creates new secret changeset forcibly
866 ---------------------------------------------------------------------
866 ---------------------------------------------------------------------
867
867
868 $ hg phase '.^::.'
868 $ hg phase '.^::.'
869 35: draft
869 35: draft
870 36: draft
870 36: draft
871 $ hg commit --amend --secret -m 'amend as secret' -q
871 $ hg commit --amend --secret -m 'amend as secret' -q
872 $ hg phase '.^::.'
872 $ hg phase '.^::.'
873 35: draft
873 35: draft
874 38: secret
874 38: secret
875
875
876 Test that amend with --edit invokes editor forcibly
876 Test that amend with --edit invokes editor forcibly
877 ---------------------------------------------------
877 ---------------------------------------------------
878
878
879 $ hg parents --template "{desc}\n"
879 $ hg parents --template "{desc}\n"
880 amend as secret
880 amend as secret
881 $ HGEDITOR=cat hg commit --amend -m "editor should be suppressed"
881 $ HGEDITOR=cat hg commit --amend -m "editor should be suppressed"
882 $ hg parents --template "{desc}\n"
882 $ hg parents --template "{desc}\n"
883 editor should be suppressed
883 editor should be suppressed
884
884
885 $ hg status --rev '.^1::.'
885 $ hg status --rev '.^1::.'
886 A foo
886 A foo
887 $ HGEDITOR=cat hg commit --amend -m "editor should be invoked" --edit
887 $ HGEDITOR=cat hg commit --amend -m "editor should be invoked" --edit
888 editor should be invoked
888 editor should be invoked
889
889
890
890
891 HG: Enter commit message. Lines beginning with 'HG:' are removed.
891 HG: Enter commit message. Lines beginning with 'HG:' are removed.
892 HG: Leave message empty to abort commit.
892 HG: Leave message empty to abort commit.
893 HG: --
893 HG: --
894 HG: user: test
894 HG: user: test
895 HG: branch 'silliness'
895 HG: branch 'silliness'
896 HG: added foo
896 HG: added foo
897 $ hg parents --template "{desc}\n"
897 $ hg parents --template "{desc}\n"
898 editor should be invoked
898 editor should be invoked
899
899
900 Test that "diff()" in committemplate works correctly for amending
900 Test that "diff()" in committemplate works correctly for amending
901 -----------------------------------------------------------------
901 -----------------------------------------------------------------
902
902
903 $ cat >> .hg/hgrc <<EOF
903 $ cat >> .hg/hgrc <<EOF
904 > [committemplate]
904 > [committemplate]
905 > changeset.commit.amend = {desc}\n
905 > changeset.commit.amend = {desc}\n
906 > HG: M: {file_mods}
906 > HG: M: {file_mods}
907 > HG: A: {file_adds}
907 > HG: A: {file_adds}
908 > HG: R: {file_dels}
908 > HG: R: {file_dels}
909 > {splitlines(diff()) % 'HG: {line}\n'}
909 > {splitlines(diff()) % 'HG: {line}\n'}
910 > EOF
910 > EOF
911
911
912 $ hg parents --template "M: {file_mods}\nA: {file_adds}\nR: {file_dels}\n"
912 $ hg parents --template "M: {file_mods}\nA: {file_adds}\nR: {file_dels}\n"
913 M:
913 M:
914 A: foo
914 A: foo
915 R:
915 R:
916 $ hg status -amr
916 $ hg status -amr
917 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of foo"
917 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of foo"
918 expecting diff of foo
918 expecting diff of foo
919
919
920 HG: M:
920 HG: M:
921 HG: A: foo
921 HG: A: foo
922 HG: R:
922 HG: R:
923 HG: diff -r 6de0c1bde1c8 foo
923 HG: diff -r 6de0c1bde1c8 foo
924 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
924 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
925 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
925 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
926 HG: @@ -0,0 +1,1 @@
926 HG: @@ -0,0 +1,1 @@
927 HG: +foo
927 HG: +foo
928
928
929 $ echo y > y
929 $ echo y > y
930 $ hg add y
930 $ hg add y
931 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of foo and y"
931 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of foo and y"
932 expecting diff of foo and y
932 expecting diff of foo and y
933
933
934 HG: M:
934 HG: M:
935 HG: A: foo y
935 HG: A: foo y
936 HG: R:
936 HG: R:
937 HG: diff -r 6de0c1bde1c8 foo
937 HG: diff -r 6de0c1bde1c8 foo
938 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
938 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
939 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
939 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
940 HG: @@ -0,0 +1,1 @@
940 HG: @@ -0,0 +1,1 @@
941 HG: +foo
941 HG: +foo
942 HG: diff -r 6de0c1bde1c8 y
942 HG: diff -r 6de0c1bde1c8 y
943 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
943 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
944 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
944 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
945 HG: @@ -0,0 +1,1 @@
945 HG: @@ -0,0 +1,1 @@
946 HG: +y
946 HG: +y
947
947
948 $ hg rm a
948 $ hg rm a
949 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of a, foo and y"
949 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of a, foo and y"
950 expecting diff of a, foo and y
950 expecting diff of a, foo and y
951
951
952 HG: M:
952 HG: M:
953 HG: A: foo y
953 HG: A: foo y
954 HG: R: a
954 HG: R: a
955 HG: diff -r 6de0c1bde1c8 a
955 HG: diff -r 6de0c1bde1c8 a
956 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
956 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
957 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
957 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
958 HG: @@ -1,2 +0,0 @@
958 HG: @@ -1,2 +0,0 @@
959 HG: -a
959 HG: -a
960 HG: -a
960 HG: -a
961 HG: diff -r 6de0c1bde1c8 foo
961 HG: diff -r 6de0c1bde1c8 foo
962 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
962 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
963 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
963 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
964 HG: @@ -0,0 +1,1 @@
964 HG: @@ -0,0 +1,1 @@
965 HG: +foo
965 HG: +foo
966 HG: diff -r 6de0c1bde1c8 y
966 HG: diff -r 6de0c1bde1c8 y
967 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
967 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
968 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
968 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
969 HG: @@ -0,0 +1,1 @@
969 HG: @@ -0,0 +1,1 @@
970 HG: +y
970 HG: +y
971
971
972 $ hg rm x
972 $ hg rm x
973 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of a, foo, x and y"
973 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of a, foo, x and y"
974 expecting diff of a, foo, x and y
974 expecting diff of a, foo, x and y
975
975
976 HG: M:
976 HG: M:
977 HG: A: foo y
977 HG: A: foo y
978 HG: R: a x
978 HG: R: a x
979 HG: diff -r 6de0c1bde1c8 a
979 HG: diff -r 6de0c1bde1c8 a
980 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
980 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
981 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
981 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
982 HG: @@ -1,2 +0,0 @@
982 HG: @@ -1,2 +0,0 @@
983 HG: -a
983 HG: -a
984 HG: -a
984 HG: -a
985 HG: diff -r 6de0c1bde1c8 foo
985 HG: diff -r 6de0c1bde1c8 foo
986 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
986 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
987 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
987 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
988 HG: @@ -0,0 +1,1 @@
988 HG: @@ -0,0 +1,1 @@
989 HG: +foo
989 HG: +foo
990 HG: diff -r 6de0c1bde1c8 x
990 HG: diff -r 6de0c1bde1c8 x
991 HG: --- a/x Thu Jan 01 00:00:00 1970 +0000
991 HG: --- a/x Thu Jan 01 00:00:00 1970 +0000
992 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
992 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
993 HG: @@ -1,1 +0,0 @@
993 HG: @@ -1,1 +0,0 @@
994 HG: -x
994 HG: -x
995 HG: diff -r 6de0c1bde1c8 y
995 HG: diff -r 6de0c1bde1c8 y
996 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
996 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
997 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
997 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
998 HG: @@ -0,0 +1,1 @@
998 HG: @@ -0,0 +1,1 @@
999 HG: +y
999 HG: +y
1000
1000
1001 $ echo cccc >> cc
1001 $ echo cccc >> cc
1002 $ hg status -amr
1002 $ hg status -amr
1003 M cc
1003 M cc
1004 $ HGEDITOR=cat hg commit --amend -e -m "cc should be excluded" -X cc
1004 $ HGEDITOR=cat hg commit --amend -e -m "cc should be excluded" -X cc
1005 cc should be excluded
1005 cc should be excluded
1006
1006
1007 HG: M:
1007 HG: M:
1008 HG: A: foo y
1008 HG: A: foo y
1009 HG: R: a x
1009 HG: R: a x
1010 HG: diff -r 6de0c1bde1c8 a
1010 HG: diff -r 6de0c1bde1c8 a
1011 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
1011 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
1012 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1012 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1013 HG: @@ -1,2 +0,0 @@
1013 HG: @@ -1,2 +0,0 @@
1014 HG: -a
1014 HG: -a
1015 HG: -a
1015 HG: -a
1016 HG: diff -r 6de0c1bde1c8 foo
1016 HG: diff -r 6de0c1bde1c8 foo
1017 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1017 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1018 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
1018 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
1019 HG: @@ -0,0 +1,1 @@
1019 HG: @@ -0,0 +1,1 @@
1020 HG: +foo
1020 HG: +foo
1021 HG: diff -r 6de0c1bde1c8 x
1021 HG: diff -r 6de0c1bde1c8 x
1022 HG: --- a/x Thu Jan 01 00:00:00 1970 +0000
1022 HG: --- a/x Thu Jan 01 00:00:00 1970 +0000
1023 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1023 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1024 HG: @@ -1,1 +0,0 @@
1024 HG: @@ -1,1 +0,0 @@
1025 HG: -x
1025 HG: -x
1026 HG: diff -r 6de0c1bde1c8 y
1026 HG: diff -r 6de0c1bde1c8 y
1027 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1027 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1028 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
1028 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
1029 HG: @@ -0,0 +1,1 @@
1029 HG: @@ -0,0 +1,1 @@
1030 HG: +y
1030 HG: +y
1031
1031
1032 Check for issue4405
1032 Check for issue4405
1033 -------------------
1033 -------------------
1034
1034
1035 Setup the repo with a file that gets moved in a second commit.
1035 Setup the repo with a file that gets moved in a second commit.
1036 $ hg init repo
1036 $ hg init repo
1037 $ cd repo
1037 $ cd repo
1038 $ touch a0
1038 $ touch a0
1039 $ hg add a0
1039 $ hg add a0
1040 $ hg commit -m a0
1040 $ hg commit -m a0
1041 $ hg mv a0 a1
1041 $ hg mv a0 a1
1042 $ hg commit -m a1
1042 $ hg commit -m a1
1043 $ hg up -q 0
1043 $ hg up -q 0
1044 $ hg log -G --template '{rev} {desc}'
1044 $ hg log -G --template '{rev} {desc}'
1045 o 1 a1
1045 o 1 a1
1046 |
1046 |
1047 @ 0 a0
1047 @ 0 a0
1048
1048
1049
1049
1050 Now we branch the repro, but re-use the file contents, so we have a divergence
1050 Now we branch the repro, but re-use the file contents, so we have a divergence
1051 in the file revlog topology and the changelog topology.
1051 in the file revlog topology and the changelog topology.
1052 $ hg revert --rev 1 --all
1052 $ hg revert --rev 1 --all
1053 removing a0
1053 removing a0
1054 adding a1
1054 adding a1
1055 $ hg ci -qm 'a1-amend'
1055 $ hg ci -qm 'a1-amend'
1056 $ hg log -G --template '{rev} {desc}'
1056 $ hg log -G --template '{rev} {desc}'
1057 @ 2 a1-amend
1057 @ 2 a1-amend
1058 |
1058 |
1059 | o 1 a1
1059 | o 1 a1
1060 |/
1060 |/
1061 o 0 a0
1061 o 0 a0
1062
1062
1063
1063
1064 The way mercurial does amends is to create a temporary commit (rev 3) and then
1064 The way mercurial does amends is to create a temporary commit (rev 3) and then
1065 fold the new and old commits together into another commit (rev 4). During this
1065 fold the new and old commits together into another commit (rev 4). During this
1066 process, _findlimit is called to check how far back to look for the transitive
1066 process, _findlimit is called to check how far back to look for the transitive
1067 closure of file copy information, but due to the divergence of the filelog
1067 closure of file copy information, but due to the divergence of the filelog
1068 and changelog graph topologies, before _findlimit was fixed, it returned a rev
1068 and changelog graph topologies, before _findlimit was fixed, it returned a rev
1069 which was not far enough back in this case.
1069 which was not far enough back in this case.
1070 $ hg mv a1 a2
1070 $ hg mv a1 a2
1071 $ hg status --copies --rev 0
1071 $ hg status --copies --rev 0
1072 A a2
1072 A a2
1073 a0
1073 a0
1074 R a0
1074 R a0
1075 $ hg ci --amend -q
1075 $ hg ci --amend -q
1076 $ hg log -G --template '{rev} {desc}'
1076 $ hg log -G --template '{rev} {desc}'
1077 @ 4 a1-amend
1077 @ 4 a1-amend
1078 |
1078 |
1079 | o 1 a1
1079 | o 1 a1
1080 |/
1080 |/
1081 o 0 a0
1081 o 0 a0
1082
1082
1083
1083
1084 Before the fix, the copy information was lost.
1084 Before the fix, the copy information was lost.
1085 $ hg status --copies --rev 0
1085 $ hg status --copies --rev 0
1086 A a2
1086 A a2
1087 a0
1087 a0
1088 R a0
1088 R a0
1089 $ cd ..
1089 $ cd ..
1090
1090
1091 Check that amend properly preserve rename from directory rename (issue-4516)
1091 Check that amend properly preserve rename from directory rename (issue-4516)
1092
1092
1093 If a parent of the merge renames a full directory, any files added to the old
1093 If a parent of the merge renames a full directory, any files added to the old
1094 directory in the other parent will be renamed to the new directory. For some
1094 directory in the other parent will be renamed to the new directory. For some
1095 reason, the rename metadata was when amending such merge. This test ensure we
1095 reason, the rename metadata was when amending such merge. This test ensure we
1096 do not regress. We have a dedicated repo because it needs a setup with renamed
1096 do not regress. We have a dedicated repo because it needs a setup with renamed
1097 directory)
1097 directory)
1098
1098
1099 $ hg init issue4516
1099 $ hg init issue4516
1100 $ cd issue4516
1100 $ cd issue4516
1101 $ mkdir olddirname
1101 $ mkdir olddirname
1102 $ echo line1 > olddirname/commonfile.py
1102 $ echo line1 > olddirname/commonfile.py
1103 $ hg add olddirname/commonfile.py
1103 $ hg add olddirname/commonfile.py
1104 $ hg ci -m first
1104 $ hg ci -m first
1105
1105
1106 $ hg branch newdirname
1106 $ hg branch newdirname
1107 marked working directory as branch newdirname
1107 marked working directory as branch newdirname
1108 (branches are permanent and global, did you want a bookmark?)
1108 (branches are permanent and global, did you want a bookmark?)
1109 $ hg mv olddirname newdirname
1109 $ hg mv olddirname newdirname
1110 moving olddirname/commonfile.py to newdirname/commonfile.py (glob)
1110 moving olddirname/commonfile.py to newdirname/commonfile.py (glob)
1111 $ hg ci -m rename
1111 $ hg ci -m rename
1112
1112
1113 $ hg update default
1113 $ hg update default
1114 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1114 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1115 $ echo line1 > olddirname/newfile.py
1115 $ echo line1 > olddirname/newfile.py
1116 $ hg add olddirname/newfile.py
1116 $ hg add olddirname/newfile.py
1117 $ hg ci -m log
1117 $ hg ci -m log
1118
1118
1119 $ hg up newdirname
1119 $ hg up newdirname
1120 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
1120 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
1121 $ # create newdirname/newfile.py
1121 $ # create newdirname/newfile.py
1122 $ hg merge default
1122 $ hg merge default
1123 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1123 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1124 (branch merge, don't forget to commit)
1124 (branch merge, don't forget to commit)
1125 $ hg ci -m add
1125 $ hg ci -m add
1126 $
1126 $
1127 $ hg debugrename newdirname/newfile.py
1127 $ hg debugrename newdirname/newfile.py
1128 newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob)
1128 newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob)
1129 $ hg status -C --change .
1129 $ hg status -C --change .
1130 A newdirname/newfile.py
1130 A newdirname/newfile.py
1131 $ hg status -C --rev 1
1131 $ hg status -C --rev 1
1132 A newdirname/newfile.py
1132 A newdirname/newfile.py
1133 $ hg status -C --rev 2
1133 $ hg status -C --rev 2
1134 A newdirname/commonfile.py
1134 A newdirname/commonfile.py
1135 olddirname/commonfile.py
1135 olddirname/commonfile.py
1136 A newdirname/newfile.py
1136 A newdirname/newfile.py
1137 olddirname/newfile.py
1137 olddirname/newfile.py
1138 R olddirname/commonfile.py
1138 R olddirname/commonfile.py
1139 R olddirname/newfile.py
1139 R olddirname/newfile.py
1140 $ hg debugindex newdirname/newfile.py
1140 $ hg debugindex newdirname/newfile.py
1141 rev offset length base linkrev nodeid p1 p2
1141 rev offset length base linkrev nodeid p1 p2
1142 0 0 88 0 3 34a4d536c0c0 000000000000 000000000000
1142 0 0 88 0 3 34a4d536c0c0 000000000000 000000000000
1143
1143
1144 $ echo a >> newdirname/commonfile.py
1144 $ echo a >> newdirname/commonfile.py
1145 $ hg ci --amend -m bug
1145 $ hg ci --amend -m bug
1146 $ hg debugrename newdirname/newfile.py
1146 $ hg debugrename newdirname/newfile.py
1147 newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob)
1147 newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob)
1148 $ hg debugindex newdirname/newfile.py
1148 $ hg debugindex newdirname/newfile.py
1149 rev offset length base linkrev nodeid p1 p2
1149 rev offset length base linkrev nodeid p1 p2
1150 0 0 88 0 3 34a4d536c0c0 000000000000 000000000000
1150 0 0 88 0 3 34a4d536c0c0 000000000000 000000000000
1151
1151
@@ -1,1572 +1,1658 b''
1 $ hg init a
1 $ hg init a
2 $ mkdir a/d1
2 $ mkdir a/d1
3 $ mkdir a/d1/d2
3 $ mkdir a/d1/d2
4 $ echo line 1 > a/a
4 $ echo line 1 > a/a
5 $ echo line 1 > a/d1/d2/a
5 $ echo line 1 > a/d1/d2/a
6 $ hg --cwd a ci -Ama
6 $ hg --cwd a ci -Ama
7 adding a
7 adding a
8 adding d1/d2/a
8 adding d1/d2/a
9
9
10 $ echo line 2 >> a/a
10 $ echo line 2 >> a/a
11 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
11 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
12
12
13 import with no args:
13 import with no args:
14
14
15 $ hg --cwd a import
15 $ hg --cwd a import
16 abort: need at least one patch to import
16 abort: need at least one patch to import
17 [255]
17 [255]
18
18
19 generate patches for the test
19 generate patches for the test
20
20
21 $ hg --cwd a export tip > exported-tip.patch
21 $ hg --cwd a export tip > exported-tip.patch
22 $ hg --cwd a diff -r0:1 > diffed-tip.patch
22 $ hg --cwd a diff -r0:1 > diffed-tip.patch
23
23
24
24
25 import exported patch
25 import exported patch
26 (this also tests that editor is not invoked, if the patch contains the
26 (this also tests that editor is not invoked, if the patch contains the
27 commit message and '--edit' is not specified)
27 commit message and '--edit' is not specified)
28
28
29 $ hg clone -r0 a b
29 $ hg clone -r0 a b
30 adding changesets
30 adding changesets
31 adding manifests
31 adding manifests
32 adding file changes
32 adding file changes
33 added 1 changesets with 2 changes to 2 files
33 added 1 changesets with 2 changes to 2 files
34 updating to branch default
34 updating to branch default
35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
36 $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
37 applying ../exported-tip.patch
37 applying ../exported-tip.patch
38
38
39 message and committer and date should be same
39 message and committer and date should be same
40
40
41 $ hg --cwd b tip
41 $ hg --cwd b tip
42 changeset: 1:1d4bd90af0e4
42 changeset: 1:1d4bd90af0e4
43 tag: tip
43 tag: tip
44 user: someone
44 user: someone
45 date: Thu Jan 01 00:00:01 1970 +0000
45 date: Thu Jan 01 00:00:01 1970 +0000
46 summary: second change
46 summary: second change
47
47
48 $ rm -r b
48 $ rm -r b
49
49
50
50
51 import exported patch with external patcher
51 import exported patch with external patcher
52 (this also tests that editor is invoked, if the '--edit' is specified,
52 (this also tests that editor is invoked, if the '--edit' is specified,
53 regardless of the commit message in the patch)
53 regardless of the commit message in the patch)
54
54
55 $ cat > dummypatch.py <<EOF
55 $ cat > dummypatch.py <<EOF
56 > print 'patching file a'
56 > print 'patching file a'
57 > file('a', 'wb').write('line2\n')
57 > file('a', 'wb').write('line2\n')
58 > EOF
58 > EOF
59 $ hg clone -r0 a b
59 $ hg clone -r0 a b
60 adding changesets
60 adding changesets
61 adding manifests
61 adding manifests
62 adding file changes
62 adding file changes
63 added 1 changesets with 2 changes to 2 files
63 added 1 changesets with 2 changes to 2 files
64 updating to branch default
64 updating to branch default
65 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
65 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 $ HGEDITOR=cat hg --config ui.patch='python ../dummypatch.py' --cwd b import --edit ../exported-tip.patch
66 $ HGEDITOR=cat hg --config ui.patch='python ../dummypatch.py' --cwd b import --edit ../exported-tip.patch
67 applying ../exported-tip.patch
67 applying ../exported-tip.patch
68 second change
68 second change
69
69
70
70
71 HG: Enter commit message. Lines beginning with 'HG:' are removed.
71 HG: Enter commit message. Lines beginning with 'HG:' are removed.
72 HG: Leave message empty to abort commit.
72 HG: Leave message empty to abort commit.
73 HG: --
73 HG: --
74 HG: user: someone
74 HG: user: someone
75 HG: branch 'default'
75 HG: branch 'default'
76 HG: changed a
76 HG: changed a
77 $ cat b/a
77 $ cat b/a
78 line2
78 line2
79 $ rm -r b
79 $ rm -r b
80
80
81
81
82 import of plain diff should fail without message
82 import of plain diff should fail without message
83 (this also tests that editor is invoked, if the patch doesn't contain
83 (this also tests that editor is invoked, if the patch doesn't contain
84 the commit message, regardless of '--edit')
84 the commit message, regardless of '--edit')
85
85
86 $ hg clone -r0 a b
86 $ hg clone -r0 a b
87 adding changesets
87 adding changesets
88 adding manifests
88 adding manifests
89 adding file changes
89 adding file changes
90 added 1 changesets with 2 changes to 2 files
90 added 1 changesets with 2 changes to 2 files
91 updating to branch default
91 updating to branch default
92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 $ cat > $TESTTMP/editor.sh <<EOF
93 $ cat > $TESTTMP/editor.sh <<EOF
94 > env | grep HGEDITFORM
94 > env | grep HGEDITFORM
95 > cat \$1
95 > cat \$1
96 > EOF
96 > EOF
97 $ HGEDITOR="sh $TESTTMP/editor.sh" hg --cwd b import ../diffed-tip.patch
97 $ HGEDITOR="sh $TESTTMP/editor.sh" hg --cwd b import ../diffed-tip.patch
98 applying ../diffed-tip.patch
98 applying ../diffed-tip.patch
99 HGEDITFORM=import.normal.normal
99 HGEDITFORM=import.normal.normal
100
100
101
101
102 HG: Enter commit message. Lines beginning with 'HG:' are removed.
102 HG: Enter commit message. Lines beginning with 'HG:' are removed.
103 HG: Leave message empty to abort commit.
103 HG: Leave message empty to abort commit.
104 HG: --
104 HG: --
105 HG: user: test
105 HG: user: test
106 HG: branch 'default'
106 HG: branch 'default'
107 HG: changed a
107 HG: changed a
108 abort: empty commit message
108 abort: empty commit message
109 [255]
109 [255]
110
110
111 Test avoiding editor invocation at applying the patch with --exact,
111 Test avoiding editor invocation at applying the patch with --exact,
112 even if commit message is empty
112 even if commit message is empty
113
113
114 $ echo a >> b/a
114 $ echo a >> b/a
115 $ hg --cwd b commit -m ' '
115 $ hg --cwd b commit -m ' '
116 $ hg --cwd b tip -T "{node}\n"
116 $ hg --cwd b tip -T "{node}\n"
117 d8804f3f5396d800812f579c8452796a5993bdb2
117 d8804f3f5396d800812f579c8452796a5993bdb2
118 $ hg --cwd b export -o ../empty-log.diff .
118 $ hg --cwd b export -o ../empty-log.diff .
119 $ hg --cwd b update -q -C ".^1"
119 $ hg --cwd b update -q -C ".^1"
120 $ hg --cwd b --config extensions.strip= strip -q tip
120 $ hg --cwd b --config extensions.strip= strip -q tip
121 $ HGEDITOR=cat hg --cwd b import --exact ../empty-log.diff
121 $ HGEDITOR=cat hg --cwd b import --exact ../empty-log.diff
122 applying ../empty-log.diff
122 applying ../empty-log.diff
123 $ hg --cwd b tip -T "{node}\n"
123 $ hg --cwd b tip -T "{node}\n"
124 d8804f3f5396d800812f579c8452796a5993bdb2
124 d8804f3f5396d800812f579c8452796a5993bdb2
125
125
126 $ rm -r b
126 $ rm -r b
127
127
128
128
129 import of plain diff should be ok with message
129 import of plain diff should be ok with message
130
130
131 $ hg clone -r0 a b
131 $ hg clone -r0 a b
132 adding changesets
132 adding changesets
133 adding manifests
133 adding manifests
134 adding file changes
134 adding file changes
135 added 1 changesets with 2 changes to 2 files
135 added 1 changesets with 2 changes to 2 files
136 updating to branch default
136 updating to branch default
137 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
137 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
138 $ hg --cwd b import -mpatch ../diffed-tip.patch
138 $ hg --cwd b import -mpatch ../diffed-tip.patch
139 applying ../diffed-tip.patch
139 applying ../diffed-tip.patch
140 $ rm -r b
140 $ rm -r b
141
141
142
142
143 import of plain diff with specific date and user
143 import of plain diff with specific date and user
144 (this also tests that editor is not invoked, if
144 (this also tests that editor is not invoked, if
145 '--message'/'--logfile' is specified and '--edit' is not)
145 '--message'/'--logfile' is specified and '--edit' is not)
146
146
147 $ hg clone -r0 a b
147 $ hg clone -r0 a b
148 adding changesets
148 adding changesets
149 adding manifests
149 adding manifests
150 adding file changes
150 adding file changes
151 added 1 changesets with 2 changes to 2 files
151 added 1 changesets with 2 changes to 2 files
152 updating to branch default
152 updating to branch default
153 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
153 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
154 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
155 applying ../diffed-tip.patch
155 applying ../diffed-tip.patch
156 $ hg -R b tip -pv
156 $ hg -R b tip -pv
157 changeset: 1:ca68f19f3a40
157 changeset: 1:ca68f19f3a40
158 tag: tip
158 tag: tip
159 user: user@nowhere.net
159 user: user@nowhere.net
160 date: Thu Jan 01 00:00:01 1970 +0000
160 date: Thu Jan 01 00:00:01 1970 +0000
161 files: a
161 files: a
162 description:
162 description:
163 patch
163 patch
164
164
165
165
166 diff -r 80971e65b431 -r ca68f19f3a40 a
166 diff -r 80971e65b431 -r ca68f19f3a40 a
167 --- a/a Thu Jan 01 00:00:00 1970 +0000
167 --- a/a Thu Jan 01 00:00:00 1970 +0000
168 +++ b/a Thu Jan 01 00:00:01 1970 +0000
168 +++ b/a Thu Jan 01 00:00:01 1970 +0000
169 @@ -1,1 +1,2 @@
169 @@ -1,1 +1,2 @@
170 line 1
170 line 1
171 +line 2
171 +line 2
172
172
173 $ rm -r b
173 $ rm -r b
174
174
175
175
176 import of plain diff should be ok with --no-commit
176 import of plain diff should be ok with --no-commit
177 (this also tests that editor is not invoked, if '--no-commit' is
177 (this also tests that editor is not invoked, if '--no-commit' is
178 specified, regardless of '--edit')
178 specified, regardless of '--edit')
179
179
180 $ hg clone -r0 a b
180 $ hg clone -r0 a b
181 adding changesets
181 adding changesets
182 adding manifests
182 adding manifests
183 adding file changes
183 adding file changes
184 added 1 changesets with 2 changes to 2 files
184 added 1 changesets with 2 changes to 2 files
185 updating to branch default
185 updating to branch default
186 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
186 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
187 $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
187 $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
188 applying ../diffed-tip.patch
188 applying ../diffed-tip.patch
189 $ hg --cwd b diff --nodates
189 $ hg --cwd b diff --nodates
190 diff -r 80971e65b431 a
190 diff -r 80971e65b431 a
191 --- a/a
191 --- a/a
192 +++ b/a
192 +++ b/a
193 @@ -1,1 +1,2 @@
193 @@ -1,1 +1,2 @@
194 line 1
194 line 1
195 +line 2
195 +line 2
196 $ rm -r b
196 $ rm -r b
197
197
198
198
199 import of malformed plain diff should fail
199 import of malformed plain diff should fail
200
200
201 $ hg clone -r0 a b
201 $ hg clone -r0 a b
202 adding changesets
202 adding changesets
203 adding manifests
203 adding manifests
204 adding file changes
204 adding file changes
205 added 1 changesets with 2 changes to 2 files
205 added 1 changesets with 2 changes to 2 files
206 updating to branch default
206 updating to branch default
207 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
207 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
208 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
208 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
209 $ hg --cwd b import -mpatch ../broken.patch
209 $ hg --cwd b import -mpatch ../broken.patch
210 applying ../broken.patch
210 applying ../broken.patch
211 abort: bad hunk #1
211 abort: bad hunk #1
212 [255]
212 [255]
213 $ rm -r b
213 $ rm -r b
214
214
215
215
216 hg -R repo import
216 hg -R repo import
217 put the clone in a subdir - having a directory named "a"
217 put the clone in a subdir - having a directory named "a"
218 used to hide a bug.
218 used to hide a bug.
219
219
220 $ mkdir dir
220 $ mkdir dir
221 $ hg clone -r0 a dir/b
221 $ hg clone -r0 a dir/b
222 adding changesets
222 adding changesets
223 adding manifests
223 adding manifests
224 adding file changes
224 adding file changes
225 added 1 changesets with 2 changes to 2 files
225 added 1 changesets with 2 changes to 2 files
226 updating to branch default
226 updating to branch default
227 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
227 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
228 $ cd dir
228 $ cd dir
229 $ hg -R b import ../exported-tip.patch
229 $ hg -R b import ../exported-tip.patch
230 applying ../exported-tip.patch
230 applying ../exported-tip.patch
231 $ cd ..
231 $ cd ..
232 $ rm -r dir
232 $ rm -r dir
233
233
234
234
235 import from stdin
235 import from stdin
236
236
237 $ hg clone -r0 a b
237 $ hg clone -r0 a b
238 adding changesets
238 adding changesets
239 adding manifests
239 adding manifests
240 adding file changes
240 adding file changes
241 added 1 changesets with 2 changes to 2 files
241 added 1 changesets with 2 changes to 2 files
242 updating to branch default
242 updating to branch default
243 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
243 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 $ hg --cwd b import - < exported-tip.patch
244 $ hg --cwd b import - < exported-tip.patch
245 applying patch from stdin
245 applying patch from stdin
246 $ rm -r b
246 $ rm -r b
247
247
248
248
249 import two patches in one stream
249 import two patches in one stream
250
250
251 $ hg init b
251 $ hg init b
252 $ hg --cwd a export 0:tip | hg --cwd b import -
252 $ hg --cwd a export 0:tip | hg --cwd b import -
253 applying patch from stdin
253 applying patch from stdin
254 $ hg --cwd a id
254 $ hg --cwd a id
255 1d4bd90af0e4 tip
255 1d4bd90af0e4 tip
256 $ hg --cwd b id
256 $ hg --cwd b id
257 1d4bd90af0e4 tip
257 1d4bd90af0e4 tip
258 $ rm -r b
258 $ rm -r b
259
259
260
260
261 override commit message
261 override commit message
262
262
263 $ hg clone -r0 a b
263 $ hg clone -r0 a b
264 adding changesets
264 adding changesets
265 adding manifests
265 adding manifests
266 adding file changes
266 adding file changes
267 added 1 changesets with 2 changes to 2 files
267 added 1 changesets with 2 changes to 2 files
268 updating to branch default
268 updating to branch default
269 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
269 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
270 $ hg --cwd b import -m 'override' - < exported-tip.patch
270 $ hg --cwd b import -m 'override' - < exported-tip.patch
271 applying patch from stdin
271 applying patch from stdin
272 $ hg --cwd b tip | grep override
272 $ hg --cwd b tip | grep override
273 summary: override
273 summary: override
274 $ rm -r b
274 $ rm -r b
275
275
276 $ cat > mkmsg.py <<EOF
276 $ cat > mkmsg.py <<EOF
277 > import email.Message, sys
277 > import email.Message, sys
278 > msg = email.Message.Message()
278 > msg = email.Message.Message()
279 > patch = open(sys.argv[1], 'rb').read()
279 > patch = open(sys.argv[1], 'rb').read()
280 > msg.set_payload('email commit message\n' + patch)
280 > msg.set_payload('email commit message\n' + patch)
281 > msg['Subject'] = 'email patch'
281 > msg['Subject'] = 'email patch'
282 > msg['From'] = 'email patcher'
282 > msg['From'] = 'email patcher'
283 > file(sys.argv[2], 'wb').write(msg.as_string())
283 > file(sys.argv[2], 'wb').write(msg.as_string())
284 > EOF
284 > EOF
285
285
286
286
287 plain diff in email, subject, message body
287 plain diff in email, subject, message body
288
288
289 $ hg clone -r0 a b
289 $ hg clone -r0 a b
290 adding changesets
290 adding changesets
291 adding manifests
291 adding manifests
292 adding file changes
292 adding file changes
293 added 1 changesets with 2 changes to 2 files
293 added 1 changesets with 2 changes to 2 files
294 updating to branch default
294 updating to branch default
295 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
295 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 $ python mkmsg.py diffed-tip.patch msg.patch
296 $ python mkmsg.py diffed-tip.patch msg.patch
297 $ hg --cwd b import ../msg.patch
297 $ hg --cwd b import ../msg.patch
298 applying ../msg.patch
298 applying ../msg.patch
299 $ hg --cwd b tip | grep email
299 $ hg --cwd b tip | grep email
300 user: email patcher
300 user: email patcher
301 summary: email patch
301 summary: email patch
302 $ rm -r b
302 $ rm -r b
303
303
304
304
305 plain diff in email, no subject, message body
305 plain diff in email, no subject, message body
306
306
307 $ hg clone -r0 a b
307 $ hg clone -r0 a b
308 adding changesets
308 adding changesets
309 adding manifests
309 adding manifests
310 adding file changes
310 adding file changes
311 added 1 changesets with 2 changes to 2 files
311 added 1 changesets with 2 changes to 2 files
312 updating to branch default
312 updating to branch default
313 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
313 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
314 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
314 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
315 applying patch from stdin
315 applying patch from stdin
316 $ rm -r b
316 $ rm -r b
317
317
318
318
319 plain diff in email, subject, no message body
319 plain diff in email, subject, no message body
320
320
321 $ hg clone -r0 a b
321 $ hg clone -r0 a b
322 adding changesets
322 adding changesets
323 adding manifests
323 adding manifests
324 adding file changes
324 adding file changes
325 added 1 changesets with 2 changes to 2 files
325 added 1 changesets with 2 changes to 2 files
326 updating to branch default
326 updating to branch default
327 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
327 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
328 $ grep -v '^email ' msg.patch | hg --cwd b import -
328 $ grep -v '^email ' msg.patch | hg --cwd b import -
329 applying patch from stdin
329 applying patch from stdin
330 $ rm -r b
330 $ rm -r b
331
331
332
332
333 plain diff in email, no subject, no message body, should fail
333 plain diff in email, no subject, no message body, should fail
334
334
335 $ hg clone -r0 a b
335 $ hg clone -r0 a b
336 adding changesets
336 adding changesets
337 adding manifests
337 adding manifests
338 adding file changes
338 adding file changes
339 added 1 changesets with 2 changes to 2 files
339 added 1 changesets with 2 changes to 2 files
340 updating to branch default
340 updating to branch default
341 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
341 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
342 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
342 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
343 applying patch from stdin
343 applying patch from stdin
344 abort: empty commit message
344 abort: empty commit message
345 [255]
345 [255]
346 $ rm -r b
346 $ rm -r b
347
347
348
348
349 hg export in email, should use patch header
349 hg export in email, should use patch header
350
350
351 $ hg clone -r0 a b
351 $ hg clone -r0 a b
352 adding changesets
352 adding changesets
353 adding manifests
353 adding manifests
354 adding file changes
354 adding file changes
355 added 1 changesets with 2 changes to 2 files
355 added 1 changesets with 2 changes to 2 files
356 updating to branch default
356 updating to branch default
357 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
357 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
358 $ python mkmsg.py exported-tip.patch msg.patch
358 $ python mkmsg.py exported-tip.patch msg.patch
359 $ cat msg.patch | hg --cwd b import -
359 $ cat msg.patch | hg --cwd b import -
360 applying patch from stdin
360 applying patch from stdin
361 $ hg --cwd b tip | grep second
361 $ hg --cwd b tip | grep second
362 summary: second change
362 summary: second change
363 $ rm -r b
363 $ rm -r b
364
364
365
365
366 subject: duplicate detection, removal of [PATCH]
366 subject: duplicate detection, removal of [PATCH]
367 The '---' tests the gitsendmail handling without proper mail headers
367 The '---' tests the gitsendmail handling without proper mail headers
368
368
369 $ cat > mkmsg2.py <<EOF
369 $ cat > mkmsg2.py <<EOF
370 > import email.Message, sys
370 > import email.Message, sys
371 > msg = email.Message.Message()
371 > msg = email.Message.Message()
372 > patch = open(sys.argv[1], 'rb').read()
372 > patch = open(sys.argv[1], 'rb').read()
373 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
373 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
374 > msg['Subject'] = '[PATCH] email patch'
374 > msg['Subject'] = '[PATCH] email patch'
375 > msg['From'] = 'email patcher'
375 > msg['From'] = 'email patcher'
376 > file(sys.argv[2], 'wb').write(msg.as_string())
376 > file(sys.argv[2], 'wb').write(msg.as_string())
377 > EOF
377 > EOF
378
378
379
379
380 plain diff in email, [PATCH] subject, message body with subject
380 plain diff in email, [PATCH] subject, message body with subject
381
381
382 $ hg clone -r0 a b
382 $ hg clone -r0 a b
383 adding changesets
383 adding changesets
384 adding manifests
384 adding manifests
385 adding file changes
385 adding file changes
386 added 1 changesets with 2 changes to 2 files
386 added 1 changesets with 2 changes to 2 files
387 updating to branch default
387 updating to branch default
388 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
388 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
389 $ python mkmsg2.py diffed-tip.patch msg.patch
389 $ python mkmsg2.py diffed-tip.patch msg.patch
390 $ cat msg.patch | hg --cwd b import -
390 $ cat msg.patch | hg --cwd b import -
391 applying patch from stdin
391 applying patch from stdin
392 $ hg --cwd b tip --template '{desc}\n'
392 $ hg --cwd b tip --template '{desc}\n'
393 email patch
393 email patch
394
394
395 next line
395 next line
396 $ rm -r b
396 $ rm -r b
397
397
398
398
399 Issue963: Parent of working dir incorrect after import of multiple
399 Issue963: Parent of working dir incorrect after import of multiple
400 patches and rollback
400 patches and rollback
401
401
402 We weren't backing up the correct dirstate file when importing many
402 We weren't backing up the correct dirstate file when importing many
403 patches: import patch1 patch2; rollback
403 patches: import patch1 patch2; rollback
404
404
405 $ echo line 3 >> a/a
405 $ echo line 3 >> a/a
406 $ hg --cwd a ci -m'third change'
406 $ hg --cwd a ci -m'third change'
407 $ hg --cwd a export -o '../patch%R' 1 2
407 $ hg --cwd a export -o '../patch%R' 1 2
408 $ hg clone -qr0 a b
408 $ hg clone -qr0 a b
409 $ hg --cwd b parents --template 'parent: {rev}\n'
409 $ hg --cwd b parents --template 'parent: {rev}\n'
410 parent: 0
410 parent: 0
411 $ hg --cwd b import -v ../patch1 ../patch2
411 $ hg --cwd b import -v ../patch1 ../patch2
412 applying ../patch1
412 applying ../patch1
413 patching file a
413 patching file a
414 committing files:
414 committing files:
415 a
415 a
416 committing manifest
416 committing manifest
417 committing changelog
417 committing changelog
418 created 1d4bd90af0e4
418 created 1d4bd90af0e4
419 applying ../patch2
419 applying ../patch2
420 patching file a
420 patching file a
421 committing files:
421 committing files:
422 a
422 a
423 committing manifest
423 committing manifest
424 committing changelog
424 committing changelog
425 created 6d019af21222
425 created 6d019af21222
426 $ hg --cwd b rollback
426 $ hg --cwd b rollback
427 repository tip rolled back to revision 0 (undo import)
427 repository tip rolled back to revision 0 (undo import)
428 working directory now based on revision 0
428 working directory now based on revision 0
429 $ hg --cwd b parents --template 'parent: {rev}\n'
429 $ hg --cwd b parents --template 'parent: {rev}\n'
430 parent: 0
430 parent: 0
431
431
432 Test that "hg rollback" doesn't restore dirstate to one at the
432 Test that "hg rollback" doesn't restore dirstate to one at the
433 beginning of the rollbacked transaction in not-"parent-gone" case.
433 beginning of the rollbacked transaction in not-"parent-gone" case.
434
434
435 invoking pretxncommit hook will cause marking '.hg/dirstate' as a file
435 invoking pretxncommit hook will cause marking '.hg/dirstate' as a file
436 to be restored at rollbacking, after DirstateTransactionPlan (see wiki
436 to be restored at rollbacking, after DirstateTransactionPlan (see wiki
437 page for detail).
437 page for detail).
438
438
439 $ hg --cwd b branch -q foobar
439 $ hg --cwd b branch -q foobar
440 $ hg --cwd b commit -m foobar
440 $ hg --cwd b commit -m foobar
441 $ hg --cwd b update 0 -q
441 $ hg --cwd b update 0 -q
442 $ hg --cwd b import ../patch1 ../patch2 --config hooks.pretxncommit=true
442 $ hg --cwd b import ../patch1 ../patch2 --config hooks.pretxncommit=true
443 applying ../patch1
443 applying ../patch1
444 applying ../patch2
444 applying ../patch2
445 $ hg --cwd b update -q 1
445 $ hg --cwd b update -q 1
446 $ hg --cwd b rollback -q
446 $ hg --cwd b rollback -q
447 $ hg --cwd b parents --template 'parent: {rev}\n'
447 $ hg --cwd b parents --template 'parent: {rev}\n'
448 parent: 1
448 parent: 1
449
449
450 $ hg --cwd b update -q -C 0
451 $ hg --cwd b --config extensions.strip= strip -q 1
452
453 Test visibility of in-memory distate changes inside transaction to
454 external process
455
456 $ echo foo > a/foo
457 $ hg --cwd a commit -A -m 'adding foo' foo
458 $ hg --cwd a export -o '../patch%R' 3
459
460 $ cat > $TESTTMP/checkvisibility.sh <<EOF
461 > echo "===="
462 > hg parents --template "VISIBLE {rev}:{node|short}\n"
463 > hg status -amr
464 > # test that pending changes are hidden
465 > unset HG_PENDING
466 > hg parents --template "ACTUAL {rev}:{node|short}\n"
467 > hg status -amr
468 > echo "===="
469 > EOF
470
471 == test visibility to external editor
472
473 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
474 ====
475 VISIBLE 0:80971e65b431
476 ACTUAL 0:80971e65b431
477 ====
478
479 $ HGEDITOR="sh $TESTTMP/checkvisibility.sh" hg --cwd b import -v --edit ../patch1 ../patch2 ../patch3
480 applying ../patch1
481 patching file a
482 ====
483 VISIBLE 0:80971e65b431
484 M a
485 ACTUAL 0:80971e65b431
486 M a
487 ====
488 committing files:
489 a
490 committing manifest
491 committing changelog
492 created 1d4bd90af0e4
493 applying ../patch2
494 patching file a
495 ====
496 VISIBLE 1:1d4bd90af0e4
497 M a
498 ACTUAL 0:80971e65b431
499 M a
500 ====
501 committing files:
502 a
503 committing manifest
504 committing changelog
505 created 6d019af21222
506 applying ../patch3
507 patching file foo
508 adding foo
509 ====
510 VISIBLE 2:6d019af21222
511 A foo
512 ACTUAL 0:80971e65b431
513 M a
514 ====
515 committing files:
516 foo
517 committing manifest
518 committing changelog
519 created 55e3f75b2378
520
521 $ hg --cwd b rollback -q
522
523 (content of file "a" is already changed and it should be recognized as
524 "M", even though dirstate is restored to one before "hg import")
525
526 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
527 ====
528 VISIBLE 0:80971e65b431
529 M a
530 ACTUAL 0:80971e65b431
531 M a
532 ====
533 $ hg --cwd b revert --no-backup a
534 $ rm -f b/foo
535
450 $ rm -r b
536 $ rm -r b
451
537
452
538
453 importing a patch in a subdirectory failed at the commit stage
539 importing a patch in a subdirectory failed at the commit stage
454
540
455 $ echo line 2 >> a/d1/d2/a
541 $ echo line 2 >> a/d1/d2/a
456 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
542 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
457
543
458 hg import in a subdirectory
544 hg import in a subdirectory
459
545
460 $ hg clone -r0 a b
546 $ hg clone -r0 a b
461 adding changesets
547 adding changesets
462 adding manifests
548 adding manifests
463 adding file changes
549 adding file changes
464 added 1 changesets with 2 changes to 2 files
550 added 1 changesets with 2 changes to 2 files
465 updating to branch default
551 updating to branch default
466 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
552 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
467 $ hg --cwd a export tip > tmp
553 $ hg --cwd a export tip > tmp
468 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
554 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
469 $ dir=`pwd`
555 $ dir=`pwd`
470 $ cd b/d1/d2 2>&1 > /dev/null
556 $ cd b/d1/d2 2>&1 > /dev/null
471 $ hg import ../../../subdir-tip.patch
557 $ hg import ../../../subdir-tip.patch
472 applying ../../../subdir-tip.patch
558 applying ../../../subdir-tip.patch
473 $ cd "$dir"
559 $ cd "$dir"
474
560
475 message should be 'subdir change'
561 message should be 'subdir change'
476 committer should be 'someoneelse'
562 committer should be 'someoneelse'
477
563
478 $ hg --cwd b tip
564 $ hg --cwd b tip
479 changeset: 1:3577f5aea227
565 changeset: 1:3577f5aea227
480 tag: tip
566 tag: tip
481 user: someoneelse
567 user: someoneelse
482 date: Thu Jan 01 00:00:01 1970 +0000
568 date: Thu Jan 01 00:00:01 1970 +0000
483 summary: subdir change
569 summary: subdir change
484
570
485
571
486 should be empty
572 should be empty
487
573
488 $ hg --cwd b status
574 $ hg --cwd b status
489
575
490
576
491 Test fuzziness (ambiguous patch location, fuzz=2)
577 Test fuzziness (ambiguous patch location, fuzz=2)
492
578
493 $ hg init fuzzy
579 $ hg init fuzzy
494 $ cd fuzzy
580 $ cd fuzzy
495 $ echo line1 > a
581 $ echo line1 > a
496 $ echo line0 >> a
582 $ echo line0 >> a
497 $ echo line3 >> a
583 $ echo line3 >> a
498 $ hg ci -Am adda
584 $ hg ci -Am adda
499 adding a
585 adding a
500 $ echo line1 > a
586 $ echo line1 > a
501 $ echo line2 >> a
587 $ echo line2 >> a
502 $ echo line0 >> a
588 $ echo line0 >> a
503 $ echo line3 >> a
589 $ echo line3 >> a
504 $ hg ci -m change a
590 $ hg ci -m change a
505 $ hg export tip > fuzzy-tip.patch
591 $ hg export tip > fuzzy-tip.patch
506 $ hg up -C 0
592 $ hg up -C 0
507 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
593 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
508 $ echo line1 > a
594 $ echo line1 > a
509 $ echo line0 >> a
595 $ echo line0 >> a
510 $ echo line1 >> a
596 $ echo line1 >> a
511 $ echo line0 >> a
597 $ echo line0 >> a
512 $ hg ci -m brancha
598 $ hg ci -m brancha
513 created new head
599 created new head
514 $ hg import --config patch.fuzz=0 -v fuzzy-tip.patch
600 $ hg import --config patch.fuzz=0 -v fuzzy-tip.patch
515 applying fuzzy-tip.patch
601 applying fuzzy-tip.patch
516 patching file a
602 patching file a
517 Hunk #1 FAILED at 0
603 Hunk #1 FAILED at 0
518 1 out of 1 hunks FAILED -- saving rejects to file a.rej
604 1 out of 1 hunks FAILED -- saving rejects to file a.rej
519 abort: patch failed to apply
605 abort: patch failed to apply
520 [255]
606 [255]
521 $ hg import --no-commit -v fuzzy-tip.patch
607 $ hg import --no-commit -v fuzzy-tip.patch
522 applying fuzzy-tip.patch
608 applying fuzzy-tip.patch
523 patching file a
609 patching file a
524 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
610 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
525 applied to working directory
611 applied to working directory
526 $ hg revert -a
612 $ hg revert -a
527 reverting a
613 reverting a
528
614
529
615
530 import with --no-commit should have written .hg/last-message.txt
616 import with --no-commit should have written .hg/last-message.txt
531
617
532 $ cat .hg/last-message.txt
618 $ cat .hg/last-message.txt
533 change (no-eol)
619 change (no-eol)
534
620
535
621
536 test fuzziness with eol=auto
622 test fuzziness with eol=auto
537
623
538 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
624 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
539 applying fuzzy-tip.patch
625 applying fuzzy-tip.patch
540 patching file a
626 patching file a
541 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
627 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
542 applied to working directory
628 applied to working directory
543 $ cd ..
629 $ cd ..
544
630
545
631
546 Test hunk touching empty files (issue906)
632 Test hunk touching empty files (issue906)
547
633
548 $ hg init empty
634 $ hg init empty
549 $ cd empty
635 $ cd empty
550 $ touch a
636 $ touch a
551 $ touch b1
637 $ touch b1
552 $ touch c1
638 $ touch c1
553 $ echo d > d
639 $ echo d > d
554 $ hg ci -Am init
640 $ hg ci -Am init
555 adding a
641 adding a
556 adding b1
642 adding b1
557 adding c1
643 adding c1
558 adding d
644 adding d
559 $ echo a > a
645 $ echo a > a
560 $ echo b > b1
646 $ echo b > b1
561 $ hg mv b1 b2
647 $ hg mv b1 b2
562 $ echo c > c1
648 $ echo c > c1
563 $ hg copy c1 c2
649 $ hg copy c1 c2
564 $ rm d
650 $ rm d
565 $ touch d
651 $ touch d
566 $ hg diff --git
652 $ hg diff --git
567 diff --git a/a b/a
653 diff --git a/a b/a
568 --- a/a
654 --- a/a
569 +++ b/a
655 +++ b/a
570 @@ -0,0 +1,1 @@
656 @@ -0,0 +1,1 @@
571 +a
657 +a
572 diff --git a/b1 b/b2
658 diff --git a/b1 b/b2
573 rename from b1
659 rename from b1
574 rename to b2
660 rename to b2
575 --- a/b1
661 --- a/b1
576 +++ b/b2
662 +++ b/b2
577 @@ -0,0 +1,1 @@
663 @@ -0,0 +1,1 @@
578 +b
664 +b
579 diff --git a/c1 b/c1
665 diff --git a/c1 b/c1
580 --- a/c1
666 --- a/c1
581 +++ b/c1
667 +++ b/c1
582 @@ -0,0 +1,1 @@
668 @@ -0,0 +1,1 @@
583 +c
669 +c
584 diff --git a/c1 b/c2
670 diff --git a/c1 b/c2
585 copy from c1
671 copy from c1
586 copy to c2
672 copy to c2
587 --- a/c1
673 --- a/c1
588 +++ b/c2
674 +++ b/c2
589 @@ -0,0 +1,1 @@
675 @@ -0,0 +1,1 @@
590 +c
676 +c
591 diff --git a/d b/d
677 diff --git a/d b/d
592 --- a/d
678 --- a/d
593 +++ b/d
679 +++ b/d
594 @@ -1,1 +0,0 @@
680 @@ -1,1 +0,0 @@
595 -d
681 -d
596 $ hg ci -m empty
682 $ hg ci -m empty
597 $ hg export --git tip > empty.diff
683 $ hg export --git tip > empty.diff
598 $ hg up -C 0
684 $ hg up -C 0
599 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
685 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
600 $ hg import empty.diff
686 $ hg import empty.diff
601 applying empty.diff
687 applying empty.diff
602 $ for name in a b1 b2 c1 c2 d; do
688 $ for name in a b1 b2 c1 c2 d; do
603 > echo % $name file
689 > echo % $name file
604 > test -f $name && cat $name
690 > test -f $name && cat $name
605 > done
691 > done
606 % a file
692 % a file
607 a
693 a
608 % b1 file
694 % b1 file
609 % b2 file
695 % b2 file
610 b
696 b
611 % c1 file
697 % c1 file
612 c
698 c
613 % c2 file
699 % c2 file
614 c
700 c
615 % d file
701 % d file
616 $ cd ..
702 $ cd ..
617
703
618
704
619 Test importing a patch ending with a binary file removal
705 Test importing a patch ending with a binary file removal
620
706
621 $ hg init binaryremoval
707 $ hg init binaryremoval
622 $ cd binaryremoval
708 $ cd binaryremoval
623 $ echo a > a
709 $ echo a > a
624 $ $PYTHON -c "file('b', 'wb').write('a\x00b')"
710 $ $PYTHON -c "file('b', 'wb').write('a\x00b')"
625 $ hg ci -Am addall
711 $ hg ci -Am addall
626 adding a
712 adding a
627 adding b
713 adding b
628 $ hg rm a
714 $ hg rm a
629 $ hg rm b
715 $ hg rm b
630 $ hg st
716 $ hg st
631 R a
717 R a
632 R b
718 R b
633 $ hg ci -m remove
719 $ hg ci -m remove
634 $ hg export --git . > remove.diff
720 $ hg export --git . > remove.diff
635 $ cat remove.diff | grep git
721 $ cat remove.diff | grep git
636 diff --git a/a b/a
722 diff --git a/a b/a
637 diff --git a/b b/b
723 diff --git a/b b/b
638 $ hg up -C 0
724 $ hg up -C 0
639 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
725 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
640 $ hg import remove.diff
726 $ hg import remove.diff
641 applying remove.diff
727 applying remove.diff
642 $ hg manifest
728 $ hg manifest
643 $ cd ..
729 $ cd ..
644
730
645
731
646 Issue927: test update+rename with common name
732 Issue927: test update+rename with common name
647
733
648 $ hg init t
734 $ hg init t
649 $ cd t
735 $ cd t
650 $ touch a
736 $ touch a
651 $ hg ci -Am t
737 $ hg ci -Am t
652 adding a
738 adding a
653 $ echo a > a
739 $ echo a > a
654
740
655 Here, bfile.startswith(afile)
741 Here, bfile.startswith(afile)
656
742
657 $ hg copy a a2
743 $ hg copy a a2
658 $ hg ci -m copya
744 $ hg ci -m copya
659 $ hg export --git tip > copy.diff
745 $ hg export --git tip > copy.diff
660 $ hg up -C 0
746 $ hg up -C 0
661 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
747 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
662 $ hg import copy.diff
748 $ hg import copy.diff
663 applying copy.diff
749 applying copy.diff
664
750
665 a should contain an 'a'
751 a should contain an 'a'
666
752
667 $ cat a
753 $ cat a
668 a
754 a
669
755
670 and a2 should have duplicated it
756 and a2 should have duplicated it
671
757
672 $ cat a2
758 $ cat a2
673 a
759 a
674 $ cd ..
760 $ cd ..
675
761
676
762
677 test -p0
763 test -p0
678
764
679 $ hg init p0
765 $ hg init p0
680 $ cd p0
766 $ cd p0
681 $ echo a > a
767 $ echo a > a
682 $ hg ci -Am t
768 $ hg ci -Am t
683 adding a
769 adding a
684 $ hg import -p foo
770 $ hg import -p foo
685 abort: invalid value 'foo' for option -p, expected int
771 abort: invalid value 'foo' for option -p, expected int
686 [255]
772 [255]
687 $ hg import -p0 - << EOF
773 $ hg import -p0 - << EOF
688 > foobar
774 > foobar
689 > --- a Sat Apr 12 22:43:58 2008 -0400
775 > --- a Sat Apr 12 22:43:58 2008 -0400
690 > +++ a Sat Apr 12 22:44:05 2008 -0400
776 > +++ a Sat Apr 12 22:44:05 2008 -0400
691 > @@ -1,1 +1,1 @@
777 > @@ -1,1 +1,1 @@
692 > -a
778 > -a
693 > +bb
779 > +bb
694 > EOF
780 > EOF
695 applying patch from stdin
781 applying patch from stdin
696 $ hg status
782 $ hg status
697 $ cat a
783 $ cat a
698 bb
784 bb
699
785
700 test --prefix
786 test --prefix
701
787
702 $ mkdir -p dir/dir2
788 $ mkdir -p dir/dir2
703 $ echo b > dir/dir2/b
789 $ echo b > dir/dir2/b
704 $ hg ci -Am b
790 $ hg ci -Am b
705 adding dir/dir2/b
791 adding dir/dir2/b
706 $ hg import -p2 --prefix dir - << EOF
792 $ hg import -p2 --prefix dir - << EOF
707 > foobar
793 > foobar
708 > --- drop1/drop2/dir2/b
794 > --- drop1/drop2/dir2/b
709 > +++ drop1/drop2/dir2/b
795 > +++ drop1/drop2/dir2/b
710 > @@ -1,1 +1,1 @@
796 > @@ -1,1 +1,1 @@
711 > -b
797 > -b
712 > +cc
798 > +cc
713 > EOF
799 > EOF
714 applying patch from stdin
800 applying patch from stdin
715 $ hg status
801 $ hg status
716 $ cat dir/dir2/b
802 $ cat dir/dir2/b
717 cc
803 cc
718 $ cd ..
804 $ cd ..
719
805
720
806
721 test paths outside repo root
807 test paths outside repo root
722
808
723 $ mkdir outside
809 $ mkdir outside
724 $ touch outside/foo
810 $ touch outside/foo
725 $ hg init inside
811 $ hg init inside
726 $ cd inside
812 $ cd inside
727 $ hg import - <<EOF
813 $ hg import - <<EOF
728 > diff --git a/a b/b
814 > diff --git a/a b/b
729 > rename from ../outside/foo
815 > rename from ../outside/foo
730 > rename to bar
816 > rename to bar
731 > EOF
817 > EOF
732 applying patch from stdin
818 applying patch from stdin
733 abort: path contains illegal component: ../outside/foo (glob)
819 abort: path contains illegal component: ../outside/foo (glob)
734 [255]
820 [255]
735 $ cd ..
821 $ cd ..
736
822
737
823
738 test import with similarity and git and strip (issue295 et al.)
824 test import with similarity and git and strip (issue295 et al.)
739
825
740 $ hg init sim
826 $ hg init sim
741 $ cd sim
827 $ cd sim
742 $ echo 'this is a test' > a
828 $ echo 'this is a test' > a
743 $ hg ci -Ama
829 $ hg ci -Ama
744 adding a
830 adding a
745 $ cat > ../rename.diff <<EOF
831 $ cat > ../rename.diff <<EOF
746 > diff --git a/foo/a b/foo/a
832 > diff --git a/foo/a b/foo/a
747 > deleted file mode 100644
833 > deleted file mode 100644
748 > --- a/foo/a
834 > --- a/foo/a
749 > +++ /dev/null
835 > +++ /dev/null
750 > @@ -1,1 +0,0 @@
836 > @@ -1,1 +0,0 @@
751 > -this is a test
837 > -this is a test
752 > diff --git a/foo/b b/foo/b
838 > diff --git a/foo/b b/foo/b
753 > new file mode 100644
839 > new file mode 100644
754 > --- /dev/null
840 > --- /dev/null
755 > +++ b/foo/b
841 > +++ b/foo/b
756 > @@ -0,0 +1,2 @@
842 > @@ -0,0 +1,2 @@
757 > +this is a test
843 > +this is a test
758 > +foo
844 > +foo
759 > EOF
845 > EOF
760 $ hg import --no-commit -v -s 1 ../rename.diff -p2
846 $ hg import --no-commit -v -s 1 ../rename.diff -p2
761 applying ../rename.diff
847 applying ../rename.diff
762 patching file a
848 patching file a
763 patching file b
849 patching file b
764 adding b
850 adding b
765 recording removal of a as rename to b (88% similar)
851 recording removal of a as rename to b (88% similar)
766 applied to working directory
852 applied to working directory
767 $ hg st -C
853 $ hg st -C
768 A b
854 A b
769 a
855 a
770 R a
856 R a
771 $ hg revert -a
857 $ hg revert -a
772 undeleting a
858 undeleting a
773 forgetting b
859 forgetting b
774 $ rm b
860 $ rm b
775 $ hg import --no-commit -v -s 100 ../rename.diff -p2
861 $ hg import --no-commit -v -s 100 ../rename.diff -p2
776 applying ../rename.diff
862 applying ../rename.diff
777 patching file a
863 patching file a
778 patching file b
864 patching file b
779 adding b
865 adding b
780 applied to working directory
866 applied to working directory
781 $ hg st -C
867 $ hg st -C
782 A b
868 A b
783 R a
869 R a
784 $ cd ..
870 $ cd ..
785
871
786
872
787 Issue1495: add empty file from the end of patch
873 Issue1495: add empty file from the end of patch
788
874
789 $ hg init addemptyend
875 $ hg init addemptyend
790 $ cd addemptyend
876 $ cd addemptyend
791 $ touch a
877 $ touch a
792 $ hg addremove
878 $ hg addremove
793 adding a
879 adding a
794 $ hg ci -m "commit"
880 $ hg ci -m "commit"
795 $ cat > a.patch <<EOF
881 $ cat > a.patch <<EOF
796 > add a, b
882 > add a, b
797 > diff --git a/a b/a
883 > diff --git a/a b/a
798 > --- a/a
884 > --- a/a
799 > +++ b/a
885 > +++ b/a
800 > @@ -0,0 +1,1 @@
886 > @@ -0,0 +1,1 @@
801 > +a
887 > +a
802 > diff --git a/b b/b
888 > diff --git a/b b/b
803 > new file mode 100644
889 > new file mode 100644
804 > EOF
890 > EOF
805 $ hg import --no-commit a.patch
891 $ hg import --no-commit a.patch
806 applying a.patch
892 applying a.patch
807
893
808 apply a good patch followed by an empty patch (mainly to ensure
894 apply a good patch followed by an empty patch (mainly to ensure
809 that dirstate is *not* updated when import crashes)
895 that dirstate is *not* updated when import crashes)
810 $ hg update -q -C .
896 $ hg update -q -C .
811 $ rm b
897 $ rm b
812 $ touch empty.patch
898 $ touch empty.patch
813 $ hg import a.patch empty.patch
899 $ hg import a.patch empty.patch
814 applying a.patch
900 applying a.patch
815 applying empty.patch
901 applying empty.patch
816 transaction abort!
902 transaction abort!
817 rollback completed
903 rollback completed
818 abort: empty.patch: no diffs found
904 abort: empty.patch: no diffs found
819 [255]
905 [255]
820 $ hg tip --template '{rev} {desc|firstline}\n'
906 $ hg tip --template '{rev} {desc|firstline}\n'
821 0 commit
907 0 commit
822 $ hg -q status
908 $ hg -q status
823 M a
909 M a
824 $ cd ..
910 $ cd ..
825
911
826 create file when source is not /dev/null
912 create file when source is not /dev/null
827
913
828 $ cat > create.patch <<EOF
914 $ cat > create.patch <<EOF
829 > diff -Naur proj-orig/foo proj-new/foo
915 > diff -Naur proj-orig/foo proj-new/foo
830 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
916 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
831 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
917 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
832 > @@ -0,0 +1,1 @@
918 > @@ -0,0 +1,1 @@
833 > +a
919 > +a
834 > EOF
920 > EOF
835
921
836 some people have patches like the following too
922 some people have patches like the following too
837
923
838 $ cat > create2.patch <<EOF
924 $ cat > create2.patch <<EOF
839 > diff -Naur proj-orig/foo proj-new/foo
925 > diff -Naur proj-orig/foo proj-new/foo
840 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
926 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
841 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
927 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
842 > @@ -0,0 +1,1 @@
928 > @@ -0,0 +1,1 @@
843 > +a
929 > +a
844 > EOF
930 > EOF
845 $ hg init oddcreate
931 $ hg init oddcreate
846 $ cd oddcreate
932 $ cd oddcreate
847 $ hg import --no-commit ../create.patch
933 $ hg import --no-commit ../create.patch
848 applying ../create.patch
934 applying ../create.patch
849 $ cat foo
935 $ cat foo
850 a
936 a
851 $ rm foo
937 $ rm foo
852 $ hg revert foo
938 $ hg revert foo
853 $ hg import --no-commit ../create2.patch
939 $ hg import --no-commit ../create2.patch
854 applying ../create2.patch
940 applying ../create2.patch
855 $ cat foo
941 $ cat foo
856 a
942 a
857
943
858 $ cd ..
944 $ cd ..
859
945
860 Issue1859: first line mistaken for email headers
946 Issue1859: first line mistaken for email headers
861
947
862 $ hg init emailconfusion
948 $ hg init emailconfusion
863 $ cd emailconfusion
949 $ cd emailconfusion
864 $ cat > a.patch <<EOF
950 $ cat > a.patch <<EOF
865 > module: summary
951 > module: summary
866 >
952 >
867 > description
953 > description
868 >
954 >
869 >
955 >
870 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
956 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
871 > --- /dev/null
957 > --- /dev/null
872 > +++ b/a
958 > +++ b/a
873 > @@ -0,0 +1,1 @@
959 > @@ -0,0 +1,1 @@
874 > +a
960 > +a
875 > EOF
961 > EOF
876 $ hg import -d '0 0' a.patch
962 $ hg import -d '0 0' a.patch
877 applying a.patch
963 applying a.patch
878 $ hg parents -v
964 $ hg parents -v
879 changeset: 0:5a681217c0ad
965 changeset: 0:5a681217c0ad
880 tag: tip
966 tag: tip
881 user: test
967 user: test
882 date: Thu Jan 01 00:00:00 1970 +0000
968 date: Thu Jan 01 00:00:00 1970 +0000
883 files: a
969 files: a
884 description:
970 description:
885 module: summary
971 module: summary
886
972
887 description
973 description
888
974
889
975
890 $ cd ..
976 $ cd ..
891
977
892
978
893 in commit message
979 in commit message
894
980
895 $ hg init commitconfusion
981 $ hg init commitconfusion
896 $ cd commitconfusion
982 $ cd commitconfusion
897 $ cat > a.patch <<EOF
983 $ cat > a.patch <<EOF
898 > module: summary
984 > module: summary
899 >
985 >
900 > --- description
986 > --- description
901 >
987 >
902 > diff --git a/a b/a
988 > diff --git a/a b/a
903 > new file mode 100644
989 > new file mode 100644
904 > --- /dev/null
990 > --- /dev/null
905 > +++ b/a
991 > +++ b/a
906 > @@ -0,0 +1,1 @@
992 > @@ -0,0 +1,1 @@
907 > +a
993 > +a
908 > EOF
994 > EOF
909 > hg import -d '0 0' a.patch
995 > hg import -d '0 0' a.patch
910 > hg parents -v
996 > hg parents -v
911 > cd ..
997 > cd ..
912 >
998 >
913 > echo '% tricky header splitting'
999 > echo '% tricky header splitting'
914 > cat > trickyheaders.patch <<EOF
1000 > cat > trickyheaders.patch <<EOF
915 > From: User A <user@a>
1001 > From: User A <user@a>
916 > Subject: [PATCH] from: tricky!
1002 > Subject: [PATCH] from: tricky!
917 >
1003 >
918 > # HG changeset patch
1004 > # HG changeset patch
919 > # User User B
1005 > # User User B
920 > # Date 1266264441 18000
1006 > # Date 1266264441 18000
921 > # Branch stable
1007 > # Branch stable
922 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
1008 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
923 > # Parent 0000000000000000000000000000000000000000
1009 > # Parent 0000000000000000000000000000000000000000
924 > from: tricky!
1010 > from: tricky!
925 >
1011 >
926 > That is not a header.
1012 > That is not a header.
927 >
1013 >
928 > diff -r 000000000000 -r f2be6a1170ac foo
1014 > diff -r 000000000000 -r f2be6a1170ac foo
929 > --- /dev/null
1015 > --- /dev/null
930 > +++ b/foo
1016 > +++ b/foo
931 > @@ -0,0 +1,1 @@
1017 > @@ -0,0 +1,1 @@
932 > +foo
1018 > +foo
933 > EOF
1019 > EOF
934 applying a.patch
1020 applying a.patch
935 changeset: 0:f34d9187897d
1021 changeset: 0:f34d9187897d
936 tag: tip
1022 tag: tip
937 user: test
1023 user: test
938 date: Thu Jan 01 00:00:00 1970 +0000
1024 date: Thu Jan 01 00:00:00 1970 +0000
939 files: a
1025 files: a
940 description:
1026 description:
941 module: summary
1027 module: summary
942
1028
943
1029
944 % tricky header splitting
1030 % tricky header splitting
945
1031
946 $ hg init trickyheaders
1032 $ hg init trickyheaders
947 $ cd trickyheaders
1033 $ cd trickyheaders
948 $ hg import -d '0 0' ../trickyheaders.patch
1034 $ hg import -d '0 0' ../trickyheaders.patch
949 applying ../trickyheaders.patch
1035 applying ../trickyheaders.patch
950 $ hg export --git tip
1036 $ hg export --git tip
951 # HG changeset patch
1037 # HG changeset patch
952 # User User B
1038 # User User B
953 # Date 0 0
1039 # Date 0 0
954 # Thu Jan 01 00:00:00 1970 +0000
1040 # Thu Jan 01 00:00:00 1970 +0000
955 # Node ID eb56ab91903632294ac504838508cb370c0901d2
1041 # Node ID eb56ab91903632294ac504838508cb370c0901d2
956 # Parent 0000000000000000000000000000000000000000
1042 # Parent 0000000000000000000000000000000000000000
957 from: tricky!
1043 from: tricky!
958
1044
959 That is not a header.
1045 That is not a header.
960
1046
961 diff --git a/foo b/foo
1047 diff --git a/foo b/foo
962 new file mode 100644
1048 new file mode 100644
963 --- /dev/null
1049 --- /dev/null
964 +++ b/foo
1050 +++ b/foo
965 @@ -0,0 +1,1 @@
1051 @@ -0,0 +1,1 @@
966 +foo
1052 +foo
967 $ cd ..
1053 $ cd ..
968
1054
969
1055
970 Issue2102: hg export and hg import speak different languages
1056 Issue2102: hg export and hg import speak different languages
971
1057
972 $ hg init issue2102
1058 $ hg init issue2102
973 $ cd issue2102
1059 $ cd issue2102
974 $ mkdir -p src/cmd/gc
1060 $ mkdir -p src/cmd/gc
975 $ touch src/cmd/gc/mksys.bash
1061 $ touch src/cmd/gc/mksys.bash
976 $ hg ci -Am init
1062 $ hg ci -Am init
977 adding src/cmd/gc/mksys.bash
1063 adding src/cmd/gc/mksys.bash
978 $ hg import - <<EOF
1064 $ hg import - <<EOF
979 > # HG changeset patch
1065 > # HG changeset patch
980 > # User Rob Pike
1066 > # User Rob Pike
981 > # Date 1216685449 25200
1067 > # Date 1216685449 25200
982 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
1068 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
983 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
1069 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
984 > help management of empty pkg and lib directories in perforce
1070 > help management of empty pkg and lib directories in perforce
985 >
1071 >
986 > R=gri
1072 > R=gri
987 > DELTA=4 (4 added, 0 deleted, 0 changed)
1073 > DELTA=4 (4 added, 0 deleted, 0 changed)
988 > OCL=13328
1074 > OCL=13328
989 > CL=13328
1075 > CL=13328
990 >
1076 >
991 > diff --git a/lib/place-holder b/lib/place-holder
1077 > diff --git a/lib/place-holder b/lib/place-holder
992 > new file mode 100644
1078 > new file mode 100644
993 > --- /dev/null
1079 > --- /dev/null
994 > +++ b/lib/place-holder
1080 > +++ b/lib/place-holder
995 > @@ -0,0 +1,2 @@
1081 > @@ -0,0 +1,2 @@
996 > +perforce does not maintain empty directories.
1082 > +perforce does not maintain empty directories.
997 > +this file helps.
1083 > +this file helps.
998 > diff --git a/pkg/place-holder b/pkg/place-holder
1084 > diff --git a/pkg/place-holder b/pkg/place-holder
999 > new file mode 100644
1085 > new file mode 100644
1000 > --- /dev/null
1086 > --- /dev/null
1001 > +++ b/pkg/place-holder
1087 > +++ b/pkg/place-holder
1002 > @@ -0,0 +1,2 @@
1088 > @@ -0,0 +1,2 @@
1003 > +perforce does not maintain empty directories.
1089 > +perforce does not maintain empty directories.
1004 > +this file helps.
1090 > +this file helps.
1005 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1091 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1006 > old mode 100644
1092 > old mode 100644
1007 > new mode 100755
1093 > new mode 100755
1008 > EOF
1094 > EOF
1009 applying patch from stdin
1095 applying patch from stdin
1010
1096
1011 #if execbit
1097 #if execbit
1012
1098
1013 $ hg sum
1099 $ hg sum
1014 parent: 1:d59915696727 tip
1100 parent: 1:d59915696727 tip
1015 help management of empty pkg and lib directories in perforce
1101 help management of empty pkg and lib directories in perforce
1016 branch: default
1102 branch: default
1017 commit: (clean)
1103 commit: (clean)
1018 update: (current)
1104 update: (current)
1019 phases: 2 draft
1105 phases: 2 draft
1020
1106
1021 $ hg diff --git -c tip
1107 $ hg diff --git -c tip
1022 diff --git a/lib/place-holder b/lib/place-holder
1108 diff --git a/lib/place-holder b/lib/place-holder
1023 new file mode 100644
1109 new file mode 100644
1024 --- /dev/null
1110 --- /dev/null
1025 +++ b/lib/place-holder
1111 +++ b/lib/place-holder
1026 @@ -0,0 +1,2 @@
1112 @@ -0,0 +1,2 @@
1027 +perforce does not maintain empty directories.
1113 +perforce does not maintain empty directories.
1028 +this file helps.
1114 +this file helps.
1029 diff --git a/pkg/place-holder b/pkg/place-holder
1115 diff --git a/pkg/place-holder b/pkg/place-holder
1030 new file mode 100644
1116 new file mode 100644
1031 --- /dev/null
1117 --- /dev/null
1032 +++ b/pkg/place-holder
1118 +++ b/pkg/place-holder
1033 @@ -0,0 +1,2 @@
1119 @@ -0,0 +1,2 @@
1034 +perforce does not maintain empty directories.
1120 +perforce does not maintain empty directories.
1035 +this file helps.
1121 +this file helps.
1036 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1122 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1037 old mode 100644
1123 old mode 100644
1038 new mode 100755
1124 new mode 100755
1039
1125
1040 #else
1126 #else
1041
1127
1042 $ hg sum
1128 $ hg sum
1043 parent: 1:28f089cc9ccc tip
1129 parent: 1:28f089cc9ccc tip
1044 help management of empty pkg and lib directories in perforce
1130 help management of empty pkg and lib directories in perforce
1045 branch: default
1131 branch: default
1046 commit: (clean)
1132 commit: (clean)
1047 update: (current)
1133 update: (current)
1048 phases: 2 draft
1134 phases: 2 draft
1049
1135
1050 $ hg diff --git -c tip
1136 $ hg diff --git -c tip
1051 diff --git a/lib/place-holder b/lib/place-holder
1137 diff --git a/lib/place-holder b/lib/place-holder
1052 new file mode 100644
1138 new file mode 100644
1053 --- /dev/null
1139 --- /dev/null
1054 +++ b/lib/place-holder
1140 +++ b/lib/place-holder
1055 @@ -0,0 +1,2 @@
1141 @@ -0,0 +1,2 @@
1056 +perforce does not maintain empty directories.
1142 +perforce does not maintain empty directories.
1057 +this file helps.
1143 +this file helps.
1058 diff --git a/pkg/place-holder b/pkg/place-holder
1144 diff --git a/pkg/place-holder b/pkg/place-holder
1059 new file mode 100644
1145 new file mode 100644
1060 --- /dev/null
1146 --- /dev/null
1061 +++ b/pkg/place-holder
1147 +++ b/pkg/place-holder
1062 @@ -0,0 +1,2 @@
1148 @@ -0,0 +1,2 @@
1063 +perforce does not maintain empty directories.
1149 +perforce does not maintain empty directories.
1064 +this file helps.
1150 +this file helps.
1065
1151
1066 /* The mode change for mksys.bash is missing here, because on platforms */
1152 /* The mode change for mksys.bash is missing here, because on platforms */
1067 /* that don't support execbits, mode changes in patches are ignored when */
1153 /* that don't support execbits, mode changes in patches are ignored when */
1068 /* they are imported. This is obviously also the reason for why the hash */
1154 /* they are imported. This is obviously also the reason for why the hash */
1069 /* in the created changeset is different to the one you see above the */
1155 /* in the created changeset is different to the one you see above the */
1070 /* #else clause */
1156 /* #else clause */
1071
1157
1072 #endif
1158 #endif
1073 $ cd ..
1159 $ cd ..
1074
1160
1075
1161
1076 diff lines looking like headers
1162 diff lines looking like headers
1077
1163
1078 $ hg init difflineslikeheaders
1164 $ hg init difflineslikeheaders
1079 $ cd difflineslikeheaders
1165 $ cd difflineslikeheaders
1080 $ echo a >a
1166 $ echo a >a
1081 $ echo b >b
1167 $ echo b >b
1082 $ echo c >c
1168 $ echo c >c
1083 $ hg ci -Am1
1169 $ hg ci -Am1
1084 adding a
1170 adding a
1085 adding b
1171 adding b
1086 adding c
1172 adding c
1087
1173
1088 $ echo "key: value" >>a
1174 $ echo "key: value" >>a
1089 $ echo "key: value" >>b
1175 $ echo "key: value" >>b
1090 $ echo "foo" >>c
1176 $ echo "foo" >>c
1091 $ hg ci -m2
1177 $ hg ci -m2
1092
1178
1093 $ hg up -C 0
1179 $ hg up -C 0
1094 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1180 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1095 $ hg diff --git -c1 >want
1181 $ hg diff --git -c1 >want
1096 $ hg diff -c1 | hg import --no-commit -
1182 $ hg diff -c1 | hg import --no-commit -
1097 applying patch from stdin
1183 applying patch from stdin
1098 $ hg diff --git >have
1184 $ hg diff --git >have
1099 $ diff want have
1185 $ diff want have
1100 $ cd ..
1186 $ cd ..
1101
1187
1102 import a unified diff with no lines of context (diff -U0)
1188 import a unified diff with no lines of context (diff -U0)
1103
1189
1104 $ hg init diffzero
1190 $ hg init diffzero
1105 $ cd diffzero
1191 $ cd diffzero
1106 $ cat > f << EOF
1192 $ cat > f << EOF
1107 > c2
1193 > c2
1108 > c4
1194 > c4
1109 > c5
1195 > c5
1110 > EOF
1196 > EOF
1111 $ hg commit -Am0
1197 $ hg commit -Am0
1112 adding f
1198 adding f
1113
1199
1114 $ hg import --no-commit - << EOF
1200 $ hg import --no-commit - << EOF
1115 > # HG changeset patch
1201 > # HG changeset patch
1116 > # User test
1202 > # User test
1117 > # Date 0 0
1203 > # Date 0 0
1118 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
1204 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
1119 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
1205 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
1120 > 1
1206 > 1
1121 > diff -r 8679a12a975b -r f4974ab632f3 f
1207 > diff -r 8679a12a975b -r f4974ab632f3 f
1122 > --- a/f Thu Jan 01 00:00:00 1970 +0000
1208 > --- a/f Thu Jan 01 00:00:00 1970 +0000
1123 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
1209 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
1124 > @@ -0,0 +1,1 @@
1210 > @@ -0,0 +1,1 @@
1125 > +c1
1211 > +c1
1126 > @@ -1,0 +3,1 @@
1212 > @@ -1,0 +3,1 @@
1127 > +c3
1213 > +c3
1128 > @@ -3,1 +4,0 @@
1214 > @@ -3,1 +4,0 @@
1129 > -c5
1215 > -c5
1130 > EOF
1216 > EOF
1131 applying patch from stdin
1217 applying patch from stdin
1132
1218
1133 $ cat f
1219 $ cat f
1134 c1
1220 c1
1135 c2
1221 c2
1136 c3
1222 c3
1137 c4
1223 c4
1138
1224
1139 $ cd ..
1225 $ cd ..
1140
1226
1141 no segfault while importing a unified diff which start line is zero but chunk
1227 no segfault while importing a unified diff which start line is zero but chunk
1142 size is non-zero
1228 size is non-zero
1143
1229
1144 $ hg init startlinezero
1230 $ hg init startlinezero
1145 $ cd startlinezero
1231 $ cd startlinezero
1146 $ echo foo > foo
1232 $ echo foo > foo
1147 $ hg commit -Amfoo
1233 $ hg commit -Amfoo
1148 adding foo
1234 adding foo
1149
1235
1150 $ hg import --no-commit - << EOF
1236 $ hg import --no-commit - << EOF
1151 > diff a/foo b/foo
1237 > diff a/foo b/foo
1152 > --- a/foo
1238 > --- a/foo
1153 > +++ b/foo
1239 > +++ b/foo
1154 > @@ -0,1 +0,1 @@
1240 > @@ -0,1 +0,1 @@
1155 > foo
1241 > foo
1156 > EOF
1242 > EOF
1157 applying patch from stdin
1243 applying patch from stdin
1158
1244
1159 $ cd ..
1245 $ cd ..
1160
1246
1161 Test corner case involving fuzz and skew
1247 Test corner case involving fuzz and skew
1162
1248
1163 $ hg init morecornercases
1249 $ hg init morecornercases
1164 $ cd morecornercases
1250 $ cd morecornercases
1165
1251
1166 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1252 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1167 > diff --git a/a b/a
1253 > diff --git a/a b/a
1168 > --- a/a
1254 > --- a/a
1169 > +++ b/a
1255 > +++ b/a
1170 > @@ -1,0 +1,1 @@
1256 > @@ -1,0 +1,1 @@
1171 > +line
1257 > +line
1172 > EOF
1258 > EOF
1173
1259
1174 $ cat > 02-no-context-middle-of-file.diff <<EOF
1260 $ cat > 02-no-context-middle-of-file.diff <<EOF
1175 > diff --git a/a b/a
1261 > diff --git a/a b/a
1176 > --- a/a
1262 > --- a/a
1177 > +++ b/a
1263 > +++ b/a
1178 > @@ -1,1 +1,1 @@
1264 > @@ -1,1 +1,1 @@
1179 > -2
1265 > -2
1180 > +add some skew
1266 > +add some skew
1181 > @@ -2,0 +2,1 @@
1267 > @@ -2,0 +2,1 @@
1182 > +line
1268 > +line
1183 > EOF
1269 > EOF
1184
1270
1185 $ cat > 03-no-context-end-of-file.diff <<EOF
1271 $ cat > 03-no-context-end-of-file.diff <<EOF
1186 > diff --git a/a b/a
1272 > diff --git a/a b/a
1187 > --- a/a
1273 > --- a/a
1188 > +++ b/a
1274 > +++ b/a
1189 > @@ -10,0 +10,1 @@
1275 > @@ -10,0 +10,1 @@
1190 > +line
1276 > +line
1191 > EOF
1277 > EOF
1192
1278
1193 $ cat > 04-middle-of-file-completely-fuzzed.diff <<EOF
1279 $ cat > 04-middle-of-file-completely-fuzzed.diff <<EOF
1194 > diff --git a/a b/a
1280 > diff --git a/a b/a
1195 > --- a/a
1281 > --- a/a
1196 > +++ b/a
1282 > +++ b/a
1197 > @@ -1,1 +1,1 @@
1283 > @@ -1,1 +1,1 @@
1198 > -2
1284 > -2
1199 > +add some skew
1285 > +add some skew
1200 > @@ -2,2 +2,3 @@
1286 > @@ -2,2 +2,3 @@
1201 > not matching, should fuzz
1287 > not matching, should fuzz
1202 > ... a bit
1288 > ... a bit
1203 > +line
1289 > +line
1204 > EOF
1290 > EOF
1205
1291
1206 $ cat > a <<EOF
1292 $ cat > a <<EOF
1207 > 1
1293 > 1
1208 > 2
1294 > 2
1209 > 3
1295 > 3
1210 > 4
1296 > 4
1211 > EOF
1297 > EOF
1212 $ hg ci -Am adda a
1298 $ hg ci -Am adda a
1213 $ for p in *.diff; do
1299 $ for p in *.diff; do
1214 > hg import -v --no-commit $p
1300 > hg import -v --no-commit $p
1215 > cat a
1301 > cat a
1216 > hg revert -aqC a
1302 > hg revert -aqC a
1217 > # patch -p1 < $p
1303 > # patch -p1 < $p
1218 > # cat a
1304 > # cat a
1219 > # hg revert -aC a
1305 > # hg revert -aC a
1220 > done
1306 > done
1221 applying 01-no-context-beginning-of-file.diff
1307 applying 01-no-context-beginning-of-file.diff
1222 patching file a
1308 patching file a
1223 applied to working directory
1309 applied to working directory
1224 1
1310 1
1225 line
1311 line
1226 2
1312 2
1227 3
1313 3
1228 4
1314 4
1229 applying 02-no-context-middle-of-file.diff
1315 applying 02-no-context-middle-of-file.diff
1230 patching file a
1316 patching file a
1231 Hunk #1 succeeded at 2 (offset 1 lines).
1317 Hunk #1 succeeded at 2 (offset 1 lines).
1232 Hunk #2 succeeded at 4 (offset 1 lines).
1318 Hunk #2 succeeded at 4 (offset 1 lines).
1233 applied to working directory
1319 applied to working directory
1234 1
1320 1
1235 add some skew
1321 add some skew
1236 3
1322 3
1237 line
1323 line
1238 4
1324 4
1239 applying 03-no-context-end-of-file.diff
1325 applying 03-no-context-end-of-file.diff
1240 patching file a
1326 patching file a
1241 Hunk #1 succeeded at 5 (offset -6 lines).
1327 Hunk #1 succeeded at 5 (offset -6 lines).
1242 applied to working directory
1328 applied to working directory
1243 1
1329 1
1244 2
1330 2
1245 3
1331 3
1246 4
1332 4
1247 line
1333 line
1248 applying 04-middle-of-file-completely-fuzzed.diff
1334 applying 04-middle-of-file-completely-fuzzed.diff
1249 patching file a
1335 patching file a
1250 Hunk #1 succeeded at 2 (offset 1 lines).
1336 Hunk #1 succeeded at 2 (offset 1 lines).
1251 Hunk #2 succeeded at 5 with fuzz 2 (offset 1 lines).
1337 Hunk #2 succeeded at 5 with fuzz 2 (offset 1 lines).
1252 applied to working directory
1338 applied to working directory
1253 1
1339 1
1254 add some skew
1340 add some skew
1255 3
1341 3
1256 4
1342 4
1257 line
1343 line
1258 $ cd ..
1344 $ cd ..
1259
1345
1260 Test partial application
1346 Test partial application
1261 ------------------------
1347 ------------------------
1262
1348
1263 prepare a stack of patches depending on each other
1349 prepare a stack of patches depending on each other
1264
1350
1265 $ hg init partial
1351 $ hg init partial
1266 $ cd partial
1352 $ cd partial
1267 $ cat << EOF > a
1353 $ cat << EOF > a
1268 > one
1354 > one
1269 > two
1355 > two
1270 > three
1356 > three
1271 > four
1357 > four
1272 > five
1358 > five
1273 > six
1359 > six
1274 > seven
1360 > seven
1275 > EOF
1361 > EOF
1276 $ hg add a
1362 $ hg add a
1277 $ echo 'b' > b
1363 $ echo 'b' > b
1278 $ hg add b
1364 $ hg add b
1279 $ hg commit -m 'initial' -u Babar
1365 $ hg commit -m 'initial' -u Babar
1280 $ cat << EOF > a
1366 $ cat << EOF > a
1281 > one
1367 > one
1282 > two
1368 > two
1283 > 3
1369 > 3
1284 > four
1370 > four
1285 > five
1371 > five
1286 > six
1372 > six
1287 > seven
1373 > seven
1288 > EOF
1374 > EOF
1289 $ hg commit -m 'three' -u Celeste
1375 $ hg commit -m 'three' -u Celeste
1290 $ cat << EOF > a
1376 $ cat << EOF > a
1291 > one
1377 > one
1292 > two
1378 > two
1293 > 3
1379 > 3
1294 > 4
1380 > 4
1295 > five
1381 > five
1296 > six
1382 > six
1297 > seven
1383 > seven
1298 > EOF
1384 > EOF
1299 $ hg commit -m 'four' -u Rataxes
1385 $ hg commit -m 'four' -u Rataxes
1300 $ cat << EOF > a
1386 $ cat << EOF > a
1301 > one
1387 > one
1302 > two
1388 > two
1303 > 3
1389 > 3
1304 > 4
1390 > 4
1305 > 5
1391 > 5
1306 > six
1392 > six
1307 > seven
1393 > seven
1308 > EOF
1394 > EOF
1309 $ echo bb >> b
1395 $ echo bb >> b
1310 $ hg commit -m 'five' -u Arthur
1396 $ hg commit -m 'five' -u Arthur
1311 $ echo 'Babar' > jungle
1397 $ echo 'Babar' > jungle
1312 $ hg add jungle
1398 $ hg add jungle
1313 $ hg ci -m 'jungle' -u Zephir
1399 $ hg ci -m 'jungle' -u Zephir
1314 $ echo 'Celeste' >> jungle
1400 $ echo 'Celeste' >> jungle
1315 $ hg ci -m 'extended jungle' -u Cornelius
1401 $ hg ci -m 'extended jungle' -u Cornelius
1316 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1402 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1317 @ extended jungle [Cornelius] 1: +1/-0
1403 @ extended jungle [Cornelius] 1: +1/-0
1318 |
1404 |
1319 o jungle [Zephir] 1: +1/-0
1405 o jungle [Zephir] 1: +1/-0
1320 |
1406 |
1321 o five [Arthur] 2: +2/-1
1407 o five [Arthur] 2: +2/-1
1322 |
1408 |
1323 o four [Rataxes] 1: +1/-1
1409 o four [Rataxes] 1: +1/-1
1324 |
1410 |
1325 o three [Celeste] 1: +1/-1
1411 o three [Celeste] 1: +1/-1
1326 |
1412 |
1327 o initial [Babar] 2: +8/-0
1413 o initial [Babar] 2: +8/-0
1328
1414
1329
1415
1330 Importing with some success and some errors:
1416 Importing with some success and some errors:
1331
1417
1332 $ hg update --rev 'desc(initial)'
1418 $ hg update --rev 'desc(initial)'
1333 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1419 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1334 $ hg export --rev 'desc(five)' | hg import --partial -
1420 $ hg export --rev 'desc(five)' | hg import --partial -
1335 applying patch from stdin
1421 applying patch from stdin
1336 patching file a
1422 patching file a
1337 Hunk #1 FAILED at 1
1423 Hunk #1 FAILED at 1
1338 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1424 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1339 patch applied partially
1425 patch applied partially
1340 (fix the .rej files and run `hg commit --amend`)
1426 (fix the .rej files and run `hg commit --amend`)
1341 [1]
1427 [1]
1342
1428
1343 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1429 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1344 @ five [Arthur] 1: +1/-0
1430 @ five [Arthur] 1: +1/-0
1345 |
1431 |
1346 | o extended jungle [Cornelius] 1: +1/-0
1432 | o extended jungle [Cornelius] 1: +1/-0
1347 | |
1433 | |
1348 | o jungle [Zephir] 1: +1/-0
1434 | o jungle [Zephir] 1: +1/-0
1349 | |
1435 | |
1350 | o five [Arthur] 2: +2/-1
1436 | o five [Arthur] 2: +2/-1
1351 | |
1437 | |
1352 | o four [Rataxes] 1: +1/-1
1438 | o four [Rataxes] 1: +1/-1
1353 | |
1439 | |
1354 | o three [Celeste] 1: +1/-1
1440 | o three [Celeste] 1: +1/-1
1355 |/
1441 |/
1356 o initial [Babar] 2: +8/-0
1442 o initial [Babar] 2: +8/-0
1357
1443
1358 $ hg export
1444 $ hg export
1359 # HG changeset patch
1445 # HG changeset patch
1360 # User Arthur
1446 # User Arthur
1361 # Date 0 0
1447 # Date 0 0
1362 # Thu Jan 01 00:00:00 1970 +0000
1448 # Thu Jan 01 00:00:00 1970 +0000
1363 # Node ID 26e6446bb2526e2be1037935f5fca2b2706f1509
1449 # Node ID 26e6446bb2526e2be1037935f5fca2b2706f1509
1364 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1450 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1365 five
1451 five
1366
1452
1367 diff -r 8e4f0351909e -r 26e6446bb252 b
1453 diff -r 8e4f0351909e -r 26e6446bb252 b
1368 --- a/b Thu Jan 01 00:00:00 1970 +0000
1454 --- a/b Thu Jan 01 00:00:00 1970 +0000
1369 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1455 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1370 @@ -1,1 +1,2 @@
1456 @@ -1,1 +1,2 @@
1371 b
1457 b
1372 +bb
1458 +bb
1373 $ hg status -c .
1459 $ hg status -c .
1374 C a
1460 C a
1375 C b
1461 C b
1376 $ ls
1462 $ ls
1377 a
1463 a
1378 a.rej
1464 a.rej
1379 b
1465 b
1380
1466
1381 Importing with zero success:
1467 Importing with zero success:
1382
1468
1383 $ hg update --rev 'desc(initial)'
1469 $ hg update --rev 'desc(initial)'
1384 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1470 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1385 $ hg export --rev 'desc(four)' | hg import --partial -
1471 $ hg export --rev 'desc(four)' | hg import --partial -
1386 applying patch from stdin
1472 applying patch from stdin
1387 patching file a
1473 patching file a
1388 Hunk #1 FAILED at 0
1474 Hunk #1 FAILED at 0
1389 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1475 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1390 patch applied partially
1476 patch applied partially
1391 (fix the .rej files and run `hg commit --amend`)
1477 (fix the .rej files and run `hg commit --amend`)
1392 [1]
1478 [1]
1393
1479
1394 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1480 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1395 @ four [Rataxes] 0: +0/-0
1481 @ four [Rataxes] 0: +0/-0
1396 |
1482 |
1397 | o five [Arthur] 1: +1/-0
1483 | o five [Arthur] 1: +1/-0
1398 |/
1484 |/
1399 | o extended jungle [Cornelius] 1: +1/-0
1485 | o extended jungle [Cornelius] 1: +1/-0
1400 | |
1486 | |
1401 | o jungle [Zephir] 1: +1/-0
1487 | o jungle [Zephir] 1: +1/-0
1402 | |
1488 | |
1403 | o five [Arthur] 2: +2/-1
1489 | o five [Arthur] 2: +2/-1
1404 | |
1490 | |
1405 | o four [Rataxes] 1: +1/-1
1491 | o four [Rataxes] 1: +1/-1
1406 | |
1492 | |
1407 | o three [Celeste] 1: +1/-1
1493 | o three [Celeste] 1: +1/-1
1408 |/
1494 |/
1409 o initial [Babar] 2: +8/-0
1495 o initial [Babar] 2: +8/-0
1410
1496
1411 $ hg export
1497 $ hg export
1412 # HG changeset patch
1498 # HG changeset patch
1413 # User Rataxes
1499 # User Rataxes
1414 # Date 0 0
1500 # Date 0 0
1415 # Thu Jan 01 00:00:00 1970 +0000
1501 # Thu Jan 01 00:00:00 1970 +0000
1416 # Node ID cb9b1847a74d9ad52e93becaf14b98dbcc274e1e
1502 # Node ID cb9b1847a74d9ad52e93becaf14b98dbcc274e1e
1417 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1503 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1418 four
1504 four
1419
1505
1420 $ hg status -c .
1506 $ hg status -c .
1421 C a
1507 C a
1422 C b
1508 C b
1423 $ ls
1509 $ ls
1424 a
1510 a
1425 a.rej
1511 a.rej
1426 b
1512 b
1427
1513
1428 Importing with unknown file:
1514 Importing with unknown file:
1429
1515
1430 $ hg update --rev 'desc(initial)'
1516 $ hg update --rev 'desc(initial)'
1431 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1517 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1432 $ hg export --rev 'desc("extended jungle")' | hg import --partial -
1518 $ hg export --rev 'desc("extended jungle")' | hg import --partial -
1433 applying patch from stdin
1519 applying patch from stdin
1434 unable to find 'jungle' for patching
1520 unable to find 'jungle' for patching
1435 1 out of 1 hunks FAILED -- saving rejects to file jungle.rej
1521 1 out of 1 hunks FAILED -- saving rejects to file jungle.rej
1436 patch applied partially
1522 patch applied partially
1437 (fix the .rej files and run `hg commit --amend`)
1523 (fix the .rej files and run `hg commit --amend`)
1438 [1]
1524 [1]
1439
1525
1440 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1526 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1441 @ extended jungle [Cornelius] 0: +0/-0
1527 @ extended jungle [Cornelius] 0: +0/-0
1442 |
1528 |
1443 | o four [Rataxes] 0: +0/-0
1529 | o four [Rataxes] 0: +0/-0
1444 |/
1530 |/
1445 | o five [Arthur] 1: +1/-0
1531 | o five [Arthur] 1: +1/-0
1446 |/
1532 |/
1447 | o extended jungle [Cornelius] 1: +1/-0
1533 | o extended jungle [Cornelius] 1: +1/-0
1448 | |
1534 | |
1449 | o jungle [Zephir] 1: +1/-0
1535 | o jungle [Zephir] 1: +1/-0
1450 | |
1536 | |
1451 | o five [Arthur] 2: +2/-1
1537 | o five [Arthur] 2: +2/-1
1452 | |
1538 | |
1453 | o four [Rataxes] 1: +1/-1
1539 | o four [Rataxes] 1: +1/-1
1454 | |
1540 | |
1455 | o three [Celeste] 1: +1/-1
1541 | o three [Celeste] 1: +1/-1
1456 |/
1542 |/
1457 o initial [Babar] 2: +8/-0
1543 o initial [Babar] 2: +8/-0
1458
1544
1459 $ hg export
1545 $ hg export
1460 # HG changeset patch
1546 # HG changeset patch
1461 # User Cornelius
1547 # User Cornelius
1462 # Date 0 0
1548 # Date 0 0
1463 # Thu Jan 01 00:00:00 1970 +0000
1549 # Thu Jan 01 00:00:00 1970 +0000
1464 # Node ID 1fb1f86bef43c5a75918178f8d23c29fb0a7398d
1550 # Node ID 1fb1f86bef43c5a75918178f8d23c29fb0a7398d
1465 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1551 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1466 extended jungle
1552 extended jungle
1467
1553
1468 $ hg status -c .
1554 $ hg status -c .
1469 C a
1555 C a
1470 C b
1556 C b
1471 $ ls
1557 $ ls
1472 a
1558 a
1473 a.rej
1559 a.rej
1474 b
1560 b
1475 jungle.rej
1561 jungle.rej
1476
1562
1477 Importing multiple failing patches:
1563 Importing multiple failing patches:
1478
1564
1479 $ hg update --rev 'desc(initial)'
1565 $ hg update --rev 'desc(initial)'
1480 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1566 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1481 $ echo 'B' > b # just to make another commit
1567 $ echo 'B' > b # just to make another commit
1482 $ hg commit -m "a new base"
1568 $ hg commit -m "a new base"
1483 created new head
1569 created new head
1484 $ hg export --rev 'desc("four") + desc("extended jungle")' | hg import --partial -
1570 $ hg export --rev 'desc("four") + desc("extended jungle")' | hg import --partial -
1485 applying patch from stdin
1571 applying patch from stdin
1486 patching file a
1572 patching file a
1487 Hunk #1 FAILED at 0
1573 Hunk #1 FAILED at 0
1488 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1574 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1489 patch applied partially
1575 patch applied partially
1490 (fix the .rej files and run `hg commit --amend`)
1576 (fix the .rej files and run `hg commit --amend`)
1491 [1]
1577 [1]
1492 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1578 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1493 @ four [Rataxes] 0: +0/-0
1579 @ four [Rataxes] 0: +0/-0
1494 |
1580 |
1495 o a new base [test] 1: +1/-1
1581 o a new base [test] 1: +1/-1
1496 |
1582 |
1497 | o extended jungle [Cornelius] 0: +0/-0
1583 | o extended jungle [Cornelius] 0: +0/-0
1498 |/
1584 |/
1499 | o four [Rataxes] 0: +0/-0
1585 | o four [Rataxes] 0: +0/-0
1500 |/
1586 |/
1501 | o five [Arthur] 1: +1/-0
1587 | o five [Arthur] 1: +1/-0
1502 |/
1588 |/
1503 | o extended jungle [Cornelius] 1: +1/-0
1589 | o extended jungle [Cornelius] 1: +1/-0
1504 | |
1590 | |
1505 | o jungle [Zephir] 1: +1/-0
1591 | o jungle [Zephir] 1: +1/-0
1506 | |
1592 | |
1507 | o five [Arthur] 2: +2/-1
1593 | o five [Arthur] 2: +2/-1
1508 | |
1594 | |
1509 | o four [Rataxes] 1: +1/-1
1595 | o four [Rataxes] 1: +1/-1
1510 | |
1596 | |
1511 | o three [Celeste] 1: +1/-1
1597 | o three [Celeste] 1: +1/-1
1512 |/
1598 |/
1513 o initial [Babar] 2: +8/-0
1599 o initial [Babar] 2: +8/-0
1514
1600
1515 $ hg export
1601 $ hg export
1516 # HG changeset patch
1602 # HG changeset patch
1517 # User Rataxes
1603 # User Rataxes
1518 # Date 0 0
1604 # Date 0 0
1519 # Thu Jan 01 00:00:00 1970 +0000
1605 # Thu Jan 01 00:00:00 1970 +0000
1520 # Node ID a9d7b6d0ffbb4eb12b7d5939250fcd42e8930a1d
1606 # Node ID a9d7b6d0ffbb4eb12b7d5939250fcd42e8930a1d
1521 # Parent f59f8d2e95a8ca5b1b4ca64320140da85f3b44fd
1607 # Parent f59f8d2e95a8ca5b1b4ca64320140da85f3b44fd
1522 four
1608 four
1523
1609
1524 $ hg status -c .
1610 $ hg status -c .
1525 C a
1611 C a
1526 C b
1612 C b
1527
1613
1528 Importing some extra header
1614 Importing some extra header
1529 ===========================
1615 ===========================
1530
1616
1531 $ cat > $TESTTMP/parseextra.py <<EOF
1617 $ cat > $TESTTMP/parseextra.py <<EOF
1532 > import mercurial.patch
1618 > import mercurial.patch
1533 > import mercurial.cmdutil
1619 > import mercurial.cmdutil
1534 >
1620 >
1535 > def processfoo(repo, data, extra, opts):
1621 > def processfoo(repo, data, extra, opts):
1536 > if 'foo' in data:
1622 > if 'foo' in data:
1537 > extra['foo'] = data['foo']
1623 > extra['foo'] = data['foo']
1538 > def postimport(ctx):
1624 > def postimport(ctx):
1539 > if 'foo' in ctx.extra():
1625 > if 'foo' in ctx.extra():
1540 > ctx.repo().ui.write('imported-foo: %s\n' % ctx.extra()['foo'])
1626 > ctx.repo().ui.write('imported-foo: %s\n' % ctx.extra()['foo'])
1541 >
1627 >
1542 > mercurial.patch.patchheadermap.append(('Foo', 'foo'))
1628 > mercurial.patch.patchheadermap.append(('Foo', 'foo'))
1543 > mercurial.cmdutil.extrapreimport.append('foo')
1629 > mercurial.cmdutil.extrapreimport.append('foo')
1544 > mercurial.cmdutil.extrapreimportmap['foo'] = processfoo
1630 > mercurial.cmdutil.extrapreimportmap['foo'] = processfoo
1545 > mercurial.cmdutil.extrapostimport.append('foo')
1631 > mercurial.cmdutil.extrapostimport.append('foo')
1546 > mercurial.cmdutil.extrapostimportmap['foo'] = postimport
1632 > mercurial.cmdutil.extrapostimportmap['foo'] = postimport
1547 > EOF
1633 > EOF
1548 $ printf "[extensions]\nparseextra=$TESTTMP/parseextra.py" >> $HGRCPATH
1634 $ printf "[extensions]\nparseextra=$TESTTMP/parseextra.py" >> $HGRCPATH
1549 $ hg up -C tip
1635 $ hg up -C tip
1550 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1636 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1551 $ cat > $TESTTMP/foo.patch <<EOF
1637 $ cat > $TESTTMP/foo.patch <<EOF
1552 > # HG changeset patch
1638 > # HG changeset patch
1553 > # User Rataxes
1639 > # User Rataxes
1554 > # Date 0 0
1640 > # Date 0 0
1555 > # Thu Jan 01 00:00:00 1970 +0000
1641 > # Thu Jan 01 00:00:00 1970 +0000
1556 > # Foo bar
1642 > # Foo bar
1557 > height
1643 > height
1558 >
1644 >
1559 > --- a/a Thu Jan 01 00:00:00 1970 +0000
1645 > --- a/a Thu Jan 01 00:00:00 1970 +0000
1560 > +++ b/a Wed Oct 07 09:17:44 2015 +0000
1646 > +++ b/a Wed Oct 07 09:17:44 2015 +0000
1561 > @@ -5,3 +5,4 @@
1647 > @@ -5,3 +5,4 @@
1562 > five
1648 > five
1563 > six
1649 > six
1564 > seven
1650 > seven
1565 > +heigt
1651 > +heigt
1566 > EOF
1652 > EOF
1567 $ hg import $TESTTMP/foo.patch
1653 $ hg import $TESTTMP/foo.patch
1568 applying $TESTTMP/foo.patch
1654 applying $TESTTMP/foo.patch
1569 imported-foo: bar
1655 imported-foo: bar
1570 $ hg log --debug -r . | grep extra
1656 $ hg log --debug -r . | grep extra
1571 extra: branch=default
1657 extra: branch=default
1572 extra: foo=bar
1658 extra: foo=bar
@@ -1,193 +1,244 b''
1 Environment setup for MQ
1 Environment setup for MQ
2
2
3 $ echo "[extensions]" >> $HGRCPATH
3 $ echo "[extensions]" >> $HGRCPATH
4 $ echo "mq=" >> $HGRCPATH
4 $ echo "mq=" >> $HGRCPATH
5 $ cat >> $HGRCPATH <<EOF
6 > [defaults]
7 > # explicit date to commit with fixed hashid
8 > qnew = -d "0 0"
9 > qrefresh = -d "0 0"
10 > qfold = -d "0 0"
11 > EOF
5 $ hg init
12 $ hg init
6 $ hg qinit
13 $ hg qinit
7
14
8 Should fail if no patches applied
15 Should fail if no patches applied
9 (this tests also that editor is not invoked if '--edit' is not
16 (this tests also that editor is not invoked if '--edit' is not
10 specified)
17 specified)
11
18
12 $ hg qrefresh
19 $ hg qrefresh
13 no patches applied
20 no patches applied
14 [1]
21 [1]
15 $ hg qrefresh -e
22 $ hg qrefresh -e
16 no patches applied
23 no patches applied
17 [1]
24 [1]
18 $ hg qnew -m "First commit message" first-patch
25 $ hg qnew -m "First commit message" first-patch
19 $ echo aaaa > file
26 $ echo aaaa > file
20 $ hg add file
27 $ hg add file
21 $ HGEDITOR=cat hg qrefresh
28 $ HGEDITOR=cat hg qrefresh
22
29
23 Should display 'First commit message'
30 Should display 'First commit message'
24
31
25 $ hg log -l1 --template "{desc}\n"
32 $ hg log -l1 --template "{desc}\n"
26 First commit message
33 First commit message
27
34
28 Testing changing message with -m
35 Testing changing message with -m
29 (this tests also that '--edit' can be used with '--message', and
36 (this tests also that '--edit' can be used with '--message', and
30 that '[committemplate] changeset' definition and commit log specific
37 that '[committemplate] changeset' definition and commit log specific
31 template keyword 'extramsg' work well)
38 template keyword 'extramsg' work well)
32
39
33 $ cat >> .hg/hgrc <<EOF
40 $ cat >> .hg/hgrc <<EOF
34 > [committemplate]
41 > [committemplate]
35 > listupfiles = {file_adds %
42 > listupfiles = {file_adds %
36 > "HG: added {file}\n" }{file_mods %
43 > "HG: added {file}\n" }{file_mods %
37 > "HG: changed {file}\n" }{file_dels %
44 > "HG: changed {file}\n" }{file_dels %
38 > "HG: removed {file}\n" }{if(files, "",
45 > "HG: removed {file}\n" }{if(files, "",
39 > "HG: no files changed\n")}
46 > "HG: no files changed\n")}
40 >
47 >
41 > changeset = HG: this is customized commit template
48 > changeset = HG: this is customized commit template
42 > {desc}\n\n
49 > {desc}\n\n
43 > HG: Enter commit message. Lines beginning with 'HG:' are removed.
50 > HG: Enter commit message. Lines beginning with 'HG:' are removed.
44 > HG: {extramsg}
51 > HG: {extramsg}
45 > HG: --
52 > HG: --
46 > HG: user: {author}
53 > HG: user: {author}
47 > HG: branch '{branch}'\n{listupfiles}
54 > HG: branch '{branch}'\n{listupfiles}
48 > EOF
55 > EOF
49
56
50 $ echo bbbb > file
57 $ echo bbbb > file
51 $ HGEDITOR=cat hg qrefresh -m "Second commit message" -e
58 $ HGEDITOR=cat hg qrefresh -m "Second commit message" -e
52 HG: this is customized commit template
59 HG: this is customized commit template
53 Second commit message
60 Second commit message
54
61
55
62
56 HG: Enter commit message. Lines beginning with 'HG:' are removed.
63 HG: Enter commit message. Lines beginning with 'HG:' are removed.
57 HG: Leave message empty to use default message.
64 HG: Leave message empty to use default message.
58 HG: --
65 HG: --
59 HG: user: test
66 HG: user: test
60 HG: branch 'default'
67 HG: branch 'default'
61 HG: added file
68 HG: added file
62
69
63 $ cat >> .hg/hgrc <<EOF
70 $ cat >> .hg/hgrc <<EOF
64 > # disable customizing for subsequent tests
71 > # disable customizing for subsequent tests
65 > [committemplate]
72 > [committemplate]
66 > changeset =
73 > changeset =
67 > EOF
74 > EOF
68
75
69 Should display 'Second commit message'
76 Should display 'Second commit message'
70
77
71 $ hg log -l1 --template "{desc}\n"
78 $ hg log -l1 --template "{desc}\n"
72 Second commit message
79 Second commit message
73
80
74 Testing changing message with -l
81 Testing changing message with -l
75
82
76 $ echo "Third commit message" > logfile
83 $ echo "Third commit message" > logfile
77 $ echo " This is the 3rd log message" >> logfile
84 $ echo " This is the 3rd log message" >> logfile
78 $ echo bbbb > file
85 $ echo bbbb > file
79 $ hg qrefresh -l logfile
86 $ hg qrefresh -l logfile
80
87
81 Should display 'Third commit message\\\n This is the 3rd log message'
88 Should display 'Third commit message\\\n This is the 3rd log message'
82
89
83 $ hg log -l1 --template "{desc}\n"
90 $ hg log -l1 --template "{desc}\n"
84 Third commit message
91 Third commit message
85 This is the 3rd log message
92 This is the 3rd log message
86
93
87 Testing changing message with -l-
94 Testing changing message with -l-
88
95
89 $ hg qnew -m "First commit message" second-patch
96 $ hg qnew -m "First commit message" second-patch
90 $ echo aaaa > file2
97 $ echo aaaa > file2
91 $ hg add file2
98 $ hg add file2
92 $ echo bbbb > file2
99 $ echo bbbb > file2
93 $ (echo "Fifth commit message"; echo " This is the 5th log message") | hg qrefresh -l-
100 $ (echo "Fifth commit message"; echo " This is the 5th log message") | hg qrefresh -l-
94
101
95 Should display 'Fifth commit message\\\n This is the 5th log message'
102 Should display 'Fifth commit message\\\n This is the 5th log message'
96
103
97 $ hg log -l1 --template "{desc}\n"
104 $ hg log -l1 --template "{desc}\n"
98 Fifth commit message
105 Fifth commit message
99 This is the 5th log message
106 This is the 5th log message
100
107
101 Test saving last-message.txt:
108 Test saving last-message.txt:
102
109
103 $ cat > $TESTTMP/editor.sh << EOF
110 $ cat > $TESTTMP/editor.sh << EOF
104 > echo "==== before editing"
111 > echo "==== before editing"
105 > cat \$1
112 > cat \$1
106 > echo "===="
113 > echo "===="
107 > (echo; echo "test saving last-message.txt") >> \$1
114 > (echo; echo "test saving last-message.txt") >> \$1
108 > EOF
115 > EOF
109
116
110 $ cat > $TESTTMP/commitfailure.py <<EOF
117 $ cat > $TESTTMP/commitfailure.py <<EOF
111 > from mercurial import error
118 > from mercurial import error
112 > def reposetup(ui, repo):
119 > def reposetup(ui, repo):
113 > class commitfailure(repo.__class__):
120 > class commitfailure(repo.__class__):
114 > def commit(self, *args, **kwargs):
121 > def commit(self, *args, **kwargs):
115 > raise error.Abort('emulating unexpected abort')
122 > raise error.Abort('emulating unexpected abort')
116 > repo.__class__ = commitfailure
123 > repo.__class__ = commitfailure
117 > EOF
124 > EOF
118
125
119 $ cat >> .hg/hgrc <<EOF
126 $ cat >> .hg/hgrc <<EOF
120 > [extensions]
127 > [extensions]
121 > # this failure occurs before editor invocation
128 > # this failure occurs before editor invocation
122 > commitfailure = $TESTTMP/commitfailure.py
129 > commitfailure = $TESTTMP/commitfailure.py
123 > EOF
130 > EOF
124
131
125 $ hg qapplied
132 $ hg qapplied
126 first-patch
133 first-patch
127 second-patch
134 second-patch
128 $ hg tip --template "{files}\n"
135 $ hg tip --template "{files}\n"
129 file2
136 file2
130
137
131 (test that editor is not invoked before transaction starting)
138 (test that editor is not invoked before transaction starting)
132
139
133 $ rm -f .hg/last-message.txt
140 $ rm -f .hg/last-message.txt
134 $ HGEDITOR="sh $TESTTMP/editor.sh" hg qrefresh -e
141 $ HGEDITOR="sh $TESTTMP/editor.sh" hg qrefresh -e
135 refresh interrupted while patch was popped! (revert --all, qpush to recover)
142 refresh interrupted while patch was popped! (revert --all, qpush to recover)
136 abort: emulating unexpected abort
143 abort: emulating unexpected abort
137 [255]
144 [255]
138 $ test -f .hg/last-message.txt
145 $ test -f .hg/last-message.txt
139 [1]
146 [1]
140
147
141 (reset applied patches and directory status)
148 (reset applied patches and directory status)
142
149
143 $ cat >> .hg/hgrc <<EOF
150 $ cat >> .hg/hgrc <<EOF
144 > [extensions]
151 > [extensions]
145 > commitfailure = !
152 > commitfailure = !
146 > EOF
153 > EOF
147
154
148 $ hg qapplied
155 $ hg qapplied
149 first-patch
156 first-patch
150 $ hg status -A file2
157 $ hg status -A file2
151 ? file2
158 ? file2
152 $ rm file2
159 $ rm file2
153 $ hg qpush -q second-patch
160 $ hg qpush -q second-patch
154 now at: second-patch
161 now at: second-patch
155
162
156 (test that editor is invoked and commit message is saved into
163 (test that editor is invoked and commit message is saved into
157 "last-message.txt")
164 "last-message.txt")
158
165
159 $ cat >> .hg/hgrc <<EOF
166 $ cat >> .hg/hgrc <<EOF
160 > [hooks]
167 > [hooks]
161 > # this failure occurs after editor invocation
168 > # this failure occurs after editor invocation
162 > pretxncommit.unexpectedabort = false
169 > pretxncommit.unexpectedabort = false
163 > EOF
170 > EOF
164
171
165 $ rm -f .hg/last-message.txt
172 $ rm -f .hg/last-message.txt
166 $ hg status --rev "second-patch^1" -arm
173 $ hg status --rev "second-patch^1" -arm
167 A file2
174 A file2
168 $ HGEDITOR="sh $TESTTMP/editor.sh" hg qrefresh -e
175 $ HGEDITOR="sh $TESTTMP/editor.sh" hg qrefresh -e
169 ==== before editing
176 ==== before editing
170 Fifth commit message
177 Fifth commit message
171 This is the 5th log message
178 This is the 5th log message
172
179
173
180
174 HG: Enter commit message. Lines beginning with 'HG:' are removed.
181 HG: Enter commit message. Lines beginning with 'HG:' are removed.
175 HG: Leave message empty to use default message.
182 HG: Leave message empty to use default message.
176 HG: --
183 HG: --
177 HG: user: test
184 HG: user: test
178 HG: branch 'default'
185 HG: branch 'default'
179 HG: added file2
186 HG: added file2
180 ====
187 ====
181 transaction abort!
188 transaction abort!
182 rollback completed
189 rollback completed
183 note: commit message saved in .hg/last-message.txt
190 note: commit message saved in .hg/last-message.txt
184 refresh interrupted while patch was popped! (revert --all, qpush to recover)
191 refresh interrupted while patch was popped! (revert --all, qpush to recover)
185 abort: pretxncommit.unexpectedabort hook exited with status 1
192 abort: pretxncommit.unexpectedabort hook exited with status 1
186 [255]
193 [255]
187 $ cat .hg/last-message.txt
194 $ cat .hg/last-message.txt
188 Fifth commit message
195 Fifth commit message
189 This is the 5th log message
196 This is the 5th log message
190
197
191
198
192
199
193 test saving last-message.txt
200 test saving last-message.txt
201
202 Test visibility of in-memory distate changes outside transaction to
203 external process
204
205 $ cat > $TESTTMP/checkvisibility.sh <<EOF
206 > echo "===="
207 > hg parents --template "{rev}:{node|short}\n"
208 > hg status -arm
209 > echo "===="
210 > EOF
211
212 == test visibility to external editor
213
214 $ hg update -C -q first-patch
215 $ rm -f file2
216 $ hg qpush -q second-patch --config hooks.pretxncommit.unexpectedabort=
217 now at: second-patch
218 $ echo bbbb >> file2
219
220 $ sh "$TESTTMP/checkvisibility.sh"
221 ====
222 1:e30108269082
223 M file2
224 ====
225
226 $ HGEDITOR='sh "$TESTTMP/checkvisibility.sh"' hg qrefresh -e
227 ====
228 0:25e397dabed2
229 A file2
230 ====
231 transaction abort!
232 rollback completed
233 note: commit message saved in .hg/last-message.txt
234 refresh interrupted while patch was popped! (revert --all, qpush to recover)
235 abort: pretxncommit.unexpectedabort hook exited with status 1
236 [255]
237
238 (rebuilding at failure of qrefresh bases on rev #0, and it causes
239 dropping status of "file2")
240
241 $ sh "$TESTTMP/checkvisibility.sh"
242 ====
243 0:25e397dabed2
244 ====
General Comments 0
You need to be logged in to leave comments. Login now