comparison mercurial/cmdutil.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents 7e9997041781
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
59 stringio = util.stringio 59 stringio = util.stringio
60 60
61 # templates of common command options 61 # templates of common command options
62 62
63 dryrunopts = [ 63 dryrunopts = [
64 ('n', 'dry-run', None, 64 ('n', 'dry-run', None, _('do not perform actions, just print output')),
65 _('do not perform actions, just print output')),
66 ] 65 ]
67 66
68 confirmopts = [ 67 confirmopts = [
69 ('', 'confirm', None, 68 ('', 'confirm', None, _('ask before applying actions')),
70 _('ask before applying actions')),
71 ] 69 ]
72 70
73 remoteopts = [ 71 remoteopts = [
74 ('e', 'ssh', '', 72 ('e', 'ssh', '', _('specify ssh command to use'), _('CMD')),
75 _('specify ssh command to use'), _('CMD')), 73 (
76 ('', 'remotecmd', '', 74 '',
77 _('specify hg command to run on the remote side'), _('CMD')), 75 'remotecmd',
78 ('', 'insecure', None, 76 '',
79 _('do not verify server certificate (ignoring web.cacerts config)')), 77 _('specify hg command to run on the remote side'),
78 _('CMD'),
79 ),
80 (
81 '',
82 'insecure',
83 None,
84 _('do not verify server certificate (ignoring web.cacerts config)'),
85 ),
80 ] 86 ]
81 87
82 walkopts = [ 88 walkopts = [
83 ('I', 'include', [], 89 (
84 _('include names matching the given patterns'), _('PATTERN')), 90 'I',
85 ('X', 'exclude', [], 91 'include',
86 _('exclude names matching the given patterns'), _('PATTERN')), 92 [],
93 _('include names matching the given patterns'),
94 _('PATTERN'),
95 ),
96 (
97 'X',
98 'exclude',
99 [],
100 _('exclude names matching the given patterns'),
101 _('PATTERN'),
102 ),
87 ] 103 ]
88 104
89 commitopts = [ 105 commitopts = [
90 ('m', 'message', '', 106 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
91 _('use text as commit message'), _('TEXT')), 107 ('l', 'logfile', '', _('read commit message from file'), _('FILE')),
92 ('l', 'logfile', '',
93 _('read commit message from file'), _('FILE')),
94 ] 108 ]
95 109
96 commitopts2 = [ 110 commitopts2 = [
97 ('d', 'date', '', 111 ('d', 'date', '', _('record the specified date as commit date'), _('DATE')),
98 _('record the specified date as commit date'), _('DATE')), 112 ('u', 'user', '', _('record the specified user as committer'), _('USER')),
99 ('u', 'user', '',
100 _('record the specified user as committer'), _('USER')),
101 ] 113 ]
102 114
103 commitopts3 = [ 115 commitopts3 = [
104 (b'D', b'currentdate', None, 116 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
105 _(b'record the current date as commit date')), 117 (b'U', b'currentuser', None, _(b'record the current user as committer')),
106 (b'U', b'currentuser', None,
107 _(b'record the current user as committer')),
108 ] 118 ]
109 119
110 formatteropts = [ 120 formatteropts = [
111 ('T', 'template', '', 121 ('T', 'template', '', _('display with template'), _('TEMPLATE')),
112 _('display with template'), _('TEMPLATE')),
113 ] 122 ]
114 123
115 templateopts = [ 124 templateopts = [
116 ('', 'style', '', 125 (
117 _('display using template map file (DEPRECATED)'), _('STYLE')), 126 '',
118 ('T', 'template', '', 127 'style',
119 _('display with template'), _('TEMPLATE')), 128 '',
129 _('display using template map file (DEPRECATED)'),
130 _('STYLE'),
131 ),
132 ('T', 'template', '', _('display with template'), _('TEMPLATE')),
120 ] 133 ]
121 134
122 logopts = [ 135 logopts = [
123 ('p', 'patch', None, _('show patch')), 136 ('p', 'patch', None, _('show patch')),
124 ('g', 'git', None, _('use git extended diff format')), 137 ('g', 'git', None, _('use git extended diff format')),
125 ('l', 'limit', '', 138 ('l', 'limit', '', _('limit number of changes displayed'), _('NUM')),
126 _('limit number of changes displayed'), _('NUM')),
127 ('M', 'no-merges', None, _('do not show merges')), 139 ('M', 'no-merges', None, _('do not show merges')),
128 ('', 'stat', None, _('output diffstat-style summary of changes')), 140 ('', 'stat', None, _('output diffstat-style summary of changes')),
129 ('G', 'graph', None, _("show the revision DAG")), 141 ('G', 'graph', None, _("show the revision DAG")),
130 ] + templateopts 142 ] + templateopts
131 143
132 diffopts = [ 144 diffopts = [
133 ('a', 'text', None, _('treat all files as text')), 145 ('a', 'text', None, _('treat all files as text')),
134 ('g', 'git', None, _('use git extended diff format')), 146 ('g', 'git', None, _('use git extended diff format')),
135 ('', 'binary', None, _('generate binary diffs in git mode (default)')), 147 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
136 ('', 'nodates', None, _('omit dates from diff headers')) 148 ('', 'nodates', None, _('omit dates from diff headers')),
137 ] 149 ]
138 150
139 diffwsopts = [ 151 diffwsopts = [
140 ('w', 'ignore-all-space', None, 152 (
141 _('ignore white space when comparing lines')), 153 'w',
142 ('b', 'ignore-space-change', None, 154 'ignore-all-space',
143 _('ignore changes in the amount of white space')), 155 None,
144 ('B', 'ignore-blank-lines', None, 156 _('ignore white space when comparing lines'),
145 _('ignore changes whose lines are all blank')), 157 ),
146 ('Z', 'ignore-space-at-eol', None, 158 (
147 _('ignore changes in whitespace at EOL')), 159 'b',
160 'ignore-space-change',
161 None,
162 _('ignore changes in the amount of white space'),
163 ),
164 (
165 'B',
166 'ignore-blank-lines',
167 None,
168 _('ignore changes whose lines are all blank'),
169 ),
170 (
171 'Z',
172 'ignore-space-at-eol',
173 None,
174 _('ignore changes in whitespace at EOL'),
175 ),
148 ] 176 ]
149 177
150 diffopts2 = [ 178 diffopts2 = (
151 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')), 179 [
152 ('p', 'show-function', None, _('show which function each change is in')), 180 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
153 ('', 'reverse', None, _('produce a diff that undoes the changes')), 181 (
154 ] + diffwsopts + [ 182 'p',
155 ('U', 'unified', '', 183 'show-function',
156 _('number of lines of context to show'), _('NUM')), 184 None,
157 ('', 'stat', None, _('output diffstat-style summary of changes')), 185 _('show which function each change is in'),
158 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')), 186 ),
159 ] 187 ('', 'reverse', None, _('produce a diff that undoes the changes')),
188 ]
189 + diffwsopts
190 + [
191 ('U', 'unified', '', _('number of lines of context to show'), _('NUM')),
192 ('', 'stat', None, _('output diffstat-style summary of changes')),
193 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
194 ]
195 )
160 196
161 mergetoolopts = [ 197 mergetoolopts = [
162 ('t', 'tool', '', _('specify merge tool'), _('TOOL')), 198 ('t', 'tool', '', _('specify merge tool'), _('TOOL')),
163 ] 199 ]
164 200
165 similarityopts = [ 201 similarityopts = [
166 ('s', 'similarity', '', 202 (
167 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY')) 203 's',
204 'similarity',
205 '',
206 _('guess renamed files by similarity (0<=s<=100)'),
207 _('SIMILARITY'),
208 )
168 ] 209 ]
169 210
170 subrepoopts = [ 211 subrepoopts = [('S', 'subrepos', None, _('recurse into subrepositories'))]
171 ('S', 'subrepos', None,
172 _('recurse into subrepositories'))
173 ]
174 212
175 debugrevlogopts = [ 213 debugrevlogopts = [
176 ('c', 'changelog', False, _('open changelog')), 214 ('c', 'changelog', False, _('open changelog')),
177 ('m', 'manifest', False, _('open manifest')), 215 ('m', 'manifest', False, _('open manifest')),
178 ('', 'dir', '', _('open directory manifest')), 216 ('', 'dir', '', _('open directory manifest')),
180 218
181 # special string such that everything below this line will be ingored in the 219 # special string such that everything below this line will be ingored in the
182 # editor text 220 # editor text
183 _linebelow = "^HG: ------------------------ >8 ------------------------$" 221 _linebelow = "^HG: ------------------------ >8 ------------------------$"
184 222
223
185 def resolvecommitoptions(ui, opts): 224 def resolvecommitoptions(ui, opts):
186 """modify commit options dict to handle related options 225 """modify commit options dict to handle related options
187 226
188 The return value indicates that ``rewrite.update-timestamp`` is the reason 227 The return value indicates that ``rewrite.update-timestamp`` is the reason
189 the ``date`` option is set. 228 the ``date`` option is set.
190 """ 229 """
191 if opts.get('date') and opts.get('currentdate'): 230 if opts.get('date') and opts.get('currentdate'):
192 raise error.Abort(_('--date and --currentdate are mutually ' 231 raise error.Abort(
193 'exclusive')) 232 _('--date and --currentdate are mutually ' 'exclusive')
233 )
194 if opts.get(b'user') and opts.get(b'currentuser'): 234 if opts.get(b'user') and opts.get(b'currentuser'):
195 raise error.Abort(_('--user and --currentuser are mutually ' 235 raise error.Abort(
196 'exclusive')) 236 _('--user and --currentuser are mutually ' 'exclusive')
237 )
197 238
198 datemaydiffer = False # date-only change should be ignored? 239 datemaydiffer = False # date-only change should be ignored?
199 240
200 if opts.get(b'currentdate'): 241 if opts.get(b'currentdate'):
201 opts[b'date'] = b'%d %d' % dateutil.makedate() 242 opts[b'date'] = b'%d %d' % dateutil.makedate()
202 elif (not opts.get('date') 243 elif (
203 and ui.configbool('rewrite', 'update-timestamp') 244 not opts.get('date')
204 and opts.get('currentdate') is None): 245 and ui.configbool('rewrite', 'update-timestamp')
246 and opts.get('currentdate') is None
247 ):
205 opts[b'date'] = b'%d %d' % dateutil.makedate() 248 opts[b'date'] = b'%d %d' % dateutil.makedate()
206 datemaydiffer = True 249 datemaydiffer = True
207 250
208 if opts.get(b'currentuser'): 251 if opts.get(b'currentuser'):
209 opts[b'user'] = ui.username() 252 opts[b'user'] = ui.username()
210 253
211 return datemaydiffer 254 return datemaydiffer
255
212 256
213 def checknotesize(ui, opts): 257 def checknotesize(ui, opts):
214 """ make sure note is of valid format """ 258 """ make sure note is of valid format """
215 259
216 note = opts.get('note') 260 note = opts.get('note')
220 if len(note) > 255: 264 if len(note) > 255:
221 raise error.Abort(_(b"cannot store a note of more than 255 bytes")) 265 raise error.Abort(_(b"cannot store a note of more than 255 bytes"))
222 if b'\n' in note: 266 if b'\n' in note:
223 raise error.Abort(_(b"note cannot contain a newline")) 267 raise error.Abort(_(b"note cannot contain a newline"))
224 268
269
225 def ishunk(x): 270 def ishunk(x):
226 hunkclasses = (crecordmod.uihunk, patch.recordhunk) 271 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
227 return isinstance(x, hunkclasses) 272 return isinstance(x, hunkclasses)
273
228 274
229 def newandmodified(chunks, originalchunks): 275 def newandmodified(chunks, originalchunks):
230 newlyaddedandmodifiedfiles = set() 276 newlyaddedandmodifiedfiles = set()
231 alsorestore = set() 277 alsorestore = set()
232 for chunk in chunks: 278 for chunk in chunks:
233 if (ishunk(chunk) and chunk.header.isnewfile() and chunk not in 279 if (
234 originalchunks): 280 ishunk(chunk)
281 and chunk.header.isnewfile()
282 and chunk not in originalchunks
283 ):
235 newlyaddedandmodifiedfiles.add(chunk.header.filename()) 284 newlyaddedandmodifiedfiles.add(chunk.header.filename())
236 alsorestore.update(set(chunk.header.files()) - 285 alsorestore.update(
237 {chunk.header.filename()}) 286 set(chunk.header.files()) - {chunk.header.filename()}
287 )
238 return newlyaddedandmodifiedfiles, alsorestore 288 return newlyaddedandmodifiedfiles, alsorestore
289
239 290
240 def parsealiases(cmd): 291 def parsealiases(cmd):
241 return cmd.split("|") 292 return cmd.split("|")
293
242 294
243 def setupwrapcolorwrite(ui): 295 def setupwrapcolorwrite(ui):
244 # wrap ui.write so diff output can be labeled/colorized 296 # wrap ui.write so diff output can be labeled/colorized
245 def wrapwrite(orig, *args, **kw): 297 def wrapwrite(orig, *args, **kw):
246 label = kw.pop(r'label', '') 298 label = kw.pop(r'label', '')
247 for chunk, l in patch.difflabel(lambda: args): 299 for chunk, l in patch.difflabel(lambda: args):
248 orig(chunk, label=label + l) 300 orig(chunk, label=label + l)
249 301
250 oldwrite = ui.write 302 oldwrite = ui.write
303
251 def wrap(*args, **kwargs): 304 def wrap(*args, **kwargs):
252 return wrapwrite(oldwrite, *args, **kwargs) 305 return wrapwrite(oldwrite, *args, **kwargs)
306
253 setattr(ui, 'write', wrap) 307 setattr(ui, 'write', wrap)
254 return oldwrite 308 return oldwrite
255 309
256 def filterchunks(ui, originalhunks, usecurses, testfile, match, 310
257 operation=None): 311 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
258 try: 312 try:
259 if usecurses: 313 if usecurses:
260 if testfile: 314 if testfile:
261 recordfn = crecordmod.testdecorator( 315 recordfn = crecordmod.testdecorator(
262 testfile, crecordmod.testchunkselector) 316 testfile, crecordmod.testchunkselector
317 )
263 else: 318 else:
264 recordfn = crecordmod.chunkselector 319 recordfn = crecordmod.chunkselector
265 320
266 return crecordmod.filterpatch(ui, originalhunks, recordfn, 321 return crecordmod.filterpatch(
267 operation) 322 ui, originalhunks, recordfn, operation
323 )
268 except crecordmod.fallbackerror as e: 324 except crecordmod.fallbackerror as e:
269 ui.warn('%s\n' % e.message) 325 ui.warn('%s\n' % e.message)
270 ui.warn(_('falling back to text mode\n')) 326 ui.warn(_('falling back to text mode\n'))
271 327
272 return patch.filterpatch(ui, originalhunks, match, operation) 328 return patch.filterpatch(ui, originalhunks, match, operation)
329
273 330
274 def recordfilter(ui, originalhunks, match, operation=None): 331 def recordfilter(ui, originalhunks, match, operation=None):
275 """ Prompts the user to filter the originalhunks and return a list of 332 """ Prompts the user to filter the originalhunks and return a list of
276 selected hunks. 333 selected hunks.
277 *operation* is used for to build ui messages to indicate the user what 334 *operation* is used for to build ui messages to indicate the user what
280 """ 337 """
281 usecurses = crecordmod.checkcurses(ui) 338 usecurses = crecordmod.checkcurses(ui)
282 testfile = ui.config('experimental', 'crecordtest') 339 testfile = ui.config('experimental', 'crecordtest')
283 oldwrite = setupwrapcolorwrite(ui) 340 oldwrite = setupwrapcolorwrite(ui)
284 try: 341 try:
285 newchunks, newopts = filterchunks(ui, originalhunks, usecurses, 342 newchunks, newopts = filterchunks(
286 testfile, match, operation) 343 ui, originalhunks, usecurses, testfile, match, operation
344 )
287 finally: 345 finally:
288 ui.write = oldwrite 346 ui.write = oldwrite
289 return newchunks, newopts 347 return newchunks, newopts
290 348
291 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, 349
292 filterfn, *pats, **opts): 350 def dorecord(
351 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
352 ):
293 opts = pycompat.byteskwargs(opts) 353 opts = pycompat.byteskwargs(opts)
294 if not ui.interactive(): 354 if not ui.interactive():
295 if cmdsuggest: 355 if cmdsuggest:
296 msg = _('running non-interactively, use %s instead') % cmdsuggest 356 msg = _('running non-interactively, use %s instead') % cmdsuggest
297 else: 357 else:
298 msg = _('running non-interactively') 358 msg = _('running non-interactively')
299 raise error.Abort(msg) 359 raise error.Abort(msg)
300 360
301 # make sure username is set before going interactive 361 # make sure username is set before going interactive
302 if not opts.get('user'): 362 if not opts.get('user'):
303 ui.username() # raise exception, username not provided 363 ui.username() # raise exception, username not provided
304 364
305 def recordfunc(ui, repo, message, match, opts): 365 def recordfunc(ui, repo, message, match, opts):
306 """This is generic record driver. 366 """This is generic record driver.
307 367
308 Its job is to interactively filter local changes, and 368 Its job is to interactively filter local changes, and
319 if not opts.get('interactive-unshelve'): 379 if not opts.get('interactive-unshelve'):
320 checkunfinished(repo, commit=True) 380 checkunfinished(repo, commit=True)
321 wctx = repo[None] 381 wctx = repo[None]
322 merge = len(wctx.parents()) > 1 382 merge = len(wctx.parents()) > 1
323 if merge: 383 if merge:
324 raise error.Abort(_('cannot partially commit a merge ' 384 raise error.Abort(
325 '(use "hg commit" instead)')) 385 _(
386 'cannot partially commit a merge '
387 '(use "hg commit" instead)'
388 )
389 )
326 390
327 def fail(f, msg): 391 def fail(f, msg):
328 raise error.Abort('%s: %s' % (f, msg)) 392 raise error.Abort('%s: %s' % (f, msg))
329 393
330 force = opts.get('force') 394 force = opts.get('force')
337 401
338 overrides = {(b'ui', b'commitsubrepos'): True} 402 overrides = {(b'ui', b'commitsubrepos'): True}
339 403
340 with repo.ui.configoverride(overrides, b'record'): 404 with repo.ui.configoverride(overrides, b'record'):
341 # subrepoutil.precommit() modifies the status 405 # subrepoutil.precommit() modifies the status
342 tmpstatus = scmutil.status(copymod.copy(status[0]), 406 tmpstatus = scmutil.status(
343 copymod.copy(status[1]), 407 copymod.copy(status[0]),
344 copymod.copy(status[2]), 408 copymod.copy(status[1]),
345 copymod.copy(status[3]), 409 copymod.copy(status[2]),
346 copymod.copy(status[4]), 410 copymod.copy(status[3]),
347 copymod.copy(status[5]), 411 copymod.copy(status[4]),
348 copymod.copy(status[6])) 412 copymod.copy(status[5]),
413 copymod.copy(status[6]),
414 )
349 415
350 # Force allows -X subrepo to skip the subrepo. 416 # Force allows -X subrepo to skip the subrepo.
351 subs, commitsubs, newstate = subrepoutil.precommit( 417 subs, commitsubs, newstate = subrepoutil.precommit(
352 repo.ui, wctx, tmpstatus, match, force=True) 418 repo.ui, wctx, tmpstatus, match, force=True
419 )
353 for s in subs: 420 for s in subs:
354 if s in commitsubs: 421 if s in commitsubs:
355 dirtyreason = wctx.sub(s).dirtyreason(True) 422 dirtyreason = wctx.sub(s).dirtyreason(True)
356 raise error.Abort(dirtyreason) 423 raise error.Abort(dirtyreason)
357 424
358 if not force: 425 if not force:
359 repo.checkcommitpatterns(wctx, vdirs, match, status, fail) 426 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
360 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True, 427 diffopts = patch.difffeatureopts(
361 section='commands', 428 ui,
362 configprefix='commit.interactive.') 429 opts=opts,
430 whitespace=True,
431 section='commands',
432 configprefix='commit.interactive.',
433 )
363 diffopts.nodates = True 434 diffopts.nodates = True
364 diffopts.git = True 435 diffopts.git = True
365 diffopts.showfunc = True 436 diffopts.showfunc = True
366 originaldiff = patch.diff(repo, changes=status, opts=diffopts) 437 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
367 originalchunks = patch.parsepatch(originaldiff) 438 originalchunks = patch.parsepatch(originaldiff)
377 # We need to keep a backup of files that have been newly added and 448 # We need to keep a backup of files that have been newly added and
378 # modified during the recording process because there is a previous 449 # modified during the recording process because there is a previous
379 # version without the edit in the workdir. We also will need to restore 450 # version without the edit in the workdir. We also will need to restore
380 # files that were the sources of renames so that the patch application 451 # files that were the sources of renames so that the patch application
381 # works. 452 # works.
382 newlyaddedandmodifiedfiles, alsorestore = newandmodified(chunks, 453 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
383 originalchunks) 454 chunks, originalchunks
455 )
384 contenders = set() 456 contenders = set()
385 for h in chunks: 457 for h in chunks:
386 try: 458 try:
387 contenders.update(set(h.files())) 459 contenders.update(set(h.files()))
388 except AttributeError: 460 except AttributeError:
399 # 2. backup changed files, so we can restore them in the end 471 # 2. backup changed files, so we can restore them in the end
400 472
401 if backupall: 473 if backupall:
402 tobackup = changed 474 tobackup = changed
403 else: 475 else:
404 tobackup = [f for f in newfiles if f in modified or f in 476 tobackup = [
405 newlyaddedandmodifiedfiles] 477 f
478 for f in newfiles
479 if f in modified or f in newlyaddedandmodifiedfiles
480 ]
406 backups = {} 481 backups = {}
407 if tobackup: 482 if tobackup:
408 backupdir = repo.vfs.join('record-backups') 483 backupdir = repo.vfs.join('record-backups')
409 try: 484 try:
410 os.mkdir(backupdir) 485 os.mkdir(backupdir)
412 if err.errno != errno.EEXIST: 487 if err.errno != errno.EEXIST:
413 raise 488 raise
414 try: 489 try:
415 # backup continues 490 # backup continues
416 for f in tobackup: 491 for f in tobackup:
417 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.', 492 fd, tmpname = pycompat.mkstemp(
418 dir=backupdir) 493 prefix=f.replace('/', '_') + '.', dir=backupdir
494 )
419 os.close(fd) 495 os.close(fd)
420 ui.debug('backup %r as %r\n' % (f, tmpname)) 496 ui.debug('backup %r as %r\n' % (f, tmpname))
421 util.copyfile(repo.wjoin(f), tmpname, copystat=True) 497 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
422 backups[f] = tmpname 498 backups[f] = tmpname
423 499
429 dopatch = fp.tell() 505 dopatch = fp.tell()
430 fp.seek(0) 506 fp.seek(0)
431 507
432 # 2.5 optionally review / modify patch in text editor 508 # 2.5 optionally review / modify patch in text editor
433 if opts.get('review', False): 509 if opts.get('review', False):
434 patchtext = (crecordmod.diffhelptext 510 patchtext = (
435 + crecordmod.patchhelptext 511 crecordmod.diffhelptext
436 + fp.read()) 512 + crecordmod.patchhelptext
437 reviewedpatch = ui.edit(patchtext, "", 513 + fp.read()
438 action="diff", 514 )
439 repopath=repo.path) 515 reviewedpatch = ui.edit(
516 patchtext, "", action="diff", repopath=repo.path
517 )
440 fp.truncate(0) 518 fp.truncate(0)
441 fp.write(reviewedpatch) 519 fp.write(reviewedpatch)
442 fp.seek(0) 520 fp.seek(0)
443 521
444 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles] 522 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
445 # 3a. apply filtered patch to clean repo (clean) 523 # 3a. apply filtered patch to clean repo (clean)
446 if backups: 524 if backups:
447 # Equivalent to hg.revert 525 # Equivalent to hg.revert
448 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore) 526 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
449 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False, 527 mergemod.update(
450 force=True, matcher=m) 528 repo,
529 repo.dirstate.p1(),
530 branchmerge=False,
531 force=True,
532 matcher=m,
533 )
451 534
452 # 3b. (apply) 535 # 3b. (apply)
453 if dopatch: 536 if dopatch:
454 try: 537 try:
455 ui.debug('applying patch\n') 538 ui.debug('applying patch\n')
495 with repo.wlock(): 578 with repo.wlock():
496 return recordfunc(ui, repo, message, match, opts) 579 return recordfunc(ui, repo, message, match, opts)
497 580
498 return commit(ui, repo, recordinwlock, pats, opts) 581 return commit(ui, repo, recordinwlock, pats, opts)
499 582
583
500 class dirnode(object): 584 class dirnode(object):
501 """ 585 """
502 Represent a directory in user working copy with information required for 586 Represent a directory in user working copy with information required for
503 the purpose of tersing its status. 587 the purpose of tersing its status.
504 588
590 674
591 # add the files to status list 675 # add the files to status list
592 for st, fpath in self.iterfilepaths(): 676 for st, fpath in self.iterfilepaths():
593 yield st, fpath 677 yield st, fpath
594 678
595 #recurse on the subdirs 679 # recurse on the subdirs
596 for dirobj in self.subdirs.values(): 680 for dirobj in self.subdirs.values():
597 for st, fpath in dirobj.tersewalk(terseargs): 681 for st, fpath in dirobj.tersewalk(terseargs):
598 yield st, fpath 682 yield st, fpath
683
599 684
600 def tersedir(statuslist, terseargs): 685 def tersedir(statuslist, terseargs):
601 """ 686 """
602 Terse the status if all the files in a directory shares the same status. 687 Terse the status if all the files in a directory shares the same status.
603 688
618 if s not in allst: 703 if s not in allst:
619 raise error.Abort(_("'%s' not recognized") % s) 704 raise error.Abort(_("'%s' not recognized") % s)
620 705
621 # creating a dirnode object for the root of the repo 706 # creating a dirnode object for the root of the repo
622 rootobj = dirnode('') 707 rootobj = dirnode('')
623 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown', 708 pstatus = (
624 'ignored', 'removed') 709 'modified',
710 'added',
711 'deleted',
712 'clean',
713 'unknown',
714 'ignored',
715 'removed',
716 )
625 717
626 tersedict = {} 718 tersedict = {}
627 for attrname in pstatus: 719 for attrname in pstatus:
628 statuschar = attrname[0:1] 720 statuschar = attrname[0:1]
629 for f in getattr(statuslist, attrname): 721 for f in getattr(statuslist, attrname):
644 tersedict[st].sort() 736 tersedict[st].sort()
645 tersedlist.append(tersedict[st]) 737 tersedlist.append(tersedict[st])
646 738
647 return tersedlist 739 return tersedlist
648 740
741
649 def _commentlines(raw): 742 def _commentlines(raw):
650 '''Surround lineswith a comment char and a new line''' 743 '''Surround lineswith a comment char and a new line'''
651 lines = raw.splitlines() 744 lines = raw.splitlines()
652 commentedlines = ['# %s' % line for line in lines] 745 commentedlines = ['# %s' % line for line in lines]
653 return '\n'.join(commentedlines) + '\n' 746 return '\n'.join(commentedlines) + '\n'
654 747
748
655 def _conflictsmsg(repo): 749 def _conflictsmsg(repo):
656 mergestate = mergemod.mergestate.read(repo) 750 mergestate = mergemod.mergestate.read(repo)
657 if not mergestate.active(): 751 if not mergestate.active():
658 return 752 return
659 753
660 m = scmutil.match(repo[None]) 754 m = scmutil.match(repo[None])
661 unresolvedlist = [f for f in mergestate.unresolved() if m(f)] 755 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
662 if unresolvedlist: 756 if unresolvedlist:
663 mergeliststr = '\n'.join( 757 mergeliststr = '\n'.join(
664 [' %s' % util.pathto(repo.root, encoding.getcwd(), path) 758 [
665 for path in sorted(unresolvedlist)]) 759 ' %s' % util.pathto(repo.root, encoding.getcwd(), path)
666 msg = _('''Unresolved merge conflicts: 760 for path in sorted(unresolvedlist)
761 ]
762 )
763 msg = (
764 _(
765 '''Unresolved merge conflicts:
667 766
668 %s 767 %s
669 768
670 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr 769 To mark files as resolved: hg resolve --mark FILE'''
770 )
771 % mergeliststr
772 )
671 else: 773 else:
672 msg = _('No unresolved merge conflicts.') 774 msg = _('No unresolved merge conflicts.')
673 775
674 return _commentlines(msg) 776 return _commentlines(msg)
777
675 778
676 def morestatus(repo, fm): 779 def morestatus(repo, fm):
677 statetuple = statemod.getrepostate(repo) 780 statetuple = statemod.getrepostate(repo)
678 label = 'status.morestatus' 781 label = 'status.morestatus'
679 if statetuple: 782 if statetuple:
683 conmsg = _conflictsmsg(repo) 786 conmsg = _conflictsmsg(repo)
684 if conmsg: 787 if conmsg:
685 fm.plain('%s\n' % conmsg, label=label) 788 fm.plain('%s\n' % conmsg, label=label)
686 if helpfulmsg: 789 if helpfulmsg:
687 fm.plain('%s\n' % _commentlines(helpfulmsg), label=label) 790 fm.plain('%s\n' % _commentlines(helpfulmsg), label=label)
791
688 792
689 def findpossible(cmd, table, strict=False): 793 def findpossible(cmd, table, strict=False):
690 """ 794 """
691 Return cmd -> (aliases, command table entry) 795 Return cmd -> (aliases, command table entry)
692 for each matching command. 796 for each matching command.
722 if not choice and debugchoice: 826 if not choice and debugchoice:
723 choice = debugchoice 827 choice = debugchoice
724 828
725 return choice, allcmds 829 return choice, allcmds
726 830
831
727 def findcmd(cmd, table, strict=True): 832 def findcmd(cmd, table, strict=True):
728 """Return (aliases, command table entry) for command string.""" 833 """Return (aliases, command table entry) for command string."""
729 choice, allcmds = findpossible(cmd, table, strict) 834 choice, allcmds = findpossible(cmd, table, strict)
730 835
731 if cmd in choice: 836 if cmd in choice:
737 842
738 if choice: 843 if choice:
739 return list(choice.values())[0] 844 return list(choice.values())[0]
740 845
741 raise error.UnknownCommand(cmd, allcmds) 846 raise error.UnknownCommand(cmd, allcmds)
847
742 848
743 def changebranch(ui, repo, revs, label): 849 def changebranch(ui, repo, revs, label):
744 """ Change the branch name of given revs to label """ 850 """ Change the branch name of given revs to label """
745 851
746 with repo.wlock(), repo.lock(), repo.transaction('branches'): 852 with repo.wlock(), repo.lock(), repo.transaction('branches'):
768 874
769 replacements = {} 875 replacements = {}
770 # avoid import cycle mercurial.cmdutil -> mercurial.context -> 876 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
771 # mercurial.subrepo -> mercurial.cmdutil 877 # mercurial.subrepo -> mercurial.cmdutil
772 from . import context 878 from . import context
879
773 for rev in revs: 880 for rev in revs:
774 ctx = repo[rev] 881 ctx = repo[rev]
775 oldbranch = ctx.branch() 882 oldbranch = ctx.branch()
776 # check if ctx has same branch 883 # check if ctx has same branch
777 if oldbranch == label: 884 if oldbranch == label:
781 try: 888 try:
782 return ctx[path] 889 return ctx[path]
783 except error.ManifestLookupError: 890 except error.ManifestLookupError:
784 return None 891 return None
785 892
786 ui.debug("changing branch of '%s' from '%s' to '%s'\n" 893 ui.debug(
787 % (hex(ctx.node()), oldbranch, label)) 894 "changing branch of '%s' from '%s' to '%s'\n"
895 % (hex(ctx.node()), oldbranch, label)
896 )
788 extra = ctx.extra() 897 extra = ctx.extra()
789 extra['branch_change'] = hex(ctx.node()) 898 extra['branch_change'] = hex(ctx.node())
790 # While changing branch of set of linear commits, make sure that 899 # While changing branch of set of linear commits, make sure that
791 # we base our commits on new parent rather than old parent which 900 # we base our commits on new parent rather than old parent which
792 # was obsoleted while changing the branch 901 # was obsoleted while changing the branch
795 if p1 in replacements: 904 if p1 in replacements:
796 p1 = replacements[p1][0] 905 p1 = replacements[p1][0]
797 if p2 in replacements: 906 if p2 in replacements:
798 p2 = replacements[p2][0] 907 p2 = replacements[p2][0]
799 908
800 mc = context.memctx(repo, (p1, p2), 909 mc = context.memctx(
801 ctx.description(), 910 repo,
802 ctx.files(), 911 (p1, p2),
803 filectxfn, 912 ctx.description(),
804 user=ctx.user(), 913 ctx.files(),
805 date=ctx.date(), 914 filectxfn,
806 extra=extra, 915 user=ctx.user(),
807 branch=label) 916 date=ctx.date(),
917 extra=extra,
918 branch=label,
919 )
808 920
809 newnode = repo.commitctx(mc) 921 newnode = repo.commitctx(mc)
810 replacements[ctx.node()] = (newnode,) 922 replacements[ctx.node()] = (newnode,)
811 ui.debug('new node id is %s\n' % hex(newnode)) 923 ui.debug('new node id is %s\n' % hex(newnode))
812 924
820 newid = replacements.get(wctx.p1().node()) 932 newid = replacements.get(wctx.p1().node())
821 if newid is not None: 933 if newid is not None:
822 # avoid import cycle mercurial.cmdutil -> mercurial.hg -> 934 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
823 # mercurial.cmdutil 935 # mercurial.cmdutil
824 from . import hg 936 from . import hg
937
825 hg.update(repo, newid[0], quietempty=True) 938 hg.update(repo, newid[0], quietempty=True)
826 939
827 ui.status(_("changed branch on %d changesets\n") % len(replacements)) 940 ui.status(_("changed branch on %d changesets\n") % len(replacements))
941
828 942
829 def findrepo(p): 943 def findrepo(p):
830 while not os.path.isdir(os.path.join(p, ".hg")): 944 while not os.path.isdir(os.path.join(p, ".hg")):
831 oldp, p = p, os.path.dirname(p) 945 oldp, p = p, os.path.dirname(p)
832 if p == oldp: 946 if p == oldp:
833 return None 947 return None
834 948
835 return p 949 return p
950
836 951
837 def bailifchanged(repo, merge=True, hint=None): 952 def bailifchanged(repo, merge=True, hint=None):
838 """ enforce the precondition that working directory must be clean. 953 """ enforce the precondition that working directory must be clean.
839 954
840 'merge' can be set to false if a pending uncommitted merge should be 955 'merge' can be set to false if a pending uncommitted merge should be
850 raise error.Abort(_('uncommitted changes'), hint=hint) 965 raise error.Abort(_('uncommitted changes'), hint=hint)
851 ctx = repo[None] 966 ctx = repo[None]
852 for s in sorted(ctx.substate): 967 for s in sorted(ctx.substate):
853 ctx.sub(s).bailifchanged(hint=hint) 968 ctx.sub(s).bailifchanged(hint=hint)
854 969
970
855 def logmessage(ui, opts): 971 def logmessage(ui, opts):
856 """ get the log message according to -m and -l option """ 972 """ get the log message according to -m and -l option """
857 message = opts.get('message') 973 message = opts.get('message')
858 logfile = opts.get('logfile') 974 logfile = opts.get('logfile')
859 975
860 if message and logfile: 976 if message and logfile:
861 raise error.Abort(_('options --message and --logfile are mutually ' 977 raise error.Abort(
862 'exclusive')) 978 _('options --message and --logfile are mutually ' 'exclusive')
979 )
863 if not message and logfile: 980 if not message and logfile:
864 try: 981 try:
865 if isstdiofilename(logfile): 982 if isstdiofilename(logfile):
866 message = ui.fin.read() 983 message = ui.fin.read()
867 else: 984 else:
868 message = '\n'.join(util.readfile(logfile).splitlines()) 985 message = '\n'.join(util.readfile(logfile).splitlines())
869 except IOError as inst: 986 except IOError as inst:
870 raise error.Abort(_("can't read commit message '%s': %s") % 987 raise error.Abort(
871 (logfile, encoding.strtolocal(inst.strerror))) 988 _("can't read commit message '%s': %s")
989 % (logfile, encoding.strtolocal(inst.strerror))
990 )
872 return message 991 return message
992
873 993
874 def mergeeditform(ctxorbool, baseformname): 994 def mergeeditform(ctxorbool, baseformname):
875 """return appropriate editform name (referencing a committemplate) 995 """return appropriate editform name (referencing a committemplate)
876 996
877 'ctxorbool' is either a ctx to be committed, or a bool indicating whether 997 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
886 elif len(ctxorbool.parents()) > 1: 1006 elif len(ctxorbool.parents()) > 1:
887 return baseformname + ".merge" 1007 return baseformname + ".merge"
888 1008
889 return baseformname + ".normal" 1009 return baseformname + ".normal"
890 1010
891 def getcommiteditor(edit=False, finishdesc=None, extramsg=None, 1011
892 editform='', **opts): 1012 def getcommiteditor(
1013 edit=False, finishdesc=None, extramsg=None, editform='', **opts
1014 ):
893 """get appropriate commit message editor according to '--edit' option 1015 """get appropriate commit message editor according to '--edit' option
894 1016
895 'finishdesc' is a function to be called with edited commit message 1017 'finishdesc' is a function to be called with edited commit message
896 (= 'description' of the new changeset) just after editing, but 1018 (= 'description' of the new changeset) just after editing, but
897 before checking empty-ness. It should return actual text to be 1019 before checking empty-ness. It should return actual text to be
908 'getcommiteditor' returns 'commitforceeditor' regardless of 1030 'getcommiteditor' returns 'commitforceeditor' regardless of
909 'edit', if one of 'finishdesc' or 'extramsg' is specified, because 1031 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
910 they are specific for usage in MQ. 1032 they are specific for usage in MQ.
911 """ 1033 """
912 if edit or finishdesc or extramsg: 1034 if edit or finishdesc or extramsg:
913 return lambda r, c, s: commitforceeditor(r, c, s, 1035 return lambda r, c, s: commitforceeditor(
914 finishdesc=finishdesc, 1036 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
915 extramsg=extramsg, 1037 )
916 editform=editform)
917 elif editform: 1038 elif editform:
918 return lambda r, c, s: commiteditor(r, c, s, editform=editform) 1039 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
919 else: 1040 else:
920 return commiteditor 1041 return commiteditor
1042
921 1043
922 def _escapecommandtemplate(tmpl): 1044 def _escapecommandtemplate(tmpl):
923 parts = [] 1045 parts = []
924 for typ, start, end in templater.scantemplate(tmpl, raw=True): 1046 for typ, start, end in templater.scantemplate(tmpl, raw=True):
925 if typ == b'string': 1047 if typ == b'string':
926 parts.append(stringutil.escapestr(tmpl[start:end])) 1048 parts.append(stringutil.escapestr(tmpl[start:end]))
927 else: 1049 else:
928 parts.append(tmpl[start:end]) 1050 parts.append(tmpl[start:end])
929 return b''.join(parts) 1051 return b''.join(parts)
1052
930 1053
931 def rendercommandtemplate(ui, tmpl, props): 1054 def rendercommandtemplate(ui, tmpl, props):
932 r"""Expand a literal template 'tmpl' in a way suitable for command line 1055 r"""Expand a literal template 'tmpl' in a way suitable for command line
933 1056
934 '\' in outermost string is not taken as an escape character because it 1057 '\' in outermost string is not taken as an escape character because it
944 if not tmpl: 1067 if not tmpl:
945 return tmpl 1068 return tmpl
946 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl)) 1069 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
947 return t.renderdefault(props) 1070 return t.renderdefault(props)
948 1071
1072
949 def rendertemplate(ctx, tmpl, props=None): 1073 def rendertemplate(ctx, tmpl, props=None):
950 """Expand a literal template 'tmpl' byte-string against one changeset 1074 """Expand a literal template 'tmpl' byte-string against one changeset
951 1075
952 Each props item must be a stringify-able value or a callable returning 1076 Each props item must be a stringify-able value or a callable returning
953 such value, i.e. no bare list nor dict should be passed. 1077 such value, i.e. no bare list nor dict should be passed.
954 """ 1078 """
955 repo = ctx.repo() 1079 repo = ctx.repo()
956 tres = formatter.templateresources(repo.ui, repo) 1080 tres = formatter.templateresources(repo.ui, repo)
957 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords, 1081 t = formatter.maketemplater(
958 resources=tres) 1082 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1083 )
959 mapping = {'ctx': ctx} 1084 mapping = {'ctx': ctx}
960 if props: 1085 if props:
961 mapping.update(props) 1086 mapping.update(props)
962 return t.renderdefault(mapping) 1087 return t.renderdefault(mapping)
1088
963 1089
964 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None): 1090 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
965 r"""Convert old-style filename format string to template string 1091 r"""Convert old-style filename format string to template string
966 1092
967 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0) 1093 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1016 if n < 0: 1142 if n < 0:
1017 newname.append(stringutil.escapestr(pat[i:end])) 1143 newname.append(stringutil.escapestr(pat[i:end]))
1018 break 1144 break
1019 newname.append(stringutil.escapestr(pat[i:n])) 1145 newname.append(stringutil.escapestr(pat[i:n]))
1020 if n + 2 > end: 1146 if n + 2 > end:
1021 raise error.Abort(_("incomplete format spec in output " 1147 raise error.Abort(
1022 "filename")) 1148 _("incomplete format spec in output " "filename")
1023 c = pat[n + 1:n + 2] 1149 )
1150 c = pat[n + 1 : n + 2]
1024 i = n + 2 1151 i = n + 2
1025 try: 1152 try:
1026 newname.append(expander[c]) 1153 newname.append(expander[c])
1027 except KeyError: 1154 except KeyError:
1028 raise error.Abort(_("invalid format spec '%%%s' in output " 1155 raise error.Abort(
1029 "filename") % c) 1156 _("invalid format spec '%%%s' in output " "filename") % c
1157 )
1030 return ''.join(newname) 1158 return ''.join(newname)
1159
1031 1160
1032 def makefilename(ctx, pat, **props): 1161 def makefilename(ctx, pat, **props):
1033 if not pat: 1162 if not pat:
1034 return pat 1163 return pat
1035 tmpl = _buildfntemplate(pat, **props) 1164 tmpl = _buildfntemplate(pat, **props)
1036 # BUG: alias expansion shouldn't be made against template fragments 1165 # BUG: alias expansion shouldn't be made against template fragments
1037 # rewritten from %-format strings, but we have no easy way to partially 1166 # rewritten from %-format strings, but we have no easy way to partially
1038 # disable the expansion. 1167 # disable the expansion.
1039 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props)) 1168 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1040 1169
1170
1041 def isstdiofilename(pat): 1171 def isstdiofilename(pat):
1042 """True if the given pat looks like a filename denoting stdin/stdout""" 1172 """True if the given pat looks like a filename denoting stdin/stdout"""
1043 return not pat or pat == '-' 1173 return not pat or pat == '-'
1044 1174
1175
1045 class _unclosablefile(object): 1176 class _unclosablefile(object):
1046 def __init__(self, fp): 1177 def __init__(self, fp):
1047 self._fp = fp 1178 self._fp = fp
1048 1179
1049 def close(self): 1180 def close(self):
1058 def __enter__(self): 1189 def __enter__(self):
1059 return self 1190 return self
1060 1191
1061 def __exit__(self, exc_type, exc_value, exc_tb): 1192 def __exit__(self, exc_type, exc_value, exc_tb):
1062 pass 1193 pass
1194
1063 1195
1064 def makefileobj(ctx, pat, mode='wb', **props): 1196 def makefileobj(ctx, pat, mode='wb', **props):
1065 writable = mode not in ('r', 'rb') 1197 writable = mode not in ('r', 'rb')
1066 1198
1067 if isstdiofilename(pat): 1199 if isstdiofilename(pat):
1071 else: 1203 else:
1072 fp = repo.ui.fin 1204 fp = repo.ui.fin
1073 return _unclosablefile(fp) 1205 return _unclosablefile(fp)
1074 fn = makefilename(ctx, pat, **props) 1206 fn = makefilename(ctx, pat, **props)
1075 return open(fn, mode) 1207 return open(fn, mode)
1208
1076 1209
1077 def openstorage(repo, cmd, file_, opts, returnrevlog=False): 1210 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1078 """opens the changelog, manifest, a filelog or a given revlog""" 1211 """opens the changelog, manifest, a filelog or a given revlog"""
1079 cl = opts['changelog'] 1212 cl = opts['changelog']
1080 mf = opts['manifest'] 1213 mf = opts['manifest']
1086 msg = _('cannot specify --changelog and --dir at the same time') 1219 msg = _('cannot specify --changelog and --dir at the same time')
1087 elif cl or mf or dir: 1220 elif cl or mf or dir:
1088 if file_: 1221 if file_:
1089 msg = _('cannot specify filename with --changelog or --manifest') 1222 msg = _('cannot specify filename with --changelog or --manifest')
1090 elif not repo: 1223 elif not repo:
1091 msg = _('cannot specify --changelog or --manifest or --dir ' 1224 msg = _(
1092 'without a repository') 1225 'cannot specify --changelog or --manifest or --dir '
1226 'without a repository'
1227 )
1093 if msg: 1228 if msg:
1094 raise error.Abort(msg) 1229 raise error.Abort(msg)
1095 1230
1096 r = None 1231 r = None
1097 if repo: 1232 if repo:
1098 if cl: 1233 if cl:
1099 r = repo.unfiltered().changelog 1234 r = repo.unfiltered().changelog
1100 elif dir: 1235 elif dir:
1101 if 'treemanifest' not in repo.requirements: 1236 if 'treemanifest' not in repo.requirements:
1102 raise error.Abort(_("--dir can only be used on repos with " 1237 raise error.Abort(
1103 "treemanifest enabled")) 1238 _(
1239 "--dir can only be used on repos with "
1240 "treemanifest enabled"
1241 )
1242 )
1104 if not dir.endswith('/'): 1243 if not dir.endswith('/'):
1105 dir = dir + '/' 1244 dir = dir + '/'
1106 dirlog = repo.manifestlog.getstorage(dir) 1245 dirlog = repo.manifestlog.getstorage(dir)
1107 if len(dirlog): 1246 if len(dirlog):
1108 r = dirlog 1247 r = dirlog
1129 1268
1130 if not file_: 1269 if not file_:
1131 raise error.CommandError(cmd, _('invalid arguments')) 1270 raise error.CommandError(cmd, _('invalid arguments'))
1132 if not os.path.isfile(file_): 1271 if not os.path.isfile(file_):
1133 raise error.Abort(_("revlog '%s' not found") % file_) 1272 raise error.Abort(_("revlog '%s' not found") % file_)
1134 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), 1273 r = revlog.revlog(
1135 file_[:-2] + ".i") 1274 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + ".i"
1275 )
1136 return r 1276 return r
1277
1137 1278
1138 def openrevlog(repo, cmd, file_, opts): 1279 def openrevlog(repo, cmd, file_, opts):
1139 """Obtain a revlog backing storage of an item. 1280 """Obtain a revlog backing storage of an item.
1140 1281
1141 This is similar to ``openstorage()`` except it always returns a revlog. 1282 This is similar to ``openstorage()`` except it always returns a revlog.
1144 revlog backing it. Therefore, this function should only be used by code 1285 revlog backing it. Therefore, this function should only be used by code
1145 that needs to examine low-level revlog implementation details. e.g. debug 1286 that needs to examine low-level revlog implementation details. e.g. debug
1146 commands. 1287 commands.
1147 """ 1288 """
1148 return openstorage(repo, cmd, file_, opts, returnrevlog=True) 1289 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1290
1149 1291
1150 def copy(ui, repo, pats, opts, rename=False): 1292 def copy(ui, repo, pats, opts, rename=False):
1151 # called with the repo lock held 1293 # called with the repo lock held
1152 # 1294 #
1153 # hgsep => pathname that uses "/" to separate directories 1295 # hgsep => pathname that uses "/" to separate directories
1157 after = opts.get("after") 1299 after = opts.get("after")
1158 dryrun = opts.get("dry_run") 1300 dryrun = opts.get("dry_run")
1159 wctx = repo[None] 1301 wctx = repo[None]
1160 1302
1161 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) 1303 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1304
1162 def walkpat(pat): 1305 def walkpat(pat):
1163 srcs = [] 1306 srcs = []
1164 if after: 1307 if after:
1165 badstates = '?' 1308 badstates = '?'
1166 else: 1309 else:
1172 exact = m.exact(abs) 1315 exact = m.exact(abs)
1173 if state in badstates: 1316 if state in badstates:
1174 if exact and state == '?': 1317 if exact and state == '?':
1175 ui.warn(_('%s: not copying - file is not managed\n') % rel) 1318 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1176 if exact and state == 'r': 1319 if exact and state == 'r':
1177 ui.warn(_('%s: not copying - file has been marked for' 1320 ui.warn(
1178 ' remove\n') % rel) 1321 _(
1322 '%s: not copying - file has been marked for'
1323 ' remove\n'
1324 )
1325 % rel
1326 )
1179 continue 1327 continue
1180 # abs: hgsep 1328 # abs: hgsep
1181 # rel: ossep 1329 # rel: ossep
1182 srcs.append((abs, rel, exact)) 1330 srcs.append((abs, rel, exact))
1183 return srcs 1331 return srcs
1200 scmutil.checkportable(ui, abstarget) 1348 scmutil.checkportable(ui, abstarget)
1201 1349
1202 # check for collisions 1350 # check for collisions
1203 prevsrc = targets.get(abstarget) 1351 prevsrc = targets.get(abstarget)
1204 if prevsrc is not None: 1352 if prevsrc is not None:
1205 ui.warn(_('%s: not overwriting - %s collides with %s\n') % 1353 ui.warn(
1206 (reltarget, repo.pathto(abssrc, cwd), 1354 _('%s: not overwriting - %s collides with %s\n')
1207 repo.pathto(prevsrc, cwd))) 1355 % (
1208 return True # report a failure 1356 reltarget,
1357 repo.pathto(abssrc, cwd),
1358 repo.pathto(prevsrc, cwd),
1359 )
1360 )
1361 return True # report a failure
1209 1362
1210 # check for overwrites 1363 # check for overwrites
1211 exists = os.path.lexists(target) 1364 exists = os.path.lexists(target)
1212 samefile = False 1365 samefile = False
1213 if exists and abssrc != abstarget: 1366 if exists and abssrc != abstarget:
1214 if (repo.dirstate.normalize(abssrc) == 1367 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1215 repo.dirstate.normalize(abstarget)): 1368 abstarget
1369 ):
1216 if not rename: 1370 if not rename:
1217 ui.warn(_("%s: can't copy - same file\n") % reltarget) 1371 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1218 return True # report a failure 1372 return True # report a failure
1219 exists = False 1373 exists = False
1220 samefile = True 1374 samefile = True
1221 1375
1222 if not after and exists or after and state in 'mn': 1376 if not after and exists or after and state in 'mn':
1223 if not opts['force']: 1377 if not opts['force']:
1226 if after: 1380 if after:
1227 flags = '--after --force' 1381 flags = '--after --force'
1228 else: 1382 else:
1229 flags = '--force' 1383 flags = '--force'
1230 if rename: 1384 if rename:
1231 hint = _("('hg rename %s' to replace the file by " 1385 hint = (
1232 'recording a rename)\n') % flags 1386 _(
1387 "('hg rename %s' to replace the file by "
1388 'recording a rename)\n'
1389 )
1390 % flags
1391 )
1233 else: 1392 else:
1234 hint = _("('hg copy %s' to replace the file by " 1393 hint = (
1235 'recording a copy)\n') % flags 1394 _(
1395 "('hg copy %s' to replace the file by "
1396 'recording a copy)\n'
1397 )
1398 % flags
1399 )
1236 else: 1400 else:
1237 msg = _('%s: not overwriting - file exists\n') 1401 msg = _('%s: not overwriting - file exists\n')
1238 if rename: 1402 if rename:
1239 hint = _("('hg rename --after' to record the rename)\n") 1403 hint = _("('hg rename --after' to record the rename)\n")
1240 else: 1404 else:
1241 hint = _("('hg copy --after' to record the copy)\n") 1405 hint = _("('hg copy --after' to record the copy)\n")
1242 ui.warn(msg % reltarget) 1406 ui.warn(msg % reltarget)
1243 ui.warn(hint) 1407 ui.warn(hint)
1244 return True # report a failure 1408 return True # report a failure
1245 1409
1246 if after: 1410 if after:
1247 if not exists: 1411 if not exists:
1248 if rename: 1412 if rename:
1249 ui.warn(_('%s: not recording move - %s does not exist\n') % 1413 ui.warn(
1250 (relsrc, reltarget)) 1414 _('%s: not recording move - %s does not exist\n')
1415 % (relsrc, reltarget)
1416 )
1251 else: 1417 else:
1252 ui.warn(_('%s: not recording copy - %s does not exist\n') % 1418 ui.warn(
1253 (relsrc, reltarget)) 1419 _('%s: not recording copy - %s does not exist\n')
1254 return True # report a failure 1420 % (relsrc, reltarget)
1421 )
1422 return True # report a failure
1255 elif not dryrun: 1423 elif not dryrun:
1256 try: 1424 try:
1257 if exists: 1425 if exists:
1258 os.unlink(target) 1426 os.unlink(target)
1259 targetdir = os.path.dirname(target) or '.' 1427 targetdir = os.path.dirname(target) or '.'
1271 except IOError as inst: 1439 except IOError as inst:
1272 if inst.errno == errno.ENOENT: 1440 if inst.errno == errno.ENOENT:
1273 ui.warn(_('%s: deleted in working directory\n') % relsrc) 1441 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1274 srcexists = False 1442 srcexists = False
1275 else: 1443 else:
1276 ui.warn(_('%s: cannot copy - %s\n') % 1444 ui.warn(
1277 (relsrc, encoding.strtolocal(inst.strerror))) 1445 _('%s: cannot copy - %s\n')
1278 return True # report a failure 1446 % (relsrc, encoding.strtolocal(inst.strerror))
1447 )
1448 return True # report a failure
1279 1449
1280 if ui.verbose or not exact: 1450 if ui.verbose or not exact:
1281 if rename: 1451 if rename:
1282 ui.status(_('moving %s to %s\n') % (relsrc, reltarget)) 1452 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1283 else: 1453 else:
1284 ui.status(_('copying %s to %s\n') % (relsrc, reltarget)) 1454 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1285 1455
1286 targets[abstarget] = abssrc 1456 targets[abstarget] = abssrc
1287 1457
1288 # fix up dirstate 1458 # fix up dirstate
1289 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget, 1459 scmutil.dirstatecopy(
1290 dryrun=dryrun, cwd=cwd) 1460 ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1461 )
1291 if rename and not dryrun: 1462 if rename and not dryrun:
1292 if not after and srcexists and not samefile: 1463 if not after and srcexists and not samefile:
1293 rmdir = repo.ui.configbool('experimental', 'removeemptydirs') 1464 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1294 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir) 1465 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1295 wctx.forget([abssrc]) 1466 wctx.forget([abssrc])
1308 striplen = len(abspfx) 1479 striplen = len(abspfx)
1309 if striplen: 1480 if striplen:
1310 striplen += len(pycompat.ossep) 1481 striplen += len(pycompat.ossep)
1311 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:]) 1482 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1312 elif destdirexists: 1483 elif destdirexists:
1313 res = lambda p: os.path.join(dest, 1484 res = lambda p: os.path.join(
1314 os.path.basename(util.localpath(p))) 1485 dest, os.path.basename(util.localpath(p))
1486 )
1315 else: 1487 else:
1316 res = lambda p: dest 1488 res = lambda p: dest
1317 return res 1489 return res
1318 1490
1319 # pat: ossep 1491 # pat: ossep
1321 # srcs: list of (hgsep, hgsep, ossep, bool) 1493 # srcs: list of (hgsep, hgsep, ossep, bool)
1322 # return: function that takes hgsep and returns ossep 1494 # return: function that takes hgsep and returns ossep
1323 def targetpathafterfn(pat, dest, srcs): 1495 def targetpathafterfn(pat, dest, srcs):
1324 if matchmod.patkind(pat): 1496 if matchmod.patkind(pat):
1325 # a mercurial pattern 1497 # a mercurial pattern
1326 res = lambda p: os.path.join(dest, 1498 res = lambda p: os.path.join(
1327 os.path.basename(util.localpath(p))) 1499 dest, os.path.basename(util.localpath(p))
1500 )
1328 else: 1501 else:
1329 abspfx = pathutil.canonpath(repo.root, cwd, pat) 1502 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1330 if len(abspfx) < len(srcs[0][0]): 1503 if len(abspfx) < len(srcs[0][0]):
1331 # A directory. Either the target path contains the last 1504 # A directory. Either the target path contains the last
1332 # component of the source path or it does not. 1505 # component of the source path or it does not.
1347 striplen1 = len(os.path.split(abspfx)[0]) 1520 striplen1 = len(os.path.split(abspfx)[0])
1348 if striplen1: 1521 if striplen1:
1349 striplen1 += len(pycompat.ossep) 1522 striplen1 += len(pycompat.ossep)
1350 if evalpath(striplen1) > score: 1523 if evalpath(striplen1) > score:
1351 striplen = striplen1 1524 striplen = striplen1
1352 res = lambda p: os.path.join(dest, 1525 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1353 util.localpath(p)[striplen:])
1354 else: 1526 else:
1355 # a file 1527 # a file
1356 if destdirexists: 1528 if destdirexists:
1357 res = lambda p: os.path.join(dest, 1529 res = lambda p: os.path.join(
1358 os.path.basename(util.localpath(p))) 1530 dest, os.path.basename(util.localpath(p))
1531 )
1359 else: 1532 else:
1360 res = lambda p: dest 1533 res = lambda p: dest
1361 return res 1534 return res
1362 1535
1363 pats = scmutil.expandpats(pats) 1536 pats = scmutil.expandpats(pats)
1367 raise error.Abort(_('no destination specified')) 1540 raise error.Abort(_('no destination specified'))
1368 dest = pats.pop() 1541 dest = pats.pop()
1369 destdirexists = os.path.isdir(dest) and not os.path.islink(dest) 1542 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1370 if not destdirexists: 1543 if not destdirexists:
1371 if len(pats) > 1 or matchmod.patkind(pats[0]): 1544 if len(pats) > 1 or matchmod.patkind(pats[0]):
1372 raise error.Abort(_('with multiple sources, destination must be an ' 1545 raise error.Abort(
1373 'existing directory')) 1546 _(
1547 'with multiple sources, destination must be an '
1548 'existing directory'
1549 )
1550 )
1374 if util.endswithsep(dest): 1551 if util.endswithsep(dest):
1375 raise error.Abort(_('destination %s is not a directory') % dest) 1552 raise error.Abort(_('destination %s is not a directory') % dest)
1376 1553
1377 tfn = targetpathfn 1554 tfn = targetpathfn
1378 if after: 1555 if after:
1392 if copyfile(abssrc, relsrc, targetpath(abssrc), exact): 1569 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1393 errors += 1 1570 errors += 1
1394 1571
1395 return errors != 0 1572 return errors != 0
1396 1573
1574
1397 ## facility to let extension process additional data into an import patch 1575 ## facility to let extension process additional data into an import patch
1398 # list of identifier to be executed in order 1576 # list of identifier to be executed in order
1399 extrapreimport = [] # run before commit 1577 extrapreimport = [] # run before commit
1400 extrapostimport = [] # run after commit 1578 extrapostimport = [] # run after commit
1401 # mapping from identifier to actual import function 1579 # mapping from identifier to actual import function
1402 # 1580 #
1403 # 'preimport' are run before the commit is made and are provided the following 1581 # 'preimport' are run before the commit is made and are provided the following
1404 # arguments: 1582 # arguments:
1405 # - repo: the localrepository instance, 1583 # - repo: the localrepository instance,
1412 extrapreimportmap = {} 1590 extrapreimportmap = {}
1413 # 'postimport' are run after the commit is made and are provided the following 1591 # 'postimport' are run after the commit is made and are provided the following
1414 # argument: 1592 # argument:
1415 # - ctx: the changectx created by import. 1593 # - ctx: the changectx created by import.
1416 extrapostimportmap = {} 1594 extrapostimportmap = {}
1595
1417 1596
1418 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc): 1597 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1419 """Utility function used by commands.import to import a single patch 1598 """Utility function used by commands.import to import a single patch
1420 1599
1421 This function is explicitly defined here to help the evolve extension to 1600 This function is explicitly defined here to help the evolve extension to
1487 p1 = parents[0] 1666 p1 = parents[0]
1488 p2 = repo[nullid] 1667 p2 = repo[nullid]
1489 except error.RepoError: 1668 except error.RepoError:
1490 p1, p2 = parents 1669 p1, p2 = parents
1491 if p2.node() == nullid: 1670 if p2.node() == nullid:
1492 ui.warn(_("warning: import the patch as a normal revision\n" 1671 ui.warn(
1493 "(use --exact to import the patch as a merge)\n")) 1672 _(
1673 "warning: import the patch as a normal revision\n"
1674 "(use --exact to import the patch as a merge)\n"
1675 )
1676 )
1494 else: 1677 else:
1495 p1, p2 = parents 1678 p1, p2 = parents
1496 1679
1497 n = None 1680 n = None
1498 if update: 1681 if update:
1505 repo.dirstate.setbranch(branch or 'default') 1688 repo.dirstate.setbranch(branch or 'default')
1506 1689
1507 partial = opts.get('partial', False) 1690 partial = opts.get('partial', False)
1508 files = set() 1691 files = set()
1509 try: 1692 try:
1510 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix, 1693 patch.patch(
1511 files=files, eolmode=None, similarity=sim / 100.0) 1694 ui,
1695 repo,
1696 tmpname,
1697 strip=strip,
1698 prefix=prefix,
1699 files=files,
1700 eolmode=None,
1701 similarity=sim / 100.0,
1702 )
1512 except error.PatchError as e: 1703 except error.PatchError as e:
1513 if not partial: 1704 if not partial:
1514 raise error.Abort(pycompat.bytestr(e)) 1705 raise error.Abort(pycompat.bytestr(e))
1515 if partial: 1706 if partial:
1516 rejects = True 1707 rejects = True
1529 m = scmutil.matchfiles(repo, files or []) 1720 m = scmutil.matchfiles(repo, files or [])
1530 editform = mergeeditform(repo[None], 'import.normal') 1721 editform = mergeeditform(repo[None], 'import.normal')
1531 if opts.get('exact'): 1722 if opts.get('exact'):
1532 editor = None 1723 editor = None
1533 else: 1724 else:
1534 editor = getcommiteditor(editform=editform, 1725 editor = getcommiteditor(
1535 **pycompat.strkwargs(opts)) 1726 editform=editform, **pycompat.strkwargs(opts)
1727 )
1536 extra = {} 1728 extra = {}
1537 for idfunc in extrapreimport: 1729 for idfunc in extrapreimport:
1538 extrapreimportmap[idfunc](repo, patchdata, extra, opts) 1730 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1539 overrides = {} 1731 overrides = {}
1540 if partial: 1732 if partial:
1541 overrides[('ui', 'allowemptycommit')] = True 1733 overrides[('ui', 'allowemptycommit')] = True
1542 with repo.ui.configoverride(overrides, 'import'): 1734 with repo.ui.configoverride(overrides, 'import'):
1543 n = repo.commit(message, user, 1735 n = repo.commit(
1544 date, match=m, 1736 message, user, date, match=m, editor=editor, extra=extra
1545 editor=editor, extra=extra) 1737 )
1546 for idfunc in extrapostimport: 1738 for idfunc in extrapostimport:
1547 extrapostimportmap[idfunc](repo[n]) 1739 extrapostimportmap[idfunc](repo[n])
1548 else: 1740 else:
1549 if opts.get('exact') or importbranch: 1741 if opts.get('exact') or importbranch:
1550 branch = branch or 'default' 1742 branch = branch or 'default'
1552 branch = p1.branch() 1744 branch = p1.branch()
1553 store = patch.filestore() 1745 store = patch.filestore()
1554 try: 1746 try:
1555 files = set() 1747 files = set()
1556 try: 1748 try:
1557 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix, 1749 patch.patchrepo(
1558 files, eolmode=None) 1750 ui,
1751 repo,
1752 p1,
1753 store,
1754 tmpname,
1755 strip,
1756 prefix,
1757 files,
1758 eolmode=None,
1759 )
1559 except error.PatchError as e: 1760 except error.PatchError as e:
1560 raise error.Abort(stringutil.forcebytestr(e)) 1761 raise error.Abort(stringutil.forcebytestr(e))
1561 if opts.get('exact'): 1762 if opts.get('exact'):
1562 editor = None 1763 editor = None
1563 else: 1764 else:
1564 editor = getcommiteditor(editform='import.bypass') 1765 editor = getcommiteditor(editform='import.bypass')
1565 memctx = context.memctx(repo, (p1.node(), p2.node()), 1766 memctx = context.memctx(
1566 message, 1767 repo,
1567 files=files, 1768 (p1.node(), p2.node()),
1568 filectxfn=store, 1769 message,
1569 user=user, 1770 files=files,
1570 date=date, 1771 filectxfn=store,
1571 branch=branch, 1772 user=user,
1572 editor=editor) 1773 date=date,
1774 branch=branch,
1775 editor=editor,
1776 )
1573 n = memctx.commit() 1777 n = memctx.commit()
1574 finally: 1778 finally:
1575 store.close() 1779 store.close()
1576 if opts.get('exact') and nocommit: 1780 if opts.get('exact') and nocommit:
1577 # --exact with --no-commit is still useful in that it does merge 1781 # --exact with --no-commit is still useful in that it does merge
1583 if n: 1787 if n:
1584 # i18n: refers to a short changeset id 1788 # i18n: refers to a short changeset id
1585 msg = _('created %s') % short(n) 1789 msg = _('created %s') % short(n)
1586 return msg, n, rejects 1790 return msg, n, rejects
1587 1791
1792
1588 # facility to let extensions include additional data in an exported patch 1793 # facility to let extensions include additional data in an exported patch
1589 # list of identifiers to be executed in order 1794 # list of identifiers to be executed in order
1590 extraexport = [] 1795 extraexport = []
1591 # mapping from identifier to actual export function 1796 # mapping from identifier to actual export function
1592 # function as to return a string to be added to the header or None 1797 # function as to return a string to be added to the header or None
1593 # it is given two arguments (sequencenumber, changectx) 1798 # it is given two arguments (sequencenumber, changectx)
1594 extraexportmap = {} 1799 extraexportmap = {}
1595 1800
1801
1596 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts): 1802 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1597 node = scmutil.binnode(ctx) 1803 node = scmutil.binnode(ctx)
1598 parents = [p.node() for p in ctx.parents() if p] 1804 parents = [p.node() for p in ctx.parents() if p]
1599 branch = ctx.branch() 1805 branch = ctx.branch()
1600 if switch_parent: 1806 if switch_parent:
1608 fm.context(ctx=ctx) 1814 fm.context(ctx=ctx)
1609 fm.plain('# HG changeset patch\n') 1815 fm.plain('# HG changeset patch\n')
1610 fm.write('user', '# User %s\n', ctx.user()) 1816 fm.write('user', '# User %s\n', ctx.user())
1611 fm.plain('# Date %d %d\n' % ctx.date()) 1817 fm.plain('# Date %d %d\n' % ctx.date())
1612 fm.write('date', '# %s\n', fm.formatdate(ctx.date())) 1818 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1613 fm.condwrite(branch and branch != 'default', 1819 fm.condwrite(
1614 'branch', '# Branch %s\n', branch) 1820 branch and branch != 'default', 'branch', '# Branch %s\n', branch
1821 )
1615 fm.write('node', '# Node ID %s\n', hex(node)) 1822 fm.write('node', '# Node ID %s\n', hex(node))
1616 fm.plain('# Parent %s\n' % hex(prev)) 1823 fm.plain('# Parent %s\n' % hex(prev))
1617 if len(parents) > 1: 1824 if len(parents) > 1:
1618 fm.plain('# Parent %s\n' % hex(parents[1])) 1825 fm.plain('# Parent %s\n' % hex(parents[1]))
1619 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node')) 1826 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1634 else: 1841 else:
1635 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts) 1842 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1636 # TODO: make it structured? 1843 # TODO: make it structured?
1637 fm.data(diff=b''.join(chunkiter)) 1844 fm.data(diff=b''.join(chunkiter))
1638 1845
1846
1639 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match): 1847 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1640 """Export changesets to stdout or a single file""" 1848 """Export changesets to stdout or a single file"""
1641 for seqno, rev in enumerate(revs, 1): 1849 for seqno, rev in enumerate(revs, 1):
1642 ctx = repo[rev] 1850 ctx = repo[rev]
1643 if not dest.startswith('<'): 1851 if not dest.startswith('<'):
1644 repo.ui.note("%s\n" % dest) 1852 repo.ui.note("%s\n" % dest)
1645 fm.startitem() 1853 fm.startitem()
1646 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts) 1854 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1647 1855
1648 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts, 1856
1649 match): 1857 def _exportfntemplate(
1858 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
1859 ):
1650 """Export changesets to possibly multiple files""" 1860 """Export changesets to possibly multiple files"""
1651 total = len(revs) 1861 total = len(revs)
1652 revwidth = max(len(str(rev)) for rev in revs) 1862 revwidth = max(len(str(rev)) for rev in revs)
1653 filemap = util.sortdict() # filename: [(seqno, rev), ...] 1863 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1654 1864
1655 for seqno, rev in enumerate(revs, 1): 1865 for seqno, rev in enumerate(revs, 1):
1656 ctx = repo[rev] 1866 ctx = repo[rev]
1657 dest = makefilename(ctx, fntemplate, 1867 dest = makefilename(
1658 total=total, seqno=seqno, revwidth=revwidth) 1868 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
1869 )
1659 filemap.setdefault(dest, []).append((seqno, rev)) 1870 filemap.setdefault(dest, []).append((seqno, rev))
1660 1871
1661 for dest in filemap: 1872 for dest in filemap:
1662 with formatter.maybereopen(basefm, dest) as fm: 1873 with formatter.maybereopen(basefm, dest) as fm:
1663 repo.ui.note("%s\n" % dest) 1874 repo.ui.note("%s\n" % dest)
1664 for seqno, rev in filemap[dest]: 1875 for seqno, rev in filemap[dest]:
1665 fm.startitem() 1876 fm.startitem()
1666 ctx = repo[rev] 1877 ctx = repo[rev]
1667 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, 1878 _exportsingle(
1668 diffopts) 1879 repo, ctx, fm, match, switch_parent, seqno, diffopts
1880 )
1881
1669 1882
1670 def _prefetchchangedfiles(repo, revs, match): 1883 def _prefetchchangedfiles(repo, revs, match):
1671 allfiles = set() 1884 allfiles = set()
1672 for rev in revs: 1885 for rev in revs:
1673 for file in repo[rev].files(): 1886 for file in repo[rev].files():
1674 if not match or match(file): 1887 if not match or match(file):
1675 allfiles.add(file) 1888 allfiles.add(file)
1676 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles)) 1889 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
1677 1890
1678 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False, 1891
1679 opts=None, match=None): 1892 def export(
1893 repo,
1894 revs,
1895 basefm,
1896 fntemplate='hg-%h.patch',
1897 switch_parent=False,
1898 opts=None,
1899 match=None,
1900 ):
1680 '''export changesets as hg patches 1901 '''export changesets as hg patches
1681 1902
1682 Args: 1903 Args:
1683 repo: The repository from which we're exporting revisions. 1904 repo: The repository from which we're exporting revisions.
1684 revs: A list of revisions to export as revision numbers. 1905 revs: A list of revisions to export as revision numbers.
1702 _prefetchchangedfiles(repo, revs, match) 1923 _prefetchchangedfiles(repo, revs, match)
1703 1924
1704 if not fntemplate: 1925 if not fntemplate:
1705 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match) 1926 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1706 else: 1927 else:
1707 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts, 1928 _exportfntemplate(
1708 match) 1929 repo, revs, basefm, fntemplate, switch_parent, opts, match
1930 )
1931
1709 1932
1710 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None): 1933 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1711 """Export changesets to the given file stream""" 1934 """Export changesets to the given file stream"""
1712 _prefetchchangedfiles(repo, revs, match) 1935 _prefetchchangedfiles(repo, revs, match)
1713 1936
1714 dest = getattr(fp, 'name', '<unnamed>') 1937 dest = getattr(fp, 'name', '<unnamed>')
1715 with formatter.formatter(repo.ui, fp, 'export', {}) as fm: 1938 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1716 _exportfile(repo, revs, fm, dest, switch_parent, opts, match) 1939 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1940
1717 1941
1718 def showmarker(fm, marker, index=None): 1942 def showmarker(fm, marker, index=None):
1719 """utility function to display obsolescence marker in a readable way 1943 """utility function to display obsolescence marker in a readable way
1720 1944
1721 To be used by debug function.""" 1945 To be used by debug function."""
1722 if index is not None: 1946 if index is not None:
1723 fm.write('index', '%i ', index) 1947 fm.write('index', '%i ', index)
1724 fm.write('prednode', '%s ', hex(marker.prednode())) 1948 fm.write('prednode', '%s ', hex(marker.prednode()))
1725 succs = marker.succnodes() 1949 succs = marker.succnodes()
1726 fm.condwrite(succs, 'succnodes', '%s ', 1950 fm.condwrite(
1727 fm.formatlist(map(hex, succs), name='node')) 1951 succs, 'succnodes', '%s ', fm.formatlist(map(hex, succs), name='node')
1952 )
1728 fm.write('flag', '%X ', marker.flags()) 1953 fm.write('flag', '%X ', marker.flags())
1729 parents = marker.parentnodes() 1954 parents = marker.parentnodes()
1730 if parents is not None: 1955 if parents is not None:
1731 fm.write('parentnodes', '{%s} ', 1956 fm.write(
1732 fm.formatlist(map(hex, parents), name='node', sep=', ')) 1957 'parentnodes',
1958 '{%s} ',
1959 fm.formatlist(map(hex, parents), name='node', sep=', '),
1960 )
1733 fm.write('date', '(%s) ', fm.formatdate(marker.date())) 1961 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1734 meta = marker.metadata().copy() 1962 meta = marker.metadata().copy()
1735 meta.pop('date', None) 1963 meta.pop('date', None)
1736 smeta = pycompat.rapply(pycompat.maybebytestr, meta) 1964 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1737 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', ')) 1965 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1738 fm.plain('\n') 1966 fm.plain('\n')
1739 1967
1968
1740 def finddate(ui, repo, date): 1969 def finddate(ui, repo, date):
1741 """Find the tipmost changeset that matches the given date spec""" 1970 """Find the tipmost changeset that matches the given date spec"""
1742 1971
1743 df = dateutil.matchdate(date) 1972 df = dateutil.matchdate(date)
1744 m = scmutil.matchall(repo) 1973 m = scmutil.matchall(repo)
1750 results[ctx.rev()] = d 1979 results[ctx.rev()] = d
1751 1980
1752 for ctx in walkchangerevs(repo, m, {'rev': None}, prep): 1981 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1753 rev = ctx.rev() 1982 rev = ctx.rev()
1754 if rev in results: 1983 if rev in results:
1755 ui.status(_("found revision %s from %s\n") % 1984 ui.status(
1756 (rev, dateutil.datestr(results[rev]))) 1985 _("found revision %s from %s\n")
1986 % (rev, dateutil.datestr(results[rev]))
1987 )
1757 return '%d' % rev 1988 return '%d' % rev
1758 1989
1759 raise error.Abort(_("revision matching date not found")) 1990 raise error.Abort(_("revision matching date not found"))
1991
1760 1992
1761 def increasingwindows(windowsize=8, sizelimit=512): 1993 def increasingwindows(windowsize=8, sizelimit=512):
1762 while True: 1994 while True:
1763 yield windowsize 1995 yield windowsize
1764 if windowsize < sizelimit: 1996 if windowsize < sizelimit:
1765 windowsize *= 2 1997 windowsize *= 2
1998
1766 1999
1767 def _walkrevs(repo, opts): 2000 def _walkrevs(repo, opts):
1768 # Default --rev value depends on --follow but --follow behavior 2001 # Default --rev value depends on --follow but --follow behavior
1769 # depends on revisions resolved from --rev... 2002 # depends on revisions resolved from --rev...
1770 follow = opts.get('follow') or opts.get('follow_first') 2003 follow = opts.get('follow') or opts.get('follow_first')
1777 else: 2010 else:
1778 revs = smartset.spanset(repo) 2011 revs = smartset.spanset(repo)
1779 revs.reverse() 2012 revs.reverse()
1780 return revs 2013 return revs
1781 2014
2015
1782 class FileWalkError(Exception): 2016 class FileWalkError(Exception):
1783 pass 2017 pass
2018
1784 2019
1785 def walkfilerevs(repo, match, follow, revs, fncache): 2020 def walkfilerevs(repo, match, follow, revs, fncache):
1786 '''Walks the file history for the matched files. 2021 '''Walks the file history for the matched files.
1787 2022
1788 Returns the changeset revs that are involved in the file history. 2023 Returns the changeset revs that are involved in the file history.
1791 filelogs alone. 2026 filelogs alone.
1792 ''' 2027 '''
1793 wanted = set() 2028 wanted = set()
1794 copies = [] 2029 copies = []
1795 minrev, maxrev = min(revs), max(revs) 2030 minrev, maxrev = min(revs), max(revs)
2031
1796 def filerevs(filelog, last): 2032 def filerevs(filelog, last):
1797 """ 2033 """
1798 Only files, no patterns. Check the history of each file. 2034 Only files, no patterns. Check the history of each file.
1799 2035
1800 Examines filelog entries within minrev, maxrev linkrev range 2036 Examines filelog entries within minrev, maxrev linkrev range
1815 parentlinkrevs = [] 2051 parentlinkrevs = []
1816 for p in filelog.parentrevs(j): 2052 for p in filelog.parentrevs(j):
1817 if p != nullrev: 2053 if p != nullrev:
1818 parentlinkrevs.append(filelog.linkrev(p)) 2054 parentlinkrevs.append(filelog.linkrev(p))
1819 n = filelog.node(j) 2055 n = filelog.node(j)
1820 revs.append((linkrev, parentlinkrevs, 2056 revs.append(
1821 follow and filelog.renamed(n))) 2057 (linkrev, parentlinkrevs, follow and filelog.renamed(n))
2058 )
1822 2059
1823 return reversed(revs) 2060 return reversed(revs)
2061
1824 def iterfiles(): 2062 def iterfiles():
1825 pctx = repo['.'] 2063 pctx = repo['.']
1826 for filename in match.files(): 2064 for filename in match.files():
1827 if follow: 2065 if follow:
1828 if filename not in pctx: 2066 if filename not in pctx:
1829 raise error.Abort(_('cannot follow file not in parent ' 2067 raise error.Abort(
1830 'revision: "%s"') % filename) 2068 _('cannot follow file not in parent ' 'revision: "%s"')
2069 % filename
2070 )
1831 yield filename, pctx[filename].filenode() 2071 yield filename, pctx[filename].filenode()
1832 else: 2072 else:
1833 yield filename, None 2073 yield filename, None
1834 for filename_node in copies: 2074 for filename_node in copies:
1835 yield filename_node 2075 yield filename_node
1840 if node is None: 2080 if node is None:
1841 # A zero count may be a directory or deleted file, so 2081 # A zero count may be a directory or deleted file, so
1842 # try to find matching entries on the slow path. 2082 # try to find matching entries on the slow path.
1843 if follow: 2083 if follow:
1844 raise error.Abort( 2084 raise error.Abort(
1845 _('cannot follow nonexistent file: "%s"') % file_) 2085 _('cannot follow nonexistent file: "%s"') % file_
2086 )
1846 raise FileWalkError("Cannot walk via filelog") 2087 raise FileWalkError("Cannot walk via filelog")
1847 else: 2088 else:
1848 continue 2089 continue
1849 2090
1850 if node is None: 2091 if node is None:
1877 if copied: 2118 if copied:
1878 copies.append(copied) 2119 copies.append(copied)
1879 2120
1880 return wanted 2121 return wanted
1881 2122
2123
1882 class _followfilter(object): 2124 class _followfilter(object):
1883 def __init__(self, repo, onlyfirst=False): 2125 def __init__(self, repo, onlyfirst=False):
1884 self.repo = repo 2126 self.repo = repo
1885 self.startrev = nullrev 2127 self.startrev = nullrev
1886 self.roots = set() 2128 self.roots = set()
1889 def match(self, rev): 2131 def match(self, rev):
1890 def realparents(rev): 2132 def realparents(rev):
1891 if self.onlyfirst: 2133 if self.onlyfirst:
1892 return self.repo.changelog.parentrevs(rev)[0:1] 2134 return self.repo.changelog.parentrevs(rev)[0:1]
1893 else: 2135 else:
1894 return filter(lambda x: x != nullrev, 2136 return filter(
1895 self.repo.changelog.parentrevs(rev)) 2137 lambda x: x != nullrev, self.repo.changelog.parentrevs(rev)
2138 )
1896 2139
1897 if self.startrev == nullrev: 2140 if self.startrev == nullrev:
1898 self.startrev = rev 2141 self.startrev = rev
1899 return True 2142 return True
1900 2143
1915 self.roots.update(realparents(rev)) 2158 self.roots.update(realparents(rev))
1916 return True 2159 return True
1917 2160
1918 return False 2161 return False
1919 2162
2163
1920 def walkchangerevs(repo, match, opts, prepare): 2164 def walkchangerevs(repo, match, opts, prepare):
1921 '''Iterate over files and the revs in which they changed. 2165 '''Iterate over files and the revs in which they changed.
1922 2166
1923 Callers most commonly need to iterate backwards over the history 2167 Callers most commonly need to iterate backwards over the history
1924 in which they are interested. Doing so has awful (quadratic-looking) 2168 in which they are interested. Doing so has awful (quadratic-looking)
1970 if slowpath: 2214 if slowpath:
1971 # We have to read the changelog to match filenames against 2215 # We have to read the changelog to match filenames against
1972 # changed files 2216 # changed files
1973 2217
1974 if follow: 2218 if follow:
1975 raise error.Abort(_('can only follow copies/renames for explicit ' 2219 raise error.Abort(
1976 'filenames')) 2220 _('can only follow copies/renames for explicit ' 'filenames')
2221 )
1977 2222
1978 # The slow path checks files modified in every changeset. 2223 # The slow path checks files modified in every changeset.
1979 # This is really slow on large repos, so compute the set lazily. 2224 # This is really slow on large repos, so compute the set lazily.
1980 class lazywantedset(object): 2225 class lazywantedset(object):
1981 def __init__(self): 2226 def __init__(self):
2021 # Now that wanted is correctly initialized, we can iterate over the 2266 # Now that wanted is correctly initialized, we can iterate over the
2022 # revision range, yielding only revisions in wanted. 2267 # revision range, yielding only revisions in wanted.
2023 def iterate(): 2268 def iterate():
2024 if follow and match.always(): 2269 if follow and match.always():
2025 ff = _followfilter(repo, onlyfirst=opts.get('follow_first')) 2270 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2271
2026 def want(rev): 2272 def want(rev):
2027 return ff.match(rev) and rev in wanted 2273 return ff.match(rev) and rev in wanted
2274
2028 else: 2275 else:
2276
2029 def want(rev): 2277 def want(rev):
2030 return rev in wanted 2278 return rev in wanted
2031 2279
2032 it = iter(revs) 2280 it = iter(revs)
2033 stopiteration = False 2281 stopiteration = False
2042 nrevs.append(rev) 2290 nrevs.append(rev)
2043 for rev in sorted(nrevs): 2291 for rev in sorted(nrevs):
2044 fns = fncache.get(rev) 2292 fns = fncache.get(rev)
2045 ctx = change(rev) 2293 ctx = change(rev)
2046 if not fns: 2294 if not fns:
2295
2047 def fns_generator(): 2296 def fns_generator():
2048 if allfiles: 2297 if allfiles:
2049 fiter = iter(ctx) 2298 fiter = iter(ctx)
2050 else: 2299 else:
2051 fiter = ctx.files() 2300 fiter = ctx.files()
2052 for f in fiter: 2301 for f in fiter:
2053 if match(f): 2302 if match(f):
2054 yield f 2303 yield f
2304
2055 fns = fns_generator() 2305 fns = fns_generator()
2056 prepare(ctx, fns) 2306 prepare(ctx, fns)
2057 for rev in nrevs: 2307 for rev in nrevs:
2058 yield change(rev) 2308 yield change(rev)
2059 2309
2060 if stopiteration: 2310 if stopiteration:
2061 break 2311 break
2062 2312
2063 return iterate() 2313 return iterate()
2314
2064 2315
2065 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts): 2316 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2066 bad = [] 2317 bad = []
2067 2318
2068 badfn = lambda x, y: bad.append(x) or match.bad(x, y) 2319 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2076 match = repo.narrowmatch(match, includeexact=True) 2327 match = repo.narrowmatch(match, includeexact=True)
2077 badmatch = matchmod.badmatch(match, badfn) 2328 badmatch = matchmod.badmatch(match, badfn)
2078 dirstate = repo.dirstate 2329 dirstate = repo.dirstate
2079 # We don't want to just call wctx.walk here, since it would return a lot of 2330 # We don't want to just call wctx.walk here, since it would return a lot of
2080 # clean files, which we aren't interested in and takes time. 2331 # clean files, which we aren't interested in and takes time.
2081 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate), 2332 for f in sorted(
2082 unknown=True, ignored=False, full=False)): 2333 dirstate.walk(
2334 badmatch,
2335 subrepos=sorted(wctx.substate),
2336 unknown=True,
2337 ignored=False,
2338 full=False,
2339 )
2340 ):
2083 exact = match.exact(f) 2341 exact = match.exact(f)
2084 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f): 2342 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2085 if cca: 2343 if cca:
2086 cca(f) 2344 cca(f)
2087 names.append(f) 2345 names.append(f)
2088 if ui.verbose or not exact: 2346 if ui.verbose or not exact:
2089 ui.status(_('adding %s\n') % uipathfn(f), 2347 ui.status(
2090 label='ui.addremove.added') 2348 _('adding %s\n') % uipathfn(f), label='ui.addremove.added'
2349 )
2091 2350
2092 for subpath in sorted(wctx.substate): 2351 for subpath in sorted(wctx.substate):
2093 sub = wctx.sub(subpath) 2352 sub = wctx.sub(subpath)
2094 try: 2353 try:
2095 submatch = matchmod.subdirmatcher(subpath, match) 2354 submatch = matchmod.subdirmatcher(subpath, match)
2096 subprefix = repo.wvfs.reljoin(prefix, subpath) 2355 subprefix = repo.wvfs.reljoin(prefix, subpath)
2097 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) 2356 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2098 if opts.get(r'subrepos'): 2357 if opts.get(r'subrepos'):
2099 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, False, 2358 bad.extend(
2100 **opts)) 2359 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2360 )
2101 else: 2361 else:
2102 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, True, 2362 bad.extend(
2103 **opts)) 2363 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2364 )
2104 except error.LookupError: 2365 except error.LookupError:
2105 ui.status(_("skipping missing subrepository: %s\n") 2366 ui.status(
2106 % uipathfn(subpath)) 2367 _("skipping missing subrepository: %s\n") % uipathfn(subpath)
2368 )
2107 2369
2108 if not opts.get(r'dry_run'): 2370 if not opts.get(r'dry_run'):
2109 rejected = wctx.add(names, prefix) 2371 rejected = wctx.add(names, prefix)
2110 bad.extend(f for f in rejected if f in match.files()) 2372 bad.extend(f for f in rejected if f in match.files())
2111 return bad 2373 return bad
2112 2374
2375
2113 def addwebdirpath(repo, serverpath, webconf): 2376 def addwebdirpath(repo, serverpath, webconf):
2114 webconf[serverpath] = repo.root 2377 webconf[serverpath] = repo.root
2115 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root)) 2378 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2116 2379
2117 for r in repo.revs('filelog("path:.hgsub")'): 2380 for r in repo.revs('filelog("path:.hgsub")'):
2118 ctx = repo[r] 2381 ctx = repo[r]
2119 for subpath in ctx.substate: 2382 for subpath in ctx.substate:
2120 ctx.sub(subpath).addwebdirpath(serverpath, webconf) 2383 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2121 2384
2122 def forget(ui, repo, match, prefix, uipathfn, explicitonly, dryrun, 2385
2123 interactive): 2386 def forget(
2387 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2388 ):
2124 if dryrun and interactive: 2389 if dryrun and interactive:
2125 raise error.Abort(_("cannot specify both --dry-run and --interactive")) 2390 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2126 bad = [] 2391 bad = []
2127 badfn = lambda x, y: bad.append(x) or match.bad(x, y) 2392 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2128 wctx = repo[None] 2393 wctx = repo[None]
2137 sub = wctx.sub(subpath) 2402 sub = wctx.sub(subpath)
2138 submatch = matchmod.subdirmatcher(subpath, match) 2403 submatch = matchmod.subdirmatcher(subpath, match)
2139 subprefix = repo.wvfs.reljoin(prefix, subpath) 2404 subprefix = repo.wvfs.reljoin(prefix, subpath)
2140 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) 2405 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2141 try: 2406 try:
2142 subbad, subforgot = sub.forget(submatch, subprefix, subuipathfn, 2407 subbad, subforgot = sub.forget(
2143 dryrun=dryrun, 2408 submatch,
2144 interactive=interactive) 2409 subprefix,
2410 subuipathfn,
2411 dryrun=dryrun,
2412 interactive=interactive,
2413 )
2145 bad.extend([subpath + '/' + f for f in subbad]) 2414 bad.extend([subpath + '/' + f for f in subbad])
2146 forgot.extend([subpath + '/' + f for f in subforgot]) 2415 forgot.extend([subpath + '/' + f for f in subforgot])
2147 except error.LookupError: 2416 except error.LookupError:
2148 ui.status(_("skipping missing subrepository: %s\n") 2417 ui.status(
2149 % uipathfn(subpath)) 2418 _("skipping missing subrepository: %s\n") % uipathfn(subpath)
2419 )
2150 2420
2151 if not explicitonly: 2421 if not explicitonly:
2152 for f in match.files(): 2422 for f in match.files():
2153 if f not in repo.dirstate and not repo.wvfs.isdir(f): 2423 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2154 if f not in forgot: 2424 if f not in forgot:
2157 # But don't do this until after checking 'forgot', so 2427 # But don't do this until after checking 'forgot', so
2158 # that subrepo files aren't normalized, and this op is 2428 # that subrepo files aren't normalized, and this op is
2159 # purely from data cached by the status walk above. 2429 # purely from data cached by the status walk above.
2160 if repo.dirstate.normalize(f) in repo.dirstate: 2430 if repo.dirstate.normalize(f) in repo.dirstate:
2161 continue 2431 continue
2162 ui.warn(_('not removing %s: ' 2432 ui.warn(
2163 'file is already untracked\n') 2433 _('not removing %s: ' 'file is already untracked\n')
2164 % uipathfn(f)) 2434 % uipathfn(f)
2435 )
2165 bad.append(f) 2436 bad.append(f)
2166 2437
2167 if interactive: 2438 if interactive:
2168 responses = _('[Ynsa?]' 2439 responses = _(
2169 '$$ &Yes, forget this file' 2440 '[Ynsa?]'
2170 '$$ &No, skip this file' 2441 '$$ &Yes, forget this file'
2171 '$$ &Skip remaining files' 2442 '$$ &No, skip this file'
2172 '$$ Include &all remaining files' 2443 '$$ &Skip remaining files'
2173 '$$ &? (display help)') 2444 '$$ Include &all remaining files'
2445 '$$ &? (display help)'
2446 )
2174 for filename in forget[:]: 2447 for filename in forget[:]:
2175 r = ui.promptchoice(_('forget %s %s') % 2448 r = ui.promptchoice(
2176 (uipathfn(filename), responses)) 2449 _('forget %s %s') % (uipathfn(filename), responses)
2177 if r == 4: # ? 2450 )
2451 if r == 4: # ?
2178 while r == 4: 2452 while r == 4:
2179 for c, t in ui.extractchoices(responses)[1]: 2453 for c, t in ui.extractchoices(responses)[1]:
2180 ui.write('%s - %s\n' % (c, encoding.lower(t))) 2454 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2181 r = ui.promptchoice(_('forget %s %s') % 2455 r = ui.promptchoice(
2182 (uipathfn(filename), responses)) 2456 _('forget %s %s') % (uipathfn(filename), responses)
2183 if r == 0: # yes 2457 )
2458 if r == 0: # yes
2184 continue 2459 continue
2185 elif r == 1: # no 2460 elif r == 1: # no
2186 forget.remove(filename) 2461 forget.remove(filename)
2187 elif r == 2: # Skip 2462 elif r == 2: # Skip
2188 fnindex = forget.index(filename) 2463 fnindex = forget.index(filename)
2189 del forget[fnindex:] 2464 del forget[fnindex:]
2190 break 2465 break
2191 elif r == 3: # All 2466 elif r == 3: # All
2192 break 2467 break
2193 2468
2194 for f in forget: 2469 for f in forget:
2195 if ui.verbose or not match.exact(f) or interactive: 2470 if ui.verbose or not match.exact(f) or interactive:
2196 ui.status(_('removing %s\n') % uipathfn(f), 2471 ui.status(
2197 label='ui.addremove.removed') 2472 _('removing %s\n') % uipathfn(f), label='ui.addremove.removed'
2473 )
2198 2474
2199 if not dryrun: 2475 if not dryrun:
2200 rejected = wctx.forget(forget, prefix) 2476 rejected = wctx.forget(forget, prefix)
2201 bad.extend(f for f in rejected if f in match.files()) 2477 bad.extend(f for f in rejected if f in match.files())
2202 forgot.extend(f for f in forget if f not in rejected) 2478 forgot.extend(f for f in forget if f not in rejected)
2203 return bad, forgot 2479 return bad, forgot
2480
2204 2481
2205 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos): 2482 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2206 ret = 1 2483 ret = 1
2207 2484
2208 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint() 2485 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2217 ret = 0 2494 ret = 0
2218 2495
2219 for subpath in sorted(ctx.substate): 2496 for subpath in sorted(ctx.substate):
2220 submatch = matchmod.subdirmatcher(subpath, m) 2497 submatch = matchmod.subdirmatcher(subpath, m)
2221 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) 2498 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2222 if (subrepos or m.exact(subpath) or any(submatch.files())): 2499 if subrepos or m.exact(subpath) or any(submatch.files()):
2223 sub = ctx.sub(subpath) 2500 sub = ctx.sub(subpath)
2224 try: 2501 try:
2225 recurse = m.exact(subpath) or subrepos 2502 recurse = m.exact(subpath) or subrepos
2226 if sub.printfiles(ui, submatch, subuipathfn, fm, fmt, 2503 if (
2227 recurse) == 0: 2504 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2505 == 0
2506 ):
2228 ret = 0 2507 ret = 0
2229 except error.LookupError: 2508 except error.LookupError:
2230 ui.status(_("skipping missing subrepository: %s\n") 2509 ui.status(
2231 % uipathfn(subpath)) 2510 _("skipping missing subrepository: %s\n")
2511 % uipathfn(subpath)
2512 )
2232 2513
2233 return ret 2514 return ret
2234 2515
2235 def remove(ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, 2516
2236 warnings=None): 2517 def remove(
2518 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2519 ):
2237 ret = 0 2520 ret = 0
2238 s = repo.status(match=m, clean=True) 2521 s = repo.status(match=m, clean=True)
2239 modified, added, deleted, clean = s[0], s[1], s[3], s[6] 2522 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2240 2523
2241 wctx = repo[None] 2524 wctx = repo[None]
2245 warn = True 2528 warn = True
2246 else: 2529 else:
2247 warn = False 2530 warn = False
2248 2531
2249 subs = sorted(wctx.substate) 2532 subs = sorted(wctx.substate)
2250 progress = ui.makeprogress(_('searching'), total=len(subs), 2533 progress = ui.makeprogress(
2251 unit=_('subrepos')) 2534 _('searching'), total=len(subs), unit=_('subrepos')
2535 )
2252 for subpath in subs: 2536 for subpath in subs:
2253 submatch = matchmod.subdirmatcher(subpath, m) 2537 submatch = matchmod.subdirmatcher(subpath, m)
2254 subprefix = repo.wvfs.reljoin(prefix, subpath) 2538 subprefix = repo.wvfs.reljoin(prefix, subpath)
2255 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) 2539 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2256 if subrepos or m.exact(subpath) or any(submatch.files()): 2540 if subrepos or m.exact(subpath) or any(submatch.files()):
2257 progress.increment() 2541 progress.increment()
2258 sub = wctx.sub(subpath) 2542 sub = wctx.sub(subpath)
2259 try: 2543 try:
2260 if sub.removefiles(submatch, subprefix, subuipathfn, after, 2544 if sub.removefiles(
2261 force, subrepos, dryrun, warnings): 2545 submatch,
2546 subprefix,
2547 subuipathfn,
2548 after,
2549 force,
2550 subrepos,
2551 dryrun,
2552 warnings,
2553 ):
2262 ret = 1 2554 ret = 1
2263 except error.LookupError: 2555 except error.LookupError:
2264 warnings.append(_("skipping missing subrepository: %s\n") 2556 warnings.append(
2265 % uipathfn(subpath)) 2557 _("skipping missing subrepository: %s\n")
2558 % uipathfn(subpath)
2559 )
2266 progress.complete() 2560 progress.complete()
2267 2561
2268 # warn about failure to delete explicit files/dirs 2562 # warn about failure to delete explicit files/dirs
2269 deleteddirs = util.dirs(deleted) 2563 deleteddirs = util.dirs(deleted)
2270 files = m.files() 2564 files = m.files()
2271 progress = ui.makeprogress(_('deleting'), total=len(files), 2565 progress = ui.makeprogress(_('deleting'), total=len(files), unit=_('files'))
2272 unit=_('files'))
2273 for f in files: 2566 for f in files:
2567
2274 def insubrepo(): 2568 def insubrepo():
2275 for subpath in wctx.substate: 2569 for subpath in wctx.substate:
2276 if f.startswith(subpath + '/'): 2570 if f.startswith(subpath + '/'):
2277 return True 2571 return True
2278 return False 2572 return False
2279 2573
2280 progress.increment() 2574 progress.increment()
2281 isdir = f in deleteddirs or wctx.hasdir(f) 2575 isdir = f in deleteddirs or wctx.hasdir(f)
2282 if (f in repo.dirstate or isdir or f == '.' 2576 if f in repo.dirstate or isdir or f == '.' or insubrepo() or f in subs:
2283 or insubrepo() or f in subs):
2284 continue 2577 continue
2285 2578
2286 if repo.wvfs.exists(f): 2579 if repo.wvfs.exists(f):
2287 if repo.wvfs.isdir(f): 2580 if repo.wvfs.isdir(f):
2288 warnings.append(_('not removing %s: no tracked files\n') 2581 warnings.append(
2289 % uipathfn(f)) 2582 _('not removing %s: no tracked files\n') % uipathfn(f)
2583 )
2290 else: 2584 else:
2291 warnings.append(_('not removing %s: file is untracked\n') 2585 warnings.append(
2292 % uipathfn(f)) 2586 _('not removing %s: file is untracked\n') % uipathfn(f)
2587 )
2293 # missing files will generate a warning elsewhere 2588 # missing files will generate a warning elsewhere
2294 ret = 1 2589 ret = 1
2295 progress.complete() 2590 progress.complete()
2296 2591
2297 if force: 2592 if force:
2298 list = modified + deleted + clean + added 2593 list = modified + deleted + clean + added
2299 elif after: 2594 elif after:
2300 list = deleted 2595 list = deleted
2301 remaining = modified + added + clean 2596 remaining = modified + added + clean
2302 progress = ui.makeprogress(_('skipping'), total=len(remaining), 2597 progress = ui.makeprogress(
2303 unit=_('files')) 2598 _('skipping'), total=len(remaining), unit=_('files')
2599 )
2304 for f in remaining: 2600 for f in remaining:
2305 progress.increment() 2601 progress.increment()
2306 if ui.verbose or (f in files): 2602 if ui.verbose or (f in files):
2307 warnings.append(_('not removing %s: file still exists\n') 2603 warnings.append(
2308 % uipathfn(f)) 2604 _('not removing %s: file still exists\n') % uipathfn(f)
2605 )
2309 ret = 1 2606 ret = 1
2310 progress.complete() 2607 progress.complete()
2311 else: 2608 else:
2312 list = deleted + clean 2609 list = deleted + clean
2313 progress = ui.makeprogress(_('skipping'), 2610 progress = ui.makeprogress(
2314 total=(len(modified) + len(added)), 2611 _('skipping'), total=(len(modified) + len(added)), unit=_('files')
2315 unit=_('files')) 2612 )
2316 for f in modified: 2613 for f in modified:
2317 progress.increment() 2614 progress.increment()
2318 warnings.append(_('not removing %s: file is modified (use -f' 2615 warnings.append(
2319 ' to force removal)\n') % uipathfn(f)) 2616 _(
2617 'not removing %s: file is modified (use -f'
2618 ' to force removal)\n'
2619 )
2620 % uipathfn(f)
2621 )
2320 ret = 1 2622 ret = 1
2321 for f in added: 2623 for f in added:
2322 progress.increment() 2624 progress.increment()
2323 warnings.append(_("not removing %s: file has been marked for add" 2625 warnings.append(
2324 " (use 'hg forget' to undo add)\n") % uipathfn(f)) 2626 _(
2627 "not removing %s: file has been marked for add"
2628 " (use 'hg forget' to undo add)\n"
2629 )
2630 % uipathfn(f)
2631 )
2325 ret = 1 2632 ret = 1
2326 progress.complete() 2633 progress.complete()
2327 2634
2328 list = sorted(list) 2635 list = sorted(list)
2329 progress = ui.makeprogress(_('deleting'), total=len(list), 2636 progress = ui.makeprogress(_('deleting'), total=len(list), unit=_('files'))
2330 unit=_('files'))
2331 for f in list: 2637 for f in list:
2332 if ui.verbose or not m.exact(f): 2638 if ui.verbose or not m.exact(f):
2333 progress.increment() 2639 progress.increment()
2334 ui.status(_('removing %s\n') % uipathfn(f), 2640 ui.status(
2335 label='ui.addremove.removed') 2641 _('removing %s\n') % uipathfn(f), label='ui.addremove.removed'
2642 )
2336 progress.complete() 2643 progress.complete()
2337 2644
2338 if not dryrun: 2645 if not dryrun:
2339 with repo.wlock(): 2646 with repo.wlock():
2340 if not after: 2647 if not after:
2341 for f in list: 2648 for f in list:
2342 if f in added: 2649 if f in added:
2343 continue # we never unlink added files on remove 2650 continue # we never unlink added files on remove
2344 rmdir = repo.ui.configbool('experimental', 2651 rmdir = repo.ui.configbool(
2345 'removeemptydirs') 2652 'experimental', 'removeemptydirs'
2653 )
2346 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir) 2654 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2347 repo[None].forget(list) 2655 repo[None].forget(list)
2348 2656
2349 if warn: 2657 if warn:
2350 for warning in warnings: 2658 for warning in warnings:
2351 ui.warn(warning) 2659 ui.warn(warning)
2352 2660
2353 return ret 2661 return ret
2354 2662
2663
2355 def _catfmtneedsdata(fm): 2664 def _catfmtneedsdata(fm):
2356 return not fm.datahint() or 'data' in fm.datahint() 2665 return not fm.datahint() or 'data' in fm.datahint()
2666
2357 2667
2358 def _updatecatformatter(fm, ctx, matcher, path, decode): 2668 def _updatecatformatter(fm, ctx, matcher, path, decode):
2359 """Hook for adding data to the formatter used by ``hg cat``. 2669 """Hook for adding data to the formatter used by ``hg cat``.
2360 2670
2361 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call 2671 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2371 fm.startitem() 2681 fm.startitem()
2372 fm.context(ctx=ctx) 2682 fm.context(ctx=ctx)
2373 fm.write('data', '%s', data) 2683 fm.write('data', '%s', data)
2374 fm.data(path=path) 2684 fm.data(path=path)
2375 2685
2686
2376 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts): 2687 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2377 err = 1 2688 err = 1
2378 opts = pycompat.byteskwargs(opts) 2689 opts = pycompat.byteskwargs(opts)
2379 2690
2380 def write(path): 2691 def write(path):
2381 filename = None 2692 filename = None
2382 if fntemplate: 2693 if fntemplate:
2383 filename = makefilename(ctx, fntemplate, 2694 filename = makefilename(
2384 pathname=os.path.join(prefix, path)) 2695 ctx, fntemplate, pathname=os.path.join(prefix, path)
2696 )
2385 # attempt to create the directory if it does not already exist 2697 # attempt to create the directory if it does not already exist
2386 try: 2698 try:
2387 os.makedirs(os.path.dirname(filename)) 2699 os.makedirs(os.path.dirname(filename))
2388 except OSError: 2700 except OSError:
2389 pass 2701 pass
2416 for subpath in sorted(ctx.substate): 2728 for subpath in sorted(ctx.substate):
2417 sub = ctx.sub(subpath) 2729 sub = ctx.sub(subpath)
2418 try: 2730 try:
2419 submatch = matchmod.subdirmatcher(subpath, matcher) 2731 submatch = matchmod.subdirmatcher(subpath, matcher)
2420 subprefix = os.path.join(prefix, subpath) 2732 subprefix = os.path.join(prefix, subpath)
2421 if not sub.cat(submatch, basefm, fntemplate, subprefix, 2733 if not sub.cat(
2422 **pycompat.strkwargs(opts)): 2734 submatch,
2735 basefm,
2736 fntemplate,
2737 subprefix,
2738 **pycompat.strkwargs(opts)
2739 ):
2423 err = 0 2740 err = 0
2424 except error.RepoLookupError: 2741 except error.RepoLookupError:
2425 ui.status(_("skipping missing subrepository: %s\n") % 2742 ui.status(
2426 uipathfn(subpath)) 2743 _("skipping missing subrepository: %s\n") % uipathfn(subpath)
2744 )
2427 2745
2428 return err 2746 return err
2747
2429 2748
2430 def commit(ui, repo, commitfunc, pats, opts): 2749 def commit(ui, repo, commitfunc, pats, opts):
2431 '''commit the specified files or all outstanding changes''' 2750 '''commit the specified files or all outstanding changes'''
2432 date = opts.get('date') 2751 date = opts.get('date')
2433 if date: 2752 if date:
2444 if dsguard: 2763 if dsguard:
2445 relative = scmutil.anypats(pats, opts) 2764 relative = scmutil.anypats(pats, opts)
2446 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative) 2765 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2447 if scmutil.addremove(repo, matcher, "", uipathfn, opts) != 0: 2766 if scmutil.addremove(repo, matcher, "", uipathfn, opts) != 0:
2448 raise error.Abort( 2767 raise error.Abort(
2449 _("failed to mark all new/missing files as added/removed")) 2768 _("failed to mark all new/missing files as added/removed")
2769 )
2450 2770
2451 return commitfunc(ui, repo, message, matcher, opts) 2771 return commitfunc(ui, repo, message, matcher, opts)
2772
2452 2773
2453 def samefile(f, ctx1, ctx2): 2774 def samefile(f, ctx1, ctx2):
2454 if f in ctx1.manifest(): 2775 if f in ctx1.manifest():
2455 a = ctx1.filectx(f) 2776 a = ctx1.filectx(f)
2456 if f in ctx2.manifest(): 2777 if f in ctx2.manifest():
2457 b = ctx2.filectx(f) 2778 b = ctx2.filectx(f)
2458 return (not a.cmp(b) 2779 return not a.cmp(b) and a.flags() == b.flags()
2459 and a.flags() == b.flags())
2460 else: 2780 else:
2461 return False 2781 return False
2462 else: 2782 else:
2463 return f not in ctx2.manifest() 2783 return f not in ctx2.manifest()
2464 2784
2785
2465 def amend(ui, repo, old, extra, pats, opts): 2786 def amend(ui, repo, old, extra, pats, opts):
2466 # avoid cycle context -> subrepo -> cmdutil 2787 # avoid cycle context -> subrepo -> cmdutil
2467 from . import context 2788 from . import context
2468 2789
2469 # amend will reuse the existing user if not specified, but the obsolete 2790 # amend will reuse the existing user if not specified, but the obsolete
2470 # marker creation requires that the current user's name is specified. 2791 # marker creation requires that the current user's name is specified.
2471 if obsolete.isenabled(repo, obsolete.createmarkersopt): 2792 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2472 ui.username() # raise exception if username not set 2793 ui.username() # raise exception if username not set
2473 2794
2474 ui.note(_('amending changeset %s\n') % old) 2795 ui.note(_('amending changeset %s\n') % old)
2475 base = old.p1() 2796 base = old.p1()
2476 2797
2477 with repo.wlock(), repo.lock(), repo.transaction('amend'): 2798 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2512 # add/remove the files to the working copy if the "addremove" option 2833 # add/remove the files to the working copy if the "addremove" option
2513 # was specified. 2834 # was specified.
2514 matcher = scmutil.match(wctx, pats, opts) 2835 matcher = scmutil.match(wctx, pats, opts)
2515 relative = scmutil.anypats(pats, opts) 2836 relative = scmutil.anypats(pats, opts)
2516 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative) 2837 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2517 if (opts.get('addremove') 2838 if opts.get('addremove') and scmutil.addremove(
2518 and scmutil.addremove(repo, matcher, "", uipathfn, opts)): 2839 repo, matcher, "", uipathfn, opts
2840 ):
2519 raise error.Abort( 2841 raise error.Abort(
2520 _("failed to mark all new/missing files as added/removed")) 2842 _("failed to mark all new/missing files as added/removed")
2843 )
2521 2844
2522 # Check subrepos. This depends on in-place wctx._status update in 2845 # Check subrepos. This depends on in-place wctx._status update in
2523 # subrepo.precommit(). To minimize the risk of this hack, we do 2846 # subrepo.precommit(). To minimize the risk of this hack, we do
2524 # nothing if .hgsub does not exist. 2847 # nothing if .hgsub does not exist.
2525 if '.hgsub' in wctx or '.hgsub' in old: 2848 if '.hgsub' in wctx or '.hgsub' in old:
2526 subs, commitsubs, newsubstate = subrepoutil.precommit( 2849 subs, commitsubs, newsubstate = subrepoutil.precommit(
2527 ui, wctx, wctx._status, matcher) 2850 ui, wctx, wctx._status, matcher
2851 )
2528 # amend should abort if commitsubrepos is enabled 2852 # amend should abort if commitsubrepos is enabled
2529 assert not commitsubs 2853 assert not commitsubs
2530 if subs: 2854 if subs:
2531 subrepoutil.writestate(repo, newsubstate) 2855 subrepoutil.writestate(repo, newsubstate)
2532 2856
2533 ms = mergemod.mergestate.read(repo) 2857 ms = mergemod.mergestate.read(repo)
2534 mergeutil.checkunresolved(ms) 2858 mergeutil.checkunresolved(ms)
2535 2859
2536 filestoamend = set(f for f in wctx.files() if matcher(f)) 2860 filestoamend = set(f for f in wctx.files() if matcher(f))
2537 2861
2538 changes = (len(filestoamend) > 0) 2862 changes = len(filestoamend) > 0
2539 if changes: 2863 if changes:
2540 # Recompute copies (avoid recording a -> b -> a) 2864 # Recompute copies (avoid recording a -> b -> a)
2541 copied = copies.pathcopies(base, wctx, matcher) 2865 copied = copies.pathcopies(base, wctx, matcher)
2542 if old.p2: 2866 if old.p2:
2543 copied.update(copies.pathcopies(old.p2(), wctx, matcher)) 2867 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2547 # copy, then those two files are the same and 2871 # copy, then those two files are the same and
2548 # we can discard X from our list of files. Likewise if X 2872 # we can discard X from our list of files. Likewise if X
2549 # was removed, it's no longer relevant. If X is missing (aka 2873 # was removed, it's no longer relevant. If X is missing (aka
2550 # deleted), old X must be preserved. 2874 # deleted), old X must be preserved.
2551 files.update(filestoamend) 2875 files.update(filestoamend)
2552 files = [f for f in files if (f not in filestoamend 2876 files = [
2553 or not samefile(f, wctx, base))] 2877 f
2878 for f in files
2879 if (f not in filestoamend or not samefile(f, wctx, base))
2880 ]
2554 2881
2555 def filectxfn(repo, ctx_, path): 2882 def filectxfn(repo, ctx_, path):
2556 try: 2883 try:
2557 # If the file being considered is not amongst the files 2884 # If the file being considered is not amongst the files
2558 # to be amended, we should return the file context from the 2885 # to be amended, we should return the file context from the
2566 if path in wctx.removed(): 2893 if path in wctx.removed():
2567 return None 2894 return None
2568 2895
2569 fctx = wctx[path] 2896 fctx = wctx[path]
2570 flags = fctx.flags() 2897 flags = fctx.flags()
2571 mctx = context.memfilectx(repo, ctx_, 2898 mctx = context.memfilectx(
2572 fctx.path(), fctx.data(), 2899 repo,
2573 islink='l' in flags, 2900 ctx_,
2574 isexec='x' in flags, 2901 fctx.path(),
2575 copysource=copied.get(path)) 2902 fctx.data(),
2903 islink='l' in flags,
2904 isexec='x' in flags,
2905 copysource=copied.get(path),
2906 )
2576 return mctx 2907 return mctx
2577 except KeyError: 2908 except KeyError:
2578 return None 2909 return None
2910
2579 else: 2911 else:
2580 ui.note(_('copying changeset %s to %s\n') % (old, base)) 2912 ui.note(_('copying changeset %s to %s\n') % (old, base))
2581 2913
2582 # Use version of files as in the old cset 2914 # Use version of files as in the old cset
2583 def filectxfn(repo, ctx_, path): 2915 def filectxfn(repo, ctx_, path):
2605 editor = getcommiteditor(edit=doedit, editform=editform) 2937 editor = getcommiteditor(edit=doedit, editform=editform)
2606 2938
2607 pureextra = extra.copy() 2939 pureextra = extra.copy()
2608 extra['amend_source'] = old.hex() 2940 extra['amend_source'] = old.hex()
2609 2941
2610 new = context.memctx(repo, 2942 new = context.memctx(
2611 parents=[base.node(), old.p2().node()], 2943 repo,
2612 text=message, 2944 parents=[base.node(), old.p2().node()],
2613 files=files, 2945 text=message,
2614 filectxfn=filectxfn, 2946 files=files,
2615 user=user, 2947 filectxfn=filectxfn,
2616 date=date, 2948 user=user,
2617 extra=extra, 2949 date=date,
2618 editor=editor) 2950 extra=extra,
2951 editor=editor,
2952 )
2619 2953
2620 newdesc = changelog.stripdesc(new.description()) 2954 newdesc = changelog.stripdesc(new.description())
2621 if ((not changes) 2955 if (
2956 (not changes)
2622 and newdesc == old.description() 2957 and newdesc == old.description()
2623 and user == old.user() 2958 and user == old.user()
2624 and (date == old.date() or datemaydiffer) 2959 and (date == old.date() or datemaydiffer)
2625 and pureextra == old.extra()): 2960 and pureextra == old.extra()
2961 ):
2626 # nothing changed. continuing here would create a new node 2962 # nothing changed. continuing here would create a new node
2627 # anyway because of the amend_source noise. 2963 # anyway because of the amend_source noise.
2628 # 2964 #
2629 # This not what we expect from amend. 2965 # This not what we expect from amend.
2630 return old.node() 2966 return old.node()
2639 mapping = {old.node(): (newid,)} 2975 mapping = {old.node(): (newid,)}
2640 obsmetadata = None 2976 obsmetadata = None
2641 if opts.get('note'): 2977 if opts.get('note'):
2642 obsmetadata = {'note': encoding.fromlocal(opts['note'])} 2978 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2643 backup = ui.configbool('rewrite', 'backup-bundle') 2979 backup = ui.configbool('rewrite', 'backup-bundle')
2644 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata, 2980 scmutil.cleanupnodes(
2645 fixphase=True, targetphase=commitphase, 2981 repo,
2646 backup=backup) 2982 mapping,
2983 'amend',
2984 metadata=obsmetadata,
2985 fixphase=True,
2986 targetphase=commitphase,
2987 backup=backup,
2988 )
2647 2989
2648 # Fixing the dirstate because localrepo.commitctx does not update 2990 # Fixing the dirstate because localrepo.commitctx does not update
2649 # it. This is rather convenient because we did not need to update 2991 # it. This is rather convenient because we did not need to update
2650 # the dirstate for all the files in the new commit which commitctx 2992 # the dirstate for all the files in the new commit which commitctx
2651 # could have done if it updated the dirstate. Now, we can 2993 # could have done if it updated the dirstate. Now, we can
2664 for f in removedfiles: 3006 for f in removedfiles:
2665 dirstate.drop(f) 3007 dirstate.drop(f)
2666 3008
2667 return newid 3009 return newid
2668 3010
3011
2669 def commiteditor(repo, ctx, subs, editform=''): 3012 def commiteditor(repo, ctx, subs, editform=''):
2670 if ctx.description(): 3013 if ctx.description():
2671 return ctx.description() 3014 return ctx.description()
2672 return commitforceeditor(repo, ctx, subs, editform=editform, 3015 return commitforceeditor(
2673 unchangedmessagedetection=True) 3016 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
2674 3017 )
2675 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None, 3018
2676 editform='', unchangedmessagedetection=False): 3019
3020 def commitforceeditor(
3021 repo,
3022 ctx,
3023 subs,
3024 finishdesc=None,
3025 extramsg=None,
3026 editform='',
3027 unchangedmessagedetection=False,
3028 ):
2677 if not extramsg: 3029 if not extramsg:
2678 extramsg = _("Leave message empty to abort commit.") 3030 extramsg = _("Leave message empty to abort commit.")
2679 3031
2680 forms = [e for e in editform.split('.') if e] 3032 forms = [e for e in editform.split('.') if e]
2681 forms.insert(0, 'changeset') 3033 forms.insert(0, 'changeset')
2682 templatetext = None 3034 templatetext = None
2683 while forms: 3035 while forms:
2684 ref = '.'.join(forms) 3036 ref = '.'.join(forms)
2685 if repo.ui.config('committemplate', ref): 3037 if repo.ui.config('committemplate', ref):
2686 templatetext = committext = buildcommittemplate( 3038 templatetext = committext = buildcommittemplate(
2687 repo, ctx, subs, extramsg, ref) 3039 repo, ctx, subs, extramsg, ref
3040 )
2688 break 3041 break
2689 forms.pop() 3042 forms.pop()
2690 else: 3043 else:
2691 committext = buildcommittext(repo, ctx, subs, extramsg) 3044 committext = buildcommittext(repo, ctx, subs, extramsg)
2692 3045
2697 # make in-memory changes visible to external process 3050 # make in-memory changes visible to external process
2698 tr = repo.currenttransaction() 3051 tr = repo.currenttransaction()
2699 repo.dirstate.write(tr) 3052 repo.dirstate.write(tr)
2700 pending = tr and tr.writepending() and repo.root 3053 pending = tr and tr.writepending() and repo.root
2701 3054
2702 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(), 3055 editortext = repo.ui.edit(
2703 editform=editform, pending=pending, 3056 committext,
2704 repopath=repo.path, action='commit') 3057 ctx.user(),
3058 ctx.extra(),
3059 editform=editform,
3060 pending=pending,
3061 repopath=repo.path,
3062 action='commit',
3063 )
2705 text = editortext 3064 text = editortext
2706 3065
2707 # strip away anything below this special string (used for editors that want 3066 # strip away anything below this special string (used for editors that want
2708 # to display the diff) 3067 # to display the diff)
2709 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE) 3068 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2710 if stripbelow: 3069 if stripbelow:
2711 text = text[:stripbelow.start()] 3070 text = text[: stripbelow.start()]
2712 3071
2713 text = re.sub("(?m)^HG:.*(\n|$)", "", text) 3072 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2714 os.chdir(olddir) 3073 os.chdir(olddir)
2715 3074
2716 if finishdesc: 3075 if finishdesc:
2720 if unchangedmessagedetection and editortext == templatetext: 3079 if unchangedmessagedetection and editortext == templatetext:
2721 raise error.Abort(_("commit message unchanged")) 3080 raise error.Abort(_("commit message unchanged"))
2722 3081
2723 return text 3082 return text
2724 3083
3084
2725 def buildcommittemplate(repo, ctx, subs, extramsg, ref): 3085 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2726 ui = repo.ui 3086 ui = repo.ui
2727 spec = formatter.templatespec(ref, None, None) 3087 spec = formatter.templatespec(ref, None, None)
2728 t = logcmdutil.changesettemplater(ui, repo, spec) 3088 t = logcmdutil.changesettemplater(ui, repo, spec)
2729 t.t.cache.update((k, templater.unquotestring(v)) 3089 t.t.cache.update(
2730 for k, v in repo.ui.configitems('committemplate')) 3090 (k, templater.unquotestring(v))
3091 for k, v in repo.ui.configitems('committemplate')
3092 )
2731 3093
2732 if not extramsg: 3094 if not extramsg:
2733 extramsg = '' # ensure that extramsg is string 3095 extramsg = '' # ensure that extramsg is string
2734 3096
2735 ui.pushbuffer() 3097 ui.pushbuffer()
2736 t.show(ctx, extramsg=extramsg) 3098 t.show(ctx, extramsg=extramsg)
2737 return ui.popbuffer() 3099 return ui.popbuffer()
2738 3100
3101
2739 def hgprefix(msg): 3102 def hgprefix(msg):
2740 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a]) 3103 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
3104
2741 3105
2742 def buildcommittext(repo, ctx, subs, extramsg): 3106 def buildcommittext(repo, ctx, subs, extramsg):
2743 edittext = [] 3107 edittext = []
2744 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed() 3108 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2745 if ctx.description(): 3109 if ctx.description():
2746 edittext.append(ctx.description()) 3110 edittext.append(ctx.description())
2747 edittext.append("") 3111 edittext.append("")
2748 edittext.append("") # Empty line between message and comments. 3112 edittext.append("") # Empty line between message and comments.
2749 edittext.append(hgprefix(_("Enter commit message." 3113 edittext.append(
2750 " Lines beginning with 'HG:' are removed."))) 3114 hgprefix(
3115 _(
3116 "Enter commit message."
3117 " Lines beginning with 'HG:' are removed."
3118 )
3119 )
3120 )
2751 edittext.append(hgprefix(extramsg)) 3121 edittext.append(hgprefix(extramsg))
2752 edittext.append("HG: --") 3122 edittext.append("HG: --")
2753 edittext.append(hgprefix(_("user: %s") % ctx.user())) 3123 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2754 if ctx.p2(): 3124 if ctx.p2():
2755 edittext.append(hgprefix(_("branch merge"))) 3125 edittext.append(hgprefix(_("branch merge")))
2765 edittext.append(hgprefix(_("no files changed"))) 3135 edittext.append(hgprefix(_("no files changed")))
2766 edittext.append("") 3136 edittext.append("")
2767 3137
2768 return "\n".join(edittext) 3138 return "\n".join(edittext)
2769 3139
3140
2770 def commitstatus(repo, node, branch, bheads=None, opts=None): 3141 def commitstatus(repo, node, branch, bheads=None, opts=None):
2771 if opts is None: 3142 if opts is None:
2772 opts = {} 3143 opts = {}
2773 ctx = repo[node] 3144 ctx = repo[node]
2774 parents = ctx.parents() 3145 parents = ctx.parents()
2775 3146
2776 if (not opts.get('amend') and bheads and node not in bheads and not 3147 if (
2777 [x for x in parents if x.node() in bheads and x.branch() == branch]): 3148 not opts.get('amend')
3149 and bheads
3150 and node not in bheads
3151 and not [
3152 x for x in parents if x.node() in bheads and x.branch() == branch
3153 ]
3154 ):
2778 repo.ui.status(_('created new head\n')) 3155 repo.ui.status(_('created new head\n'))
2779 # The message is not printed for initial roots. For the other 3156 # The message is not printed for initial roots. For the other
2780 # changesets, it is printed in the following situations: 3157 # changesets, it is printed in the following situations:
2781 # 3158 #
2782 # Par column: for the 2 parents with ... 3159 # Par column: for the 2 parents with ...
2813 if repo.ui.debugflag: 3190 if repo.ui.debugflag:
2814 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())) 3191 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2815 elif repo.ui.verbose: 3192 elif repo.ui.verbose:
2816 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx)) 3193 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2817 3194
3195
2818 def postcommitstatus(repo, pats, opts): 3196 def postcommitstatus(repo, pats, opts):
2819 return repo.status(match=scmutil.match(repo[None], pats, opts)) 3197 return repo.status(match=scmutil.match(repo[None], pats, opts))
3198
2820 3199
2821 def revert(ui, repo, ctx, parents, *pats, **opts): 3200 def revert(ui, repo, ctx, parents, *pats, **opts):
2822 opts = pycompat.byteskwargs(opts) 3201 opts = pycompat.byteskwargs(opts)
2823 parent, p2 = parents 3202 parent, p2 = parents
2824 node = ctx.node() 3203 node = ctx.node()
2873 names[abs] = m.exact(abs) 3252 names[abs] = m.exact(abs)
2874 3253
2875 # Find status of all file in `names`. 3254 # Find status of all file in `names`.
2876 m = scmutil.matchfiles(repo, names) 3255 m = scmutil.matchfiles(repo, names)
2877 3256
2878 changes = repo.status(node1=node, match=m, 3257 changes = repo.status(
2879 unknown=True, ignored=True, clean=True) 3258 node1=node, match=m, unknown=True, ignored=True, clean=True
3259 )
2880 else: 3260 else:
2881 changes = repo.status(node1=node, match=m) 3261 changes = repo.status(node1=node, match=m)
2882 for kind in changes: 3262 for kind in changes:
2883 for abs in kind: 3263 for abs in kind:
2884 names[abs] = m.exact(abs) 3264 names[abs] = m.exact(abs)
2885 3265
2886 m = scmutil.matchfiles(repo, names) 3266 m = scmutil.matchfiles(repo, names)
2887 3267
2888 modified = set(changes.modified) 3268 modified = set(changes.modified)
2889 added = set(changes.added) 3269 added = set(changes.added)
2890 removed = set(changes.removed) 3270 removed = set(changes.removed)
2891 _deleted = set(changes.deleted) 3271 _deleted = set(changes.deleted)
2892 unknown = set(changes.unknown) 3272 unknown = set(changes.unknown)
2893 unknown.update(changes.ignored) 3273 unknown.update(changes.ignored)
2894 clean = set(changes.clean) 3274 clean = set(changes.clean)
2895 modadded = set() 3275 modadded = set()
2896 3276
2897 # We need to account for the state of the file in the dirstate, 3277 # We need to account for the state of the file in the dirstate,
2898 # even when we revert against something else than parent. This will 3278 # even when we revert against something else than parent. This will
2899 # slightly alter the behavior of revert (doing back up or not, delete 3279 # slightly alter the behavior of revert (doing back up or not, delete
2906 localchanges = dsmodified | dsadded 3286 localchanges = dsmodified | dsadded
2907 modified, added, removed = set(), set(), set() 3287 modified, added, removed = set(), set(), set()
2908 else: 3288 else:
2909 changes = repo.status(node1=parent, match=m) 3289 changes = repo.status(node1=parent, match=m)
2910 dsmodified = set(changes.modified) 3290 dsmodified = set(changes.modified)
2911 dsadded = set(changes.added) 3291 dsadded = set(changes.added)
2912 dsremoved = set(changes.removed) 3292 dsremoved = set(changes.removed)
2913 # store all local modifications, useful later for rename detection 3293 # store all local modifications, useful later for rename detection
2914 localchanges = dsmodified | dsadded 3294 localchanges = dsmodified | dsadded
2915 3295
2916 # only take into account for removes between wc and target 3296 # only take into account for removes between wc and target
2917 clean |= dsremoved - removed 3297 clean |= dsremoved - removed
2922 modadded = added & dsmodified 3302 modadded = added & dsmodified
2923 added -= modadded 3303 added -= modadded
2924 3304
2925 # tell newly modified apart. 3305 # tell newly modified apart.
2926 dsmodified &= modified 3306 dsmodified &= modified
2927 dsmodified |= modified & dsadded # dirstate added may need backup 3307 dsmodified |= modified & dsadded # dirstate added may need backup
2928 modified -= dsmodified 3308 modified -= dsmodified
2929 3309
2930 # We need to wait for some post-processing to update this set 3310 # We need to wait for some post-processing to update this set
2931 # before making the distinction. The dirstate will be used for 3311 # before making the distinction. The dirstate will be used for
2932 # that purpose. 3312 # that purpose.
2987 dsremovunk.add(abs) 3367 dsremovunk.add(abs)
2988 dsremoved -= dsremovunk 3368 dsremoved -= dsremovunk
2989 3369
2990 # action to be actually performed by revert 3370 # action to be actually performed by revert
2991 # (<list of file>, message>) tuple 3371 # (<list of file>, message>) tuple
2992 actions = {'revert': ([], _('reverting %s\n')), 3372 actions = {
2993 'add': ([], _('adding %s\n')), 3373 'revert': ([], _('reverting %s\n')),
2994 'remove': ([], _('removing %s\n')), 3374 'add': ([], _('adding %s\n')),
2995 'drop': ([], _('removing %s\n')), 3375 'remove': ([], _('removing %s\n')),
2996 'forget': ([], _('forgetting %s\n')), 3376 'drop': ([], _('removing %s\n')),
2997 'undelete': ([], _('undeleting %s\n')), 3377 'forget': ([], _('forgetting %s\n')),
2998 'noop': (None, _('no changes needed to %s\n')), 3378 'undelete': ([], _('undeleting %s\n')),
2999 'unknown': (None, _('file not managed: %s\n')), 3379 'noop': (None, _('no changes needed to %s\n')),
3000 } 3380 'unknown': (None, _('file not managed: %s\n')),
3381 }
3001 3382
3002 # "constant" that convey the backup strategy. 3383 # "constant" that convey the backup strategy.
3003 # All set to `discard` if `no-backup` is set do avoid checking 3384 # All set to `discard` if `no-backup` is set do avoid checking
3004 # no_backup lower in the code. 3385 # no_backup lower in the code.
3005 # These values are ordered for comparison purposes 3386 # These values are ordered for comparison purposes
3006 backupinteractive = 3 # do backup if interactively modified 3387 backupinteractive = 3 # do backup if interactively modified
3007 backup = 2 # unconditionally do backup 3388 backup = 2 # unconditionally do backup
3008 check = 1 # check if the existing file differs from target 3389 check = 1 # check if the existing file differs from target
3009 discard = 0 # never do backup 3390 discard = 0 # never do backup
3010 if opts.get('no_backup'): 3391 if opts.get('no_backup'):
3011 backupinteractive = backup = check = discard 3392 backupinteractive = backup = check = discard
3012 if interactive: 3393 if interactive:
3013 dsmodifiedbackup = backupinteractive 3394 dsmodifiedbackup = backupinteractive
3014 else: 3395 else:
3022 disptable = ( 3403 disptable = (
3023 # dispatch table: 3404 # dispatch table:
3024 # file state 3405 # file state
3025 # action 3406 # action
3026 # make backup 3407 # make backup
3027
3028 ## Sets that results that will change file on disk 3408 ## Sets that results that will change file on disk
3029 # Modified compared to target, no local change 3409 # Modified compared to target, no local change
3030 (modified, actions['revert'], discard), 3410 (modified, actions['revert'], discard),
3031 # Modified compared to target, but local file is deleted 3411 # Modified compared to target, but local file is deleted
3032 (deleted, actions['revert'], discard), 3412 (deleted, actions['revert'], discard),
3033 # Modified compared to target, local change 3413 # Modified compared to target, local change
3034 (dsmodified, actions['revert'], dsmodifiedbackup), 3414 (dsmodified, actions['revert'], dsmodifiedbackup),
3035 # Added since target 3415 # Added since target
3036 (added, actions['remove'], discard), 3416 (added, actions['remove'], discard),
3037 # Added in working directory 3417 # Added in working directory
3038 (dsadded, actions['forget'], discard), 3418 (dsadded, actions['forget'], discard),
3039 # Added since target, have local modification 3419 # Added since target, have local modification
3040 (modadded, backupanddel, backup), 3420 (modadded, backupanddel, backup),
3041 # Added since target but file is missing in working directory 3421 # Added since target but file is missing in working directory
3042 (deladded, actions['drop'], discard), 3422 (deladded, actions['drop'], discard),
3043 # Removed since target, before working copy parent 3423 # Removed since target, before working copy parent
3044 (removed, actions['add'], discard), 3424 (removed, actions['add'], discard),
3045 # Same as `removed` but an unknown file exists at the same path 3425 # Same as `removed` but an unknown file exists at the same path
3046 (removunk, actions['add'], check), 3426 (removunk, actions['add'], check),
3047 # Removed since targe, marked as such in working copy parent 3427 # Removed since targe, marked as such in working copy parent
3048 (dsremoved, actions['undelete'], discard), 3428 (dsremoved, actions['undelete'], discard),
3049 # Same as `dsremoved` but an unknown file exists at the same path 3429 # Same as `dsremoved` but an unknown file exists at the same path
3050 (dsremovunk, actions['undelete'], check), 3430 (dsremovunk, actions['undelete'], check),
3051 ## the following sets does not result in any file changes 3431 ## the following sets does not result in any file changes
3052 # File with no modification 3432 # File with no modification
3053 (clean, actions['noop'], discard), 3433 (clean, actions['noop'], discard),
3054 # Existing file, not tracked anywhere 3434 # Existing file, not tracked anywhere
3055 (unknown, actions['unknown'], discard), 3435 (unknown, actions['unknown'], discard),
3056 ) 3436 )
3057 3437
3058 for abs, exact in sorted(names.items()): 3438 for abs, exact in sorted(names.items()):
3059 # target file to be touch on disk (relative to cwd) 3439 # target file to be touch on disk (relative to cwd)
3060 target = repo.wjoin(abs) 3440 target = repo.wjoin(abs)
3061 # search the entry in the dispatch table. 3441 # search the entry in the dispatch table.
3069 if dobackup: 3449 if dobackup:
3070 # If in interactive mode, don't automatically create 3450 # If in interactive mode, don't automatically create
3071 # .orig files (issue4793) 3451 # .orig files (issue4793)
3072 if dobackup == backupinteractive: 3452 if dobackup == backupinteractive:
3073 tobackup.add(abs) 3453 tobackup.add(abs)
3074 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])): 3454 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3075 absbakname = scmutil.backuppath(ui, repo, abs) 3455 absbakname = scmutil.backuppath(ui, repo, abs)
3076 bakname = os.path.relpath(absbakname, 3456 bakname = os.path.relpath(
3077 start=repo.root) 3457 absbakname, start=repo.root
3078 ui.note(_('saving current version of %s as %s\n') % 3458 )
3079 (uipathfn(abs), uipathfn(bakname))) 3459 ui.note(
3460 _('saving current version of %s as %s\n')
3461 % (uipathfn(abs), uipathfn(bakname))
3462 )
3080 if not opts.get('dry_run'): 3463 if not opts.get('dry_run'):
3081 if interactive: 3464 if interactive:
3082 util.copyfile(target, absbakname) 3465 util.copyfile(target, absbakname)
3083 else: 3466 else:
3084 util.rename(target, absbakname) 3467 util.rename(target, absbakname)
3092 if not opts.get('dry_run'): 3475 if not opts.get('dry_run'):
3093 needdata = ('revert', 'add', 'undelete') 3476 needdata = ('revert', 'add', 'undelete')
3094 oplist = [actions[name][0] for name in needdata] 3477 oplist = [actions[name][0] for name in needdata]
3095 prefetch = scmutil.prefetchfiles 3478 prefetch = scmutil.prefetchfiles
3096 matchfiles = scmutil.matchfiles 3479 matchfiles = scmutil.matchfiles
3097 prefetch(repo, [ctx.rev()], 3480 prefetch(
3098 matchfiles(repo, 3481 repo,
3099 [f for sublist in oplist for f in sublist])) 3482 [ctx.rev()],
3483 matchfiles(repo, [f for sublist in oplist for f in sublist]),
3484 )
3100 match = scmutil.match(repo[None], pats) 3485 match = scmutil.match(repo[None], pats)
3101 _performrevert(repo, parents, ctx, names, uipathfn, actions, 3486 _performrevert(
3102 match, interactive, tobackup) 3487 repo,
3488 parents,
3489 ctx,
3490 names,
3491 uipathfn,
3492 actions,
3493 match,
3494 interactive,
3495 tobackup,
3496 )
3103 3497
3104 if targetsubs: 3498 if targetsubs:
3105 # Revert the subrepos on the revert list 3499 # Revert the subrepos on the revert list
3106 for sub in targetsubs: 3500 for sub in targetsubs:
3107 try: 3501 try:
3108 wctx.sub(sub).revert(ctx.substate[sub], *pats, 3502 wctx.sub(sub).revert(
3109 **pycompat.strkwargs(opts)) 3503 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3504 )
3110 except KeyError: 3505 except KeyError:
3111 raise error.Abort("subrepository '%s' does not exist in %s!" 3506 raise error.Abort(
3112 % (sub, short(ctx.node()))) 3507 "subrepository '%s' does not exist in %s!"
3113 3508 % (sub, short(ctx.node()))
3114 def _performrevert(repo, parents, ctx, names, uipathfn, actions, 3509 )
3115 match, interactive=False, tobackup=None): 3510
3511
3512 def _performrevert(
3513 repo,
3514 parents,
3515 ctx,
3516 names,
3517 uipathfn,
3518 actions,
3519 match,
3520 interactive=False,
3521 tobackup=None,
3522 ):
3116 """function that actually perform all the actions computed for revert 3523 """function that actually perform all the actions computed for revert
3117 3524
3118 This is an independent function to let extension to plug in and react to 3525 This is an independent function to let extension to plug in and react to
3119 the imminent revert. 3526 the imminent revert.
3120 3527
3143 3550
3144 audit_path = pathutil.pathauditor(repo.root, cached=True) 3551 audit_path = pathutil.pathauditor(repo.root, cached=True)
3145 for f in actions['forget'][0]: 3552 for f in actions['forget'][0]:
3146 if interactive: 3553 if interactive:
3147 choice = repo.ui.promptchoice( 3554 choice = repo.ui.promptchoice(
3148 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)) 3555 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3556 )
3149 if choice == 0: 3557 if choice == 0:
3150 prntstatusmsg('forget', f) 3558 prntstatusmsg('forget', f)
3151 repo.dirstate.drop(f) 3559 repo.dirstate.drop(f)
3152 else: 3560 else:
3153 excluded_files.append(f) 3561 excluded_files.append(f)
3156 repo.dirstate.drop(f) 3564 repo.dirstate.drop(f)
3157 for f in actions['remove'][0]: 3565 for f in actions['remove'][0]:
3158 audit_path(f) 3566 audit_path(f)
3159 if interactive: 3567 if interactive:
3160 choice = repo.ui.promptchoice( 3568 choice = repo.ui.promptchoice(
3161 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)) 3569 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3570 )
3162 if choice == 0: 3571 if choice == 0:
3163 prntstatusmsg('remove', f) 3572 prntstatusmsg('remove', f)
3164 doremove(f) 3573 doremove(f)
3165 else: 3574 else:
3166 excluded_files.append(f) 3575 excluded_files.append(f)
3185 newlyaddedandmodifiedfiles = set() 3594 newlyaddedandmodifiedfiles = set()
3186 if interactive: 3595 if interactive:
3187 # Prompt the user for changes to revert 3596 # Prompt the user for changes to revert
3188 torevert = [f for f in actions['revert'][0] if f not in excluded_files] 3597 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3189 m = scmutil.matchfiles(repo, torevert) 3598 m = scmutil.matchfiles(repo, torevert)
3190 diffopts = patch.difffeatureopts(repo.ui, whitespace=True, 3599 diffopts = patch.difffeatureopts(
3191 section='commands', 3600 repo.ui,
3192 configprefix='revert.interactive.') 3601 whitespace=True,
3602 section='commands',
3603 configprefix='revert.interactive.',
3604 )
3193 diffopts.nodates = True 3605 diffopts.nodates = True
3194 diffopts.git = True 3606 diffopts.git = True
3195 operation = 'apply' 3607 operation = 'apply'
3196 if node == parent: 3608 if node == parent:
3197 if repo.ui.configbool('experimental', 3609 if repo.ui.configbool(
3198 'revert.interactive.select-to-keep'): 3610 'experimental', 'revert.interactive.select-to-keep'
3611 ):
3199 operation = 'keep' 3612 operation = 'keep'
3200 else: 3613 else:
3201 operation = 'discard' 3614 operation = 'discard'
3202 3615
3203 if operation == 'apply': 3616 if operation == 'apply':
3206 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts) 3619 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3207 originalchunks = patch.parsepatch(diff) 3620 originalchunks = patch.parsepatch(diff)
3208 3621
3209 try: 3622 try:
3210 3623
3211 chunks, opts = recordfilter(repo.ui, originalchunks, match, 3624 chunks, opts = recordfilter(
3212 operation=operation) 3625 repo.ui, originalchunks, match, operation=operation
3626 )
3213 if operation == 'discard': 3627 if operation == 'discard':
3214 chunks = patch.reversehunks(chunks) 3628 chunks = patch.reversehunks(chunks)
3215 3629
3216 except error.PatchError as err: 3630 except error.PatchError as err:
3217 raise error.Abort(_('error parsing patch: %s') % err) 3631 raise error.Abort(_('error parsing patch: %s') % err)
3220 # performing a partial revert of the added file, the only option is 3634 # performing a partial revert of the added file, the only option is
3221 # "remove added file <name> (Yn)?", so we don't need to worry about the 3635 # "remove added file <name> (Yn)?", so we don't need to worry about the
3222 # alsorestore value. Ideally we'd be able to partially revert 3636 # alsorestore value. Ideally we'd be able to partially revert
3223 # copied/renamed files. 3637 # copied/renamed files.
3224 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified( 3638 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3225 chunks, originalchunks) 3639 chunks, originalchunks
3640 )
3226 if tobackup is None: 3641 if tobackup is None:
3227 tobackup = set() 3642 tobackup = set()
3228 # Apply changes 3643 # Apply changes
3229 fp = stringio() 3644 fp = stringio()
3230 # chunks are serialized per file, but files aren't sorted 3645 # chunks are serialized per file, but files aren't sorted
3271 if node == parent and p2 == nullid: 3686 if node == parent and p2 == nullid:
3272 normal = repo.dirstate.normal 3687 normal = repo.dirstate.normal
3273 for f in actions['undelete'][0]: 3688 for f in actions['undelete'][0]:
3274 if interactive: 3689 if interactive:
3275 choice = repo.ui.promptchoice( 3690 choice = repo.ui.promptchoice(
3276 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f) 3691 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3692 )
3277 if choice == 0: 3693 if choice == 0:
3278 prntstatusmsg('undelete', f) 3694 prntstatusmsg('undelete', f)
3279 checkout(f) 3695 checkout(f)
3280 normal(f) 3696 normal(f)
3281 else: 3697 else:
3289 3705
3290 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]: 3706 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3291 if f in copied: 3707 if f in copied:
3292 repo.dirstate.copy(copied[f], f) 3708 repo.dirstate.copy(copied[f], f)
3293 3709
3710
3294 # a list of (ui, repo, otherpeer, opts, missing) functions called by 3711 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3295 # commands.outgoing. "missing" is "missing" of the result of 3712 # commands.outgoing. "missing" is "missing" of the result of
3296 # "findcommonoutgoing()" 3713 # "findcommonoutgoing()"
3297 outgoinghooks = util.hooks() 3714 outgoinghooks = util.hooks()
3298 3715
3316 bailifchanged(). 3733 bailifchanged().
3317 ''' 3734 '''
3318 # Check for non-clearable states first, so things like rebase will take 3735 # Check for non-clearable states first, so things like rebase will take
3319 # precedence over update. 3736 # precedence over update.
3320 for state in statemod._unfinishedstates: 3737 for state in statemod._unfinishedstates:
3321 if (state._clearable or (commit and state._allowcommit) or 3738 if (
3322 state._reportonly): 3739 state._clearable
3740 or (commit and state._allowcommit)
3741 or state._reportonly
3742 ):
3323 continue 3743 continue
3324 if state.isunfinished(repo): 3744 if state.isunfinished(repo):
3325 raise error.Abort(state.msg(), hint=state.hint()) 3745 raise error.Abort(state.msg(), hint=state.hint())
3326 3746
3327 for s in statemod._unfinishedstates: 3747 for s in statemod._unfinishedstates:
3328 if (not s._clearable or (commit and s._allowcommit) or 3748 if (
3329 (s._opname == 'merge' and skipmerge) or s._reportonly): 3749 not s._clearable
3750 or (commit and s._allowcommit)
3751 or (s._opname == 'merge' and skipmerge)
3752 or s._reportonly
3753 ):
3330 continue 3754 continue
3331 if s.isunfinished(repo): 3755 if s.isunfinished(repo):
3332 raise error.Abort(s.msg(), hint=s.hint()) 3756 raise error.Abort(s.msg(), hint=s.hint())
3757
3333 3758
3334 def clearunfinished(repo): 3759 def clearunfinished(repo):
3335 '''Check for unfinished operations (as above), and clear the ones 3760 '''Check for unfinished operations (as above), and clear the ones
3336 that are clearable. 3761 that are clearable.
3337 ''' 3762 '''
3345 if s._opname == 'merge' or state._reportonly: 3770 if s._opname == 'merge' or state._reportonly:
3346 continue 3771 continue
3347 if s._clearable and s.isunfinished(repo): 3772 if s._clearable and s.isunfinished(repo):
3348 util.unlink(repo.vfs.join(s._fname)) 3773 util.unlink(repo.vfs.join(s._fname))
3349 3774
3775
3350 def getunfinishedstate(repo): 3776 def getunfinishedstate(repo):
3351 ''' Checks for unfinished operations and returns statecheck object 3777 ''' Checks for unfinished operations and returns statecheck object
3352 for it''' 3778 for it'''
3353 for state in statemod._unfinishedstates: 3779 for state in statemod._unfinishedstates:
3354 if state.isunfinished(repo): 3780 if state.isunfinished(repo):
3355 return state 3781 return state
3356 return None 3782 return None
3783
3357 3784
3358 def howtocontinue(repo): 3785 def howtocontinue(repo):
3359 '''Check for an unfinished operation and return the command to finish 3786 '''Check for an unfinished operation and return the command to finish
3360 it. 3787 it.
3361 3788
3374 return contmsg % state.continuemsg(), True 3801 return contmsg % state.continuemsg(), True
3375 if repo[None].dirty(missing=True, merge=False, branch=False): 3802 if repo[None].dirty(missing=True, merge=False, branch=False):
3376 return contmsg % _("hg commit"), False 3803 return contmsg % _("hg commit"), False
3377 return None, None 3804 return None, None
3378 3805
3806
3379 def checkafterresolved(repo): 3807 def checkafterresolved(repo):
3380 '''Inform the user about the next action after completing hg resolve 3808 '''Inform the user about the next action after completing hg resolve
3381 3809
3382 If there's a an unfinished operation that supports continue flag, 3810 If there's a an unfinished operation that supports continue flag,
3383 howtocontinue will yield repo.ui.warn as the reporter. 3811 howtocontinue will yield repo.ui.warn as the reporter.
3389 if warning: 3817 if warning:
3390 repo.ui.warn("%s\n" % msg) 3818 repo.ui.warn("%s\n" % msg)
3391 else: 3819 else:
3392 repo.ui.note("%s\n" % msg) 3820 repo.ui.note("%s\n" % msg)
3393 3821
3822
3394 def wrongtooltocontinue(repo, task): 3823 def wrongtooltocontinue(repo, task):
3395 '''Raise an abort suggesting how to properly continue if there is an 3824 '''Raise an abort suggesting how to properly continue if there is an
3396 active task. 3825 active task.
3397 3826
3398 Uses howtocontinue() to find the active task. 3827 Uses howtocontinue() to find the active task.
3403 after = howtocontinue(repo) 3832 after = howtocontinue(repo)
3404 hint = None 3833 hint = None
3405 if after[1]: 3834 if after[1]:
3406 hint = after[0] 3835 hint = after[0]
3407 raise error.Abort(_('no %s in progress') % task, hint=hint) 3836 raise error.Abort(_('no %s in progress') % task, hint=hint)
3837
3408 3838
3409 def abortgraft(ui, repo, graftstate): 3839 def abortgraft(ui, repo, graftstate):
3410 """abort the interrupted graft and rollbacks to the state before interrupted 3840 """abort the interrupted graft and rollbacks to the state before interrupted
3411 graft""" 3841 graft"""
3412 if not graftstate.exists(): 3842 if not graftstate.exists():
3424 else: 3854 else:
3425 startctx = repo['.'] 3855 startctx = repo['.']
3426 # whether to strip or not 3856 # whether to strip or not
3427 cleanup = False 3857 cleanup = False
3428 from . import hg 3858 from . import hg
3859
3429 if newnodes: 3860 if newnodes:
3430 newnodes = [repo[r].rev() for r in newnodes] 3861 newnodes = [repo[r].rev() for r in newnodes]
3431 cleanup = True 3862 cleanup = True
3432 # checking that none of the newnodes turned public or is public 3863 # checking that none of the newnodes turned public or is public
3433 immutable = [c for c in newnodes if not repo[c].mutable()] 3864 immutable = [c for c in newnodes if not repo[c].mutable()]
3434 if immutable: 3865 if immutable:
3435 repo.ui.warn(_("cannot clean up public changesets %s\n") 3866 repo.ui.warn(
3436 % ', '.join(bytes(repo[r]) for r in immutable), 3867 _("cannot clean up public changesets %s\n")
3437 hint=_("see 'hg help phases' for details")) 3868 % ', '.join(bytes(repo[r]) for r in immutable),
3869 hint=_("see 'hg help phases' for details"),
3870 )
3438 cleanup = False 3871 cleanup = False
3439 3872
3440 # checking that no new nodes are created on top of grafted revs 3873 # checking that no new nodes are created on top of grafted revs
3441 desc = set(repo.changelog.descendants(newnodes)) 3874 desc = set(repo.changelog.descendants(newnodes))
3442 if desc - set(newnodes): 3875 if desc - set(newnodes):
3443 repo.ui.warn(_("new changesets detected on destination " 3876 repo.ui.warn(
3444 "branch, can't strip\n")) 3877 _(
3878 "new changesets detected on destination "
3879 "branch, can't strip\n"
3880 )
3881 )
3445 cleanup = False 3882 cleanup = False
3446 3883
3447 if cleanup: 3884 if cleanup:
3448 with repo.wlock(), repo.lock(): 3885 with repo.wlock(), repo.lock():
3449 hg.updaterepo(repo, startctx.node(), overwrite=True) 3886 hg.updaterepo(repo, startctx.node(), overwrite=True)
3450 # stripping the new nodes created 3887 # stripping the new nodes created
3451 strippoints = [c.node() for c in repo.set("roots(%ld)", 3888 strippoints = [
3452 newnodes)] 3889 c.node() for c in repo.set("roots(%ld)", newnodes)
3890 ]
3453 repair.strip(repo.ui, repo, strippoints, backup=False) 3891 repair.strip(repo.ui, repo, strippoints, backup=False)
3454 3892
3455 if not cleanup: 3893 if not cleanup:
3456 # we don't update to the startnode if we can't strip 3894 # we don't update to the startnode if we can't strip
3457 startctx = repo['.'] 3895 startctx = repo['.']
3459 3897
3460 ui.status(_("graft aborted\n")) 3898 ui.status(_("graft aborted\n"))
3461 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12]) 3899 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
3462 graftstate.delete() 3900 graftstate.delete()
3463 return 0 3901 return 0
3902
3464 3903
3465 def readgraftstate(repo, graftstate): 3904 def readgraftstate(repo, graftstate):
3466 """read the graft state file and return a dict of the data stored in it""" 3905 """read the graft state file and return a dict of the data stored in it"""
3467 try: 3906 try:
3468 return graftstate.read() 3907 return graftstate.read()
3469 except error.CorruptedState: 3908 except error.CorruptedState:
3470 nodes = repo.vfs.read('graftstate').splitlines() 3909 nodes = repo.vfs.read('graftstate').splitlines()
3471 return {'nodes': nodes} 3910 return {'nodes': nodes}
3472 3911
3912
3473 def hgabortgraft(ui, repo): 3913 def hgabortgraft(ui, repo):
3474 """ abort logic for aborting graft using 'hg abort'""" 3914 """ abort logic for aborting graft using 'hg abort'"""
3475 with repo.wlock(): 3915 with repo.wlock():
3476 graftstate = statemod.cmdstate(repo, 'graftstate') 3916 graftstate = statemod.cmdstate(repo, 'graftstate')
3477 return abortgraft(ui, repo, graftstate) 3917 return abortgraft(ui, repo, graftstate)