hgext/largefiles/reposetup.py
changeset 15168 cfccd3bee7b3
child 15170 c1a4a3220711
equal deleted inserted replaced
15167:8df4166b6f63 15168:cfccd3bee7b3
       
     1 # Copyright 2009-2010 Gregory P. Ward
       
     2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
       
     3 # Copyright 2010-2011 Fog Creek Software
       
     4 # Copyright 2010-2011 Unity Technologies
       
     5 #
       
     6 # This software may be used and distributed according to the terms of the
       
     7 # GNU General Public License version 2 or any later version.
       
     8 
       
     9 '''setup for largefiles repositories: reposetup'''
       
    10 import copy
       
    11 import types
       
    12 import os
       
    13 import re
       
    14 
       
    15 from mercurial import context, error, manifest, match as match_, \
       
    16         node, util
       
    17 from mercurial.i18n import _
       
    18 
       
    19 import lfcommands
       
    20 import proto
       
    21 import lfutil
       
    22 
       
    23 def reposetup(ui, repo):
       
    24     # wire repositories should be given new wireproto functions but not the
       
    25     # other largefiles modifications
       
    26     if not repo.local():
       
    27         return proto.wirereposetup(ui, repo)
       
    28 
       
    29     for name in ('status', 'commitctx', 'commit', 'push'):
       
    30         method = getattr(repo, name)
       
    31         #if not (isinstance(method, types.MethodType) and
       
    32         #        method.im_func is repo.__class__.commitctx.im_func):
       
    33         if isinstance(method, types.FunctionType) and method.func_name == \
       
    34             'wrap':
       
    35             ui.warn(_('largefiles: repo method %r appears to have already been'
       
    36                     ' wrapped by another extension: '
       
    37                     'largefiles may behave incorrectly\n')
       
    38                     % name)
       
    39 
       
    40     class lfiles_repo(repo.__class__):
       
    41         lfstatus = False
       
    42         def status_nolfiles(self, *args, **kwargs):
       
    43             return super(lfiles_repo, self).status(*args, **kwargs)
       
    44 
       
    45         # When lfstatus is set, return a context that gives the names of lfiles
       
    46         # instead of their corresponding standins and identifies the lfiles as
       
    47         # always binary, regardless of their actual contents.
       
    48         def __getitem__(self, changeid):
       
    49             ctx = super(lfiles_repo, self).__getitem__(changeid)
       
    50             if self.lfstatus:
       
    51                 class lfiles_manifestdict(manifest.manifestdict):
       
    52                     def __contains__(self, filename):
       
    53                         if super(lfiles_manifestdict,
       
    54                                 self).__contains__(filename):
       
    55                             return True
       
    56                         return super(lfiles_manifestdict,
       
    57                             self).__contains__(lfutil.shortname+'/' + filename)
       
    58                 class lfiles_ctx(ctx.__class__):
       
    59                     def files(self):
       
    60                         filenames = super(lfiles_ctx, self).files()
       
    61                         return [re.sub('^\\'+lfutil.shortname+'/', '', filename) for filename
       
    62                             in filenames]
       
    63                     def manifest(self):
       
    64                         man1 = super(lfiles_ctx, self).manifest()
       
    65                         man1.__class__ = lfiles_manifestdict
       
    66                         return man1
       
    67                     def filectx(self, path, fileid=None, filelog=None):
       
    68                         try:
       
    69                             result = super(lfiles_ctx, self).filectx(path,
       
    70                                 fileid, filelog)
       
    71                         except error.LookupError:
       
    72                             # Adding a null character will cause Mercurial to
       
    73                             # identify this as a binary file.
       
    74                             result = super(lfiles_ctx, self).filectx(
       
    75                                 lfutil.shortname + '/' + path, fileid,
       
    76                                 filelog)
       
    77                             olddata = result.data
       
    78                             result.data = lambda: olddata() + '\0'
       
    79                         return result
       
    80                 ctx.__class__ = lfiles_ctx
       
    81             return ctx
       
    82 
       
    83         # Figure out the status of big files and insert them into the
       
    84         # appropriate list in the result. Also removes standin files from
       
    85         # the listing. This function reverts to the original status if
       
    86         # self.lfstatus is False
       
    87         def status(self, node1='.', node2=None, match=None, ignored=False,
       
    88                 clean=False, unknown=False, listsubrepos=False):
       
    89             listignored, listclean, listunknown = ignored, clean, unknown
       
    90             if not self.lfstatus:
       
    91                 try:
       
    92                     return super(lfiles_repo, self).status(node1, node2, match,
       
    93                         listignored, listclean, listunknown, listsubrepos)
       
    94                 except TypeError:
       
    95                     return super(lfiles_repo, self).status(node1, node2, match,
       
    96                         listignored, listclean, listunknown)
       
    97             else:
       
    98                 # some calls in this function rely on the old version of status
       
    99                 self.lfstatus = False
       
   100                 if isinstance(node1, context.changectx):
       
   101                     ctx1 = node1
       
   102                 else:
       
   103                     ctx1 = repo[node1]
       
   104                 if isinstance(node2, context.changectx):
       
   105                     ctx2 = node2
       
   106                 else:
       
   107                     ctx2 = repo[node2]
       
   108                 working = ctx2.rev() is None
       
   109                 parentworking = working and ctx1 == self['.']
       
   110 
       
   111                 def inctx(file, ctx):
       
   112                     try:
       
   113                         if ctx.rev() is None:
       
   114                             return file in ctx.manifest()
       
   115                         ctx[file]
       
   116                         return True
       
   117                     except:
       
   118                         return False
       
   119 
       
   120                 # create a copy of match that matches standins instead of
       
   121                 # lfiles if matcher not set then it is the always matcher so
       
   122                 # overwrite that
       
   123                 if match is None:
       
   124                     match = match_.always(self.root, self.getcwd())
       
   125 
       
   126                 def tostandin(file):
       
   127                     if inctx(lfutil.standin(file), ctx2):
       
   128                         return lfutil.standin(file)
       
   129                     return file
       
   130 
       
   131                 m = copy.copy(match)
       
   132                 m._files = [tostandin(f) for f in m._files]
       
   133 
       
   134                 # get ignored clean and unknown but remove them later if they
       
   135                 # were not asked for
       
   136                 try:
       
   137                     result = super(lfiles_repo, self).status(node1, node2, m,
       
   138                         True, True, True, listsubrepos)
       
   139                 except TypeError:
       
   140                     result = super(lfiles_repo, self).status(node1, node2, m,
       
   141                         True, True, True)
       
   142                 if working:
       
   143                     # Hold the wlock while we read lfiles and update the
       
   144                     # lfdirstate
       
   145                     wlock = repo.wlock()
       
   146                     try:
       
   147                         # Any non lfiles that were explicitly listed must be
       
   148                         # taken out or lfdirstate.status will report an error.
       
   149                         # The status of these files was already computed using
       
   150                         # super's status.
       
   151                         lfdirstate = lfutil.openlfdirstate(ui, self)
       
   152                         match._files = [f for f in match._files if f in
       
   153                             lfdirstate]
       
   154                         s = lfdirstate.status(match, [], listignored,
       
   155                                 listclean, listunknown)
       
   156                         (unsure, modified, added, removed, missing, unknown,
       
   157                                 ignored, clean) = s
       
   158                         if parentworking:
       
   159                             for lfile in unsure:
       
   160                                 if ctx1[lfutil.standin(lfile)].data().strip() \
       
   161                                         != lfutil.hashfile(self.wjoin(lfile)):
       
   162                                     modified.append(lfile)
       
   163                                 else:
       
   164                                     clean.append(lfile)
       
   165                                     lfdirstate.normal(lfile)
       
   166                             lfdirstate.write()
       
   167                         else:
       
   168                             tocheck = unsure + modified + added + clean
       
   169                             modified, added, clean = [], [], []
       
   170 
       
   171                             for lfile in tocheck:
       
   172                                 standin = lfutil.standin(lfile)
       
   173                                 if inctx(standin, ctx1):
       
   174                                     if ctx1[standin].data().strip() != \
       
   175                                             lfutil.hashfile(self.wjoin(lfile)):
       
   176                                         modified.append(lfile)
       
   177                                     else:
       
   178                                         clean.append(lfile)
       
   179                                 else:
       
   180                                     added.append(lfile)
       
   181                     finally:
       
   182                         wlock.release()
       
   183 
       
   184                     for standin in ctx1.manifest():
       
   185                         if not lfutil.isstandin(standin):
       
   186                             continue
       
   187                         lfile = lfutil.splitstandin(standin)
       
   188                         if not match(lfile):
       
   189                             continue
       
   190                         if lfile not in lfdirstate:
       
   191                             removed.append(lfile)
       
   192                     # Handle unknown and ignored differently
       
   193                     lfiles = (modified, added, removed, missing, [], [], clean)
       
   194                     result = list(result)
       
   195                     # Unknown files
       
   196                     result[4] = [f for f in unknown if repo.dirstate[f] == '?'\
       
   197                         and not lfutil.isstandin(f)]
       
   198                     # Ignored files must be ignored by both the dirstate and
       
   199                     # lfdirstate
       
   200                     result[5] = set(ignored).intersection(set(result[5]))
       
   201                     # combine normal files and lfiles
       
   202                     normals = [[fn for fn in filelist if not \
       
   203                         lfutil.isstandin(fn)] for filelist in result]
       
   204                     result = [sorted(list1 + list2) for (list1, list2) in \
       
   205                         zip(normals, lfiles)]
       
   206                 else:
       
   207                     def toname(f):
       
   208                         if lfutil.isstandin(f):
       
   209                             return lfutil.splitstandin(f)
       
   210                         return f
       
   211                     result = [[toname(f) for f in items] for items in result]
       
   212 
       
   213                 if not listunknown:
       
   214                     result[4] = []
       
   215                 if not listignored:
       
   216                     result[5] = []
       
   217                 if not listclean:
       
   218                     result[6] = []
       
   219                 self.lfstatus = True
       
   220                 return result
       
   221 
       
   222         # This call happens after a commit has occurred. Copy all of the lfiles
       
   223         # into the cache
       
   224         def commitctx(self, *args, **kwargs):
       
   225             node = super(lfiles_repo, self).commitctx(*args, **kwargs)
       
   226             ctx = self[node]
       
   227             for filename in ctx.files():
       
   228                 if lfutil.isstandin(filename) and filename in ctx.manifest():
       
   229                     realfile = lfutil.splitstandin(filename)
       
   230                     lfutil.copytocache(self, ctx.node(), realfile)
       
   231 
       
   232             return node
       
   233 
       
   234         # This call happens before a commit has occurred. The lfile standins
       
   235         # have not had their contents updated (to reflect the hash of their
       
   236         # lfile).  Do that here.
       
   237         def commit(self, text="", user=None, date=None, match=None,
       
   238                 force=False, editor=False, extra={}):
       
   239             orig = super(lfiles_repo, self).commit
       
   240 
       
   241             wlock = repo.wlock()
       
   242             try:
       
   243                 if getattr(repo, "_isrebasing", False):
       
   244                     # We have to take the time to pull down the new lfiles now.
       
   245                     # Otherwise if we are rebasing, any lfiles that were
       
   246                     # modified in the changesets we are rebasing on top of get
       
   247                     # overwritten either by the rebase or in the first commit
       
   248                     # after the rebase.
       
   249                     lfcommands.updatelfiles(repo.ui, repo)
       
   250                 # Case 1: user calls commit with no specific files or
       
   251                 # include/exclude patterns: refresh and commit everything.
       
   252                 if (match is None) or (not match.anypats() and not \
       
   253                         match.files()):
       
   254                     lfiles = lfutil.listlfiles(self)
       
   255                     lfdirstate = lfutil.openlfdirstate(ui, self)
       
   256                     # this only loops through lfiles that exist (not
       
   257                     # removed/renamed)
       
   258                     for lfile in lfiles:
       
   259                         if os.path.exists(self.wjoin(lfutil.standin(lfile))):
       
   260                             # this handles the case where a rebase is being
       
   261                             # performed and the working copy is not updated
       
   262                             # yet.
       
   263                             if os.path.exists(self.wjoin(lfile)):
       
   264                                 lfutil.updatestandin(self,
       
   265                                     lfutil.standin(lfile))
       
   266                                 lfdirstate.normal(lfile)
       
   267                     for lfile in lfdirstate:
       
   268                         if not os.path.exists(
       
   269                                 repo.wjoin(lfutil.standin(lfile))):
       
   270                             try:
       
   271                                 # Mercurial >= 1.9
       
   272                                 lfdirstate.drop(lfile)
       
   273                             except AttributeError:
       
   274                                 # Mercurial <= 1.8
       
   275                                 lfdirstate.forget(lfile)
       
   276                     lfdirstate.write()
       
   277 
       
   278                     return orig(text=text, user=user, date=date, match=match,
       
   279                                     force=force, editor=editor, extra=extra)
       
   280 
       
   281                 for file in match.files():
       
   282                     if lfutil.isstandin(file):
       
   283                         raise util.Abort(
       
   284                             "Don't commit largefile standin. Commit largefile.")
       
   285 
       
   286                 # Case 2: user calls commit with specified patterns: refresh
       
   287                 # any matching big files.
       
   288                 smatcher = lfutil.composestandinmatcher(self, match)
       
   289                 standins = lfutil.dirstate_walk(self.dirstate, smatcher)
       
   290 
       
   291                 # No matching big files: get out of the way and pass control to
       
   292                 # the usual commit() method.
       
   293                 if not standins:
       
   294                     return orig(text=text, user=user, date=date, match=match,
       
   295                                     force=force, editor=editor, extra=extra)
       
   296 
       
   297                 # Refresh all matching big files.  It's possible that the
       
   298                 # commit will end up failing, in which case the big files will
       
   299                 # stay refreshed.  No harm done: the user modified them and
       
   300                 # asked to commit them, so sooner or later we're going to
       
   301                 # refresh the standins.  Might as well leave them refreshed.
       
   302                 lfdirstate = lfutil.openlfdirstate(ui, self)
       
   303                 for standin in standins:
       
   304                     lfile = lfutil.splitstandin(standin)
       
   305                     if lfdirstate[lfile] <> 'r':
       
   306                         lfutil.updatestandin(self, standin)
       
   307                         lfdirstate.normal(lfile)
       
   308                     else:
       
   309                         try:
       
   310                             # Mercurial >= 1.9
       
   311                             lfdirstate.drop(lfile)
       
   312                         except AttributeError:
       
   313                             # Mercurial <= 1.8
       
   314                             lfdirstate.forget(lfile)
       
   315                 lfdirstate.write()
       
   316 
       
   317                 # Cook up a new matcher that only matches regular files or
       
   318                 # standins corresponding to the big files requested by the
       
   319                 # user.  Have to modify _files to prevent commit() from
       
   320                 # complaining "not tracked" for big files.
       
   321                 lfiles = lfutil.listlfiles(repo)
       
   322                 match = copy.copy(match)
       
   323                 orig_matchfn = match.matchfn
       
   324 
       
   325                 # Check both the list of lfiles and the list of standins
       
   326                 # because if a lfile was removed, it won't be in the list of
       
   327                 # lfiles at this point
       
   328                 match._files += sorted(standins)
       
   329 
       
   330                 actualfiles = []
       
   331                 for f in match._files:
       
   332                     fstandin = lfutil.standin(f)
       
   333 
       
   334                     # Ignore known lfiles and standins
       
   335                     if f in lfiles or fstandin in standins:
       
   336                         continue
       
   337 
       
   338                     # Append directory separator to avoid collisions
       
   339                     if not fstandin.endswith(os.sep):
       
   340                         fstandin += os.sep
       
   341 
       
   342                     # Prevalidate matching standin directories
       
   343                     if lfutil.any_(st for st in match._files if \
       
   344                             st.startswith(fstandin)):
       
   345                         continue
       
   346                     actualfiles.append(f)
       
   347                 match._files = actualfiles
       
   348 
       
   349                 def matchfn(f):
       
   350                     if orig_matchfn(f):
       
   351                         return f not in lfiles
       
   352                     else:
       
   353                         return f in standins
       
   354 
       
   355                 match.matchfn = matchfn
       
   356                 return orig(text=text, user=user, date=date, match=match,
       
   357                                 force=force, editor=editor, extra=extra)
       
   358             finally:
       
   359                 wlock.release()
       
   360 
       
   361         def push(self, remote, force=False, revs=None, newbranch=False):
       
   362             o = lfutil.findoutgoing(repo, remote, force)
       
   363             if o:
       
   364                 toupload = set()
       
   365                 o = repo.changelog.nodesbetween(o, revs)[0]
       
   366                 for n in o:
       
   367                     parents = [p for p in repo.changelog.parents(n) if p != \
       
   368                         node.nullid]
       
   369                     ctx = repo[n]
       
   370                     files = set(ctx.files())
       
   371                     if len(parents) == 2:
       
   372                         mc = ctx.manifest()
       
   373                         mp1 = ctx.parents()[0].manifest()
       
   374                         mp2 = ctx.parents()[1].manifest()
       
   375                         for f in mp1:
       
   376                             if f not in mc:
       
   377                                 files.add(f)
       
   378                         for f in mp2:
       
   379                             if f not in mc:
       
   380                                 files.add(f)
       
   381                         for f in mc:
       
   382                             if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f,
       
   383                                     None):
       
   384                                 files.add(f)
       
   385 
       
   386                     toupload = toupload.union(set([ctx[f].data().strip() for f\
       
   387                         in files if lfutil.isstandin(f) and f in ctx]))
       
   388                 lfcommands.uploadlfiles(ui, self, remote, toupload)
       
   389             # Mercurial >= 1.6 takes the newbranch argument, try that first.
       
   390             try:
       
   391                 return super(lfiles_repo, self).push(remote, force, revs,
       
   392                     newbranch)
       
   393             except TypeError:
       
   394                 return super(lfiles_repo, self).push(remote, force, revs)
       
   395 
       
   396     repo.__class__ = lfiles_repo
       
   397 
       
   398     def checkrequireslfiles(ui, repo, **kwargs):
       
   399         if 'largefiles' not in repo.requirements and lfutil.any_(
       
   400                 lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()):
       
   401             # work around bug in mercurial 1.9 whereby requirements is a list
       
   402             # on newly-cloned repos
       
   403             repo.requirements = set(repo.requirements)
       
   404 
       
   405             repo.requirements |= set(['largefiles'])
       
   406             repo._writerequirements()
       
   407 
       
   408     checkrequireslfiles(ui, repo)
       
   409 
       
   410     ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles)
       
   411     ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles)