mercurial/cmdutil.py
changeset 43076 2372284d9457
parent 42933 7e9997041781
child 43077 687b865b95ad
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)