comparison contrib/convert-repo @ 3953:fad134931327

convert-repo: add basic CVS import support
author Matt Mackall <mpm@selenic.com>
date Fri, 22 Dec 2006 17:59:34 -0600
parents b58c1681d23b
children 9af4b853ed4d
comparison
equal deleted inserted replaced
3952:32c1653b7dad 3953: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 = {}