mercurial/sparse.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43106 d783f945a701
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    42     excludes = set()
    42     excludes = set()
    43     profiles = set()
    43     profiles = set()
    44     current = None
    44     current = None
    45     havesection = False
    45     havesection = False
    46 
    46 
    47     for line in raw.split('\n'):
    47     for line in raw.split(b'\n'):
    48         line = line.strip()
    48         line = line.strip()
    49         if not line or line.startswith('#'):
    49         if not line or line.startswith(b'#'):
    50             # empty or comment line, skip
    50             # empty or comment line, skip
    51             continue
    51             continue
    52         elif line.startswith('%include '):
    52         elif line.startswith(b'%include '):
    53             line = line[9:].strip()
    53             line = line[9:].strip()
    54             if line:
    54             if line:
    55                 profiles.add(line)
    55                 profiles.add(line)
    56         elif line == '[include]':
    56         elif line == b'[include]':
    57             if havesection and current != includes:
    57             if havesection and current != includes:
    58                 # TODO pass filename into this API so we can report it.
    58                 # TODO pass filename into this API so we can report it.
    59                 raise error.Abort(
    59                 raise error.Abort(
    60                     _(
    60                     _(
    61                         '%(action)s config cannot have includes '
    61                         b'%(action)s config cannot have includes '
    62                         'after excludes'
    62                         b'after excludes'
    63                     )
    63                     )
    64                     % {'action': action}
    64                     % {b'action': action}
    65                 )
    65                 )
    66             havesection = True
    66             havesection = True
    67             current = includes
    67             current = includes
    68             continue
    68             continue
    69         elif line == '[exclude]':
    69         elif line == b'[exclude]':
    70             havesection = True
    70             havesection = True
    71             current = excludes
    71             current = excludes
    72         elif line:
    72         elif line:
    73             if current is None:
    73             if current is None:
    74                 raise error.Abort(
    74                 raise error.Abort(
    75                     _('%(action)s config entry outside of ' 'section: %(line)s')
    75                     _(
    76                     % {'action': action, 'line': line},
    76                         b'%(action)s config entry outside of '
       
    77                         b'section: %(line)s'
       
    78                     )
       
    79                     % {b'action': action, b'line': line},
    77                     hint=_(
    80                     hint=_(
    78                         'add an [include] or [exclude] line '
    81                         b'add an [include] or [exclude] line '
    79                         'to declare the entry type'
    82                         b'to declare the entry type'
    80                     ),
    83                     ),
    81                 )
    84                 )
    82 
    85 
    83             if line.strip().startswith('/'):
    86             if line.strip().startswith(b'/'):
    84                 ui.warn(
    87                 ui.warn(
    85                     _(
    88                     _(
    86                         'warning: %(action)s profile cannot use'
    89                         b'warning: %(action)s profile cannot use'
    87                         ' paths starting with /, ignoring %(line)s\n'
    90                         b' paths starting with /, ignoring %(line)s\n'
    88                     )
    91                     )
    89                     % {'action': action, 'line': line}
    92                     % {b'action': action, b'line': line}
    90                 )
    93                 )
    91                 continue
    94                 continue
    92             current.add(line)
    95             current.add(line)
    93 
    96 
    94     return includes, excludes, profiles
    97     return includes, excludes, profiles
   110     """
   113     """
   111     # Feature isn't enabled. No-op.
   114     # Feature isn't enabled. No-op.
   112     if not enabled:
   115     if not enabled:
   113         return set(), set(), set()
   116         return set(), set(), set()
   114 
   117 
   115     raw = repo.vfs.tryread('sparse')
   118     raw = repo.vfs.tryread(b'sparse')
   116     if not raw:
   119     if not raw:
   117         return set(), set(), set()
   120         return set(), set(), set()
   118 
   121 
   119     if rev is None:
   122     if rev is None:
   120         raise error.Abort(
   123         raise error.Abort(
   121             _('cannot parse sparse patterns from working ' 'directory')
   124             _(b'cannot parse sparse patterns from working ' b'directory')
   122         )
   125         )
   123 
   126 
   124     includes, excludes, profiles = parseconfig(repo.ui, raw, 'sparse')
   127     includes, excludes, profiles = parseconfig(repo.ui, raw, b'sparse')
   125     ctx = repo[rev]
   128     ctx = repo[rev]
   126 
   129 
   127     if profiles:
   130     if profiles:
   128         visited = set()
   131         visited = set()
   129         while profiles:
   132         while profiles:
   135 
   138 
   136             try:
   139             try:
   137                 raw = readprofile(repo, profile, rev)
   140                 raw = readprofile(repo, profile, rev)
   138             except error.ManifestLookupError:
   141             except error.ManifestLookupError:
   139                 msg = (
   142                 msg = (
   140                     "warning: sparse profile '%s' not found "
   143                     b"warning: sparse profile '%s' not found "
   141                     "in rev %s - ignoring it\n" % (profile, ctx)
   144                     b"in rev %s - ignoring it\n" % (profile, ctx)
   142                 )
   145                 )
   143                 # experimental config: sparse.missingwarning
   146                 # experimental config: sparse.missingwarning
   144                 if repo.ui.configbool('sparse', 'missingwarning'):
   147                 if repo.ui.configbool(b'sparse', b'missingwarning'):
   145                     repo.ui.warn(msg)
   148                     repo.ui.warn(msg)
   146                 else:
   149                 else:
   147                     repo.ui.debug(msg)
   150                     repo.ui.debug(msg)
   148                 continue
   151                 continue
   149 
   152 
   150             pincludes, pexcludes, subprofs = parseconfig(repo.ui, raw, 'sparse')
   153             pincludes, pexcludes, subprofs = parseconfig(
       
   154                 repo.ui, raw, b'sparse'
       
   155             )
   151             includes.update(pincludes)
   156             includes.update(pincludes)
   152             excludes.update(pexcludes)
   157             excludes.update(pexcludes)
   153             profiles.update(subprofs)
   158             profiles.update(subprofs)
   154 
   159 
   155         profiles = visited
   160         profiles = visited
   156 
   161 
   157     if includes:
   162     if includes:
   158         includes.add('.hg*')
   163         includes.add(b'.hg*')
   159 
   164 
   160     return includes, excludes, profiles
   165     return includes, excludes, profiles
   161 
   166 
   162 
   167 
   163 def activeconfig(repo):
   168 def activeconfig(repo):
   190 
   195 
   191     This is used to construct a cache key for matchers.
   196     This is used to construct a cache key for matchers.
   192     """
   197     """
   193     cache = repo._sparsesignaturecache
   198     cache = repo._sparsesignaturecache
   194 
   199 
   195     signature = cache.get('signature')
   200     signature = cache.get(b'signature')
   196 
   201 
   197     if includetemp:
   202     if includetemp:
   198         tempsignature = cache.get('tempsignature')
   203         tempsignature = cache.get(b'tempsignature')
   199     else:
   204     else:
   200         tempsignature = '0'
   205         tempsignature = b'0'
   201 
   206 
   202     if signature is None or (includetemp and tempsignature is None):
   207     if signature is None or (includetemp and tempsignature is None):
   203         signature = hex(hashlib.sha1(repo.vfs.tryread('sparse')).digest())
   208         signature = hex(hashlib.sha1(repo.vfs.tryread(b'sparse')).digest())
   204         cache['signature'] = signature
   209         cache[b'signature'] = signature
   205 
   210 
   206         if includetemp:
   211         if includetemp:
   207             raw = repo.vfs.tryread('tempsparse')
   212             raw = repo.vfs.tryread(b'tempsparse')
   208             tempsignature = hex(hashlib.sha1(raw).digest())
   213             tempsignature = hex(hashlib.sha1(raw).digest())
   209             cache['tempsignature'] = tempsignature
   214             cache[b'tempsignature'] = tempsignature
   210 
   215 
   211     return '%s %s' % (signature, tempsignature)
   216     return b'%s %s' % (signature, tempsignature)
   212 
   217 
   213 
   218 
   214 def writeconfig(repo, includes, excludes, profiles):
   219 def writeconfig(repo, includes, excludes, profiles):
   215     """Write the sparse config file given a sparse configuration."""
   220     """Write the sparse config file given a sparse configuration."""
   216     with repo.vfs('sparse', 'wb') as fh:
   221     with repo.vfs(b'sparse', b'wb') as fh:
   217         for p in sorted(profiles):
   222         for p in sorted(profiles):
   218             fh.write('%%include %s\n' % p)
   223             fh.write(b'%%include %s\n' % p)
   219 
   224 
   220         if includes:
   225         if includes:
   221             fh.write('[include]\n')
   226             fh.write(b'[include]\n')
   222             for i in sorted(includes):
   227             for i in sorted(includes):
   223                 fh.write(i)
   228                 fh.write(i)
   224                 fh.write('\n')
   229                 fh.write(b'\n')
   225 
   230 
   226         if excludes:
   231         if excludes:
   227             fh.write('[exclude]\n')
   232             fh.write(b'[exclude]\n')
   228             for e in sorted(excludes):
   233             for e in sorted(excludes):
   229                 fh.write(e)
   234                 fh.write(e)
   230                 fh.write('\n')
   235                 fh.write(b'\n')
   231 
   236 
   232     repo._sparsesignaturecache.clear()
   237     repo._sparsesignaturecache.clear()
   233 
   238 
   234 
   239 
   235 def readtemporaryincludes(repo):
   240 def readtemporaryincludes(repo):
   236     raw = repo.vfs.tryread('tempsparse')
   241     raw = repo.vfs.tryread(b'tempsparse')
   237     if not raw:
   242     if not raw:
   238         return set()
   243         return set()
   239 
   244 
   240     return set(raw.split('\n'))
   245     return set(raw.split(b'\n'))
   241 
   246 
   242 
   247 
   243 def writetemporaryincludes(repo, includes):
   248 def writetemporaryincludes(repo, includes):
   244     repo.vfs.write('tempsparse', '\n'.join(sorted(includes)))
   249     repo.vfs.write(b'tempsparse', b'\n'.join(sorted(includes)))
   245     repo._sparsesignaturecache.clear()
   250     repo._sparsesignaturecache.clear()
   246 
   251 
   247 
   252 
   248 def addtemporaryincludes(repo, additional):
   253 def addtemporaryincludes(repo, additional):
   249     includes = readtemporaryincludes(repo)
   254     includes = readtemporaryincludes(repo)
   251         includes.add(i)
   256         includes.add(i)
   252     writetemporaryincludes(repo, includes)
   257     writetemporaryincludes(repo, includes)
   253 
   258 
   254 
   259 
   255 def prunetemporaryincludes(repo):
   260 def prunetemporaryincludes(repo):
   256     if not enabled or not repo.vfs.exists('tempsparse'):
   261     if not enabled or not repo.vfs.exists(b'tempsparse'):
   257         return
   262         return
   258 
   263 
   259     s = repo.status()
   264     s = repo.status()
   260     if s.modified or s.added or s.removed or s.deleted:
   265     if s.modified or s.added or s.removed or s.deleted:
   261         # Still have pending changes. Don't bother trying to prune.
   266         # Still have pending changes. Don't bother trying to prune.
   266     actions = []
   271     actions = []
   267     dropped = []
   272     dropped = []
   268     tempincludes = readtemporaryincludes(repo)
   273     tempincludes = readtemporaryincludes(repo)
   269     for file in tempincludes:
   274     for file in tempincludes:
   270         if file in dirstate and not sparsematch(file):
   275         if file in dirstate and not sparsematch(file):
   271             message = _('dropping temporarily included sparse files')
   276             message = _(b'dropping temporarily included sparse files')
   272             actions.append((file, None, message))
   277             actions.append((file, None, message))
   273             dropped.append(file)
   278             dropped.append(file)
   274 
   279 
   275     typeactions = mergemod.emptyactions()
   280     typeactions = mergemod.emptyactions()
   276     typeactions['r'] = actions
   281     typeactions[b'r'] = actions
   277     mergemod.applyupdates(
   282     mergemod.applyupdates(
   278         repo, typeactions, repo[None], repo['.'], False, wantfiledata=False
   283         repo, typeactions, repo[None], repo[b'.'], False, wantfiledata=False
   279     )
   284     )
   280 
   285 
   281     # Fix dirstate
   286     # Fix dirstate
   282     for file in dropped:
   287     for file in dropped:
   283         dirstate.drop(file)
   288         dirstate.drop(file)
   284 
   289 
   285     repo.vfs.unlink('tempsparse')
   290     repo.vfs.unlink(b'tempsparse')
   286     repo._sparsesignaturecache.clear()
   291     repo._sparsesignaturecache.clear()
   287     msg = _(
   292     msg = _(
   288         'cleaned up %d temporarily added file(s) from the ' 'sparse checkout\n'
   293         b'cleaned up %d temporarily added file(s) from the '
       
   294         b'sparse checkout\n'
   289     )
   295     )
   290     repo.ui.status(msg % len(tempincludes))
   296     repo.ui.status(msg % len(tempincludes))
   291 
   297 
   292 
   298 
   293 def forceincludematcher(matcher, includes):
   299 def forceincludematcher(matcher, includes):
   294     """Returns a matcher that returns true for any of the forced includes
   300     """Returns a matcher that returns true for any of the forced includes
   295     before testing against the actual matcher."""
   301     before testing against the actual matcher."""
   296     kindpats = [('path', include, '') for include in includes]
   302     kindpats = [(b'path', include, b'') for include in includes]
   297     includematcher = matchmod.includematcher('', kindpats)
   303     includematcher = matchmod.includematcher(b'', kindpats)
   298     return matchmod.unionmatcher([includematcher, matcher])
   304     return matchmod.unionmatcher([includematcher, matcher])
   299 
   305 
   300 
   306 
   301 def matcher(repo, revs=None, includetemp=True):
   307 def matcher(repo, revs=None, includetemp=True):
   302     """Obtain a matcher for sparse working directories for the given revs.
   308     """Obtain a matcher for sparse working directories for the given revs.
   317             if node != nullid
   323             if node != nullid
   318         ]
   324         ]
   319 
   325 
   320     signature = configsignature(repo, includetemp=includetemp)
   326     signature = configsignature(repo, includetemp=includetemp)
   321 
   327 
   322     key = '%s %s' % (signature, ' '.join(map(pycompat.bytestr, revs)))
   328     key = b'%s %s' % (signature, b' '.join(map(pycompat.bytestr, revs)))
   323 
   329 
   324     result = repo._sparsematchercache.get(key)
   330     result = repo._sparsematchercache.get(key)
   325     if result:
   331     if result:
   326         return result
   332         return result
   327 
   333 
   331             includes, excludes, profiles = patternsforrev(repo, rev)
   337             includes, excludes, profiles = patternsforrev(repo, rev)
   332 
   338 
   333             if includes or excludes:
   339             if includes or excludes:
   334                 matcher = matchmod.match(
   340                 matcher = matchmod.match(
   335                     repo.root,
   341                     repo.root,
   336                     '',
   342                     b'',
   337                     [],
   343                     [],
   338                     include=includes,
   344                     include=includes,
   339                     exclude=excludes,
   345                     exclude=excludes,
   340                     default='relpath',
   346                     default=b'relpath',
   341                 )
   347                 )
   342                 matchers.append(matcher)
   348                 matchers.append(matcher)
   343         except IOError:
   349         except IOError:
   344             pass
   350             pass
   345 
   351 
   386     for file, action in actions.iteritems():
   392     for file, action in actions.iteritems():
   387         type, args, msg = action
   393         type, args, msg = action
   388         files.add(file)
   394         files.add(file)
   389         if sparsematch(file):
   395         if sparsematch(file):
   390             prunedactions[file] = action
   396             prunedactions[file] = action
   391         elif type == 'm':
   397         elif type == b'm':
   392             temporaryfiles.append(file)
   398             temporaryfiles.append(file)
   393             prunedactions[file] = action
   399             prunedactions[file] = action
   394         elif branchmerge:
   400         elif branchmerge:
   395             if type != 'k':
   401             if type != b'k':
   396                 temporaryfiles.append(file)
   402                 temporaryfiles.append(file)
   397                 prunedactions[file] = action
   403                 prunedactions[file] = action
   398         elif type == 'f':
   404         elif type == b'f':
   399             prunedactions[file] = action
   405             prunedactions[file] = action
   400         elif file in wctx:
   406         elif file in wctx:
   401             prunedactions[file] = ('r', args, msg)
   407             prunedactions[file] = (b'r', args, msg)
   402 
   408 
   403         if branchmerge and type == mergemod.ACTION_MERGE:
   409         if branchmerge and type == mergemod.ACTION_MERGE:
   404             f1, f2, fa, move, anc = args
   410             f1, f2, fa, move, anc = args
   405             if not sparsematch(f1):
   411             if not sparsematch(f1):
   406                 temporaryfiles.append(f1)
   412                 temporaryfiles.append(f1)
   407 
   413 
   408     if len(temporaryfiles) > 0:
   414     if len(temporaryfiles) > 0:
   409         repo.ui.status(
   415         repo.ui.status(
   410             _(
   416             _(
   411                 'temporarily included %d file(s) in the sparse '
   417                 b'temporarily included %d file(s) in the sparse '
   412                 'checkout for merging\n'
   418                 b'checkout for merging\n'
   413             )
   419             )
   414             % len(temporaryfiles)
   420             % len(temporaryfiles)
   415         )
   421         )
   416         addtemporaryincludes(repo, temporaryfiles)
   422         addtemporaryincludes(repo, temporaryfiles)
   417 
   423 
   418         # Add the new files to the working copy so they can be merged, etc
   424         # Add the new files to the working copy so they can be merged, etc
   419         actions = []
   425         actions = []
   420         message = 'temporarily adding to sparse checkout'
   426         message = b'temporarily adding to sparse checkout'
   421         wctxmanifest = repo[None].manifest()
   427         wctxmanifest = repo[None].manifest()
   422         for file in temporaryfiles:
   428         for file in temporaryfiles:
   423             if file in wctxmanifest:
   429             if file in wctxmanifest:
   424                 fctx = repo[None][file]
   430                 fctx = repo[None][file]
   425                 actions.append((file, (fctx.flags(), False), message))
   431                 actions.append((file, (fctx.flags(), False), message))
   426 
   432 
   427         typeactions = mergemod.emptyactions()
   433         typeactions = mergemod.emptyactions()
   428         typeactions['g'] = actions
   434         typeactions[b'g'] = actions
   429         mergemod.applyupdates(
   435         mergemod.applyupdates(
   430             repo, typeactions, repo[None], repo['.'], False, wantfiledata=False
   436             repo, typeactions, repo[None], repo[b'.'], False, wantfiledata=False
   431         )
   437         )
   432 
   438 
   433         dirstate = repo.dirstate
   439         dirstate = repo.dirstate
   434         for file, flags, msg in actions:
   440         for file, flags, msg in actions:
   435             dirstate.normal(file)
   441             dirstate.normal(file)
   444         for file in mf:
   450         for file in mf:
   445             old = oldsparsematch(file)
   451             old = oldsparsematch(file)
   446             new = sparsematch(file)
   452             new = sparsematch(file)
   447             if not old and new:
   453             if not old and new:
   448                 flags = mf.flags(file)
   454                 flags = mf.flags(file)
   449                 prunedactions[file] = ('g', (flags, False), '')
   455                 prunedactions[file] = (b'g', (flags, False), b'')
   450             elif old and not new:
   456             elif old and not new:
   451                 prunedactions[file] = ('r', [], '')
   457                 prunedactions[file] = (b'r', [], b'')
   452 
   458 
   453     return prunedactions
   459     return prunedactions
   454 
   460 
   455 
   461 
   456 def refreshwdir(repo, origstatus, origsparsematch, force=False):
   462 def refreshwdir(repo, origstatus, origsparsematch, force=False):
   470     sparsematch = matcher(repo)
   476     sparsematch = matcher(repo)
   471     abort = False
   477     abort = False
   472 
   478 
   473     for f in pending:
   479     for f in pending:
   474         if not sparsematch(f):
   480         if not sparsematch(f):
   475             repo.ui.warn(_("pending changes to '%s'\n") % f)
   481             repo.ui.warn(_(b"pending changes to '%s'\n") % f)
   476             abort = not force
   482             abort = not force
   477 
   483 
   478     if abort:
   484     if abort:
   479         raise error.Abort(
   485         raise error.Abort(
   480             _('could not update sparseness due to pending ' 'changes')
   486             _(b'could not update sparseness due to pending ' b'changes')
   481         )
   487         )
   482 
   488 
   483     # Calculate actions
   489     # Calculate actions
   484     dirstate = repo.dirstate
   490     dirstate = repo.dirstate
   485     ctx = repo['.']
   491     ctx = repo[b'.']
   486     added = []
   492     added = []
   487     lookup = []
   493     lookup = []
   488     dropped = []
   494     dropped = []
   489     mf = ctx.manifest()
   495     mf = ctx.manifest()
   490     files = set(mf)
   496     files = set(mf)
   497         # Add files that are newly included, or that don't exist in
   503         # Add files that are newly included, or that don't exist in
   498         # the dirstate yet.
   504         # the dirstate yet.
   499         if (new and not old) or (old and new and not file in dirstate):
   505         if (new and not old) or (old and new and not file in dirstate):
   500             fl = mf.flags(file)
   506             fl = mf.flags(file)
   501             if repo.wvfs.exists(file):
   507             if repo.wvfs.exists(file):
   502                 actions[file] = ('e', (fl,), '')
   508                 actions[file] = (b'e', (fl,), b'')
   503                 lookup.append(file)
   509                 lookup.append(file)
   504             else:
   510             else:
   505                 actions[file] = ('g', (fl, False), '')
   511                 actions[file] = (b'g', (fl, False), b'')
   506                 added.append(file)
   512                 added.append(file)
   507         # Drop files that are newly excluded, or that still exist in
   513         # Drop files that are newly excluded, or that still exist in
   508         # the dirstate.
   514         # the dirstate.
   509         elif (old and not new) or (not old and not new and file in dirstate):
   515         elif (old and not new) or (not old and not new and file in dirstate):
   510             dropped.append(file)
   516             dropped.append(file)
   511             if file not in pending:
   517             if file not in pending:
   512                 actions[file] = ('r', [], '')
   518                 actions[file] = (b'r', [], b'')
   513 
   519 
   514     # Verify there are no pending changes in newly included files
   520     # Verify there are no pending changes in newly included files
   515     abort = False
   521     abort = False
   516     for file in lookup:
   522     for file in lookup:
   517         repo.ui.warn(_("pending changes to '%s'\n") % file)
   523         repo.ui.warn(_(b"pending changes to '%s'\n") % file)
   518         abort = not force
   524         abort = not force
   519     if abort:
   525     if abort:
   520         raise error.Abort(
   526         raise error.Abort(
   521             _(
   527             _(
   522                 'cannot change sparseness due to pending '
   528                 b'cannot change sparseness due to pending '
   523                 'changes (delete the files or use '
   529                 b'changes (delete the files or use '
   524                 '--force to bring them back dirty)'
   530                 b'--force to bring them back dirty)'
   525             )
   531             )
   526         )
   532         )
   527 
   533 
   528     # Check for files that were only in the dirstate.
   534     # Check for files that were only in the dirstate.
   529     for file, state in dirstate.iteritems():
   535     for file, state in dirstate.iteritems():
   537     typeactions = mergemod.emptyactions()
   543     typeactions = mergemod.emptyactions()
   538     for f, (m, args, msg) in actions.iteritems():
   544     for f, (m, args, msg) in actions.iteritems():
   539         typeactions[m].append((f, args, msg))
   545         typeactions[m].append((f, args, msg))
   540 
   546 
   541     mergemod.applyupdates(
   547     mergemod.applyupdates(
   542         repo, typeactions, repo[None], repo['.'], False, wantfiledata=False
   548         repo, typeactions, repo[None], repo[b'.'], False, wantfiledata=False
   543     )
   549     )
   544 
   550 
   545     # Fix dirstate
   551     # Fix dirstate
   546     for file in added:
   552     for file in added:
   547         dirstate.normal(file)
   553         dirstate.normal(file)
   575 
   581 
   576 def _updateconfigandrefreshwdir(
   582 def _updateconfigandrefreshwdir(
   577     repo, includes, excludes, profiles, force=False, removing=False
   583     repo, includes, excludes, profiles, force=False, removing=False
   578 ):
   584 ):
   579     """Update the sparse config and working directory state."""
   585     """Update the sparse config and working directory state."""
   580     raw = repo.vfs.tryread('sparse')
   586     raw = repo.vfs.tryread(b'sparse')
   581     oldincludes, oldexcludes, oldprofiles = parseconfig(repo.ui, raw, 'sparse')
   587     oldincludes, oldexcludes, oldprofiles = parseconfig(repo.ui, raw, b'sparse')
   582 
   588 
   583     oldstatus = repo.status()
   589     oldstatus = repo.status()
   584     oldmatch = matcher(repo)
   590     oldmatch = matcher(repo)
   585     oldrequires = set(repo.requirements)
   591     oldrequires = set(repo.requirements)
   586 
   592 
   590     # re-read. We ideally want to update the cached matcher on the
   596     # re-read. We ideally want to update the cached matcher on the
   591     # repo instance then flush the new config to disk once wdir is
   597     # repo instance then flush the new config to disk once wdir is
   592     # updated. But this requires massive rework to matcher() and its
   598     # updated. But this requires massive rework to matcher() and its
   593     # consumers.
   599     # consumers.
   594 
   600 
   595     if 'exp-sparse' in oldrequires and removing:
   601     if b'exp-sparse' in oldrequires and removing:
   596         repo.requirements.discard('exp-sparse')
   602         repo.requirements.discard(b'exp-sparse')
   597         scmutil.writerequires(repo.vfs, repo.requirements)
   603         scmutil.writerequires(repo.vfs, repo.requirements)
   598     elif 'exp-sparse' not in oldrequires:
   604     elif b'exp-sparse' not in oldrequires:
   599         repo.requirements.add('exp-sparse')
   605         repo.requirements.add(b'exp-sparse')
   600         scmutil.writerequires(repo.vfs, repo.requirements)
   606         scmutil.writerequires(repo.vfs, repo.requirements)
   601 
   607 
   602     try:
   608     try:
   603         writeconfig(repo, includes, excludes, profiles)
   609         writeconfig(repo, includes, excludes, profiles)
   604         return refreshwdir(repo, oldstatus, oldmatch, force=force)
   610         return refreshwdir(repo, oldstatus, oldmatch, force=force)
   616 
   622 
   617     The remaining sparse config only has profiles, if defined. The working
   623     The remaining sparse config only has profiles, if defined. The working
   618     directory is refreshed, as needed.
   624     directory is refreshed, as needed.
   619     """
   625     """
   620     with repo.wlock():
   626     with repo.wlock():
   621         raw = repo.vfs.tryread('sparse')
   627         raw = repo.vfs.tryread(b'sparse')
   622         includes, excludes, profiles = parseconfig(repo.ui, raw, 'sparse')
   628         includes, excludes, profiles = parseconfig(repo.ui, raw, b'sparse')
   623 
   629 
   624         if not includes and not excludes:
   630         if not includes and not excludes:
   625             return
   631             return
   626 
   632 
   627         _updateconfigandrefreshwdir(repo, set(), set(), profiles, force=force)
   633         _updateconfigandrefreshwdir(repo, set(), set(), profiles, force=force)
   633     The updated sparse config is written out and the working directory
   639     The updated sparse config is written out and the working directory
   634     is refreshed, as needed.
   640     is refreshed, as needed.
   635     """
   641     """
   636     with repo.wlock():
   642     with repo.wlock():
   637         # read current configuration
   643         # read current configuration
   638         raw = repo.vfs.tryread('sparse')
   644         raw = repo.vfs.tryread(b'sparse')
   639         includes, excludes, profiles = parseconfig(repo.ui, raw, 'sparse')
   645         includes, excludes, profiles = parseconfig(repo.ui, raw, b'sparse')
   640         aincludes, aexcludes, aprofiles = activeconfig(repo)
   646         aincludes, aexcludes, aprofiles = activeconfig(repo)
   641 
   647 
   642         # Import rules on top; only take in rules that are not yet
   648         # Import rules on top; only take in rules that are not yet
   643         # part of the active rules.
   649         # part of the active rules.
   644         changed = False
   650         changed = False
   645         for p in paths:
   651         for p in paths:
   646             with util.posixfile(util.expandpath(p), mode='rb') as fh:
   652             with util.posixfile(util.expandpath(p), mode=b'rb') as fh:
   647                 raw = fh.read()
   653                 raw = fh.read()
   648 
   654 
   649             iincludes, iexcludes, iprofiles = parseconfig(
   655             iincludes, iexcludes, iprofiles = parseconfig(
   650                 repo.ui, raw, 'sparse'
   656                 repo.ui, raw, b'sparse'
   651             )
   657             )
   652             oldsize = len(includes) + len(excludes) + len(profiles)
   658             oldsize = len(includes) + len(excludes) + len(profiles)
   653             includes.update(iincludes - aincludes)
   659             includes.update(iincludes - aincludes)
   654             excludes.update(iexcludes - aexcludes)
   660             excludes.update(iexcludes - aexcludes)
   655             profiles.update(iprofiles - aprofiles)
   661             profiles.update(iprofiles - aprofiles)
   694     Only one of the actions may be performed.
   700     Only one of the actions may be performed.
   695 
   701 
   696     The new config is written out and a working directory refresh is performed.
   702     The new config is written out and a working directory refresh is performed.
   697     """
   703     """
   698     with repo.wlock():
   704     with repo.wlock():
   699         raw = repo.vfs.tryread('sparse')
   705         raw = repo.vfs.tryread(b'sparse')
   700         oldinclude, oldexclude, oldprofiles = parseconfig(
   706         oldinclude, oldexclude, oldprofiles = parseconfig(
   701             repo.ui, raw, 'sparse'
   707             repo.ui, raw, b'sparse'
   702         )
   708         )
   703 
   709 
   704         if reset:
   710         if reset:
   705             newinclude = set()
   711             newinclude = set()
   706             newexclude = set()
   712             newexclude = set()
   709             newinclude = set(oldinclude)
   715             newinclude = set(oldinclude)
   710             newexclude = set(oldexclude)
   716             newexclude = set(oldexclude)
   711             newprofiles = set(oldprofiles)
   717             newprofiles = set(oldprofiles)
   712 
   718 
   713         if any(os.path.isabs(pat) for pat in pats):
   719         if any(os.path.isabs(pat) for pat in pats):
   714             raise error.Abort(_('paths cannot be absolute'))
   720             raise error.Abort(_(b'paths cannot be absolute'))
   715 
   721 
   716         if not usereporootpaths:
   722         if not usereporootpaths:
   717             # let's treat paths as relative to cwd
   723             # let's treat paths as relative to cwd
   718             root, cwd = repo.root, repo.getcwd()
   724             root, cwd = repo.root, repo.getcwd()
   719             abspats = []
   725             abspats = []
   720             for kindpat in pats:
   726             for kindpat in pats:
   721                 kind, pat = matchmod._patsplit(kindpat, None)
   727                 kind, pat = matchmod._patsplit(kindpat, None)
   722                 if kind in matchmod.cwdrelativepatternkinds or kind is None:
   728                 if kind in matchmod.cwdrelativepatternkinds or kind is None:
   723                     ap = (kind + ':' if kind else '') + pathutil.canonpath(
   729                     ap = (kind + b':' if kind else b'') + pathutil.canonpath(
   724                         root, cwd, pat
   730                         root, cwd, pat
   725                     )
   731                     )
   726                     abspats.append(ap)
   732                     abspats.append(ap)
   727                 else:
   733                 else:
   728                     abspats.append(kindpat)
   734                     abspats.append(kindpat)
   776     added=0,
   782     added=0,
   777     dropped=0,
   783     dropped=0,
   778     conflicting=0,
   784     conflicting=0,
   779 ):
   785 ):
   780     """Print output summarizing sparse config changes."""
   786     """Print output summarizing sparse config changes."""
   781     with ui.formatter('sparse', opts) as fm:
   787     with ui.formatter(b'sparse', opts) as fm:
   782         fm.startitem()
   788         fm.startitem()
   783         fm.condwrite(
   789         fm.condwrite(
   784             ui.verbose,
   790             ui.verbose,
   785             'profiles_added',
   791             b'profiles_added',
   786             _('Profiles changed: %d\n'),
   792             _(b'Profiles changed: %d\n'),
   787             profilecount,
   793             profilecount,
   788         )
   794         )
   789         fm.condwrite(
   795         fm.condwrite(
   790             ui.verbose,
   796             ui.verbose,
   791             'include_rules_added',
   797             b'include_rules_added',
   792             _('Include rules changed: %d\n'),
   798             _(b'Include rules changed: %d\n'),
   793             includecount,
   799             includecount,
   794         )
   800         )
   795         fm.condwrite(
   801         fm.condwrite(
   796             ui.verbose,
   802             ui.verbose,
   797             'exclude_rules_added',
   803             b'exclude_rules_added',
   798             _('Exclude rules changed: %d\n'),
   804             _(b'Exclude rules changed: %d\n'),
   799             excludecount,
   805             excludecount,
   800         )
   806         )
   801 
   807 
   802         # In 'plain' verbose mode, mergemod.applyupdates already outputs what
   808         # In 'plain' verbose mode, mergemod.applyupdates already outputs what
   803         # files are added or removed outside of the templating formatter
   809         # files are added or removed outside of the templating formatter
   804         # framework. No point in repeating ourselves in that case.
   810         # framework. No point in repeating ourselves in that case.
   805         if not fm.isplain():
   811         if not fm.isplain():
   806             fm.condwrite(
   812             fm.condwrite(
   807                 ui.verbose, 'files_added', _('Files added: %d\n'), added
   813                 ui.verbose, b'files_added', _(b'Files added: %d\n'), added
   808             )
   814             )
   809             fm.condwrite(
   815             fm.condwrite(
   810                 ui.verbose, 'files_dropped', _('Files dropped: %d\n'), dropped
   816                 ui.verbose, b'files_dropped', _(b'Files dropped: %d\n'), dropped
   811             )
   817             )
   812             fm.condwrite(
   818             fm.condwrite(
   813                 ui.verbose,
   819                 ui.verbose,
   814                 'files_conflicting',
   820                 b'files_conflicting',
   815                 _('Files conflicting: %d\n'),
   821                 _(b'Files conflicting: %d\n'),
   816                 conflicting,
   822                 conflicting,
   817             )
   823             )