mercurial/subrepoutil.py
changeset 43076 2372284d9457
parent 42588 f6540aba8e3e
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    19     filemerge,
    19     filemerge,
    20     pathutil,
    20     pathutil,
    21     phases,
    21     phases,
    22     util,
    22     util,
    23 )
    23 )
    24 from .utils import (
    24 from .utils import stringutil
    25     stringutil,
       
    26 )
       
    27 
    25 
    28 nullstate = ('', '', 'empty')
    26 nullstate = ('', '', 'empty')
       
    27 
    29 
    28 
    30 def state(ctx, ui):
    29 def state(ctx, ui):
    31     """return a state dict, mapping subrepo paths configured in .hgsub
    30     """return a state dict, mapping subrepo paths configured in .hgsub
    32     to tuple: (source from .hgsub, revision from .hgsubstate, kind
    31     to tuple: (source from .hgsub, revision from .hgsubstate, kind
    33     (key in types dict))
    32     (key in types dict))
    34     """
    33     """
    35     p = config.config()
    34     p = config.config()
    36     repo = ctx.repo()
    35     repo = ctx.repo()
       
    36 
    37     def read(f, sections=None, remap=None):
    37     def read(f, sections=None, remap=None):
    38         if f in ctx:
    38         if f in ctx:
    39             try:
    39             try:
    40                 data = ctx[f].data()
    40                 data = ctx[f].data()
    41             except IOError as err:
    41             except IOError as err:
    42                 if err.errno != errno.ENOENT:
    42                 if err.errno != errno.ENOENT:
    43                     raise
    43                     raise
    44                 # handle missing subrepo spec files as removed
    44                 # handle missing subrepo spec files as removed
    45                 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
    45                 ui.warn(
    46                         repo.pathto(f))
    46                     _("warning: subrepo spec file \'%s\' not found\n")
       
    47                     % repo.pathto(f)
       
    48                 )
    47                 return
    49                 return
    48             p.parse(f, data, sections, remap, read)
    50             p.parse(f, data, sections, remap, read)
    49         else:
    51         else:
    50             raise error.Abort(_("subrepo spec file \'%s\' not found") %
    52             raise error.Abort(
    51                              repo.pathto(f))
    53                 _("subrepo spec file \'%s\' not found") % repo.pathto(f)
       
    54             )
       
    55 
    52     if '.hgsub' in ctx:
    56     if '.hgsub' in ctx:
    53         read('.hgsub')
    57         read('.hgsub')
    54 
    58 
    55     for path, src in ui.configitems('subpaths'):
    59     for path, src in ui.configitems('subpaths'):
    56         p.set('subpaths', path, src, ui.configsource('subpaths', path))
    60         p.set('subpaths', path, src, ui.configsource('subpaths', path))
    63                 if not l:
    67                 if not l:
    64                     continue
    68                     continue
    65                 try:
    69                 try:
    66                     revision, path = l.split(" ", 1)
    70                     revision, path = l.split(" ", 1)
    67                 except ValueError:
    71                 except ValueError:
    68                     raise error.Abort(_("invalid subrepository revision "
    72                     raise error.Abort(
    69                                        "specifier in \'%s\' line %d")
    73                         _(
    70                                      % (repo.pathto('.hgsubstate'), (i + 1)))
    74                             "invalid subrepository revision "
       
    75                             "specifier in \'%s\' line %d"
       
    76                         )
       
    77                         % (repo.pathto('.hgsubstate'), (i + 1))
       
    78                     )
    71                 rev[path] = revision
    79                 rev[path] = revision
    72         except IOError as err:
    80         except IOError as err:
    73             if err.errno != errno.ENOENT:
    81             if err.errno != errno.ENOENT:
    74                 raise
    82                 raise
    75 
    83 
    83             # extra escapes are needed because re.sub string decodes.
    91             # extra escapes are needed because re.sub string decodes.
    84             repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl)
    92             repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl)
    85             try:
    93             try:
    86                 src = re.sub(pattern, repl, src, 1)
    94                 src = re.sub(pattern, repl, src, 1)
    87             except re.error as e:
    95             except re.error as e:
    88                 raise error.Abort(_("bad subrepository pattern in %s: %s")
    96                 raise error.Abort(
    89                                  % (p.source('subpaths', pattern),
    97                     _("bad subrepository pattern in %s: %s")
    90                                     stringutil.forcebytestr(e)))
    98                     % (
       
    99                         p.source('subpaths', pattern),
       
   100                         stringutil.forcebytestr(e),
       
   101                     )
       
   102                 )
    91         return src
   103         return src
    92 
   104 
    93     state = {}
   105     state = {}
    94     for path, src in p[''].items():
   106     for path, src in p[''].items():
    95         kind = 'hg'
   107         kind = 'hg'
    96         if src.startswith('['):
   108         if src.startswith('['):
    97             if ']' not in src:
   109             if ']' not in src:
    98                 raise error.Abort(_('missing ] in subrepository source'))
   110                 raise error.Abort(_('missing ] in subrepository source'))
    99             kind, src = src.split(']', 1)
   111             kind, src = src.split(']', 1)
   100             kind = kind[1:]
   112             kind = kind[1:]
   101             src = src.lstrip() # strip any extra whitespace after ']'
   113             src = src.lstrip()  # strip any extra whitespace after ']'
   102 
   114 
   103         if not util.url(src).isabs():
   115         if not util.url(src).isabs():
   104             parent = _abssource(repo, abort=False)
   116             parent = _abssource(repo, abort=False)
   105             if parent:
   117             if parent:
   106                 parent = util.url(parent)
   118                 parent = util.url(parent)
   118         src = remap(src)
   130         src = remap(src)
   119         state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
   131         state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
   120 
   132 
   121     return state
   133     return state
   122 
   134 
       
   135 
   123 def writestate(repo, state):
   136 def writestate(repo, state):
   124     """rewrite .hgsubstate in (outer) repo with these subrepo states"""
   137     """rewrite .hgsubstate in (outer) repo with these subrepo states"""
   125     lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
   138     lines = [
   126                                                 if state[s][1] != nullstate[1]]
   139         '%s %s\n' % (state[s][1], s)
       
   140         for s in sorted(state)
       
   141         if state[s][1] != nullstate[1]
       
   142     ]
   127     repo.wwrite('.hgsubstate', ''.join(lines), '')
   143     repo.wwrite('.hgsubstate', ''.join(lines), '')
       
   144 
   128 
   145 
   129 def submerge(repo, wctx, mctx, actx, overwrite, labels=None):
   146 def submerge(repo, wctx, mctx, actx, overwrite, labels=None):
   130     """delegated from merge.applyupdates: merging of .hgsubstate file
   147     """delegated from merge.applyupdates: merging of .hgsubstate file
   131     in working context, merging context and ancestor context"""
   148     in working context, merging context and ancestor context"""
   132     if mctx == actx: # backwards?
   149     if mctx == actx:  # backwards?
   133         actx = wctx.p1()
   150         actx = wctx.p1()
   134     s1 = wctx.substate
   151     s1 = wctx.substate
   135     s2 = mctx.substate
   152     s2 = mctx.substate
   136     sa = actx.substate
   153     sa = actx.substate
   137     sm = {}
   154     sm = {}
   144         repo.ui.debug("  subrepo %s: %s %s\n" % (s, msg, r))
   161         repo.ui.debug("  subrepo %s: %s %s\n" % (s, msg, r))
   145 
   162 
   146     promptssrc = filemerge.partextras(labels)
   163     promptssrc = filemerge.partextras(labels)
   147     for s, l in sorted(s1.iteritems()):
   164     for s, l in sorted(s1.iteritems()):
   148         a = sa.get(s, nullstate)
   165         a = sa.get(s, nullstate)
   149         ld = l # local state with possible dirty flag for compares
   166         ld = l  # local state with possible dirty flag for compares
   150         if wctx.sub(s).dirty():
   167         if wctx.sub(s).dirty():
   151             ld = (l[0], l[1] + "+")
   168             ld = (l[0], l[1] + "+")
   152         if wctx == actx: # overwrite
   169         if wctx == actx:  # overwrite
   153             a = ld
   170             a = ld
   154 
   171 
   155         prompts = promptssrc.copy()
   172         prompts = promptssrc.copy()
   156         prompts['s'] = s
   173         prompts['s'] = s
   157         if s in s2:
   174         if s in s2:
   158             r = s2[s]
   175             r = s2[s]
   159             if ld == r or r == a: # no change or local is newer
   176             if ld == r or r == a:  # no change or local is newer
   160                 sm[s] = l
   177                 sm[s] = l
   161                 continue
   178                 continue
   162             elif ld == a: # other side changed
   179             elif ld == a:  # other side changed
   163                 debug(s, "other changed, get", r)
   180                 debug(s, "other changed, get", r)
   164                 wctx.sub(s).get(r, overwrite)
   181                 wctx.sub(s).get(r, overwrite)
   165                 sm[s] = r
   182                 sm[s] = r
   166             elif ld[0] != r[0]: # sources differ
   183             elif ld[0] != r[0]:  # sources differ
   167                 prompts['lo'] = l[0]
   184                 prompts['lo'] = l[0]
   168                 prompts['ro'] = r[0]
   185                 prompts['ro'] = r[0]
   169                 if repo.ui.promptchoice(
   186                 if repo.ui.promptchoice(
   170                     _(' subrepository sources for %(s)s differ\n'
   187                     _(
   171                       'you can use (l)ocal%(l)s source (%(lo)s)'
   188                         ' subrepository sources for %(s)s differ\n'
   172                       ' or (r)emote%(o)s source (%(ro)s).\n'
   189                         'you can use (l)ocal%(l)s source (%(lo)s)'
   173                       'what do you want to do?'
   190                         ' or (r)emote%(o)s source (%(ro)s).\n'
   174                       '$$ &Local $$ &Remote') % prompts, 0):
   191                         'what do you want to do?'
       
   192                         '$$ &Local $$ &Remote'
       
   193                     )
       
   194                     % prompts,
       
   195                     0,
       
   196                 ):
   175                     debug(s, "prompt changed, get", r)
   197                     debug(s, "prompt changed, get", r)
   176                     wctx.sub(s).get(r, overwrite)
   198                     wctx.sub(s).get(r, overwrite)
   177                     sm[s] = r
   199                     sm[s] = r
   178             elif ld[1] == a[1]: # local side is unchanged
   200             elif ld[1] == a[1]:  # local side is unchanged
   179                 debug(s, "other side changed, get", r)
   201                 debug(s, "other side changed, get", r)
   180                 wctx.sub(s).get(r, overwrite)
   202                 wctx.sub(s).get(r, overwrite)
   181                 sm[s] = r
   203                 sm[s] = r
   182             else:
   204             else:
   183                 debug(s, "both sides changed")
   205                 debug(s, "both sides changed")
   184                 srepo = wctx.sub(s)
   206                 srepo = wctx.sub(s)
   185                 prompts['sl'] = srepo.shortid(l[1])
   207                 prompts['sl'] = srepo.shortid(l[1])
   186                 prompts['sr'] = srepo.shortid(r[1])
   208                 prompts['sr'] = srepo.shortid(r[1])
   187                 option = repo.ui.promptchoice(
   209                 option = repo.ui.promptchoice(
   188                     _(' subrepository %(s)s diverged (local revision: %(sl)s, '
   210                     _(
   189                       'remote revision: %(sr)s)\n'
   211                         ' subrepository %(s)s diverged (local revision: %(sl)s, '
   190                       'you can (m)erge, keep (l)ocal%(l)s or keep '
   212                         'remote revision: %(sr)s)\n'
   191                       '(r)emote%(o)s.\n'
   213                         'you can (m)erge, keep (l)ocal%(l)s or keep '
   192                       'what do you want to do?'
   214                         '(r)emote%(o)s.\n'
   193                       '$$ &Merge $$ &Local $$ &Remote')
   215                         'what do you want to do?'
   194                     % prompts, 0)
   216                         '$$ &Merge $$ &Local $$ &Remote'
       
   217                     )
       
   218                     % prompts,
       
   219                     0,
       
   220                 )
   195                 if option == 0:
   221                 if option == 0:
   196                     wctx.sub(s).merge(r)
   222                     wctx.sub(s).merge(r)
   197                     sm[s] = l
   223                     sm[s] = l
   198                     debug(s, "merge with", r)
   224                     debug(s, "merge with", r)
   199                 elif option == 1:
   225                 elif option == 1:
   201                     debug(s, "keep local subrepo revision", l)
   227                     debug(s, "keep local subrepo revision", l)
   202                 else:
   228                 else:
   203                     wctx.sub(s).get(r, overwrite)
   229                     wctx.sub(s).get(r, overwrite)
   204                     sm[s] = r
   230                     sm[s] = r
   205                     debug(s, "get remote subrepo revision", r)
   231                     debug(s, "get remote subrepo revision", r)
   206         elif ld == a: # remote removed, local unchanged
   232         elif ld == a:  # remote removed, local unchanged
   207             debug(s, "remote removed, remove")
   233             debug(s, "remote removed, remove")
   208             wctx.sub(s).remove()
   234             wctx.sub(s).remove()
   209         elif a == nullstate: # not present in remote or ancestor
   235         elif a == nullstate:  # not present in remote or ancestor
   210             debug(s, "local added, keep")
   236             debug(s, "local added, keep")
   211             sm[s] = l
   237             sm[s] = l
   212             continue
   238             continue
   213         else:
   239         else:
   214             if repo.ui.promptchoice(
   240             if repo.ui.promptchoice(
   215                 _(' local%(l)s changed subrepository %(s)s'
   241                 _(
   216                   ' which remote%(o)s removed\n'
   242                     ' local%(l)s changed subrepository %(s)s'
   217                   'use (c)hanged version or (d)elete?'
   243                     ' which remote%(o)s removed\n'
   218                   '$$ &Changed $$ &Delete') % prompts, 0):
   244                     'use (c)hanged version or (d)elete?'
       
   245                     '$$ &Changed $$ &Delete'
       
   246                 )
       
   247                 % prompts,
       
   248                 0,
       
   249             ):
   219                 debug(s, "prompt remove")
   250                 debug(s, "prompt remove")
   220                 wctx.sub(s).remove()
   251                 wctx.sub(s).remove()
   221 
   252 
   222     for s, r in sorted(s2.items()):
   253     for s, r in sorted(s2.items()):
   223         if s in s1:
   254         if s in s1:
   227             mctx.sub(s).get(r)
   258             mctx.sub(s).get(r)
   228             sm[s] = r
   259             sm[s] = r
   229         elif r != sa[s]:
   260         elif r != sa[s]:
   230             prompts = promptssrc.copy()
   261             prompts = promptssrc.copy()
   231             prompts['s'] = s
   262             prompts['s'] = s
   232             if repo.ui.promptchoice(
   263             if (
   233                 _(' remote%(o)s changed subrepository %(s)s'
   264                 repo.ui.promptchoice(
   234                   ' which local%(l)s removed\n'
   265                     _(
   235                   'use (c)hanged version or (d)elete?'
   266                         ' remote%(o)s changed subrepository %(s)s'
   236                   '$$ &Changed $$ &Delete') % prompts, 0) == 0:
   267                         ' which local%(l)s removed\n'
       
   268                         'use (c)hanged version or (d)elete?'
       
   269                         '$$ &Changed $$ &Delete'
       
   270                     )
       
   271                     % prompts,
       
   272                     0,
       
   273                 )
       
   274                 == 0
       
   275             ):
   237                 debug(s, "prompt recreate", r)
   276                 debug(s, "prompt recreate", r)
   238                 mctx.sub(s).get(r)
   277                 mctx.sub(s).get(r)
   239                 sm[s] = r
   278                 sm[s] = r
   240 
   279 
   241     # record merged .hgsubstate
   280     # record merged .hgsubstate
   242     writestate(repo, sm)
   281     writestate(repo, sm)
   243     return sm
   282     return sm
       
   283 
   244 
   284 
   245 def precommit(ui, wctx, status, match, force=False):
   285 def precommit(ui, wctx, status, match, force=False):
   246     """Calculate .hgsubstate changes that should be applied before committing
   286     """Calculate .hgsubstate changes that should be applied before committing
   247 
   287 
   248     Returns (subs, commitsubs, newstate) where
   288     Returns (subs, commitsubs, newstate) where
   272                 if s in oldstate:
   312                 if s in oldstate:
   273                     newstate[s] = oldstate[s]
   313                     newstate[s] = oldstate[s]
   274                     continue
   314                     continue
   275                 if not force:
   315                 if not force:
   276                     raise error.Abort(
   316                     raise error.Abort(
   277                         _("commit with new subrepo %s excluded") % s)
   317                         _("commit with new subrepo %s excluded") % s
       
   318                     )
   278             dirtyreason = wctx.sub(s).dirtyreason(True)
   319             dirtyreason = wctx.sub(s).dirtyreason(True)
   279             if dirtyreason:
   320             if dirtyreason:
   280                 if not ui.configbool('ui', 'commitsubrepos'):
   321                 if not ui.configbool('ui', 'commitsubrepos'):
   281                     raise error.Abort(dirtyreason,
   322                     raise error.Abort(
   282                         hint=_("use --subrepos for recursive commit"))
   323                         dirtyreason,
       
   324                         hint=_("use --subrepos for recursive commit"),
       
   325                     )
   283                 subs.append(s)
   326                 subs.append(s)
   284                 commitsubs.add(s)
   327                 commitsubs.add(s)
   285             else:
   328             else:
   286                 bs = wctx.sub(s).basestate()
   329                 bs = wctx.sub(s).basestate()
   287                 newstate[s] = (newstate[s][0], bs, newstate[s][2])
   330                 newstate[s] = (newstate[s][0], bs, newstate[s][2])
   291         # check for removed subrepos
   334         # check for removed subrepos
   292         for p in wctx.parents():
   335         for p in wctx.parents():
   293             r = [s for s in p.substate if s not in newstate]
   336             r = [s for s in p.substate if s not in newstate]
   294             subs += [s for s in r if match(s)]
   337             subs += [s for s in r if match(s)]
   295         if subs:
   338         if subs:
   296             if (not match('.hgsub') and
   339             if not match('.hgsub') and '.hgsub' in (
   297                 '.hgsub' in (wctx.modified() + wctx.added())):
   340                 wctx.modified() + wctx.added()
       
   341             ):
   298                 raise error.Abort(_("can't commit subrepos without .hgsub"))
   342                 raise error.Abort(_("can't commit subrepos without .hgsub"))
   299             status.modified.insert(0, '.hgsubstate')
   343             status.modified.insert(0, '.hgsubstate')
   300 
   344 
   301     elif '.hgsub' in status.removed:
   345     elif '.hgsub' in status.removed:
   302         # clean up .hgsubstate when .hgsub is removed
   346         # clean up .hgsubstate when .hgsub is removed
   303         if ('.hgsubstate' in wctx and
   347         if '.hgsubstate' in wctx and '.hgsubstate' not in (
   304             '.hgsubstate' not in (status.modified + status.added +
   348             status.modified + status.added + status.removed
   305                                   status.removed)):
   349         ):
   306             status.removed.insert(0, '.hgsubstate')
   350             status.removed.insert(0, '.hgsubstate')
   307 
   351 
   308     return subs, commitsubs, newstate
   352     return subs, commitsubs, newstate
       
   353 
   309 
   354 
   310 def reporelpath(repo):
   355 def reporelpath(repo):
   311     """return path to this (sub)repo as seen from outermost repo"""
   356     """return path to this (sub)repo as seen from outermost repo"""
   312     parent = repo
   357     parent = repo
   313     while util.safehasattr(parent, '_subparent'):
   358     while util.safehasattr(parent, '_subparent'):
   314         parent = parent._subparent
   359         parent = parent._subparent
   315     return repo.root[len(pathutil.normasprefix(parent.root)):]
   360     return repo.root[len(pathutil.normasprefix(parent.root)) :]
       
   361 
   316 
   362 
   317 def subrelpath(sub):
   363 def subrelpath(sub):
   318     """return path to this subrepo as seen from outermost repo"""
   364     """return path to this subrepo as seen from outermost repo"""
   319     return sub._relpath
   365     return sub._relpath
       
   366 
   320 
   367 
   321 def _abssource(repo, push=False, abort=True):
   368 def _abssource(repo, push=False, abort=True):
   322     """return pull/push path of repo - either based on parent repo .hgsub info
   369     """return pull/push path of repo - either based on parent repo .hgsub info
   323     or on the top repo config. Abort or return None if no source found."""
   370     or on the top repo config. Abort or return None if no source found."""
   324     if util.safehasattr(repo, '_subparent'):
   371     if util.safehasattr(repo, '_subparent'):
   330         if parent:
   377         if parent:
   331             parent = util.url(util.pconvert(parent))
   378             parent = util.url(util.pconvert(parent))
   332             parent.path = posixpath.join(parent.path or '', source.path)
   379             parent.path = posixpath.join(parent.path or '', source.path)
   333             parent.path = posixpath.normpath(parent.path)
   380             parent.path = posixpath.normpath(parent.path)
   334             return bytes(parent)
   381             return bytes(parent)
   335     else: # recursion reached top repo
   382     else:  # recursion reached top repo
   336         path = None
   383         path = None
   337         if util.safehasattr(repo, '_subtoppath'):
   384         if util.safehasattr(repo, '_subtoppath'):
   338             path = repo._subtoppath
   385             path = repo._subtoppath
   339         elif push and repo.ui.config('paths', 'default-push'):
   386         elif push and repo.ui.config('paths', 'default-push'):
   340             path = repo.ui.config('paths', 'default-push')
   387             path = repo.ui.config('paths', 'default-push')
   363             return path
   410             return path
   364 
   411 
   365     if abort:
   412     if abort:
   366         raise error.Abort(_("default path for subrepository not found"))
   413         raise error.Abort(_("default path for subrepository not found"))
   367 
   414 
       
   415 
   368 def newcommitphase(ui, ctx):
   416 def newcommitphase(ui, ctx):
   369     commitphase = phases.newcommitphase(ui)
   417     commitphase = phases.newcommitphase(ui)
   370     substate = getattr(ctx, "substate", None)
   418     substate = getattr(ctx, "substate", None)
   371     if not substate:
   419     if not substate:
   372         return commitphase
   420         return commitphase
   373     check = ui.config('phases', 'checksubrepos')
   421     check = ui.config('phases', 'checksubrepos')
   374     if check not in ('ignore', 'follow', 'abort'):
   422     if check not in ('ignore', 'follow', 'abort'):
   375         raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
   423         raise error.Abort(
   376                          % (check))
   424             _('invalid phases.checksubrepos configuration: %s') % check
       
   425         )
   377     if check == 'ignore':
   426     if check == 'ignore':
   378         return commitphase
   427         return commitphase
   379     maxphase = phases.public
   428     maxphase = phases.public
   380     maxsub = None
   429     maxsub = None
   381     for s in sorted(substate):
   430     for s in sorted(substate):
   384         if maxphase < subphase:
   433         if maxphase < subphase:
   385             maxphase = subphase
   434             maxphase = subphase
   386             maxsub = s
   435             maxsub = s
   387     if commitphase < maxphase:
   436     if commitphase < maxphase:
   388         if check == 'abort':
   437         if check == 'abort':
   389             raise error.Abort(_("can't commit in %s phase"
   438             raise error.Abort(
   390                                " conflicting %s from subrepository %s") %
   439                 _(
   391                              (phases.phasenames[commitphase],
   440                     "can't commit in %s phase"
   392                               phases.phasenames[maxphase], maxsub))
   441                     " conflicting %s from subrepository %s"
   393         ui.warn(_("warning: changes are committed in"
   442                 )
   394                   " %s phase from subrepository %s\n") %
   443                 % (
   395                 (phases.phasenames[maxphase], maxsub))
   444                     phases.phasenames[commitphase],
       
   445                     phases.phasenames[maxphase],
       
   446                     maxsub,
       
   447                 )
       
   448             )
       
   449         ui.warn(
       
   450             _(
       
   451                 "warning: changes are committed in"
       
   452                 " %s phase from subrepository %s\n"
       
   453             )
       
   454             % (phases.phasenames[maxphase], maxsub)
       
   455         )
   396         return maxphase
   456         return maxphase
   397     return commitphase
   457     return commitphase