##// END OF EJS Templates
convert: record the source revision in the changelog
Brendan Cully -
r4873:28b23b90 default
parent child Browse files
Show More
@@ -1,118 +1,121 b''
1 1 # common code for the convert extension
2 2
3 3 class NoRepo(Exception): pass
4 4
5 5 class commit(object):
6 6 def __init__(self, **parts):
7 self.rev = None
8 self.branch = None
9
7 10 for x in "author date desc parents".split():
8 11 if not x in parts:
9 12 raise util.Abort("commit missing field %s" % x)
10 13 self.__dict__.update(parts)
11 14 if not self.desc or self.desc.isspace():
12 15 self.desc = '*** empty log message ***'
13 16
14 17 class converter_source(object):
15 18 """Conversion source interface"""
16 19
17 20 def __init__(self, ui, path, rev=None):
18 21 """Initialize conversion source (or raise NoRepo("message")
19 22 exception if path is not a valid repository)"""
20 23 self.ui = ui
21 24 self.path = path
22 25 self.rev = rev
23 26
24 27 self.encoding = 'utf-8'
25 28
26 29 def setrevmap(self, revmap):
27 30 """set the map of already-converted revisions"""
28 31 pass
29 32
30 33 def getheads(self):
31 34 """Return a list of this repository's heads"""
32 35 raise NotImplementedError()
33 36
34 37 def getfile(self, name, rev):
35 38 """Return file contents as a string"""
36 39 raise NotImplementedError()
37 40
38 41 def getmode(self, name, rev):
39 42 """Return file mode, eg. '', 'x', or 'l'"""
40 43 raise NotImplementedError()
41 44
42 45 def getchanges(self, version):
43 46 """Return sorted list of (filename, id) tuples for all files changed in rev.
44 47
45 48 id just tells us which revision to return in getfile(), e.g. in
46 49 git it's an object hash."""
47 50 raise NotImplementedError()
48 51
49 52 def getcommit(self, version):
50 53 """Return the commit object for version"""
51 54 raise NotImplementedError()
52 55
53 56 def gettags(self):
54 57 """Return the tags as a dictionary of name: revision"""
55 58 raise NotImplementedError()
56 59
57 60 def recode(self, s, encoding=None):
58 61 if not encoding:
59 62 encoding = self.encoding or 'utf-8'
60 63
61 64 try:
62 65 return s.decode(encoding).encode("utf-8")
63 66 except:
64 67 try:
65 68 return s.decode("latin-1").encode("utf-8")
66 69 except:
67 70 return s.decode(encoding, "replace").encode("utf-8")
68 71
69 72 class converter_sink(object):
70 73 """Conversion sink (target) interface"""
71 74
72 75 def __init__(self, ui, path):
73 76 """Initialize conversion sink (or raise NoRepo("message")
74 77 exception if path is not a valid repository)"""
75 78 raise NotImplementedError()
76 79
77 80 def getheads(self):
78 81 """Return a list of this repository's heads"""
79 82 raise NotImplementedError()
80 83
81 84 def mapfile(self):
82 85 """Path to a file that will contain lines
83 86 source_rev_id sink_rev_id
84 87 mapping equivalent revision identifiers for each system."""
85 88 raise NotImplementedError()
86 89
87 90 def authorfile(self):
88 91 """Path to a file that will contain lines
89 92 srcauthor=dstauthor
90 93 mapping equivalent authors identifiers for each system."""
91 94 return None
92 95
93 96 def putfile(self, f, e, data):
94 97 """Put file for next putcommit().
95 98 f: path to file
96 99 e: '', 'x', or 'l' (regular file, executable, or symlink)
97 100 data: file contents"""
98 101 raise NotImplementedError()
99 102
100 103 def delfile(self, f):
101 104 """Delete file for next putcommit().
102 105 f: path to file"""
103 106 raise NotImplementedError()
104 107
105 108 def putcommit(self, files, parents, commit):
106 109 """Create a revision with all changed files listed in 'files'
107 110 and having listed parents. 'commit' is a commit object containing
108 111 at a minimum the author, date, and message for this changeset.
109 112 Called after putfile() and delfile() calls. Note that the sink
110 113 repository is not told to update itself to a particular revision
111 114 (or even what that revision would be) before it receives the
112 115 file data."""
113 116 raise NotImplementedError()
114 117
115 118 def puttags(self, tags):
116 119 """Put tags into sink.
117 120 tags: {tagname: sink_rev_id, ...}"""
118 121 raise NotImplementedError()
@@ -1,100 +1,101 b''
1 1 # git support for the convert extension
2 2
3 3 import os
4 4
5 5 from common import NoRepo, commit, converter_source
6 6
7 7 class convert_git(converter_source):
8 8 def gitcmd(self, s):
9 9 return os.popen('GIT_DIR=%s %s' % (self.path, s))
10 10
11 11 def __init__(self, ui, path, rev=None):
12 12 super(convert_git, self).__init__(ui, path, rev=rev)
13 13
14 14 if os.path.isdir(path + "/.git"):
15 15 path += "/.git"
16 16 if not os.path.exists(path + "/objects"):
17 17 raise NoRepo("couldn't open GIT repo %s" % path)
18 18 self.path = path
19 19
20 20 def getheads(self):
21 21 if not self.rev:
22 22 return self.gitcmd('git-rev-parse --branches').read().splitlines()
23 23 else:
24 24 fh = self.gitcmd("git-rev-parse --verify %s" % self.rev)
25 25 return [fh.read()[:-1]]
26 26
27 27 def catfile(self, rev, type):
28 28 if rev == "0" * 40: raise IOError()
29 29 fh = self.gitcmd("git-cat-file %s %s 2>/dev/null" % (type, rev))
30 30 return fh.read()
31 31
32 32 def getfile(self, name, rev):
33 33 return self.catfile(rev, "blob")
34 34
35 35 def getmode(self, name, rev):
36 36 return self.modecache[(name, rev)]
37 37
38 38 def getchanges(self, version):
39 39 self.modecache = {}
40 40 fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
41 41 changes = []
42 42 for l in fh:
43 43 if "\t" not in l: continue
44 44 m, f = l[:-1].split("\t")
45 45 m = m.split()
46 46 h = m[3]
47 47 p = (m[1] == "100755")
48 48 s = (m[1] == "120000")
49 49 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
50 50 changes.append((f, h))
51 51 return changes
52 52
53 53 def getcommit(self, version):
54 54 c = self.catfile(version, "commit") # read the commit hash
55 55 end = c.find("\n\n")
56 56 message = c[end+2:]
57 57 message = self.recode(message)
58 58 l = c[:end].splitlines()
59 59 manifest = l[0].split()[1]
60 60 parents = []
61 61 for e in l[1:]:
62 62 n, v = e.split(" ", 1)
63 63 if n == "author":
64 64 p = v.split()
65 65 tm, tz = p[-2:]
66 66 author = " ".join(p[:-2])
67 67 if author[0] == "<": author = author[1:-1]
68 68 author = self.recode(author)
69 69 if n == "committer":
70 70 p = v.split()
71 71 tm, tz = p[-2:]
72 72 committer = " ".join(p[:-2])
73 73 if committer[0] == "<": committer = committer[1:-1]
74 74 committer = self.recode(committer)
75 75 message += "\ncommitter: %s\n" % committer
76 76 if n == "parent": parents.append(v)
77 77
78 78 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
79 79 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
80 80 date = tm + " " + str(tz)
81 81 author = author or "unknown"
82 82
83 c = commit(parents=parents, date=date, author=author, desc=message)
83 c = commit(parents=parents, date=date, author=author, desc=message,
84 rev=version)
84 85 return c
85 86
86 87 def gettags(self):
87 88 tags = {}
88 89 fh = self.gitcmd('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
89 90 prefix = 'refs/tags/'
90 91 for line in fh:
91 92 line = line.strip()
92 93 if not line.endswith("^{}"):
93 94 continue
94 95 node, tag = line.split(None, 1)
95 96 if not tag.startswith(prefix):
96 97 continue
97 98 tag = tag[len(prefix):-3]
98 99 tags[tag] = node
99 100
100 101 return tags
@@ -1,97 +1,97 b''
1 1 # hg backend for convert extension
2 2
3 3 import os, time
4 4 from mercurial import hg
5 5
6 6 from common import NoRepo, converter_sink
7 7
8 8 class convert_mercurial(converter_sink):
9 9 def __init__(self, ui, path):
10 10 self.path = path
11 11 self.ui = ui
12 12 try:
13 13 self.repo = hg.repository(self.ui, path)
14 14 except:
15 15 raise NoRepo("could open hg repo %s" % path)
16 16
17 17 def mapfile(self):
18 18 return os.path.join(self.path, ".hg", "shamap")
19 19
20 20 def authorfile(self):
21 21 return os.path.join(self.path, ".hg", "authormap")
22 22
23 23 def getheads(self):
24 24 h = self.repo.changelog.heads()
25 25 return [ hg.hex(x) for x in h ]
26 26
27 27 def putfile(self, f, e, data):
28 28 self.repo.wwrite(f, data, e)
29 29 if self.repo.dirstate.state(f) == '?':
30 30 self.repo.dirstate.update([f], "a")
31 31
32 32 def copyfile(self, source, dest):
33 33 self.repo.copy(source, dest)
34 34
35 35 def delfile(self, f):
36 36 try:
37 37 os.unlink(self.repo.wjoin(f))
38 38 #self.repo.remove([f])
39 39 except:
40 40 pass
41 41
42 42 def putcommit(self, files, parents, commit):
43 43 seen = {}
44 44 pl = []
45 45 for p in parents:
46 46 if p not in seen:
47 47 pl.append(p)
48 48 seen[p] = 1
49 49 parents = pl
50 50
51 51 if len(parents) < 2: parents.append("0" * 40)
52 52 if len(parents) < 2: parents.append("0" * 40)
53 53 p2 = parents.pop(0)
54 54
55 55 text = commit.desc
56 56 extra = {}
57 try:
58 extra["branch"] = commit.branch
59 except AttributeError:
60 pass
61
57 if commit.branch:
58 extra['branch'] = commit.branch
59 if commit.rev:
60 extra['convert_revision'] = commit.rev
61
62 62 while parents:
63 63 p1 = p2
64 64 p2 = parents.pop(0)
65 65 a = self.repo.rawcommit(files, text, commit.author, commit.date,
66 66 hg.bin(p1), hg.bin(p2), extra=extra)
67 67 text = "(octopus merge fixup)\n"
68 68 p2 = hg.hex(self.repo.changelog.tip())
69 69
70 70 return p2
71 71
72 72 def puttags(self, tags):
73 73 try:
74 74 old = self.repo.wfile(".hgtags").read()
75 75 oldlines = old.splitlines(1)
76 76 oldlines.sort()
77 77 except:
78 78 oldlines = []
79 79
80 80 k = tags.keys()
81 81 k.sort()
82 82 newlines = []
83 83 for tag in k:
84 84 newlines.append("%s %s\n" % (tags[tag], tag))
85 85
86 86 newlines.sort()
87 87
88 88 if newlines != oldlines:
89 89 self.ui.status("updating tags\n")
90 90 f = self.repo.wfile(".hgtags", "w")
91 91 f.write("".join(newlines))
92 92 f.close()
93 93 if not oldlines: self.repo.add([".hgtags"])
94 94 date = "%s 0" % int(time.mktime(time.gmtime()))
95 95 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
96 96 date, self.repo.changelog.tip(), hg.nullid)
97 97 return hg.hex(self.repo.changelog.tip())
@@ -1,588 +1,589 b''
1 1 # Subversion 1.4/1.5 Python API backend
2 2 #
3 3 # Copyright(C) 2007 Daniel Holth et al
4 4
5 5 import pprint
6 6 import locale
7 7
8 8 from mercurial import util
9 9
10 10 # Subversion stuff. Works best with very recent Python SVN bindings
11 11 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
12 12 # these bindings.
13 13
14 14 from cStringIO import StringIO
15 15
16 16 from common import NoRepo, commit, converter_source
17 17
18 18 try:
19 19 from svn.core import SubversionException, Pool
20 20 import svn.core
21 21 import svn.ra
22 22 import svn.delta
23 23 import svn
24 24 import transport
25 25 except ImportError:
26 26 pass
27 27
28 28 class CompatibilityException(Exception): pass
29 29
30 30 # SVN conversion code stolen from bzr-svn and tailor
31 31 class convert_svn(converter_source):
32 32 def __init__(self, ui, url, rev=None):
33 33 super(convert_svn, self).__init__(ui, url, rev=rev)
34 34
35 35 try:
36 36 SubversionException
37 37 except NameError:
38 38 msg = 'subversion python bindings could not be loaded\n'
39 39 ui.warn(msg)
40 40 raise NoRepo(msg)
41 41
42 42 self.encoding = locale.getpreferredencoding()
43 43 self.lastrevs = {}
44 44
45 45 latest = None
46 46 if rev:
47 47 try:
48 48 latest = int(rev)
49 49 except ValueError:
50 50 raise util.Abort('svn: revision %s is not an integer' % rev)
51 51 try:
52 52 # Support file://path@rev syntax. Useful e.g. to convert
53 53 # deleted branches.
54 54 url, latest = url.rsplit("@", 1)
55 55 latest = int(latest)
56 56 except ValueError, e:
57 57 pass
58 58 self.url = url
59 59 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
60 60 try:
61 61 self.transport = transport.SvnRaTransport(url = url)
62 62 self.ra = self.transport.ra
63 63 self.ctx = svn.client.create_context()
64 64 self.base = svn.ra.get_repos_root(self.ra)
65 65 self.module = self.url[len(self.base):]
66 66 self.modulemap = {} # revision, module
67 67 self.commits = {}
68 68 self.files = {}
69 69 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
70 70 except SubversionException, e:
71 71 raise NoRepo("couldn't open SVN repo %s" % url)
72 72
73 73 try:
74 74 self.get_blacklist()
75 75 except IOError, e:
76 76 pass
77 77
78 78 self.last_changed = self.latest(self.module, latest)
79 79
80 80 self.head = self.revid(self.last_changed)
81 81
82 82 def setrevmap(self, revmap):
83 83 lastrevs = {}
84 84 for revid in revmap.keys():
85 85 uuid, module, revnum = self.revsplit(revid)
86 86 lastrevnum = lastrevs.setdefault(module, revnum)
87 87 if revnum > lastrevnum:
88 88 lastrevs[module] = revnum
89 89 self.lastrevs = lastrevs
90 90
91 91 def getheads(self):
92 92 # detect standard /branches, /tags, /trunk layout
93 93 optrev = svn.core.svn_opt_revision_t()
94 94 optrev.kind = svn.core.svn_opt_revision_number
95 95 optrev.value.number = self.last_changed
96 96 rpath = self.url.strip('/')
97 97 paths = svn.client.ls(rpath, optrev, False, self.ctx)
98 98 if 'branches' in paths and 'trunk' in paths:
99 99 self.module += '/trunk'
100 100 lt = self.latest(self.module, self.last_changed)
101 101 self.head = self.revid(lt)
102 102 self.heads = [self.head]
103 103 branches = svn.client.ls(rpath + '/branches', optrev, False, self.ctx)
104 104 for branch in branches.keys():
105 105 module = '/branches/' + branch
106 106 brevnum = self.latest(module, self.last_changed)
107 107 brev = self.revid(brevnum, module)
108 108 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
109 109 self.heads.append(brev)
110 110 else:
111 111 self.heads = [self.head]
112 112 return self.heads
113 113
114 114 def getfile(self, file, rev):
115 115 data, mode = self._getfile(file, rev)
116 116 self.modecache[(file, rev)] = mode
117 117 return data
118 118
119 119 def getmode(self, file, rev):
120 120 return self.modecache[(file, rev)]
121 121
122 122 def getchanges(self, rev):
123 123 self.modecache = {}
124 124 files = self.files[rev]
125 125 cl = files
126 126 cl.sort()
127 127 # caller caches the result, so free it here to release memory
128 128 del self.files[rev]
129 129 return cl
130 130
131 131 def getcommit(self, rev):
132 132 if rev not in self.commits:
133 133 uuid, module, revnum = self.revsplit(rev)
134 134 self.module = module
135 135 self.reparent(module)
136 136 stop = self.lastrevs.get(module, 0)
137 137 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
138 138 commit = self.commits[rev]
139 139 # caller caches the result, so free it here to release memory
140 140 del self.commits[rev]
141 141 return commit
142 142
143 143 def gettags(self):
144 144 tags = {}
145 145 def parselogentry(*arg, **args):
146 146 orig_paths, revnum, author, date, message, pool = arg
147 147 for path in orig_paths:
148 148 if not path.startswith('/tags/'):
149 149 continue
150 150 ent = orig_paths[path]
151 151 source = ent.copyfrom_path
152 152 rev = ent.copyfrom_rev
153 153 tag = path.split('/', 2)[2]
154 154 tags[tag] = self.revid(rev, module=source)
155 155
156 156 start = self.revnum(self.head)
157 157 try:
158 158 svn.ra.get_log(self.ra, ['/tags'], 0, start, 0, True, False,
159 159 parselogentry)
160 160 return tags
161 161 except SubversionException:
162 162 self.ui.note('no tags found at revision %d\n' % start)
163 163 return {}
164 164
165 165 # -- helper functions --
166 166
167 167 def revid(self, revnum, module=None):
168 168 if not module:
169 169 module = self.module
170 170 return (u"svn:%s%s@%s" % (self.uuid, module, revnum)).decode(self.encoding)
171 171
172 172 def revnum(self, rev):
173 173 return int(rev.split('@')[-1])
174 174
175 175 def revsplit(self, rev):
176 176 url, revnum = rev.encode(self.encoding).split('@', 1)
177 177 revnum = int(revnum)
178 178 parts = url.split('/', 1)
179 179 uuid = parts.pop(0)[4:]
180 180 mod = ''
181 181 if parts:
182 182 mod = '/' + parts[0]
183 183 return uuid, mod, revnum
184 184
185 185 def latest(self, path, stop=0):
186 186 'find the latest revision affecting path, up to stop'
187 187 if not stop:
188 188 stop = svn.ra.get_latest_revnum(self.ra)
189 189 try:
190 190 self.reparent('')
191 191 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
192 192 self.reparent(self.module)
193 193 except SubversionException:
194 194 dirent = None
195 195 if not dirent:
196 196 raise util.Abort('%s not found up to revision %d' \
197 197 % (path, stop))
198 198
199 199 return dirent.created_rev
200 200
201 201 def get_blacklist(self):
202 202 """Avoid certain revision numbers.
203 203 It is not uncommon for two nearby revisions to cancel each other
204 204 out, e.g. 'I copied trunk into a subdirectory of itself instead
205 205 of making a branch'. The converted repository is significantly
206 206 smaller if we ignore such revisions."""
207 207 self.blacklist = set()
208 208 blacklist = self.blacklist
209 209 for line in file("blacklist.txt", "r"):
210 210 if not line.startswith("#"):
211 211 try:
212 212 svn_rev = int(line.strip())
213 213 blacklist.add(svn_rev)
214 214 except ValueError, e:
215 215 pass # not an integer or a comment
216 216
217 217 def is_blacklisted(self, svn_rev):
218 218 return svn_rev in self.blacklist
219 219
220 220 def reparent(self, module):
221 221 svn_url = self.base + module
222 222 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
223 223 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
224 224
225 225 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
226 226 def get_entry_from_path(path, module=self.module):
227 227 # Given the repository url of this wc, say
228 228 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
229 229 # extract the "entry" portion (a relative path) from what
230 230 # svn log --xml says, ie
231 231 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
232 232 # that is to say "tests/PloneTestCase.py"
233 233
234 234 if path.startswith(module):
235 235 relative = path[len(module):]
236 236 if relative.startswith('/'):
237 237 return relative[1:]
238 238 else:
239 239 return relative
240 240
241 241 # The path is outside our tracked tree...
242 242 self.ui.debug('Ignoring %r since it is not under %r\n' % (path, module))
243 243 return None
244 244
245 245 received = []
246 246 # svn.ra.get_log requires no other calls to the ra until it completes,
247 247 # so we just collect the log entries and parse them afterwards
248 248 def receivelog(*arg, **args):
249 249 received.append(arg)
250 250
251 251 self.child_cset = None
252 252 def parselogentry(*arg, **args):
253 253 orig_paths, revnum, author, date, message, pool = arg
254 254
255 255 if self.is_blacklisted(revnum):
256 256 self.ui.note('skipping blacklisted revision %d\n' % revnum)
257 257 return
258 258
259 259 self.ui.debug("parsing revision %d\n" % revnum)
260 260
261 261 if orig_paths is None:
262 262 self.ui.debug('revision %d has no entries\n' % revnum)
263 263 return
264 264
265 265 if revnum in self.modulemap:
266 266 new_module = self.modulemap[revnum]
267 267 if new_module != self.module:
268 268 self.module = new_module
269 269 self.reparent(self.module)
270 270
271 271 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
272 272 copies = {}
273 273 entries = []
274 274 rev = self.revid(revnum)
275 275 parents = []
276 276
277 277 # branch log might return entries for a parent we already have
278 278 if (rev in self.commits or
279 279 (revnum < self.lastrevs.get(self.module, 0))):
280 280 return
281 281
282 282 try:
283 283 branch = self.module.split("/")[-1]
284 284 if branch == 'trunk':
285 285 branch = ''
286 286 except IndexError:
287 287 branch = None
288 288
289 289 paths = orig_paths.keys()
290 290 paths.sort()
291 291 for path in paths:
292 292 # self.ui.write("path %s\n" % path)
293 293 if path == self.module: # Follow branching back in history
294 294 ent = orig_paths[path]
295 295 if ent:
296 296 if ent.copyfrom_path:
297 297 # ent.copyfrom_rev may not be the actual last revision
298 298 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
299 299 self.modulemap[prev] = ent.copyfrom_path
300 300 parents = [self.revid(prev, ent.copyfrom_path)]
301 301 self.ui.note('found parent of branch %s at %d: %s\n' % \
302 302 (self.module, prev, ent.copyfrom_path))
303 303 else:
304 304 self.ui.debug("No copyfrom path, don't know what to do.\n")
305 305 # Maybe it was added and there is no more history.
306 306 entrypath = get_entry_from_path(path, module=self.module)
307 307 # self.ui.write("entrypath %s\n" % entrypath)
308 308 if entrypath is None:
309 309 # Outside our area of interest
310 310 self.ui.debug("boring@%s: %s\n" % (revnum, path))
311 311 continue
312 312 entry = entrypath.decode(self.encoding)
313 313 ent = orig_paths[path]
314 314
315 315 kind = svn.ra.check_path(self.ra, entrypath, revnum)
316 316 if kind == svn.core.svn_node_file:
317 317 if ent.copyfrom_path:
318 318 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
319 319 if copyfrom_path:
320 320 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
321 321 # It's probably important for hg that the source
322 322 # exists in the revision's parent, not just the
323 323 # ent.copyfrom_rev
324 324 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
325 325 if fromkind != 0:
326 326 copies[self.recode(entry)] = self.recode(copyfrom_path)
327 327 entries.append(self.recode(entry))
328 328 elif kind == 0: # gone, but had better be a deleted *file*
329 329 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
330 330
331 331 # if a branch is created but entries are removed in the same
332 332 # changeset, get the right fromrev
333 333 if parents:
334 334 uuid, old_module, fromrev = self.revsplit(parents[0])
335 335 else:
336 336 fromrev = revnum - 1
337 337 # might always need to be revnum - 1 in these 3 lines?
338 338 old_module = self.modulemap.get(fromrev, self.module)
339 339
340 340 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
341 341 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
342 342
343 343 def lookup_parts(p):
344 344 rc = None
345 345 parts = p.split("/")
346 346 for i in range(len(parts)):
347 347 part = "/".join(parts[:i])
348 348 info = part, copyfrom.get(part, None)
349 349 if info[1] is not None:
350 350 self.ui.debug("Found parent directory %s\n" % info[1])
351 351 rc = info
352 352 return rc
353 353
354 354 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
355 355
356 356 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
357 357
358 358 # need to remove fragment from lookup_parts and replace with copyfrom_path
359 359 if frompath is not None:
360 360 self.ui.debug("munge-o-matic\n")
361 361 self.ui.debug(entrypath + '\n')
362 362 self.ui.debug(entrypath[len(frompath):] + '\n')
363 363 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
364 364 fromrev = froment.copyfrom_rev
365 365 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
366 366
367 367 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
368 368 if fromkind == svn.core.svn_node_file: # a deleted file
369 369 entries.append(self.recode(entry))
370 370 elif fromkind == svn.core.svn_node_dir:
371 371 # print "Deleted/moved non-file:", revnum, path, ent
372 372 # children = self._find_children(path, revnum - 1)
373 373 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
374 374 # Sometimes this is tricky. For example: in
375 375 # The Subversion Repository revision 6940 a dir
376 376 # was copied and one of its files was deleted
377 377 # from the new location in the same commit. This
378 378 # code can't deal with that yet.
379 379 if ent.action == 'C':
380 380 children = self._find_children(path, fromrev)
381 381 else:
382 382 oroot = entrypath.strip('/')
383 383 nroot = path.strip('/')
384 384 children = self._find_children(oroot, fromrev)
385 385 children = [s.replace(oroot,nroot) for s in children]
386 386 # Mark all [files, not directories] as deleted.
387 387 for child in children:
388 388 # Can we move a child directory and its
389 389 # parent in the same commit? (probably can). Could
390 390 # cause problems if instead of revnum -1,
391 391 # we have to look in (copyfrom_path, revnum - 1)
392 392 entrypath = get_entry_from_path("/" + child, module=old_module)
393 393 if entrypath:
394 394 entry = self.recode(entrypath.decode(self.encoding))
395 395 if entry in copies:
396 396 # deleted file within a copy
397 397 del copies[entry]
398 398 else:
399 399 entries.append(entry)
400 400 else:
401 401 self.ui.debug('unknown path in revision %d: %s\n' % \
402 402 (revnum, path))
403 403 elif kind == svn.core.svn_node_dir:
404 404 # Should probably synthesize normal file entries
405 405 # and handle as above to clean up copy/rename handling.
406 406
407 407 # If the directory just had a prop change,
408 408 # then we shouldn't need to look for its children.
409 409 # Also this could create duplicate entries. Not sure
410 410 # whether this will matter. Maybe should make entries a set.
411 411 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
412 412 # This will fail if a directory was copied
413 413 # from another branch and then some of its files
414 414 # were deleted in the same transaction.
415 415 children = self._find_children(path, revnum)
416 416 children.sort()
417 417 for child in children:
418 418 # Can we move a child directory and its
419 419 # parent in the same commit? (probably can). Could
420 420 # cause problems if instead of revnum -1,
421 421 # we have to look in (copyfrom_path, revnum - 1)
422 422 entrypath = get_entry_from_path("/" + child, module=self.module)
423 423 # print child, self.module, entrypath
424 424 if entrypath:
425 425 # Need to filter out directories here...
426 426 kind = svn.ra.check_path(self.ra, entrypath, revnum)
427 427 if kind != svn.core.svn_node_dir:
428 428 entries.append(self.recode(entrypath))
429 429
430 430 # Copies here (must copy all from source)
431 431 # Probably not a real problem for us if
432 432 # source does not exist
433 433
434 434 # Can do this with the copy command "hg copy"
435 435 # if ent.copyfrom_path:
436 436 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
437 437 # module=self.module)
438 438 # copyto_entry = entrypath
439 439 #
440 440 # print "copy directory", copyfrom_entry, 'to', copyto_entry
441 441 #
442 442 # copies.append((copyfrom_entry, copyto_entry))
443 443
444 444 if ent.copyfrom_path:
445 445 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
446 446 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
447 447 if copyfrom_entry:
448 448 copyfrom[path] = ent
449 449 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
450 450
451 451 # Good, /probably/ a regular copy. Really should check
452 452 # to see whether the parent revision actually contains
453 453 # the directory in question.
454 454 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
455 455 children.sort()
456 456 for child in children:
457 457 entrypath = get_entry_from_path("/" + child, module=self.module)
458 458 if entrypath:
459 459 entry = entrypath.decode(self.encoding)
460 460 # print "COPY COPY From", copyfrom_entry, entry
461 461 copyto_path = path + entry[len(copyfrom_entry):]
462 462 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
463 463 # print "COPY", entry, "COPY To", copyto_entry
464 464 copies[self.recode(copyto_entry)] = self.recode(entry)
465 465 # copy from quux splort/quuxfile
466 466
467 467 self.modulemap[revnum] = self.module # track backwards in time
468 468 # a list of (filename, id) where id lets us retrieve the file.
469 469 # eg in git, id is the object hash. for svn it'll be the
470 470 self.files[rev] = zip(entries, [rev] * len(entries))
471 471 if not entries:
472 472 return
473 473
474 474 # Example SVN datetime. Includes microseconds.
475 475 # ISO-8601 conformant
476 476 # '2007-01-04T17:35:00.902377Z'
477 477 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
478 478
479 479 log = message and self.recode(message)
480 480 author = author and self.recode(author) or ''
481 481
482 482 cset = commit(author=author,
483 483 date=util.datestr(date),
484 484 desc=log,
485 485 parents=parents,
486 486 copies=copies,
487 branch=branch)
487 branch=branch,
488 rev=rev.encode('utf-8'))
488 489
489 490 self.commits[rev] = cset
490 491 if self.child_cset and not self.child_cset.parents:
491 492 self.child_cset.parents = [rev]
492 493 self.child_cset = cset
493 494
494 495 self.ui.note('fetching revision log for "%s" from %d to %d\n' % \
495 496 (self.module, from_revnum, to_revnum))
496 497
497 498 try:
498 499 discover_changed_paths = True
499 500 strict_node_history = False
500 501 svn.ra.get_log(self.ra, [self.module], from_revnum, to_revnum, 0,
501 502 discover_changed_paths, strict_node_history,
502 503 receivelog)
503 504 for entry in received:
504 505 parselogentry(*entry)
505 506 except SubversionException, (_, num):
506 507 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
507 508 raise NoSuchRevision(branch=self,
508 509 revision="Revision number %d" % to_revnum)
509 510 raise
510 511
511 512 def _getfile(self, file, rev):
512 513 io = StringIO()
513 514 # TODO: ra.get_file transmits the whole file instead of diffs.
514 515 mode = ''
515 516 try:
516 517 revnum = self.revnum(rev)
517 518 if self.module != self.modulemap[revnum]:
518 519 self.module = self.modulemap[revnum]
519 520 self.reparent(self.module)
520 521 info = svn.ra.get_file(self.ra, file, revnum, io)
521 522 if isinstance(info, list):
522 523 info = info[-1]
523 524 mode = ("svn:executable" in info) and 'x' or ''
524 525 mode = ("svn:special" in info) and 'l' or mode
525 526 except SubversionException, e:
526 527 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
527 528 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
528 529 if e.apr_err in notfound: # File not found
529 530 raise IOError()
530 531 raise
531 532 data = io.getvalue()
532 533 if mode == 'l':
533 534 link_prefix = "link "
534 535 if data.startswith(link_prefix):
535 536 data = data[len(link_prefix):]
536 537 return data, mode
537 538
538 539 def _find_children(self, path, revnum):
539 540 path = path.strip("/")
540 541
541 542 def _find_children_fallback(path, revnum):
542 543 # SWIG python bindings for getdir are broken up to at least 1.4.3
543 544 pool = Pool()
544 545 optrev = svn.core.svn_opt_revision_t()
545 546 optrev.kind = svn.core.svn_opt_revision_number
546 547 optrev.value.number = revnum
547 548 rpath = '/'.join([self.base, path]).strip('/')
548 549 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev, True, self.ctx, pool).keys()]
549 550
550 551 if hasattr(self, '_find_children_fallback'):
551 552 return _find_children_fallback(path, revnum)
552 553
553 554 self.reparent("/" + path)
554 555 pool = Pool()
555 556
556 557 children = []
557 558 def find_children_inner(children, path, revnum = revnum):
558 559 if hasattr(svn.ra, 'get_dir2'): # Since SVN 1.4
559 560 fields = 0xffffffff # Binding does not provide SVN_DIRENT_ALL
560 561 getdir = svn.ra.get_dir2(self.ra, path, revnum, fields, pool)
561 562 else:
562 563 getdir = svn.ra.get_dir(self.ra, path, revnum, pool)
563 564 if type(getdir) == dict:
564 565 # python binding for getdir is broken up to at least 1.4.3
565 566 raise CompatibilityException()
566 567 dirents = getdir[0]
567 568 if type(dirents) == int:
568 569 # got here once due to infinite recursion bug
569 570 # pprint.pprint(getdir)
570 571 return
571 572 c = dirents.keys()
572 573 c.sort()
573 574 for child in c:
574 575 dirent = dirents[child]
575 576 if dirent.kind == svn.core.svn_node_dir:
576 577 find_children_inner(children, (path + "/" + child).strip("/"))
577 578 else:
578 579 children.append((path + "/" + child).strip("/"))
579 580
580 581 try:
581 582 find_children_inner(children, "")
582 583 except CompatibilityException:
583 584 self._find_children_fallback = True
584 585 self.reparent(self.module)
585 586 return _find_children_fallback(path, revnum)
586 587
587 588 self.reparent(self.module)
588 589 return [path + "/" + c for c in children]
General Comments 0
You need to be logged in to leave comments. Login now