##// END OF EJS Templates
convert: add default constructor for converter_sink
Bryan O'Sullivan -
r5440:b4ae8535 default
parent child Browse files
Show More
@@ -1,181 +1,182
1 1 # common code for the convert extension
2 2 import base64
3 3 import cPickle as pickle
4 4
5 5 def encodeargs(args):
6 6 def encodearg(s):
7 7 lines = base64.encodestring(s)
8 8 lines = [l.splitlines()[0] for l in lines]
9 9 return ''.join(lines)
10 10
11 11 s = pickle.dumps(args)
12 12 return encodearg(s)
13 13
14 14 def decodeargs(s):
15 15 s = base64.decodestring(s)
16 16 return pickle.loads(s)
17 17
18 18 class NoRepo(Exception): pass
19 19
20 20 SKIPREV = 'SKIP'
21 21
22 22 class commit(object):
23 23 def __init__(self, author, date, desc, parents, branch=None, rev=None,
24 24 extra={}):
25 25 self.author = author
26 26 self.date = date
27 27 self.desc = desc
28 28 self.parents = parents
29 29 self.branch = branch
30 30 self.rev = rev
31 31 self.extra = extra
32 32
33 33 class converter_source(object):
34 34 """Conversion source interface"""
35 35
36 36 def __init__(self, ui, path, rev=None):
37 37 """Initialize conversion source (or raise NoRepo("message")
38 38 exception if path is not a valid repository)"""
39 39 self.ui = ui
40 40 self.path = path
41 41 self.rev = rev
42 42
43 43 self.encoding = 'utf-8'
44 44
45 45 def before(self):
46 46 pass
47 47
48 48 def after(self):
49 49 pass
50 50
51 51 def setrevmap(self, revmap, order):
52 52 """set the map of already-converted revisions
53 53
54 54 order is a list with the keys from revmap in the order they
55 55 appear in the revision map file."""
56 56 pass
57 57
58 58 def getheads(self):
59 59 """Return a list of this repository's heads"""
60 60 raise NotImplementedError()
61 61
62 62 def getfile(self, name, rev):
63 63 """Return file contents as a string"""
64 64 raise NotImplementedError()
65 65
66 66 def getmode(self, name, rev):
67 67 """Return file mode, eg. '', 'x', or 'l'"""
68 68 raise NotImplementedError()
69 69
70 70 def getchanges(self, version):
71 71 """Returns a tuple of (files, copies)
72 72 Files is a sorted list of (filename, id) tuples for all files changed
73 73 in version, where id is the source revision id of the file.
74 74
75 75 copies is a dictionary of dest: source
76 76 """
77 77 raise NotImplementedError()
78 78
79 79 def getcommit(self, version):
80 80 """Return the commit object for version"""
81 81 raise NotImplementedError()
82 82
83 83 def gettags(self):
84 84 """Return the tags as a dictionary of name: revision"""
85 85 raise NotImplementedError()
86 86
87 87 def recode(self, s, encoding=None):
88 88 if not encoding:
89 89 encoding = self.encoding or 'utf-8'
90 90
91 91 if isinstance(s, unicode):
92 92 return s.encode("utf-8")
93 93 try:
94 94 return s.decode(encoding).encode("utf-8")
95 95 except:
96 96 try:
97 97 return s.decode("latin-1").encode("utf-8")
98 98 except:
99 99 return s.decode(encoding, "replace").encode("utf-8")
100 100
101 101 def getchangedfiles(self, rev, i):
102 102 """Return the files changed by rev compared to parent[i].
103 103
104 104 i is an index selecting one of the parents of rev. The return
105 105 value should be the list of files that are different in rev and
106 106 this parent.
107 107
108 108 If rev has no parents, i is None.
109 109
110 110 This function is only needed to support --filemap
111 111 """
112 112 raise NotImplementedError()
113 113
114 114 class converter_sink(object):
115 115 """Conversion sink (target) interface"""
116 116
117 117 def __init__(self, ui, path):
118 118 """Initialize conversion sink (or raise NoRepo("message")
119 119 exception if path is not a valid repository)"""
120 raise NotImplementedError()
120 self.path = path
121 self.ui = ui
121 122
122 123 def getheads(self):
123 124 """Return a list of this repository's heads"""
124 125 raise NotImplementedError()
125 126
126 127 def revmapfile(self):
127 128 """Path to a file that will contain lines
128 129 source_rev_id sink_rev_id
129 130 mapping equivalent revision identifiers for each system."""
130 131 raise NotImplementedError()
131 132
132 133 def authorfile(self):
133 134 """Path to a file that will contain lines
134 135 srcauthor=dstauthor
135 136 mapping equivalent authors identifiers for each system."""
136 137 return None
137 138
138 139 def putfile(self, f, e, data):
139 140 """Put file for next putcommit().
140 141 f: path to file
141 142 e: '', 'x', or 'l' (regular file, executable, or symlink)
142 143 data: file contents"""
143 144 raise NotImplementedError()
144 145
145 146 def delfile(self, f):
146 147 """Delete file for next putcommit().
147 148 f: path to file"""
148 149 raise NotImplementedError()
149 150
150 151 def putcommit(self, files, parents, commit):
151 152 """Create a revision with all changed files listed in 'files'
152 153 and having listed parents. 'commit' is a commit object containing
153 154 at a minimum the author, date, and message for this changeset.
154 155 Called after putfile() and delfile() calls. Note that the sink
155 156 repository is not told to update itself to a particular revision
156 157 (or even what that revision would be) before it receives the
157 158 file data."""
158 159 raise NotImplementedError()
159 160
160 161 def puttags(self, tags):
161 162 """Put tags into sink.
162 163 tags: {tagname: sink_rev_id, ...}"""
163 164 raise NotImplementedError()
164 165
165 166 def setbranch(self, branch, pbranch, parents):
166 167 """Set the current branch name. Called before the first putfile
167 168 on the branch.
168 169 branch: branch name for subsequent commits
169 170 pbranch: branch name of parent commit
170 171 parents: destination revisions of parent"""
171 172 pass
172 173
173 174 def setfilemapmode(self, active):
174 175 """Tell the destination that we're using a filemap
175 176
176 177 Some converter_sources (svn in particular) can claim that a file
177 178 was changed in a revision, even if there was no change. This method
178 179 tells the destination that we're using a filemap and that it should
179 180 filter empty revisions.
180 181 """
181 182 pass
@@ -1,248 +1,247
1 1 # hg backend for convert extension
2 2
3 3 # Note for hg->hg conversion: Old versions of Mercurial didn't trim
4 4 # the whitespace from the ends of commit messages, but new versions
5 5 # do. Changesets created by those older versions, then converted, may
6 6 # thus have different hashes for changesets that are otherwise
7 7 # identical.
8 8
9 9
10 10 import os, time
11 11 from mercurial.i18n import _
12 12 from mercurial.node import *
13 13 from mercurial import hg, lock, revlog, util
14 14
15 15 from common import NoRepo, commit, converter_source, converter_sink
16 16
17 17 class mercurial_sink(converter_sink):
18 18 def __init__(self, ui, path):
19 self.path = path
20 self.ui = ui
19 converter_sink.__init__(self, ui, path)
21 20 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
22 21 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
23 22 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
24 23 self.lastbranch = None
25 24 try:
26 25 self.repo = hg.repository(self.ui, path)
27 26 except:
28 27 raise NoRepo("could not open hg repo %s as sink" % path)
29 28 self.lock = None
30 29 self.wlock = None
31 30 self.filemapmode = False
32 31
33 32 def before(self):
34 33 self.wlock = self.repo.wlock()
35 34 self.lock = self.repo.lock()
36 35 self.repo.dirstate.clear()
37 36
38 37 def after(self):
39 38 self.repo.dirstate.invalidate()
40 39 self.lock = None
41 40 self.wlock = None
42 41
43 42 def revmapfile(self):
44 43 return os.path.join(self.path, ".hg", "shamap")
45 44
46 45 def authorfile(self):
47 46 return os.path.join(self.path, ".hg", "authormap")
48 47
49 48 def getheads(self):
50 49 h = self.repo.changelog.heads()
51 50 return [ hex(x) for x in h ]
52 51
53 52 def putfile(self, f, e, data):
54 53 self.repo.wwrite(f, data, e)
55 54 if f not in self.repo.dirstate:
56 55 self.repo.dirstate.normallookup(f)
57 56
58 57 def copyfile(self, source, dest):
59 58 self.repo.copy(source, dest)
60 59
61 60 def delfile(self, f):
62 61 try:
63 62 util.unlink(self.repo.wjoin(f))
64 63 #self.repo.remove([f])
65 64 except OSError:
66 65 pass
67 66
68 67 def setbranch(self, branch, pbranch, parents):
69 68 if (not self.clonebranches) or (branch == self.lastbranch):
70 69 return
71 70
72 71 self.lastbranch = branch
73 72 self.after()
74 73 if not branch:
75 74 branch = 'default'
76 75 if not pbranch:
77 76 pbranch = 'default'
78 77
79 78 branchpath = os.path.join(self.path, branch)
80 79 try:
81 80 self.repo = hg.repository(self.ui, branchpath)
82 81 except:
83 82 if not parents:
84 83 self.repo = hg.repository(self.ui, branchpath, create=True)
85 84 else:
86 85 self.ui.note(_('cloning branch %s to %s\n') % (pbranch, branch))
87 86 hg.clone(self.ui, os.path.join(self.path, pbranch),
88 87 branchpath, rev=parents, update=False,
89 88 stream=True)
90 89 self.repo = hg.repository(self.ui, branchpath)
91 90 self.before()
92 91
93 92 def putcommit(self, files, parents, commit):
94 93 seen = {}
95 94 pl = []
96 95 for p in parents:
97 96 if p not in seen:
98 97 pl.append(p)
99 98 seen[p] = 1
100 99 parents = pl
101 100 nparents = len(parents)
102 101 if self.filemapmode and nparents == 1:
103 102 m1node = self.repo.changelog.read(bin(parents[0]))[0]
104 103 parent = parents[0]
105 104
106 105 if len(parents) < 2: parents.append("0" * 40)
107 106 if len(parents) < 2: parents.append("0" * 40)
108 107 p2 = parents.pop(0)
109 108
110 109 text = commit.desc
111 110 extra = commit.extra.copy()
112 111 if self.branchnames and commit.branch:
113 112 extra['branch'] = commit.branch
114 113 if commit.rev:
115 114 extra['convert_revision'] = commit.rev
116 115
117 116 while parents:
118 117 p1 = p2
119 118 p2 = parents.pop(0)
120 119 a = self.repo.rawcommit(files, text, commit.author, commit.date,
121 120 bin(p1), bin(p2), extra=extra)
122 121 self.repo.dirstate.clear()
123 122 text = "(octopus merge fixup)\n"
124 123 p2 = hg.hex(self.repo.changelog.tip())
125 124
126 125 if self.filemapmode and nparents == 1:
127 126 man = self.repo.manifest
128 127 mnode = self.repo.changelog.read(bin(p2))[0]
129 128 if not man.cmp(m1node, man.revision(mnode)):
130 129 self.repo.rollback()
131 130 self.repo.dirstate.clear()
132 131 return parent
133 132 return p2
134 133
135 134 def puttags(self, tags):
136 135 try:
137 136 old = self.repo.wfile(".hgtags").read()
138 137 oldlines = old.splitlines(1)
139 138 oldlines.sort()
140 139 except:
141 140 oldlines = []
142 141
143 142 k = tags.keys()
144 143 k.sort()
145 144 newlines = []
146 145 for tag in k:
147 146 newlines.append("%s %s\n" % (tags[tag], tag))
148 147
149 148 newlines.sort()
150 149
151 150 if newlines != oldlines:
152 151 self.ui.status("updating tags\n")
153 152 f = self.repo.wfile(".hgtags", "w")
154 153 f.write("".join(newlines))
155 154 f.close()
156 155 if not oldlines: self.repo.add([".hgtags"])
157 156 date = "%s 0" % int(time.mktime(time.gmtime()))
158 157 extra = {}
159 158 if self.tagsbranch != 'default':
160 159 extra['branch'] = self.tagsbranch
161 160 try:
162 161 tagparent = self.repo.changectx(self.tagsbranch).node()
163 162 except hg.RepoError, inst:
164 163 tagparent = nullid
165 164 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
166 165 date, tagparent, nullid)
167 166 return hex(self.repo.changelog.tip())
168 167
169 168 def setfilemapmode(self, active):
170 169 self.filemapmode = active
171 170
172 171 class mercurial_source(converter_source):
173 172 def __init__(self, ui, path, rev=None):
174 173 converter_source.__init__(self, ui, path, rev)
175 174 try:
176 175 self.repo = hg.repository(self.ui, path)
177 176 # try to provoke an exception if this isn't really a hg
178 177 # repo, but some other bogus compatible-looking url
179 178 self.repo.heads()
180 179 except hg.RepoError:
181 180 ui.print_exc()
182 181 raise NoRepo("could not open hg repo %s as source" % path)
183 182 self.lastrev = None
184 183 self.lastctx = None
185 184 self._changescache = None
186 185
187 186 def changectx(self, rev):
188 187 if self.lastrev != rev:
189 188 self.lastctx = self.repo.changectx(rev)
190 189 self.lastrev = rev
191 190 return self.lastctx
192 191
193 192 def getheads(self):
194 193 if self.rev:
195 194 return [hex(self.repo.changectx(self.rev).node())]
196 195 else:
197 196 return [hex(node) for node in self.repo.heads()]
198 197
199 198 def getfile(self, name, rev):
200 199 try:
201 200 return self.changectx(rev).filectx(name).data()
202 201 except revlog.LookupError, err:
203 202 raise IOError(err)
204 203
205 204 def getmode(self, name, rev):
206 205 m = self.changectx(rev).manifest()
207 206 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
208 207
209 208 def getchanges(self, rev):
210 209 ctx = self.changectx(rev)
211 210 if self._changescache and self._changescache[0] == rev:
212 211 m, a, r = self._changescache[1]
213 212 else:
214 213 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
215 214 changes = [(name, rev) for name in m + a + r]
216 215 changes.sort()
217 216 return (changes, self.getcopies(ctx, m + a))
218 217
219 218 def getcopies(self, ctx, files):
220 219 copies = {}
221 220 for name in files:
222 221 try:
223 222 copies[name] = ctx.filectx(name).renamed()[0]
224 223 except TypeError:
225 224 pass
226 225 return copies
227 226
228 227 def getcommit(self, rev):
229 228 ctx = self.changectx(rev)
230 229 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
231 230 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
232 231 desc=ctx.description(), parents=parents,
233 232 branch=ctx.branch(), extra=ctx.extra())
234 233
235 234 def gettags(self):
236 235 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
237 236 return dict([(name, hex(node)) for name, node in tags])
238 237
239 238 def getchangedfiles(self, rev, i):
240 239 ctx = self.changectx(rev)
241 240 i = i or 0
242 241 changes = self.repo.status(ctx.parents()[i].node(), ctx.node())[:3]
243 242
244 243 if i == 0:
245 244 self._changescache = (rev, changes)
246 245
247 246 return changes[0] + changes[1] + changes[2]
248 247
General Comments 0
You need to be logged in to leave comments. Login now