##// END OF EJS Templates
convert extension: Save a few opens on the map file...
Edouard Gomez -
r4588:9855939d default
parent child Browse files
Show More
@@ -1,242 +1,262 b''
1 # convert.py Foreign SCM converter
1 # convert.py Foreign SCM converter
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from common import NoRepo
8 from common import NoRepo
9 from cvs import convert_cvs
9 from cvs import convert_cvs
10 from git import convert_git
10 from git import convert_git
11 from hg import convert_mercurial
11 from hg import convert_mercurial
12
12
13 import os
13 import os
14 from mercurial import hg, ui, util, commands
14 from mercurial import hg, ui, util, commands
15
15
16 commands.norepo += " convert"
16 commands.norepo += " convert"
17
17
18 converters = [convert_cvs, convert_git, convert_mercurial]
18 converters = [convert_cvs, convert_git, convert_mercurial]
19
19
20 def converter(ui, path):
20 def converter(ui, path):
21 if not os.path.isdir(path):
21 if not os.path.isdir(path):
22 raise util.Abort("%s: not a directory" % path)
22 raise util.Abort("%s: not a directory" % path)
23 for c in converters:
23 for c in converters:
24 try:
24 try:
25 return c(ui, path)
25 return c(ui, path)
26 except NoRepo:
26 except NoRepo:
27 pass
27 pass
28 raise util.Abort("%s: unknown repository type" % path)
28 raise util.Abort("%s: unknown repository type" % path)
29
29
30 class convert(object):
30 class convert(object):
31 def __init__(self, ui, source, dest, mapfile, opts):
31 def __init__(self, ui, source, dest, mapfile, opts):
32
32
33 self.source = source
33 self.source = source
34 self.dest = dest
34 self.dest = dest
35 self.ui = ui
35 self.ui = ui
36 self.mapfile = mapfile
37 self.opts = opts
36 self.opts = opts
38 self.commitcache = {}
37 self.commitcache = {}
38 self.mapfile = mapfile
39 self.mapfilefd = None
39
40
40 self.map = {}
41 self.map = {}
41 try:
42 try:
42 for l in file(self.mapfile):
43 origmapfile = open(self.mapfile, 'r')
44 for l in origmapfile:
43 sv, dv = l[:-1].split()
45 sv, dv = l[:-1].split()
44 self.map[sv] = dv
46 self.map[sv] = dv
47 origmapfile.close()
45 except IOError:
48 except IOError:
46 pass
49 pass
47
50
48 def walktree(self, heads):
51 def walktree(self, heads):
49 visit = heads
52 visit = heads
50 known = {}
53 known = {}
51 parents = {}
54 parents = {}
52 while visit:
55 while visit:
53 n = visit.pop(0)
56 n = visit.pop(0)
54 if n in known or n in self.map: continue
57 if n in known or n in self.map: continue
55 known[n] = 1
58 known[n] = 1
56 self.commitcache[n] = self.source.getcommit(n)
59 self.commitcache[n] = self.source.getcommit(n)
57 cp = self.commitcache[n].parents
60 cp = self.commitcache[n].parents
58 for p in cp:
61 for p in cp:
59 parents.setdefault(n, []).append(p)
62 parents.setdefault(n, []).append(p)
60 visit.append(p)
63 visit.append(p)
61
64
62 return parents
65 return parents
63
66
64 def toposort(self, parents):
67 def toposort(self, parents):
65 visit = parents.keys()
68 visit = parents.keys()
66 seen = {}
69 seen = {}
67 children = {}
70 children = {}
68
71
69 while visit:
72 while visit:
70 n = visit.pop(0)
73 n = visit.pop(0)
71 if n in seen: continue
74 if n in seen: continue
72 seen[n] = 1
75 seen[n] = 1
73 pc = 0
76 pc = 0
74 if n in parents:
77 if n in parents:
75 for p in parents[n]:
78 for p in parents[n]:
76 if p not in self.map: pc += 1
79 if p not in self.map: pc += 1
77 visit.append(p)
80 visit.append(p)
78 children.setdefault(p, []).append(n)
81 children.setdefault(p, []).append(n)
79 if not pc: root = n
82 if not pc: root = n
80
83
81 s = []
84 s = []
82 removed = {}
85 removed = {}
83 visit = children.keys()
86 visit = children.keys()
84 while visit:
87 while visit:
85 n = visit.pop(0)
88 n = visit.pop(0)
86 if n in removed: continue
89 if n in removed: continue
87 dep = 0
90 dep = 0
88 if n in parents:
91 if n in parents:
89 for p in parents[n]:
92 for p in parents[n]:
90 if p in self.map: continue
93 if p in self.map: continue
91 if p not in removed:
94 if p not in removed:
92 # we're still dependent
95 # we're still dependent
93 visit.append(n)
96 visit.append(n)
94 dep = 1
97 dep = 1
95 break
98 break
96
99
97 if not dep:
100 if not dep:
98 # all n's parents are in the list
101 # all n's parents are in the list
99 removed[n] = 1
102 removed[n] = 1
100 if n not in self.map:
103 if n not in self.map:
101 s.append(n)
104 s.append(n)
102 if n in children:
105 if n in children:
103 for c in children[n]:
106 for c in children[n]:
104 visit.insert(0, c)
107 visit.insert(0, c)
105
108
106 if self.opts.get('datesort'):
109 if self.opts.get('datesort'):
107 depth = {}
110 depth = {}
108 for n in s:
111 for n in s:
109 depth[n] = 0
112 depth[n] = 0
110 pl = [p for p in self.commitcache[n].parents
113 pl = [p for p in self.commitcache[n].parents
111 if p not in self.map]
114 if p not in self.map]
112 if pl:
115 if pl:
113 depth[n] = max([depth[p] for p in pl]) + 1
116 depth[n] = max([depth[p] for p in pl]) + 1
114
117
115 s = [(depth[n], self.commitcache[n].date, n) for n in s]
118 s = [(depth[n], self.commitcache[n].date, n) for n in s]
116 s.sort()
119 s.sort()
117 s = [e[2] for e in s]
120 s = [e[2] for e in s]
118
121
119 return s
122 return s
120
123
124 def mapentry(self, src, dst):
125 if self.mapfilefd is None:
126 try:
127 self.mapfilefd = open(self.mapfile, "a")
128 except IOError, (errno, strerror):
129 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.mapfile, errno, strerror))
130 self.map[src] = dst
131 self.mapfilefd.write("%s %s\n" % (src, dst))
132 self.mapfilefd.flush()
133
121 def copy(self, rev):
134 def copy(self, rev):
122 c = self.commitcache[rev]
135 c = self.commitcache[rev]
123 files = self.source.getchanges(rev)
136 files = self.source.getchanges(rev)
124
137
125 for f, v in files:
138 for f, v in files:
126 try:
139 try:
127 data = self.source.getfile(f, v)
140 data = self.source.getfile(f, v)
128 except IOError, inst:
141 except IOError, inst:
129 self.dest.delfile(f)
142 self.dest.delfile(f)
130 else:
143 else:
131 e = self.source.getmode(f, v)
144 e = self.source.getmode(f, v)
132 self.dest.putfile(f, e, data)
145 self.dest.putfile(f, e, data)
133
146
134 r = [self.map[v] for v in c.parents]
147 r = [self.map[v] for v in c.parents]
135 f = [f for f, v in files]
148 f = [f for f, v in files]
136 self.map[rev] = self.dest.putcommit(f, r, c)
149 newnode = self.dest.putcommit(f, r, c)
137 file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))
150 self.mapentry(rev, newnode)
138
151
139 def convert(self):
152 def convert(self):
140 self.ui.status("scanning source...\n")
153 try:
141 heads = self.source.getheads()
154 self.ui.status("scanning source...\n")
142 parents = self.walktree(heads)
155 heads = self.source.getheads()
143 self.ui.status("sorting...\n")
156 parents = self.walktree(heads)
144 t = self.toposort(parents)
157 self.ui.status("sorting...\n")
145 num = len(t)
158 t = self.toposort(parents)
146 c = None
159 num = len(t)
160 c = None
161
162 self.ui.status("converting...\n")
163 for c in t:
164 num -= 1
165 desc = self.commitcache[c].desc
166 if "\n" in desc:
167 desc = desc.splitlines()[0]
168 self.ui.status("%d %s\n" % (num, desc))
169 self.copy(c)
147
170
148 self.ui.status("converting...\n")
171 tags = self.source.gettags()
149 for c in t:
172 ctags = {}
150 num -= 1
173 for k in tags:
151 desc = self.commitcache[c].desc
174 v = tags[k]
152 if "\n" in desc:
175 if v in self.map:
153 desc = desc.splitlines()[0]
176 ctags[k] = self.map[v]
154 self.ui.status("%d %s\n" % (num, desc))
155 self.copy(c)
156
177
157 tags = self.source.gettags()
178 if c and ctags:
158 ctags = {}
179 nrev = self.dest.puttags(ctags)
159 for k in tags:
180 # write another hash correspondence to override the previous
160 v = tags[k]
181 # one so we don't end up with extra tag heads
161 if v in self.map:
182 if nrev:
162 ctags[k] = self.map[v]
183 self.mapentry(c, nrev)
184 finally:
185 self.cleanup()
163
186
164 if c and ctags:
187 def cleanup(self):
165 nrev = self.dest.puttags(ctags)
188 if self.mapfilefd:
166 # write another hash correspondence to override the previous
189 self.mapfilefd.close()
167 # one so we don't end up with extra tag heads
168 if nrev:
169 file(self.mapfile, "a").write("%s %s\n" % (c, nrev))
170
190
171 def _convert(ui, src, dest=None, mapfile=None, **opts):
191 def _convert(ui, src, dest=None, mapfile=None, **opts):
172 '''Convert a foreign SCM repository to a Mercurial one.
192 '''Convert a foreign SCM repository to a Mercurial one.
173
193
174 Accepted source formats:
194 Accepted source formats:
175 - GIT
195 - GIT
176 - CVS
196 - CVS
177
197
178 Accepted destination formats:
198 Accepted destination formats:
179 - Mercurial
199 - Mercurial
180
200
181 If destination isn't given, a new Mercurial repo named <src>-hg will
201 If destination isn't given, a new Mercurial repo named <src>-hg will
182 be created. If <mapfile> isn't given, it will be put in a default
202 be created. If <mapfile> isn't given, it will be put in a default
183 location (<dest>/.hg/shamap by default)
203 location (<dest>/.hg/shamap by default)
184
204
185 The <mapfile> is a simple text file that maps each source commit ID to
205 The <mapfile> is a simple text file that maps each source commit ID to
186 the destination ID for that revision, like so:
206 the destination ID for that revision, like so:
187
207
188 <source ID> <destination ID>
208 <source ID> <destination ID>
189
209
190 If the file doesn't exist, it's automatically created. It's updated
210 If the file doesn't exist, it's automatically created. It's updated
191 on each commit copied, so convert-repo can be interrupted and can
211 on each commit copied, so convert-repo can be interrupted and can
192 be run repeatedly to copy new commits.
212 be run repeatedly to copy new commits.
193 '''
213 '''
194
214
195 srcc = converter(ui, src)
215 srcc = converter(ui, src)
196 if not hasattr(srcc, "getcommit"):
216 if not hasattr(srcc, "getcommit"):
197 raise util.Abort("%s: can't read from this repo type" % src)
217 raise util.Abort("%s: can't read from this repo type" % src)
198
218
199 if not dest:
219 if not dest:
200 dest = src + "-hg"
220 dest = src + "-hg"
201 ui.status("assuming destination %s\n" % dest)
221 ui.status("assuming destination %s\n" % dest)
202
222
203 # Try to be smart and initalize things when required
223 # Try to be smart and initalize things when required
204 if os.path.isdir(dest):
224 if os.path.isdir(dest):
205 if len(os.listdir(dest)) > 0:
225 if len(os.listdir(dest)) > 0:
206 try:
226 try:
207 hg.repository(ui, dest)
227 hg.repository(ui, dest)
208 ui.status("destination %s is a Mercurial repository\n" % dest)
228 ui.status("destination %s is a Mercurial repository\n" % dest)
209 except hg.RepoError:
229 except hg.RepoError:
210 raise util.Abort(
230 raise util.Abort(
211 "destination directory %s is not empty.\n"
231 "destination directory %s is not empty.\n"
212 "Please specify an empty directory to be initialized\n"
232 "Please specify an empty directory to be initialized\n"
213 "or an already initialized mercurial repository"
233 "or an already initialized mercurial repository"
214 % dest)
234 % dest)
215 else:
235 else:
216 ui.status("initializing destination %s repository\n" % dest)
236 ui.status("initializing destination %s repository\n" % dest)
217 hg.repository(ui, dest, create=True)
237 hg.repository(ui, dest, create=True)
218 elif os.path.exists(dest):
238 elif os.path.exists(dest):
219 raise util.Abort("destination %s exists and is not a directory" % dest)
239 raise util.Abort("destination %s exists and is not a directory" % dest)
220 else:
240 else:
221 ui.status("initializing destination %s repository\n" % dest)
241 ui.status("initializing destination %s repository\n" % dest)
222 hg.repository(ui, dest, create=True)
242 hg.repository(ui, dest, create=True)
223
243
224 destc = converter(ui, dest)
244 destc = converter(ui, dest)
225 if not hasattr(destc, "putcommit"):
245 if not hasattr(destc, "putcommit"):
226 raise util.Abort("%s: can't write to this repo type" % src)
246 raise util.Abort("%s: can't write to this repo type" % src)
227
247
228 if not mapfile:
248 if not mapfile:
229 try:
249 try:
230 mapfile = destc.mapfile()
250 mapfile = destc.mapfile()
231 except:
251 except:
232 mapfile = os.path.join(destc, "map")
252 mapfile = os.path.join(destc, "map")
233
253
234 c = convert(ui, srcc, destc, mapfile, opts)
254 c = convert(ui, srcc, destc, mapfile, opts)
235 c.convert()
255 c.convert()
236
256
237 cmdtable = {
257 cmdtable = {
238 "convert":
258 "convert":
239 (_convert,
259 (_convert,
240 [('', 'datesort', None, 'try to sort changesets by date')],
260 [('', 'datesort', None, 'try to sort changesets by date')],
241 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
261 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
242 }
262 }
General Comments 0
You need to be logged in to leave comments. Login now