hgext/convert/p4.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43787 be8552f25cab
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    22 
    22 
    23 from . import common
    23 from . import common
    24 
    24 
    25 
    25 
    26 def loaditer(f):
    26 def loaditer(f):
    27     "Yield the dictionary objects generated by p4"
    27     b"Yield the dictionary objects generated by p4"
    28     try:
    28     try:
    29         while True:
    29         while True:
    30             d = marshal.load(f)
    30             d = marshal.load(f)
    31             if not d:
    31             if not d:
    32                 break
    32                 break
    42     >>> decodefilename(b'portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
    42     >>> decodefilename(b'portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
    43     'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid'
    43     'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid'
    44     >>> decodefilename(b'//Depot/Directory/%2525/%2523/%23%40.%2A')
    44     >>> decodefilename(b'//Depot/Directory/%2525/%2523/%23%40.%2A')
    45     '//Depot/Directory/%25/%23/#@.*'
    45     '//Depot/Directory/%25/%23/#@.*'
    46     """
    46     """
    47     replacements = [('%2A', '*'), ('%23', '#'), ('%40', '@'), ('%25', '%')]
    47     replacements = [
       
    48         (b'%2A', b'*'),
       
    49         (b'%23', b'#'),
       
    50         (b'%40', b'@'),
       
    51         (b'%25', b'%'),
       
    52     ]
    48     for k, v in replacements:
    53     for k, v in replacements:
    49         filename = filename.replace(k, v)
    54         filename = filename.replace(k, v)
    50     return filename
    55     return filename
    51 
    56 
    52 
    57 
    55         # avoid import cycle
    60         # avoid import cycle
    56         from . import convcmd
    61         from . import convcmd
    57 
    62 
    58         super(p4_source, self).__init__(ui, repotype, path, revs=revs)
    63         super(p4_source, self).__init__(ui, repotype, path, revs=revs)
    59 
    64 
    60         if "/" in path and not path.startswith('//'):
    65         if b"/" in path and not path.startswith(b'//'):
    61             raise common.NoRepo(
    66             raise common.NoRepo(
    62                 _('%s does not look like a P4 repository') % path
    67                 _(b'%s does not look like a P4 repository') % path
    63             )
    68             )
    64 
    69 
    65         common.checktool('p4', abort=False)
    70         common.checktool(b'p4', abort=False)
    66 
    71 
    67         self.revmap = {}
    72         self.revmap = {}
    68         self.encoding = self.ui.config(
    73         self.encoding = self.ui.config(
    69             'convert', 'p4.encoding', convcmd.orig_encoding
    74             b'convert', b'p4.encoding', convcmd.orig_encoding
    70         )
    75         )
    71         self.re_type = re.compile(
    76         self.re_type = re.compile(
    72             br"([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
    77             br"([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
    73             br"(\+\w+)?$"
    78             br"(\+\w+)?$"
    74         )
    79         )
    78         )
    83         )
    79         self.re_keywords_old = re.compile(br"\$(Id|Header):[^$\n]*\$")
    84         self.re_keywords_old = re.compile(br"\$(Id|Header):[^$\n]*\$")
    80 
    85 
    81         if revs and len(revs) > 1:
    86         if revs and len(revs) > 1:
    82             raise error.Abort(
    87             raise error.Abort(
    83                 _("p4 source does not support specifying " "multiple revisions")
    88                 _(
       
    89                     b"p4 source does not support specifying "
       
    90                     b"multiple revisions"
       
    91                 )
    84             )
    92             )
    85 
    93 
    86     def setrevmap(self, revmap):
    94     def setrevmap(self, revmap):
    87         """Sets the parsed revmap dictionary.
    95         """Sets the parsed revmap dictionary.
    88 
    96 
    95         imports if a revmap is provided.
   103         imports if a revmap is provided.
    96         """
   104         """
    97         self.revmap = revmap
   105         self.revmap = revmap
    98 
   106 
    99     def _parse_view(self, path):
   107     def _parse_view(self, path):
   100         "Read changes affecting the path"
   108         b"Read changes affecting the path"
   101         cmd = 'p4 -G changes -s submitted %s' % procutil.shellquote(path)
   109         cmd = b'p4 -G changes -s submitted %s' % procutil.shellquote(path)
   102         stdout = procutil.popen(cmd, mode='rb')
   110         stdout = procutil.popen(cmd, mode=b'rb')
   103         p4changes = {}
   111         p4changes = {}
   104         for d in loaditer(stdout):
   112         for d in loaditer(stdout):
   105             c = d.get("change", None)
   113             c = d.get(b"change", None)
   106             if c:
   114             if c:
   107                 p4changes[c] = True
   115                 p4changes[c] = True
   108         return p4changes
   116         return p4changes
   109 
   117 
   110     def _parse(self, ui, path):
   118     def _parse(self, ui, path):
   111         "Prepare list of P4 filenames and revisions to import"
   119         b"Prepare list of P4 filenames and revisions to import"
   112         p4changes = {}
   120         p4changes = {}
   113         changeset = {}
   121         changeset = {}
   114         files_map = {}
   122         files_map = {}
   115         copies_map = {}
   123         copies_map = {}
   116         localname = {}
   124         localname = {}
   117         depotname = {}
   125         depotname = {}
   118         heads = []
   126         heads = []
   119 
   127 
   120         ui.status(_('reading p4 views\n'))
   128         ui.status(_(b'reading p4 views\n'))
   121 
   129 
   122         # read client spec or view
   130         # read client spec or view
   123         if "/" in path:
   131         if b"/" in path:
   124             p4changes.update(self._parse_view(path))
   132             p4changes.update(self._parse_view(path))
   125             if path.startswith("//") and path.endswith("/..."):
   133             if path.startswith(b"//") and path.endswith(b"/..."):
   126                 views = {path[:-3]: ""}
   134                 views = {path[:-3]: b""}
   127             else:
   135             else:
   128                 views = {"//": ""}
   136                 views = {b"//": b""}
   129         else:
   137         else:
   130             cmd = 'p4 -G client -o %s' % procutil.shellquote(path)
   138             cmd = b'p4 -G client -o %s' % procutil.shellquote(path)
   131             clientspec = marshal.load(procutil.popen(cmd, mode='rb'))
   139             clientspec = marshal.load(procutil.popen(cmd, mode=b'rb'))
   132 
   140 
   133             views = {}
   141             views = {}
   134             for client in clientspec:
   142             for client in clientspec:
   135                 if client.startswith("View"):
   143                 if client.startswith(b"View"):
   136                     sview, cview = clientspec[client].split()
   144                     sview, cview = clientspec[client].split()
   137                     p4changes.update(self._parse_view(sview))
   145                     p4changes.update(self._parse_view(sview))
   138                     if sview.endswith("...") and cview.endswith("..."):
   146                     if sview.endswith(b"...") and cview.endswith(b"..."):
   139                         sview = sview[:-3]
   147                         sview = sview[:-3]
   140                         cview = cview[:-3]
   148                         cview = cview[:-3]
   141                     cview = cview[2:]
   149                     cview = cview[2:]
   142                     cview = cview[cview.find("/") + 1 :]
   150                     cview = cview[cview.find(b"/") + 1 :]
   143                     views[sview] = cview
   151                     views[sview] = cview
   144 
   152 
   145         # list of changes that affect our source files
   153         # list of changes that affect our source files
   146         p4changes = p4changes.keys()
   154         p4changes = p4changes.keys()
   147         p4changes.sort(key=int)
   155         p4changes.sort(key=int)
   149         # list with depot pathnames, longest first
   157         # list with depot pathnames, longest first
   150         vieworder = views.keys()
   158         vieworder = views.keys()
   151         vieworder.sort(key=len, reverse=True)
   159         vieworder.sort(key=len, reverse=True)
   152 
   160 
   153         # handle revision limiting
   161         # handle revision limiting
   154         startrev = self.ui.config('convert', 'p4.startrev')
   162         startrev = self.ui.config(b'convert', b'p4.startrev')
   155 
   163 
   156         # now read the full changelists to get the list of file revisions
   164         # now read the full changelists to get the list of file revisions
   157         ui.status(_('collecting p4 changelists\n'))
   165         ui.status(_(b'collecting p4 changelists\n'))
   158         lastid = None
   166         lastid = None
   159         for change in p4changes:
   167         for change in p4changes:
   160             if startrev and int(change) < int(startrev):
   168             if startrev and int(change) < int(startrev):
   161                 continue
   169                 continue
   162             if self.revs and int(change) > int(self.revs[0]):
   170             if self.revs and int(change) > int(self.revs[0]):
   174             d = self._fetch_revision(change)
   182             d = self._fetch_revision(change)
   175             c = self._construct_commit(d, parents)
   183             c = self._construct_commit(d, parents)
   176 
   184 
   177             descarr = c.desc.splitlines(True)
   185             descarr = c.desc.splitlines(True)
   178             if len(descarr) > 0:
   186             if len(descarr) > 0:
   179                 shortdesc = descarr[0].rstrip('\r\n')
   187                 shortdesc = descarr[0].rstrip(b'\r\n')
   180             else:
   188             else:
   181                 shortdesc = '**empty changelist description**'
   189                 shortdesc = b'**empty changelist description**'
   182 
   190 
   183             t = '%s %s' % (c.rev, repr(shortdesc)[1:-1])
   191             t = b'%s %s' % (c.rev, repr(shortdesc)[1:-1])
   184             ui.status(stringutil.ellipsis(t, 80) + '\n')
   192             ui.status(stringutil.ellipsis(t, 80) + b'\n')
   185 
   193 
   186             files = []
   194             files = []
   187             copies = {}
   195             copies = {}
   188             copiedfiles = []
   196             copiedfiles = []
   189             i = 0
   197             i = 0
   190             while ("depotFile%d" % i) in d and ("rev%d" % i) in d:
   198             while (b"depotFile%d" % i) in d and (b"rev%d" % i) in d:
   191                 oldname = d["depotFile%d" % i]
   199                 oldname = d[b"depotFile%d" % i]
   192                 filename = None
   200                 filename = None
   193                 for v in vieworder:
   201                 for v in vieworder:
   194                     if oldname.lower().startswith(v.lower()):
   202                     if oldname.lower().startswith(v.lower()):
   195                         filename = decodefilename(views[v] + oldname[len(v) :])
   203                         filename = decodefilename(views[v] + oldname[len(v) :])
   196                         break
   204                         break
   197                 if filename:
   205                 if filename:
   198                     files.append((filename, d["rev%d" % i]))
   206                     files.append((filename, d[b"rev%d" % i]))
   199                     depotname[filename] = oldname
   207                     depotname[filename] = oldname
   200                     if d.get("action%d" % i) == "move/add":
   208                     if d.get(b"action%d" % i) == b"move/add":
   201                         copiedfiles.append(filename)
   209                         copiedfiles.append(filename)
   202                     localname[oldname] = filename
   210                     localname[oldname] = filename
   203                 i += 1
   211                 i += 1
   204 
   212 
   205             # Collect information about copied files
   213             # Collect information about copied files
   206             for filename in copiedfiles:
   214             for filename in copiedfiles:
   207                 oldname = depotname[filename]
   215                 oldname = depotname[filename]
   208 
   216 
   209                 flcmd = 'p4 -G filelog %s' % procutil.shellquote(oldname)
   217                 flcmd = b'p4 -G filelog %s' % procutil.shellquote(oldname)
   210                 flstdout = procutil.popen(flcmd, mode='rb')
   218                 flstdout = procutil.popen(flcmd, mode=b'rb')
   211 
   219 
   212                 copiedfilename = None
   220                 copiedfilename = None
   213                 for d in loaditer(flstdout):
   221                 for d in loaditer(flstdout):
   214                     copiedoldname = None
   222                     copiedoldname = None
   215 
   223 
   216                     i = 0
   224                     i = 0
   217                     while ("change%d" % i) in d:
   225                     while (b"change%d" % i) in d:
   218                         if (
   226                         if (
   219                             d["change%d" % i] == change
   227                             d[b"change%d" % i] == change
   220                             and d["action%d" % i] == "move/add"
   228                             and d[b"action%d" % i] == b"move/add"
   221                         ):
   229                         ):
   222                             j = 0
   230                             j = 0
   223                             while ("file%d,%d" % (i, j)) in d:
   231                             while (b"file%d,%d" % (i, j)) in d:
   224                                 if d["how%d,%d" % (i, j)] == "moved from":
   232                                 if d[b"how%d,%d" % (i, j)] == b"moved from":
   225                                     copiedoldname = d["file%d,%d" % (i, j)]
   233                                     copiedoldname = d[b"file%d,%d" % (i, j)]
   226                                     break
   234                                     break
   227                                 j += 1
   235                                 j += 1
   228                         i += 1
   236                         i += 1
   229 
   237 
   230                     if copiedoldname and copiedoldname in localname:
   238                     if copiedoldname and copiedoldname in localname:
   233 
   241 
   234                 if copiedfilename:
   242                 if copiedfilename:
   235                     copies[filename] = copiedfilename
   243                     copies[filename] = copiedfilename
   236                 else:
   244                 else:
   237                     ui.warn(
   245                     ui.warn(
   238                         _("cannot find source for copied file: %s@%s\n")
   246                         _(b"cannot find source for copied file: %s@%s\n")
   239                         % (filename, change)
   247                         % (filename, change)
   240                     )
   248                     )
   241 
   249 
   242             changeset[change] = c
   250             changeset[change] = c
   243             files_map[change] = files
   251             files_map[change] = files
   246 
   254 
   247         if lastid and len(changeset) > 0:
   255         if lastid and len(changeset) > 0:
   248             heads = [lastid]
   256             heads = [lastid]
   249 
   257 
   250         return {
   258         return {
   251             'changeset': changeset,
   259             b'changeset': changeset,
   252             'files': files_map,
   260             b'files': files_map,
   253             'copies': copies_map,
   261             b'copies': copies_map,
   254             'heads': heads,
   262             b'heads': heads,
   255             'depotname': depotname,
   263             b'depotname': depotname,
   256         }
   264         }
   257 
   265 
   258     @util.propertycache
   266     @util.propertycache
   259     def _parse_once(self):
   267     def _parse_once(self):
   260         return self._parse(self.ui, self.path)
   268         return self._parse(self.ui, self.path)
   261 
   269 
   262     @util.propertycache
   270     @util.propertycache
   263     def copies(self):
   271     def copies(self):
   264         return self._parse_once['copies']
   272         return self._parse_once[b'copies']
   265 
   273 
   266     @util.propertycache
   274     @util.propertycache
   267     def files(self):
   275     def files(self):
   268         return self._parse_once['files']
   276         return self._parse_once[b'files']
   269 
   277 
   270     @util.propertycache
   278     @util.propertycache
   271     def changeset(self):
   279     def changeset(self):
   272         return self._parse_once['changeset']
   280         return self._parse_once[b'changeset']
   273 
   281 
   274     @util.propertycache
   282     @util.propertycache
   275     def heads(self):
   283     def heads(self):
   276         return self._parse_once['heads']
   284         return self._parse_once[b'heads']
   277 
   285 
   278     @util.propertycache
   286     @util.propertycache
   279     def depotname(self):
   287     def depotname(self):
   280         return self._parse_once['depotname']
   288         return self._parse_once[b'depotname']
   281 
   289 
   282     def getheads(self):
   290     def getheads(self):
   283         return self.heads
   291         return self.heads
   284 
   292 
   285     def getfile(self, name, rev):
   293     def getfile(self, name, rev):
   286         cmd = 'p4 -G print %s' % procutil.shellquote(
   294         cmd = b'p4 -G print %s' % procutil.shellquote(
   287             "%s#%s" % (self.depotname[name], rev)
   295             b"%s#%s" % (self.depotname[name], rev)
   288         )
   296         )
   289 
   297 
   290         lasterror = None
   298         lasterror = None
   291         while True:
   299         while True:
   292             stdout = procutil.popen(cmd, mode='rb')
   300             stdout = procutil.popen(cmd, mode=b'rb')
   293 
   301 
   294             mode = None
   302             mode = None
   295             contents = []
   303             contents = []
   296             keywords = None
   304             keywords = None
   297 
   305 
   298             for d in loaditer(stdout):
   306             for d in loaditer(stdout):
   299                 code = d["code"]
   307                 code = d[b"code"]
   300                 data = d.get("data")
   308                 data = d.get(b"data")
   301 
   309 
   302                 if code == "error":
   310                 if code == b"error":
   303                     # if this is the first time error happened
   311                     # if this is the first time error happened
   304                     # re-attempt getting the file
   312                     # re-attempt getting the file
   305                     if not lasterror:
   313                     if not lasterror:
   306                         lasterror = IOError(d["generic"], data)
   314                         lasterror = IOError(d[b"generic"], data)
   307                         # this will exit inner-most for-loop
   315                         # this will exit inner-most for-loop
   308                         break
   316                         break
   309                     else:
   317                     else:
   310                         raise lasterror
   318                         raise lasterror
   311 
   319 
   312                 elif code == "stat":
   320                 elif code == b"stat":
   313                     action = d.get("action")
   321                     action = d.get(b"action")
   314                     if action in ["purge", "delete", "move/delete"]:
   322                     if action in [b"purge", b"delete", b"move/delete"]:
   315                         return None, None
   323                         return None, None
   316                     p4type = self.re_type.match(d["type"])
   324                     p4type = self.re_type.match(d[b"type"])
   317                     if p4type:
   325                     if p4type:
   318                         mode = ""
   326                         mode = b""
   319                         flags = (p4type.group(1) or "") + (
   327                         flags = (p4type.group(1) or b"") + (
   320                             p4type.group(3) or ""
   328                             p4type.group(3) or b""
   321                         )
   329                         )
   322                         if "x" in flags:
   330                         if b"x" in flags:
   323                             mode = "x"
   331                             mode = b"x"
   324                         if p4type.group(2) == "symlink":
   332                         if p4type.group(2) == b"symlink":
   325                             mode = "l"
   333                             mode = b"l"
   326                         if "ko" in flags:
   334                         if b"ko" in flags:
   327                             keywords = self.re_keywords_old
   335                             keywords = self.re_keywords_old
   328                         elif "k" in flags:
   336                         elif b"k" in flags:
   329                             keywords = self.re_keywords
   337                             keywords = self.re_keywords
   330 
   338 
   331                 elif code == "text" or code == "binary":
   339                 elif code == b"text" or code == b"binary":
   332                     contents.append(data)
   340                     contents.append(data)
   333 
   341 
   334                 lasterror = None
   342                 lasterror = None
   335 
   343 
   336             if not lasterror:
   344             if not lasterror:
   337                 break
   345                 break
   338 
   346 
   339         if mode is None:
   347         if mode is None:
   340             return None, None
   348             return None, None
   341 
   349 
   342         contents = ''.join(contents)
   350         contents = b''.join(contents)
   343 
   351 
   344         if keywords:
   352         if keywords:
   345             contents = keywords.sub("$\\1$", contents)
   353             contents = keywords.sub(b"$\\1$", contents)
   346         if mode == "l" and contents.endswith("\n"):
   354         if mode == b"l" and contents.endswith(b"\n"):
   347             contents = contents[:-1]
   355             contents = contents[:-1]
   348 
   356 
   349         return contents, mode
   357         return contents, mode
   350 
   358 
   351     def getchanges(self, rev, full):
   359     def getchanges(self, rev, full):
   352         if full:
   360         if full:
   353             raise error.Abort(_("convert from p4 does not support --full"))
   361             raise error.Abort(_(b"convert from p4 does not support --full"))
   354         return self.files[rev], self.copies[rev], set()
   362         return self.files[rev], self.copies[rev], set()
   355 
   363 
   356     def _construct_commit(self, obj, parents=None):
   364     def _construct_commit(self, obj, parents=None):
   357         """
   365         """
   358         Constructs a common.commit object from an unmarshalled
   366         Constructs a common.commit object from an unmarshalled
   359         `p4 describe` output
   367         `p4 describe` output
   360         """
   368         """
   361         desc = self.recode(obj.get("desc", ""))
   369         desc = self.recode(obj.get(b"desc", b""))
   362         date = (int(obj["time"]), 0)  # timezone not set
   370         date = (int(obj[b"time"]), 0)  # timezone not set
   363         if parents is None:
   371         if parents is None:
   364             parents = []
   372             parents = []
   365 
   373 
   366         return common.commit(
   374         return common.commit(
   367             author=self.recode(obj["user"]),
   375             author=self.recode(obj[b"user"]),
   368             date=dateutil.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'),
   376             date=dateutil.datestr(date, b'%Y-%m-%d %H:%M:%S %1%2'),
   369             parents=parents,
   377             parents=parents,
   370             desc=desc,
   378             desc=desc,
   371             branch=None,
   379             branch=None,
   372             rev=obj['change'],
   380             rev=obj[b'change'],
   373             extra={"p4": obj['change'], "convert_revision": obj['change']},
   381             extra={b"p4": obj[b'change'], b"convert_revision": obj[b'change']},
   374         )
   382         )
   375 
   383 
   376     def _fetch_revision(self, rev):
   384     def _fetch_revision(self, rev):
   377         """Return an output of `p4 describe` including author, commit date as
   385         """Return an output of `p4 describe` including author, commit date as
   378         a dictionary."""
   386         a dictionary."""
   379         cmd = "p4 -G describe -s %s" % rev
   387         cmd = b"p4 -G describe -s %s" % rev
   380         stdout = procutil.popen(cmd, mode='rb')
   388         stdout = procutil.popen(cmd, mode=b'rb')
   381         return marshal.load(stdout)
   389         return marshal.load(stdout)
   382 
   390 
   383     def getcommit(self, rev):
   391     def getcommit(self, rev):
   384         if rev in self.changeset:
   392         if rev in self.changeset:
   385             return self.changeset[rev]
   393             return self.changeset[rev]
   386         elif rev in self.revmap:
   394         elif rev in self.revmap:
   387             d = self._fetch_revision(rev)
   395             d = self._fetch_revision(rev)
   388             return self._construct_commit(d, parents=None)
   396             return self._construct_commit(d, parents=None)
   389         raise error.Abort(
   397         raise error.Abort(
   390             _("cannot find %s in the revmap or parsed changesets") % rev
   398             _(b"cannot find %s in the revmap or parsed changesets") % rev
   391         )
   399         )
   392 
   400 
   393     def gettags(self):
   401     def gettags(self):
   394         return {}
   402         return {}
   395 
   403