contrib/convert-repo
changeset 3954 fad134931327
parent 3948 b58c1681d23b
child 3955 9af4b853ed4d
equal deleted inserted replaced
3952:32c1653b7dad 3954:fad134931327
     5 #
     5 #
     6 # To use, run:
     6 # To use, run:
     7 #
     7 #
     8 # convert-repo <source> [<dest> [<mapfile>]]
     8 # convert-repo <source> [<dest> [<mapfile>]]
     9 #
     9 #
    10 # Currently accepted source formats: git
    10 # Currently accepted source formats: git, cvs
    11 # Currently accepted destination formats: hg
    11 # Currently accepted destination formats: hg
    12 #
    12 #
    13 # If destination isn't given, a new Mercurial repo named <src>-hg will
    13 # If destination isn't given, a new Mercurial repo named <src>-hg will
    14 # be created. If <mapfile> isn't given, it will be put in a default
    14 # be created. If <mapfile> isn't given, it will be put in a default
    15 # location (<dest>/.hg/shamap by default)
    15 # location (<dest>/.hg/shamap by default)
    21 #
    21 #
    22 # If the file doesn't exist, it's automatically created.  It's updated
    22 # If the file doesn't exist, it's automatically created.  It's updated
    23 # on each commit copied, so convert-repo can be interrupted and can
    23 # on each commit copied, so convert-repo can be interrupted and can
    24 # be run repeatedly to copy new commits.
    24 # be run repeatedly to copy new commits.
    25 
    25 
    26 import sys, os, zlib, sha, time
    26 import sys, os, zlib, sha, time, re, locale
    27 os.environ["HGENCODING"] = "utf-8"
    27 os.environ["HGENCODING"] = "utf-8"
    28 from mercurial import hg, ui, util, fancyopts
    28 from mercurial import hg, ui, util, fancyopts
    29 
    29 
    30 class Abort(Exception): pass
    30 class Abort(Exception): pass
       
    31 class NoRepo(Exception): pass
    31 
    32 
    32 quiet = 0
    33 quiet = 0
    33 def status(msg):
    34 def status(msg):
    34     if not quiet: sys.stdout.write(str(msg))
    35     if not quiet: sys.stdout.write(str(msg))
    35 
    36 
    45     except:
    46     except:
    46         try:
    47         try:
    47             return s.decode("latin-1").encode("utf-8")
    48             return s.decode("latin-1").encode("utf-8")
    48         except:
    49         except:
    49             return s.decode("utf-8", "replace").encode("utf-8")
    50             return s.decode("utf-8", "replace").encode("utf-8")
       
    51 
       
    52 # CVS conversion code inspired by hg-cvs-import and git-cvsimport
       
    53 class convert_cvs:
       
    54     def __init__(self, path):
       
    55         self.path = path
       
    56         cvs = os.path.join(path, "CVS")
       
    57         if not os.path.exists(cvs):
       
    58             raise NoRepo("couldn't open CVS repo %s" % path)
       
    59 
       
    60         self.changeset = {}
       
    61         self.tags = {}
       
    62         self.lastbranch = {}
       
    63         self.parent = {}
       
    64         self.socket = None
       
    65         self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
       
    66         self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
       
    67         self.encoding = locale.getpreferredencoding()
       
    68         self._parse()
       
    69         self._connect()
       
    70 
       
    71     def _parse(self):
       
    72         if self.changeset:
       
    73             return
       
    74 
       
    75         d = os.getcwd()
       
    76         try:
       
    77             os.chdir(self.path)
       
    78             id = None
       
    79             state = 0
       
    80             for l in os.popen("cvsps -A"):
       
    81                 if state == 0: # header
       
    82                     if l.startswith("PatchSet"):
       
    83                         id = l[9:-2]
       
    84                     elif l.startswith("Date"):
       
    85                         date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
       
    86                         date = util.datestr(date)
       
    87                     elif l.startswith("Branch"):
       
    88                         branch = l[8:-1]
       
    89                         self.parent[id] = self.lastbranch.get(branch,'bad')
       
    90                         self.lastbranch[branch] = id
       
    91                     elif l.startswith("Ancestor branch"):
       
    92                         ancestor = l[17:-1]
       
    93                         self.parent[id] = self.lastbranch[ancestor]
       
    94                     elif l.startswith("Author"):
       
    95                         author = self.recode(l[8:-1])
       
    96                     elif l.startswith("Tag: "):
       
    97                         t = l[5:-1]
       
    98                         if t != "(none) ":
       
    99                             self.tags[t] = id
       
   100                     elif l.startswith("Log:"):
       
   101                         state = 1
       
   102                         log = ""
       
   103                 elif state == 1: # log
       
   104                     if l == "Members: \n":
       
   105                         files = {}
       
   106                         log = self.recode(log[:-1])
       
   107                         if log.isspace():
       
   108                             log = "*** empty log message ***\n"
       
   109                         state = 2
       
   110                     else:
       
   111                         log += l
       
   112                 elif state == 2:
       
   113                     if l == "\n": #
       
   114                         state = 0
       
   115                         self.changeset[id] = (date, author, log, files)
       
   116                     else:
       
   117                         file,rev = l[1:-2].rsplit(':',1)
       
   118                         rev = rev.split("->")[1]
       
   119                         files[file] = rev
       
   120 
       
   121             self.heads = self.lastbranch.values()
       
   122         finally:
       
   123             os.chdir(d)
       
   124 
       
   125     def _connect(self):
       
   126         root = self.cvsroot
       
   127         local = False
       
   128         user, host = None, None
       
   129         cmd = ['cvs', 'server']
       
   130 
       
   131         status("connecting to %s\n" % root)
       
   132 
       
   133         # only non-pserver for now
       
   134         if root.startswith(":pserver"):
       
   135             abort("can't handle pserver mode yet: %s\n" % root)
       
   136 
       
   137         if root.startswith(":local:"):
       
   138             local = True
       
   139             root = root[7:]
       
   140         else:
       
   141             # :ext:user@host/home/user/path/to/cvsroot
       
   142             if root.startswith(":ext:"):
       
   143                 root = root[5:]
       
   144             m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
       
   145             if not m:
       
   146                 local = True
       
   147             else:
       
   148                 local = False
       
   149                 user, host, root = m.group(1), m.group(2), m.group(3)
       
   150 
       
   151         if not local:
       
   152             rsh = os.environ.get("CVS_RSH" or "rsh")
       
   153             if user:
       
   154                 cmd = [rsh, '-l', user, host] + cmd
       
   155             else:
       
   156                 cmd = [rsh, host] + cmd
       
   157 
       
   158         self.writep, self.readp = os.popen2(cmd)
       
   159         self.realroot = root
       
   160 
       
   161         self.writep.write("Root %s\n" % root)
       
   162         self.writep.write("Valid-responses ok error Valid-requests Mode"
       
   163                           " M Mbinary E Checked-in Created Updated"
       
   164                           " Merged Removed\n")
       
   165         self.writep.write("valid-requests\n")
       
   166         self.writep.flush()
       
   167         r = self.readp.readline()
       
   168         if not r.startswith("Valid-requests"):
       
   169             abort("server sucks\n")
       
   170         if "UseUnchanged" in r:
       
   171             self.writep.write("UseUnchanged\n")
       
   172             self.writep.flush()
       
   173             r = self.readp.readline()
       
   174 
       
   175     def getheads(self):
       
   176         return self.heads
       
   177 
       
   178     def getfile(self, name, rev):
       
   179         if rev.endswith("(DEAD)"):
       
   180             raise IOError
       
   181 
       
   182         args = ("-N -P -kk -r %s --" % rev).split()
       
   183         args.append(os.path.join(self.cvsrepo, name))
       
   184         for x in args:
       
   185             self.writep.write("Argument %s\n" % x)
       
   186         self.writep.write("Directory .\n%s\nco\n" % self.realroot)
       
   187         self.writep.flush()
       
   188 
       
   189         data = ""
       
   190         while 1:
       
   191             line = self.readp.readline()
       
   192             if line.startswith("Created ") or line.startswith("Updated "):
       
   193                 self.readp.readline() # path
       
   194                 self.readp.readline() # entries
       
   195                 mode = self.readp.readline()[:-1]
       
   196                 count = int(self.readp.readline()[:-1])
       
   197                 data = self.readp.read(count)
       
   198             elif line.startswith(" "):
       
   199                 data += line[1:]
       
   200             elif line.startswith("M "):
       
   201                 pass
       
   202             elif line.startswith("Mbinary "):
       
   203                 count = int(self.readp.readline()[:-1])
       
   204                 data = self.readp.read(count)
       
   205             else:
       
   206                 if line == "ok\n":
       
   207                     return data
       
   208                 elif line.startswith("E "):
       
   209                     warn("cvs server: %s\n" % line[2:])
       
   210                 elif line.startswith("Remove"):
       
   211                     l = self.readp.readline()
       
   212                     l = self.readp.readline()
       
   213                     if l != "ok\n":
       
   214                         abort("unknown CVS response: %s\n" % l)
       
   215                 else:
       
   216                     abort("unknown CVS response: %s\n" % line)
       
   217 
       
   218     def getchanges(self, rev):
       
   219         files = self.changeset[rev][3]
       
   220         cl = [ (f, r, 0) for f,r in files.items() ]
       
   221         cl.sort()
       
   222         return cl
       
   223 
       
   224     def recode(self, text):
       
   225         return text.decode(self.encoding, "replace").encode("utf-8")
       
   226 
       
   227     def getcommit(self, rev):
       
   228         cs = self.changeset[rev]
       
   229         parents = [self.parent[rev]]
       
   230         if rev == "1":
       
   231             parents = []
       
   232         return (parents, cs[1], cs[0], cs[2])
       
   233 
       
   234     def gettags(self):
       
   235         return self.tags
    50 
   236 
    51 class convert_git:
   237 class convert_git:
    52     def __init__(self, path):
   238     def __init__(self, path):
    53         if os.path.isdir(path + "/.git"):
   239         if os.path.isdir(path + "/.git"):
    54             path += "/.git"
   240             path += "/.git"
    55         self.path = path
   241         self.path = path
    56         if not os.path.exists(path + "/HEAD"):
   242         if not os.path.exists(path + "/HEAD"):
    57             raise TypeError("couldn't open GIT repo %s" % path)
   243             raise NoRepo("couldn't open GIT repo %s" % path)
    58 
   244 
    59     def getheads(self):
   245     def getheads(self):
    60         fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path)
   246         fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path)
    61         return [fh.read()[:-1]]
   247         return [fh.read()[:-1]]
    62 
   248 
   127         self.path = path
   313         self.path = path
   128         u = ui.ui()
   314         u = ui.ui()
   129         try:
   315         try:
   130             self.repo = hg.repository(u, path)
   316             self.repo = hg.repository(u, path)
   131         except:
   317         except:
   132             raise TypeError("could open hg repo %s" % path)
   318             raise NoRepo("could open hg repo %s" % path)
   133 
   319 
   134     def mapfile(self):
   320     def mapfile(self):
   135         return os.path.join(self.path, ".hg", "shamap")
   321         return os.path.join(self.path, ".hg", "shamap")
   136 
   322 
   137     def getheads(self):
   323     def getheads(self):
   166         p2 = parents.pop(0)
   352         p2 = parents.pop(0)
   167 
   353 
   168         while parents:
   354         while parents:
   169             p1 = p2
   355             p1 = p2
   170             p2 = parents.pop(0)
   356             p2 = parents.pop(0)
   171             self.repo.rawcommit(files, text, author, dest,
   357             a = self.repo.rawcommit(files, text, author, dest,
   172                                 hg.bin(p1), hg.bin(p2))
   358                                     hg.bin(p1), hg.bin(p2))
   173             text = "(octopus merge fixup)\n"
   359             text = "(octopus merge fixup)\n"
   174             p2 = hg.hex(self.repo.changelog.tip())
   360             p2 = hg.hex(self.repo.changelog.tip())
   175 
   361 
   176         return p2
   362         return p2
   177 
   363 
   200             date = "%s 0" % int(time.mktime(time.gmtime()))
   386             date = "%s 0" % int(time.mktime(time.gmtime()))
   201             self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
   387             self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
   202                                 date, self.repo.changelog.tip(), hg.nullid)
   388                                 date, self.repo.changelog.tip(), hg.nullid)
   203             return hg.hex(self.repo.changelog.tip())
   389             return hg.hex(self.repo.changelog.tip())
   204 
   390 
   205 converters = [convert_git, convert_mercurial]
   391 converters = [convert_cvs, convert_git, convert_mercurial]
   206 
   392 
   207 def converter(path):
   393 def converter(path):
   208     if not os.path.isdir(path):
   394     if not os.path.isdir(path):
   209         abort("%s: not a directory\n" % path)
   395         abort("%s: not a directory\n" % path)
   210     for c in converters:
   396     for c in converters:
   211         try:
   397         try:
   212             return c(path)
   398             return c(path)
   213         except TypeError:
   399         except NoRepo:
   214             pass
   400             pass
   215     abort("%s: unknown repository type\n" % path)
   401     abort("%s: unknown repository type\n" % path)
   216 
   402 
   217 class convert:
   403 class convert:
   218     def __init__(self, source, dest, mapfile):
   404     def __init__(self, source, dest, mapfile):
   317         c = None
   503         c = None
   318 
   504 
   319         status("converting...\n")
   505         status("converting...\n")
   320         for c in t:
   506         for c in t:
   321             num -= 1
   507             num -= 1
   322             desc = self.commitcache[c][3].splitlines()[0]
   508             desc = self.commitcache[c][3]
       
   509             if "\n" in desc:
       
   510                 desc = desc.splitlines()[0]
   323             status("%d %s\n" % (num, desc))
   511             status("%d %s\n" % (num, desc))
   324             self.copy(c)
   512             self.copy(c)
   325 
   513 
   326         tags = self.source.gettags()
   514         tags = self.source.gettags()
   327         ctags = {}
   515         ctags = {}