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