##// END OF EJS Templates
convert: merge sources getmode() into getfile()
Patrick Mezard -
r11134:33010ff1 default
parent child Browse files
Show More
@@ -1,262 +1,260 b''
1 # bzr.py - bzr support for the convert extension
1 # bzr.py - bzr support for the convert extension
2 #
2 #
3 # Copyright 2008, 2009 Marek Kubica <marek@xivilization.net> and others
3 # Copyright 2008, 2009 Marek Kubica <marek@xivilization.net> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 # This module is for handling 'bzr', that was formerly known as Bazaar-NG;
8 # This module is for handling 'bzr', that was formerly known as Bazaar-NG;
9 # it cannot access 'bar' repositories, but they were never used very much
9 # it cannot access 'bar' repositories, but they were never used very much
10
10
11 import os
11 import os
12 from mercurial import demandimport
12 from mercurial import demandimport
13 # these do not work with demandimport, blacklist
13 # these do not work with demandimport, blacklist
14 demandimport.ignore.extend([
14 demandimport.ignore.extend([
15 'bzrlib.transactions',
15 'bzrlib.transactions',
16 'bzrlib.urlutils',
16 'bzrlib.urlutils',
17 'ElementPath',
17 'ElementPath',
18 ])
18 ])
19
19
20 from mercurial.i18n import _
20 from mercurial.i18n import _
21 from mercurial import util
21 from mercurial import util
22 from common import NoRepo, commit, converter_source
22 from common import NoRepo, commit, converter_source
23
23
24 try:
24 try:
25 # bazaar imports
25 # bazaar imports
26 from bzrlib import branch, revision, errors
26 from bzrlib import branch, revision, errors
27 from bzrlib.revisionspec import RevisionSpec
27 from bzrlib.revisionspec import RevisionSpec
28 except ImportError:
28 except ImportError:
29 pass
29 pass
30
30
31 supportedkinds = ('file', 'symlink')
31 supportedkinds = ('file', 'symlink')
32
32
33 class bzr_source(converter_source):
33 class bzr_source(converter_source):
34 """Reads Bazaar repositories by using the Bazaar Python libraries"""
34 """Reads Bazaar repositories by using the Bazaar Python libraries"""
35
35
36 def __init__(self, ui, path, rev=None):
36 def __init__(self, ui, path, rev=None):
37 super(bzr_source, self).__init__(ui, path, rev=rev)
37 super(bzr_source, self).__init__(ui, path, rev=rev)
38
38
39 if not os.path.exists(os.path.join(path, '.bzr')):
39 if not os.path.exists(os.path.join(path, '.bzr')):
40 raise NoRepo(_('%s does not look like a Bazaar repository')
40 raise NoRepo(_('%s does not look like a Bazaar repository')
41 % path)
41 % path)
42
42
43 try:
43 try:
44 # access bzrlib stuff
44 # access bzrlib stuff
45 branch
45 branch
46 except NameError:
46 except NameError:
47 raise NoRepo(_('Bazaar modules could not be loaded'))
47 raise NoRepo(_('Bazaar modules could not be loaded'))
48
48
49 path = os.path.abspath(path)
49 path = os.path.abspath(path)
50 self._checkrepotype(path)
50 self._checkrepotype(path)
51 self.branch = branch.Branch.open(path)
51 self.branch = branch.Branch.open(path)
52 self.sourcerepo = self.branch.repository
52 self.sourcerepo = self.branch.repository
53 self._parentids = {}
53 self._parentids = {}
54
54
55 def _checkrepotype(self, path):
55 def _checkrepotype(self, path):
56 # Lightweight checkouts detection is informational but probably
56 # Lightweight checkouts detection is informational but probably
57 # fragile at API level. It should not terminate the conversion.
57 # fragile at API level. It should not terminate the conversion.
58 try:
58 try:
59 from bzrlib import bzrdir
59 from bzrlib import bzrdir
60 dir = bzrdir.BzrDir.open_containing(path)[0]
60 dir = bzrdir.BzrDir.open_containing(path)[0]
61 try:
61 try:
62 tree = dir.open_workingtree(recommend_upgrade=False)
62 tree = dir.open_workingtree(recommend_upgrade=False)
63 branch = tree.branch
63 branch = tree.branch
64 except (errors.NoWorkingTree, errors.NotLocalUrl), e:
64 except (errors.NoWorkingTree, errors.NotLocalUrl), e:
65 tree = None
65 tree = None
66 branch = dir.open_branch()
66 branch = dir.open_branch()
67 if (tree is not None and tree.bzrdir.root_transport.base !=
67 if (tree is not None and tree.bzrdir.root_transport.base !=
68 branch.bzrdir.root_transport.base):
68 branch.bzrdir.root_transport.base):
69 self.ui.warn(_('warning: lightweight checkouts may cause '
69 self.ui.warn(_('warning: lightweight checkouts may cause '
70 'conversion failures, try with a regular '
70 'conversion failures, try with a regular '
71 'branch instead.\n'))
71 'branch instead.\n'))
72 except:
72 except:
73 self.ui.note(_('bzr source type could not be determined\n'))
73 self.ui.note(_('bzr source type could not be determined\n'))
74
74
75 def before(self):
75 def before(self):
76 """Before the conversion begins, acquire a read lock
76 """Before the conversion begins, acquire a read lock
77 for all the operations that might need it. Fortunately
77 for all the operations that might need it. Fortunately
78 read locks don't block other reads or writes to the
78 read locks don't block other reads or writes to the
79 repository, so this shouldn't have any impact on the usage of
79 repository, so this shouldn't have any impact on the usage of
80 the source repository.
80 the source repository.
81
81
82 The alternative would be locking on every operation that
82 The alternative would be locking on every operation that
83 needs locks (there are currently two: getting the file and
83 needs locks (there are currently two: getting the file and
84 getting the parent map) and releasing immediately after,
84 getting the parent map) and releasing immediately after,
85 but this approach can take even 40% longer."""
85 but this approach can take even 40% longer."""
86 self.sourcerepo.lock_read()
86 self.sourcerepo.lock_read()
87
87
88 def after(self):
88 def after(self):
89 self.sourcerepo.unlock()
89 self.sourcerepo.unlock()
90
90
91 def getheads(self):
91 def getheads(self):
92 if not self.rev:
92 if not self.rev:
93 return [self.branch.last_revision()]
93 return [self.branch.last_revision()]
94 try:
94 try:
95 r = RevisionSpec.from_string(self.rev)
95 r = RevisionSpec.from_string(self.rev)
96 info = r.in_history(self.branch)
96 info = r.in_history(self.branch)
97 except errors.BzrError:
97 except errors.BzrError:
98 raise util.Abort(_('%s is not a valid revision in current branch')
98 raise util.Abort(_('%s is not a valid revision in current branch')
99 % self.rev)
99 % self.rev)
100 return [info.rev_id]
100 return [info.rev_id]
101
101
102 def getfile(self, name, rev):
102 def getfile(self, name, rev):
103 revtree = self.sourcerepo.revision_tree(rev)
103 revtree = self.sourcerepo.revision_tree(rev)
104 fileid = revtree.path2id(name.decode(self.encoding or 'utf-8'))
104 fileid = revtree.path2id(name.decode(self.encoding or 'utf-8'))
105 kind = None
105 kind = None
106 if fileid is not None:
106 if fileid is not None:
107 kind = revtree.kind(fileid)
107 kind = revtree.kind(fileid)
108 if kind not in supportedkinds:
108 if kind not in supportedkinds:
109 # the file is not available anymore - was deleted
109 # the file is not available anymore - was deleted
110 raise IOError(_('%s is not available in %s anymore') %
110 raise IOError(_('%s is not available in %s anymore') %
111 (name, rev))
111 (name, rev))
112 mode = self._modecache[(name, rev)]
112 if kind == 'symlink':
113 if kind == 'symlink':
113 target = revtree.get_symlink_target(fileid)
114 target = revtree.get_symlink_target(fileid)
114 if target is None:
115 if target is None:
115 raise util.Abort(_('%s.%s symlink has no target')
116 raise util.Abort(_('%s.%s symlink has no target')
116 % (name, rev))
117 % (name, rev))
117 return target
118 return target, mode
118 else:
119 else:
119 sio = revtree.get_file(fileid)
120 sio = revtree.get_file(fileid)
120 return sio.read()
121 return sio.read(), mode
121
122 def getmode(self, name, rev):
123 return self._modecache[(name, rev)]
124
122
125 def getchanges(self, version):
123 def getchanges(self, version):
126 # set up caches: modecache and revtree
124 # set up caches: modecache and revtree
127 self._modecache = {}
125 self._modecache = {}
128 self._revtree = self.sourcerepo.revision_tree(version)
126 self._revtree = self.sourcerepo.revision_tree(version)
129 # get the parentids from the cache
127 # get the parentids from the cache
130 parentids = self._parentids.pop(version)
128 parentids = self._parentids.pop(version)
131 # only diff against first parent id
129 # only diff against first parent id
132 prevtree = self.sourcerepo.revision_tree(parentids[0])
130 prevtree = self.sourcerepo.revision_tree(parentids[0])
133 return self._gettreechanges(self._revtree, prevtree)
131 return self._gettreechanges(self._revtree, prevtree)
134
132
135 def getcommit(self, version):
133 def getcommit(self, version):
136 rev = self.sourcerepo.get_revision(version)
134 rev = self.sourcerepo.get_revision(version)
137 # populate parent id cache
135 # populate parent id cache
138 if not rev.parent_ids:
136 if not rev.parent_ids:
139 parents = []
137 parents = []
140 self._parentids[version] = (revision.NULL_REVISION,)
138 self._parentids[version] = (revision.NULL_REVISION,)
141 else:
139 else:
142 parents = self._filterghosts(rev.parent_ids)
140 parents = self._filterghosts(rev.parent_ids)
143 self._parentids[version] = parents
141 self._parentids[version] = parents
144
142
145 return commit(parents=parents,
143 return commit(parents=parents,
146 date='%d %d' % (rev.timestamp, -rev.timezone),
144 date='%d %d' % (rev.timestamp, -rev.timezone),
147 author=self.recode(rev.committer),
145 author=self.recode(rev.committer),
148 # bzr returns bytestrings or unicode, depending on the content
146 # bzr returns bytestrings or unicode, depending on the content
149 desc=self.recode(rev.message),
147 desc=self.recode(rev.message),
150 rev=version)
148 rev=version)
151
149
152 def gettags(self):
150 def gettags(self):
153 if not self.branch.supports_tags():
151 if not self.branch.supports_tags():
154 return {}
152 return {}
155 tagdict = self.branch.tags.get_tag_dict()
153 tagdict = self.branch.tags.get_tag_dict()
156 bytetags = {}
154 bytetags = {}
157 for name, rev in tagdict.iteritems():
155 for name, rev in tagdict.iteritems():
158 bytetags[self.recode(name)] = rev
156 bytetags[self.recode(name)] = rev
159 return bytetags
157 return bytetags
160
158
161 def getchangedfiles(self, rev, i):
159 def getchangedfiles(self, rev, i):
162 self._modecache = {}
160 self._modecache = {}
163 curtree = self.sourcerepo.revision_tree(rev)
161 curtree = self.sourcerepo.revision_tree(rev)
164 if i is not None:
162 if i is not None:
165 parentid = self._parentids[rev][i]
163 parentid = self._parentids[rev][i]
166 else:
164 else:
167 # no parent id, get the empty revision
165 # no parent id, get the empty revision
168 parentid = revision.NULL_REVISION
166 parentid = revision.NULL_REVISION
169
167
170 prevtree = self.sourcerepo.revision_tree(parentid)
168 prevtree = self.sourcerepo.revision_tree(parentid)
171 changes = [e[0] for e in self._gettreechanges(curtree, prevtree)[0]]
169 changes = [e[0] for e in self._gettreechanges(curtree, prevtree)[0]]
172 return changes
170 return changes
173
171
174 def _gettreechanges(self, current, origin):
172 def _gettreechanges(self, current, origin):
175 revid = current._revision_id
173 revid = current._revision_id
176 changes = []
174 changes = []
177 renames = {}
175 renames = {}
178 for (fileid, paths, changed_content, versioned, parent, name,
176 for (fileid, paths, changed_content, versioned, parent, name,
179 kind, executable) in current.iter_changes(origin):
177 kind, executable) in current.iter_changes(origin):
180
178
181 if paths[0] == u'' or paths[1] == u'':
179 if paths[0] == u'' or paths[1] == u'':
182 # ignore changes to tree root
180 # ignore changes to tree root
183 continue
181 continue
184
182
185 # bazaar tracks directories, mercurial does not, so
183 # bazaar tracks directories, mercurial does not, so
186 # we have to rename the directory contents
184 # we have to rename the directory contents
187 if kind[1] == 'directory':
185 if kind[1] == 'directory':
188 if kind[0] not in (None, 'directory'):
186 if kind[0] not in (None, 'directory'):
189 # Replacing 'something' with a directory, record it
187 # Replacing 'something' with a directory, record it
190 # so it can be removed.
188 # so it can be removed.
191 changes.append((self.recode(paths[0]), revid))
189 changes.append((self.recode(paths[0]), revid))
192
190
193 if None not in paths and paths[0] != paths[1]:
191 if None not in paths and paths[0] != paths[1]:
194 # neither an add nor an delete - a move
192 # neither an add nor an delete - a move
195 # rename all directory contents manually
193 # rename all directory contents manually
196 subdir = origin.inventory.path2id(paths[0])
194 subdir = origin.inventory.path2id(paths[0])
197 # get all child-entries of the directory
195 # get all child-entries of the directory
198 for name, entry in origin.inventory.iter_entries(subdir):
196 for name, entry in origin.inventory.iter_entries(subdir):
199 # hg does not track directory renames
197 # hg does not track directory renames
200 if entry.kind == 'directory':
198 if entry.kind == 'directory':
201 continue
199 continue
202 frompath = self.recode(paths[0] + '/' + name)
200 frompath = self.recode(paths[0] + '/' + name)
203 topath = self.recode(paths[1] + '/' + name)
201 topath = self.recode(paths[1] + '/' + name)
204 # register the files as changed
202 # register the files as changed
205 changes.append((frompath, revid))
203 changes.append((frompath, revid))
206 changes.append((topath, revid))
204 changes.append((topath, revid))
207 # add to mode cache
205 # add to mode cache
208 mode = ((entry.executable and 'x')
206 mode = ((entry.executable and 'x')
209 or (entry.kind == 'symlink' and 's')
207 or (entry.kind == 'symlink' and 's')
210 or '')
208 or '')
211 self._modecache[(topath, revid)] = mode
209 self._modecache[(topath, revid)] = mode
212 # register the change as move
210 # register the change as move
213 renames[topath] = frompath
211 renames[topath] = frompath
214
212
215 # no futher changes, go to the next change
213 # no futher changes, go to the next change
216 continue
214 continue
217
215
218 # we got unicode paths, need to convert them
216 # we got unicode paths, need to convert them
219 path, topath = [self.recode(part) for part in paths]
217 path, topath = [self.recode(part) for part in paths]
220
218
221 if topath is None:
219 if topath is None:
222 # file deleted
220 # file deleted
223 changes.append((path, revid))
221 changes.append((path, revid))
224 continue
222 continue
225
223
226 # renamed
224 # renamed
227 if path and path != topath:
225 if path and path != topath:
228 renames[topath] = path
226 renames[topath] = path
229 changes.append((path, revid))
227 changes.append((path, revid))
230
228
231 # populate the mode cache
229 # populate the mode cache
232 kind, executable = [e[1] for e in (kind, executable)]
230 kind, executable = [e[1] for e in (kind, executable)]
233 mode = ((executable and 'x') or (kind == 'symlink' and 'l')
231 mode = ((executable and 'x') or (kind == 'symlink' and 'l')
234 or '')
232 or '')
235 self._modecache[(topath, revid)] = mode
233 self._modecache[(topath, revid)] = mode
236 changes.append((topath, revid))
234 changes.append((topath, revid))
237
235
238 return changes, renames
236 return changes, renames
239
237
240 def _filterghosts(self, ids):
238 def _filterghosts(self, ids):
241 """Filters out ghost revisions which hg does not support, see
239 """Filters out ghost revisions which hg does not support, see
242 <http://bazaar-vcs.org/GhostRevision>
240 <http://bazaar-vcs.org/GhostRevision>
243 """
241 """
244 parentmap = self.sourcerepo.get_parent_map(ids)
242 parentmap = self.sourcerepo.get_parent_map(ids)
245 parents = tuple([parent for parent in ids if parent in parentmap])
243 parents = tuple([parent for parent in ids if parent in parentmap])
246 return parents
244 return parents
247
245
248 def recode(self, s, encoding=None):
246 def recode(self, s, encoding=None):
249 """This version of recode tries to encode unicode to bytecode,
247 """This version of recode tries to encode unicode to bytecode,
250 and preferably using the UTF-8 codec.
248 and preferably using the UTF-8 codec.
251 Other types than Unicode are silently returned, this is by
249 Other types than Unicode are silently returned, this is by
252 intention, e.g. the None-type is not going to be encoded but instead
250 intention, e.g. the None-type is not going to be encoded but instead
253 just passed through
251 just passed through
254 """
252 """
255 if not encoding:
253 if not encoding:
256 encoding = self.encoding or 'utf-8'
254 encoding = self.encoding or 'utf-8'
257
255
258 if isinstance(s, unicode):
256 if isinstance(s, unicode):
259 return s.encode(encoding)
257 return s.encode(encoding)
260 else:
258 else:
261 # leave it alone
259 # leave it alone
262 return s
260 return s
@@ -1,394 +1,389 b''
1 # common.py - common code for the convert extension
1 # common.py - common code for the convert extension
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import base64, errno
8 import base64, errno
9 import os
9 import os
10 import cPickle as pickle
10 import cPickle as pickle
11 from mercurial import util
11 from mercurial import util
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13
13
14 def encodeargs(args):
14 def encodeargs(args):
15 def encodearg(s):
15 def encodearg(s):
16 lines = base64.encodestring(s)
16 lines = base64.encodestring(s)
17 lines = [l.splitlines()[0] for l in lines]
17 lines = [l.splitlines()[0] for l in lines]
18 return ''.join(lines)
18 return ''.join(lines)
19
19
20 s = pickle.dumps(args)
20 s = pickle.dumps(args)
21 return encodearg(s)
21 return encodearg(s)
22
22
23 def decodeargs(s):
23 def decodeargs(s):
24 s = base64.decodestring(s)
24 s = base64.decodestring(s)
25 return pickle.loads(s)
25 return pickle.loads(s)
26
26
27 class MissingTool(Exception):
27 class MissingTool(Exception):
28 pass
28 pass
29
29
30 def checktool(exe, name=None, abort=True):
30 def checktool(exe, name=None, abort=True):
31 name = name or exe
31 name = name or exe
32 if not util.find_exe(exe):
32 if not util.find_exe(exe):
33 exc = abort and util.Abort or MissingTool
33 exc = abort and util.Abort or MissingTool
34 raise exc(_('cannot find required "%s" tool') % name)
34 raise exc(_('cannot find required "%s" tool') % name)
35
35
36 class NoRepo(Exception):
36 class NoRepo(Exception):
37 pass
37 pass
38
38
39 SKIPREV = 'SKIP'
39 SKIPREV = 'SKIP'
40
40
41 class commit(object):
41 class commit(object):
42 def __init__(self, author, date, desc, parents, branch=None, rev=None,
42 def __init__(self, author, date, desc, parents, branch=None, rev=None,
43 extra={}, sortkey=None):
43 extra={}, sortkey=None):
44 self.author = author or 'unknown'
44 self.author = author or 'unknown'
45 self.date = date or '0 0'
45 self.date = date or '0 0'
46 self.desc = desc
46 self.desc = desc
47 self.parents = parents
47 self.parents = parents
48 self.branch = branch
48 self.branch = branch
49 self.rev = rev
49 self.rev = rev
50 self.extra = extra
50 self.extra = extra
51 self.sortkey = sortkey
51 self.sortkey = sortkey
52
52
53 class converter_source(object):
53 class converter_source(object):
54 """Conversion source interface"""
54 """Conversion source interface"""
55
55
56 def __init__(self, ui, path=None, rev=None):
56 def __init__(self, ui, path=None, rev=None):
57 """Initialize conversion source (or raise NoRepo("message")
57 """Initialize conversion source (or raise NoRepo("message")
58 exception if path is not a valid repository)"""
58 exception if path is not a valid repository)"""
59 self.ui = ui
59 self.ui = ui
60 self.path = path
60 self.path = path
61 self.rev = rev
61 self.rev = rev
62
62
63 self.encoding = 'utf-8'
63 self.encoding = 'utf-8'
64
64
65 def before(self):
65 def before(self):
66 pass
66 pass
67
67
68 def after(self):
68 def after(self):
69 pass
69 pass
70
70
71 def setrevmap(self, revmap):
71 def setrevmap(self, revmap):
72 """set the map of already-converted revisions"""
72 """set the map of already-converted revisions"""
73 pass
73 pass
74
74
75 def getheads(self):
75 def getheads(self):
76 """Return a list of this repository's heads"""
76 """Return a list of this repository's heads"""
77 raise NotImplementedError()
77 raise NotImplementedError()
78
78
79 def getfile(self, name, rev):
79 def getfile(self, name, rev):
80 """Return file contents as a string. rev is the identifier returned
80 """Return a pair (data, mode) where data is the file content
81 by a previous call to getchanges(). Raise IOError to indicate that
81 as a string and mode one of '', 'x' or 'l'. rev is the
82 name was deleted in rev.
82 identifier returned by a previous call to getchanges(). Raise
83 """
83 IOError to indicate that name was deleted in rev.
84 raise NotImplementedError()
85
86 def getmode(self, name, rev):
87 """Return file mode, eg. '', 'x', or 'l'. rev is the identifier
88 returned by a previous call to getchanges().
89 """
84 """
90 raise NotImplementedError()
85 raise NotImplementedError()
91
86
92 def getchanges(self, version):
87 def getchanges(self, version):
93 """Returns a tuple of (files, copies).
88 """Returns a tuple of (files, copies).
94
89
95 files is a sorted list of (filename, id) tuples for all files
90 files is a sorted list of (filename, id) tuples for all files
96 changed between version and its first parent returned by
91 changed between version and its first parent returned by
97 getcommit(). id is the source revision id of the file.
92 getcommit(). id is the source revision id of the file.
98
93
99 copies is a dictionary of dest: source
94 copies is a dictionary of dest: source
100 """
95 """
101 raise NotImplementedError()
96 raise NotImplementedError()
102
97
103 def getcommit(self, version):
98 def getcommit(self, version):
104 """Return the commit object for version"""
99 """Return the commit object for version"""
105 raise NotImplementedError()
100 raise NotImplementedError()
106
101
107 def gettags(self):
102 def gettags(self):
108 """Return the tags as a dictionary of name: revision
103 """Return the tags as a dictionary of name: revision
109
104
110 Tag names must be UTF-8 strings.
105 Tag names must be UTF-8 strings.
111 """
106 """
112 raise NotImplementedError()
107 raise NotImplementedError()
113
108
114 def recode(self, s, encoding=None):
109 def recode(self, s, encoding=None):
115 if not encoding:
110 if not encoding:
116 encoding = self.encoding or 'utf-8'
111 encoding = self.encoding or 'utf-8'
117
112
118 if isinstance(s, unicode):
113 if isinstance(s, unicode):
119 return s.encode("utf-8")
114 return s.encode("utf-8")
120 try:
115 try:
121 return s.decode(encoding).encode("utf-8")
116 return s.decode(encoding).encode("utf-8")
122 except:
117 except:
123 try:
118 try:
124 return s.decode("latin-1").encode("utf-8")
119 return s.decode("latin-1").encode("utf-8")
125 except:
120 except:
126 return s.decode(encoding, "replace").encode("utf-8")
121 return s.decode(encoding, "replace").encode("utf-8")
127
122
128 def getchangedfiles(self, rev, i):
123 def getchangedfiles(self, rev, i):
129 """Return the files changed by rev compared to parent[i].
124 """Return the files changed by rev compared to parent[i].
130
125
131 i is an index selecting one of the parents of rev. The return
126 i is an index selecting one of the parents of rev. The return
132 value should be the list of files that are different in rev and
127 value should be the list of files that are different in rev and
133 this parent.
128 this parent.
134
129
135 If rev has no parents, i is None.
130 If rev has no parents, i is None.
136
131
137 This function is only needed to support --filemap
132 This function is only needed to support --filemap
138 """
133 """
139 raise NotImplementedError()
134 raise NotImplementedError()
140
135
141 def converted(self, rev, sinkrev):
136 def converted(self, rev, sinkrev):
142 '''Notify the source that a revision has been converted.'''
137 '''Notify the source that a revision has been converted.'''
143 pass
138 pass
144
139
145 def hasnativeorder(self):
140 def hasnativeorder(self):
146 """Return true if this source has a meaningful, native revision
141 """Return true if this source has a meaningful, native revision
147 order. For instance, Mercurial revisions are store sequentially
142 order. For instance, Mercurial revisions are store sequentially
148 while there is no such global ordering with Darcs.
143 while there is no such global ordering with Darcs.
149 """
144 """
150 return False
145 return False
151
146
152 def lookuprev(self, rev):
147 def lookuprev(self, rev):
153 """If rev is a meaningful revision reference in source, return
148 """If rev is a meaningful revision reference in source, return
154 the referenced identifier in the same format used by getcommit().
149 the referenced identifier in the same format used by getcommit().
155 return None otherwise.
150 return None otherwise.
156 """
151 """
157 return None
152 return None
158
153
159 class converter_sink(object):
154 class converter_sink(object):
160 """Conversion sink (target) interface"""
155 """Conversion sink (target) interface"""
161
156
162 def __init__(self, ui, path):
157 def __init__(self, ui, path):
163 """Initialize conversion sink (or raise NoRepo("message")
158 """Initialize conversion sink (or raise NoRepo("message")
164 exception if path is not a valid repository)
159 exception if path is not a valid repository)
165
160
166 created is a list of paths to remove if a fatal error occurs
161 created is a list of paths to remove if a fatal error occurs
167 later"""
162 later"""
168 self.ui = ui
163 self.ui = ui
169 self.path = path
164 self.path = path
170 self.created = []
165 self.created = []
171
166
172 def getheads(self):
167 def getheads(self):
173 """Return a list of this repository's heads"""
168 """Return a list of this repository's heads"""
174 raise NotImplementedError()
169 raise NotImplementedError()
175
170
176 def revmapfile(self):
171 def revmapfile(self):
177 """Path to a file that will contain lines
172 """Path to a file that will contain lines
178 source_rev_id sink_rev_id
173 source_rev_id sink_rev_id
179 mapping equivalent revision identifiers for each system."""
174 mapping equivalent revision identifiers for each system."""
180 raise NotImplementedError()
175 raise NotImplementedError()
181
176
182 def authorfile(self):
177 def authorfile(self):
183 """Path to a file that will contain lines
178 """Path to a file that will contain lines
184 srcauthor=dstauthor
179 srcauthor=dstauthor
185 mapping equivalent authors identifiers for each system."""
180 mapping equivalent authors identifiers for each system."""
186 return None
181 return None
187
182
188 def putcommit(self, files, copies, parents, commit, source, revmap):
183 def putcommit(self, files, copies, parents, commit, source, revmap):
189 """Create a revision with all changed files listed in 'files'
184 """Create a revision with all changed files listed in 'files'
190 and having listed parents. 'commit' is a commit object
185 and having listed parents. 'commit' is a commit object
191 containing at a minimum the author, date, and message for this
186 containing at a minimum the author, date, and message for this
192 changeset. 'files' is a list of (path, version) tuples,
187 changeset. 'files' is a list of (path, version) tuples,
193 'copies' is a dictionary mapping destinations to sources,
188 'copies' is a dictionary mapping destinations to sources,
194 'source' is the source repository, and 'revmap' is a mapfile
189 'source' is the source repository, and 'revmap' is a mapfile
195 of source revisions to converted revisions. Only getfile(),
190 of source revisions to converted revisions. Only getfile() and
196 getmode(), and lookuprev() should be called on 'source'.
191 lookuprev() should be called on 'source'.
197
192
198 Note that the sink repository is not told to update itself to
193 Note that the sink repository is not told to update itself to
199 a particular revision (or even what that revision would be)
194 a particular revision (or even what that revision would be)
200 before it receives the file data.
195 before it receives the file data.
201 """
196 """
202 raise NotImplementedError()
197 raise NotImplementedError()
203
198
204 def puttags(self, tags):
199 def puttags(self, tags):
205 """Put tags into sink.
200 """Put tags into sink.
206
201
207 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
202 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
208 Return a pair (tag_revision, tag_parent_revision), or (None, None)
203 Return a pair (tag_revision, tag_parent_revision), or (None, None)
209 if nothing was changed.
204 if nothing was changed.
210 """
205 """
211 raise NotImplementedError()
206 raise NotImplementedError()
212
207
213 def setbranch(self, branch, pbranches):
208 def setbranch(self, branch, pbranches):
214 """Set the current branch name. Called before the first putcommit
209 """Set the current branch name. Called before the first putcommit
215 on the branch.
210 on the branch.
216 branch: branch name for subsequent commits
211 branch: branch name for subsequent commits
217 pbranches: (converted parent revision, parent branch) tuples"""
212 pbranches: (converted parent revision, parent branch) tuples"""
218 pass
213 pass
219
214
220 def setfilemapmode(self, active):
215 def setfilemapmode(self, active):
221 """Tell the destination that we're using a filemap
216 """Tell the destination that we're using a filemap
222
217
223 Some converter_sources (svn in particular) can claim that a file
218 Some converter_sources (svn in particular) can claim that a file
224 was changed in a revision, even if there was no change. This method
219 was changed in a revision, even if there was no change. This method
225 tells the destination that we're using a filemap and that it should
220 tells the destination that we're using a filemap and that it should
226 filter empty revisions.
221 filter empty revisions.
227 """
222 """
228 pass
223 pass
229
224
230 def before(self):
225 def before(self):
231 pass
226 pass
232
227
233 def after(self):
228 def after(self):
234 pass
229 pass
235
230
236
231
237 class commandline(object):
232 class commandline(object):
238 def __init__(self, ui, command):
233 def __init__(self, ui, command):
239 self.ui = ui
234 self.ui = ui
240 self.command = command
235 self.command = command
241
236
242 def prerun(self):
237 def prerun(self):
243 pass
238 pass
244
239
245 def postrun(self):
240 def postrun(self):
246 pass
241 pass
247
242
248 def _cmdline(self, cmd, *args, **kwargs):
243 def _cmdline(self, cmd, *args, **kwargs):
249 cmdline = [self.command, cmd] + list(args)
244 cmdline = [self.command, cmd] + list(args)
250 for k, v in kwargs.iteritems():
245 for k, v in kwargs.iteritems():
251 if len(k) == 1:
246 if len(k) == 1:
252 cmdline.append('-' + k)
247 cmdline.append('-' + k)
253 else:
248 else:
254 cmdline.append('--' + k.replace('_', '-'))
249 cmdline.append('--' + k.replace('_', '-'))
255 try:
250 try:
256 if len(k) == 1:
251 if len(k) == 1:
257 cmdline.append('' + v)
252 cmdline.append('' + v)
258 else:
253 else:
259 cmdline[-1] += '=' + v
254 cmdline[-1] += '=' + v
260 except TypeError:
255 except TypeError:
261 pass
256 pass
262 cmdline = [util.shellquote(arg) for arg in cmdline]
257 cmdline = [util.shellquote(arg) for arg in cmdline]
263 if not self.ui.debugflag:
258 if not self.ui.debugflag:
264 cmdline += ['2>', util.nulldev]
259 cmdline += ['2>', util.nulldev]
265 cmdline += ['<', util.nulldev]
260 cmdline += ['<', util.nulldev]
266 cmdline = ' '.join(cmdline)
261 cmdline = ' '.join(cmdline)
267 return cmdline
262 return cmdline
268
263
269 def _run(self, cmd, *args, **kwargs):
264 def _run(self, cmd, *args, **kwargs):
270 cmdline = self._cmdline(cmd, *args, **kwargs)
265 cmdline = self._cmdline(cmd, *args, **kwargs)
271 self.ui.debug('running: %s\n' % (cmdline,))
266 self.ui.debug('running: %s\n' % (cmdline,))
272 self.prerun()
267 self.prerun()
273 try:
268 try:
274 return util.popen(cmdline)
269 return util.popen(cmdline)
275 finally:
270 finally:
276 self.postrun()
271 self.postrun()
277
272
278 def run(self, cmd, *args, **kwargs):
273 def run(self, cmd, *args, **kwargs):
279 fp = self._run(cmd, *args, **kwargs)
274 fp = self._run(cmd, *args, **kwargs)
280 output = fp.read()
275 output = fp.read()
281 self.ui.debug(output)
276 self.ui.debug(output)
282 return output, fp.close()
277 return output, fp.close()
283
278
284 def runlines(self, cmd, *args, **kwargs):
279 def runlines(self, cmd, *args, **kwargs):
285 fp = self._run(cmd, *args, **kwargs)
280 fp = self._run(cmd, *args, **kwargs)
286 output = fp.readlines()
281 output = fp.readlines()
287 self.ui.debug(''.join(output))
282 self.ui.debug(''.join(output))
288 return output, fp.close()
283 return output, fp.close()
289
284
290 def checkexit(self, status, output=''):
285 def checkexit(self, status, output=''):
291 if status:
286 if status:
292 if output:
287 if output:
293 self.ui.warn(_('%s error:\n') % self.command)
288 self.ui.warn(_('%s error:\n') % self.command)
294 self.ui.warn(output)
289 self.ui.warn(output)
295 msg = util.explain_exit(status)[0]
290 msg = util.explain_exit(status)[0]
296 raise util.Abort('%s %s' % (self.command, msg))
291 raise util.Abort('%s %s' % (self.command, msg))
297
292
298 def run0(self, cmd, *args, **kwargs):
293 def run0(self, cmd, *args, **kwargs):
299 output, status = self.run(cmd, *args, **kwargs)
294 output, status = self.run(cmd, *args, **kwargs)
300 self.checkexit(status, output)
295 self.checkexit(status, output)
301 return output
296 return output
302
297
303 def runlines0(self, cmd, *args, **kwargs):
298 def runlines0(self, cmd, *args, **kwargs):
304 output, status = self.runlines(cmd, *args, **kwargs)
299 output, status = self.runlines(cmd, *args, **kwargs)
305 self.checkexit(status, ''.join(output))
300 self.checkexit(status, ''.join(output))
306 return output
301 return output
307
302
308 def getargmax(self):
303 def getargmax(self):
309 if '_argmax' in self.__dict__:
304 if '_argmax' in self.__dict__:
310 return self._argmax
305 return self._argmax
311
306
312 # POSIX requires at least 4096 bytes for ARG_MAX
307 # POSIX requires at least 4096 bytes for ARG_MAX
313 self._argmax = 4096
308 self._argmax = 4096
314 try:
309 try:
315 self._argmax = os.sysconf("SC_ARG_MAX")
310 self._argmax = os.sysconf("SC_ARG_MAX")
316 except:
311 except:
317 pass
312 pass
318
313
319 # Windows shells impose their own limits on command line length,
314 # Windows shells impose their own limits on command line length,
320 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
315 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
321 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
316 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
322 # details about cmd.exe limitations.
317 # details about cmd.exe limitations.
323
318
324 # Since ARG_MAX is for command line _and_ environment, lower our limit
319 # Since ARG_MAX is for command line _and_ environment, lower our limit
325 # (and make happy Windows shells while doing this).
320 # (and make happy Windows shells while doing this).
326
321
327 self._argmax = self._argmax / 2 - 1
322 self._argmax = self._argmax / 2 - 1
328 return self._argmax
323 return self._argmax
329
324
330 def limit_arglist(self, arglist, cmd, *args, **kwargs):
325 def limit_arglist(self, arglist, cmd, *args, **kwargs):
331 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
326 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
332 bytes = 0
327 bytes = 0
333 fl = []
328 fl = []
334 for fn in arglist:
329 for fn in arglist:
335 b = len(fn) + 3
330 b = len(fn) + 3
336 if bytes + b < limit or len(fl) == 0:
331 if bytes + b < limit or len(fl) == 0:
337 fl.append(fn)
332 fl.append(fn)
338 bytes += b
333 bytes += b
339 else:
334 else:
340 yield fl
335 yield fl
341 fl = [fn]
336 fl = [fn]
342 bytes = b
337 bytes = b
343 if fl:
338 if fl:
344 yield fl
339 yield fl
345
340
346 def xargs(self, arglist, cmd, *args, **kwargs):
341 def xargs(self, arglist, cmd, *args, **kwargs):
347 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
342 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
348 self.run0(cmd, *(list(args) + l), **kwargs)
343 self.run0(cmd, *(list(args) + l), **kwargs)
349
344
350 class mapfile(dict):
345 class mapfile(dict):
351 def __init__(self, ui, path):
346 def __init__(self, ui, path):
352 super(mapfile, self).__init__()
347 super(mapfile, self).__init__()
353 self.ui = ui
348 self.ui = ui
354 self.path = path
349 self.path = path
355 self.fp = None
350 self.fp = None
356 self.order = []
351 self.order = []
357 self._read()
352 self._read()
358
353
359 def _read(self):
354 def _read(self):
360 if not self.path:
355 if not self.path:
361 return
356 return
362 try:
357 try:
363 fp = open(self.path, 'r')
358 fp = open(self.path, 'r')
364 except IOError, err:
359 except IOError, err:
365 if err.errno != errno.ENOENT:
360 if err.errno != errno.ENOENT:
366 raise
361 raise
367 return
362 return
368 for i, line in enumerate(fp):
363 for i, line in enumerate(fp):
369 try:
364 try:
370 key, value = line.splitlines()[0].rsplit(' ', 1)
365 key, value = line.splitlines()[0].rsplit(' ', 1)
371 except ValueError:
366 except ValueError:
372 raise util.Abort(
367 raise util.Abort(
373 _('syntax error in %s(%d): key/value pair expected')
368 _('syntax error in %s(%d): key/value pair expected')
374 % (self.path, i + 1))
369 % (self.path, i + 1))
375 if key not in self:
370 if key not in self:
376 self.order.append(key)
371 self.order.append(key)
377 super(mapfile, self).__setitem__(key, value)
372 super(mapfile, self).__setitem__(key, value)
378 fp.close()
373 fp.close()
379
374
380 def __setitem__(self, key, value):
375 def __setitem__(self, key, value):
381 if self.fp is None:
376 if self.fp is None:
382 try:
377 try:
383 self.fp = open(self.path, 'a')
378 self.fp = open(self.path, 'a')
384 except IOError, err:
379 except IOError, err:
385 raise util.Abort(_('could not open map file %r: %s') %
380 raise util.Abort(_('could not open map file %r: %s') %
386 (self.path, err.strerror))
381 (self.path, err.strerror))
387 self.fp.write('%s %s\n' % (key, value))
382 self.fp.write('%s %s\n' % (key, value))
388 self.fp.flush()
383 self.fp.flush()
389 super(mapfile, self).__setitem__(key, value)
384 super(mapfile, self).__setitem__(key, value)
390
385
391 def close(self):
386 def close(self):
392 if self.fp:
387 if self.fp:
393 self.fp.close()
388 self.fp.close()
394 self.fp = None
389 self.fp = None
@@ -1,282 +1,273 b''
1 # cvs.py: CVS conversion code inspired by hg-cvs-import and git-cvsimport
1 # cvs.py: CVS conversion code inspired by hg-cvs-import and git-cvsimport
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import os, locale, re, socket, errno
8 import os, locale, re, socket, errno
9 from cStringIO import StringIO
9 from cStringIO import StringIO
10 from mercurial import util
10 from mercurial import util
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12
12
13 from common import NoRepo, commit, converter_source, checktool
13 from common import NoRepo, commit, converter_source, checktool
14 import cvsps
14 import cvsps
15
15
16 class convert_cvs(converter_source):
16 class convert_cvs(converter_source):
17 def __init__(self, ui, path, rev=None):
17 def __init__(self, ui, path, rev=None):
18 super(convert_cvs, self).__init__(ui, path, rev=rev)
18 super(convert_cvs, self).__init__(ui, path, rev=rev)
19
19
20 cvs = os.path.join(path, "CVS")
20 cvs = os.path.join(path, "CVS")
21 if not os.path.exists(cvs):
21 if not os.path.exists(cvs):
22 raise NoRepo(_("%s does not look like a CVS checkout") % path)
22 raise NoRepo(_("%s does not look like a CVS checkout") % path)
23
23
24 checktool('cvs')
24 checktool('cvs')
25
25
26 self.changeset = None
26 self.changeset = None
27 self.files = {}
27 self.files = {}
28 self.tags = {}
28 self.tags = {}
29 self.lastbranch = {}
29 self.lastbranch = {}
30 self.socket = None
30 self.socket = None
31 self.cvsroot = open(os.path.join(cvs, "Root")).read()[:-1]
31 self.cvsroot = open(os.path.join(cvs, "Root")).read()[:-1]
32 self.cvsrepo = open(os.path.join(cvs, "Repository")).read()[:-1]
32 self.cvsrepo = open(os.path.join(cvs, "Repository")).read()[:-1]
33 self.encoding = locale.getpreferredencoding()
33 self.encoding = locale.getpreferredencoding()
34
34
35 self._connect()
35 self._connect()
36
36
37 def _parse(self):
37 def _parse(self):
38 if self.changeset is not None:
38 if self.changeset is not None:
39 return
39 return
40 self.changeset = {}
40 self.changeset = {}
41
41
42 maxrev = 0
42 maxrev = 0
43 if self.rev:
43 if self.rev:
44 # TODO: handle tags
44 # TODO: handle tags
45 try:
45 try:
46 # patchset number?
46 # patchset number?
47 maxrev = int(self.rev)
47 maxrev = int(self.rev)
48 except ValueError:
48 except ValueError:
49 raise util.Abort(_('revision %s is not a patchset number')
49 raise util.Abort(_('revision %s is not a patchset number')
50 % self.rev)
50 % self.rev)
51
51
52 d = os.getcwd()
52 d = os.getcwd()
53 try:
53 try:
54 os.chdir(self.path)
54 os.chdir(self.path)
55 id = None
55 id = None
56 state = 0
56 state = 0
57 filerevids = {}
57 filerevids = {}
58
58
59 cache = 'update'
59 cache = 'update'
60 if not self.ui.configbool('convert', 'cvsps.cache', True):
60 if not self.ui.configbool('convert', 'cvsps.cache', True):
61 cache = None
61 cache = None
62 db = cvsps.createlog(self.ui, cache=cache)
62 db = cvsps.createlog(self.ui, cache=cache)
63 db = cvsps.createchangeset(self.ui, db,
63 db = cvsps.createchangeset(self.ui, db,
64 fuzz=int(self.ui.config('convert', 'cvsps.fuzz', 60)),
64 fuzz=int(self.ui.config('convert', 'cvsps.fuzz', 60)),
65 mergeto=self.ui.config('convert', 'cvsps.mergeto', None),
65 mergeto=self.ui.config('convert', 'cvsps.mergeto', None),
66 mergefrom=self.ui.config('convert', 'cvsps.mergefrom', None))
66 mergefrom=self.ui.config('convert', 'cvsps.mergefrom', None))
67
67
68 for cs in db:
68 for cs in db:
69 if maxrev and cs.id > maxrev:
69 if maxrev and cs.id > maxrev:
70 break
70 break
71 id = str(cs.id)
71 id = str(cs.id)
72 cs.author = self.recode(cs.author)
72 cs.author = self.recode(cs.author)
73 self.lastbranch[cs.branch] = id
73 self.lastbranch[cs.branch] = id
74 cs.comment = self.recode(cs.comment)
74 cs.comment = self.recode(cs.comment)
75 date = util.datestr(cs.date)
75 date = util.datestr(cs.date)
76 self.tags.update(dict.fromkeys(cs.tags, id))
76 self.tags.update(dict.fromkeys(cs.tags, id))
77
77
78 files = {}
78 files = {}
79 for f in cs.entries:
79 for f in cs.entries:
80 files[f.file] = "%s%s" % ('.'.join([str(x)
80 files[f.file] = "%s%s" % ('.'.join([str(x)
81 for x in f.revision]),
81 for x in f.revision]),
82 ['', '(DEAD)'][f.dead])
82 ['', '(DEAD)'][f.dead])
83
83
84 # add current commit to set
84 # add current commit to set
85 c = commit(author=cs.author, date=date,
85 c = commit(author=cs.author, date=date,
86 parents=[str(p.id) for p in cs.parents],
86 parents=[str(p.id) for p in cs.parents],
87 desc=cs.comment, branch=cs.branch or '')
87 desc=cs.comment, branch=cs.branch or '')
88 self.changeset[id] = c
88 self.changeset[id] = c
89 self.files[id] = files
89 self.files[id] = files
90
90
91 self.heads = self.lastbranch.values()
91 self.heads = self.lastbranch.values()
92 finally:
92 finally:
93 os.chdir(d)
93 os.chdir(d)
94
94
95 def _connect(self):
95 def _connect(self):
96 root = self.cvsroot
96 root = self.cvsroot
97 conntype = None
97 conntype = None
98 user, host = None, None
98 user, host = None, None
99 cmd = ['cvs', 'server']
99 cmd = ['cvs', 'server']
100
100
101 self.ui.status(_("connecting to %s\n") % root)
101 self.ui.status(_("connecting to %s\n") % root)
102
102
103 if root.startswith(":pserver:"):
103 if root.startswith(":pserver:"):
104 root = root[9:]
104 root = root[9:]
105 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
105 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
106 root)
106 root)
107 if m:
107 if m:
108 conntype = "pserver"
108 conntype = "pserver"
109 user, passw, serv, port, root = m.groups()
109 user, passw, serv, port, root = m.groups()
110 if not user:
110 if not user:
111 user = "anonymous"
111 user = "anonymous"
112 if not port:
112 if not port:
113 port = 2401
113 port = 2401
114 else:
114 else:
115 port = int(port)
115 port = int(port)
116 format0 = ":pserver:%s@%s:%s" % (user, serv, root)
116 format0 = ":pserver:%s@%s:%s" % (user, serv, root)
117 format1 = ":pserver:%s@%s:%d%s" % (user, serv, port, root)
117 format1 = ":pserver:%s@%s:%d%s" % (user, serv, port, root)
118
118
119 if not passw:
119 if not passw:
120 passw = "A"
120 passw = "A"
121 cvspass = os.path.expanduser("~/.cvspass")
121 cvspass = os.path.expanduser("~/.cvspass")
122 try:
122 try:
123 pf = open(cvspass)
123 pf = open(cvspass)
124 for line in pf.read().splitlines():
124 for line in pf.read().splitlines():
125 part1, part2 = line.split(' ', 1)
125 part1, part2 = line.split(' ', 1)
126 if part1 == '/1':
126 if part1 == '/1':
127 # /1 :pserver:user@example.com:2401/cvsroot/foo Ah<Z
127 # /1 :pserver:user@example.com:2401/cvsroot/foo Ah<Z
128 part1, part2 = part2.split(' ', 1)
128 part1, part2 = part2.split(' ', 1)
129 format = format1
129 format = format1
130 else:
130 else:
131 # :pserver:user@example.com:/cvsroot/foo Ah<Z
131 # :pserver:user@example.com:/cvsroot/foo Ah<Z
132 format = format0
132 format = format0
133 if part1 == format:
133 if part1 == format:
134 passw = part2
134 passw = part2
135 break
135 break
136 pf.close()
136 pf.close()
137 except IOError, inst:
137 except IOError, inst:
138 if inst.errno != errno.ENOENT:
138 if inst.errno != errno.ENOENT:
139 if not getattr(inst, 'filename', None):
139 if not getattr(inst, 'filename', None):
140 inst.filename = cvspass
140 inst.filename = cvspass
141 raise
141 raise
142
142
143 sck = socket.socket()
143 sck = socket.socket()
144 sck.connect((serv, port))
144 sck.connect((serv, port))
145 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
145 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
146 "END AUTH REQUEST", ""]))
146 "END AUTH REQUEST", ""]))
147 if sck.recv(128) != "I LOVE YOU\n":
147 if sck.recv(128) != "I LOVE YOU\n":
148 raise util.Abort(_("CVS pserver authentication failed"))
148 raise util.Abort(_("CVS pserver authentication failed"))
149
149
150 self.writep = self.readp = sck.makefile('r+')
150 self.writep = self.readp = sck.makefile('r+')
151
151
152 if not conntype and root.startswith(":local:"):
152 if not conntype and root.startswith(":local:"):
153 conntype = "local"
153 conntype = "local"
154 root = root[7:]
154 root = root[7:]
155
155
156 if not conntype:
156 if not conntype:
157 # :ext:user@host/home/user/path/to/cvsroot
157 # :ext:user@host/home/user/path/to/cvsroot
158 if root.startswith(":ext:"):
158 if root.startswith(":ext:"):
159 root = root[5:]
159 root = root[5:]
160 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
160 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
161 # Do not take Windows path "c:\foo\bar" for a connection strings
161 # Do not take Windows path "c:\foo\bar" for a connection strings
162 if os.path.isdir(root) or not m:
162 if os.path.isdir(root) or not m:
163 conntype = "local"
163 conntype = "local"
164 else:
164 else:
165 conntype = "rsh"
165 conntype = "rsh"
166 user, host, root = m.group(1), m.group(2), m.group(3)
166 user, host, root = m.group(1), m.group(2), m.group(3)
167
167
168 if conntype != "pserver":
168 if conntype != "pserver":
169 if conntype == "rsh":
169 if conntype == "rsh":
170 rsh = os.environ.get("CVS_RSH") or "ssh"
170 rsh = os.environ.get("CVS_RSH") or "ssh"
171 if user:
171 if user:
172 cmd = [rsh, '-l', user, host] + cmd
172 cmd = [rsh, '-l', user, host] + cmd
173 else:
173 else:
174 cmd = [rsh, host] + cmd
174 cmd = [rsh, host] + cmd
175
175
176 # popen2 does not support argument lists under Windows
176 # popen2 does not support argument lists under Windows
177 cmd = [util.shellquote(arg) for arg in cmd]
177 cmd = [util.shellquote(arg) for arg in cmd]
178 cmd = util.quotecommand(' '.join(cmd))
178 cmd = util.quotecommand(' '.join(cmd))
179 self.writep, self.readp = util.popen2(cmd)
179 self.writep, self.readp = util.popen2(cmd)
180
180
181 self.realroot = root
181 self.realroot = root
182
182
183 self.writep.write("Root %s\n" % root)
183 self.writep.write("Root %s\n" % root)
184 self.writep.write("Valid-responses ok error Valid-requests Mode"
184 self.writep.write("Valid-responses ok error Valid-requests Mode"
185 " M Mbinary E Checked-in Created Updated"
185 " M Mbinary E Checked-in Created Updated"
186 " Merged Removed\n")
186 " Merged Removed\n")
187 self.writep.write("valid-requests\n")
187 self.writep.write("valid-requests\n")
188 self.writep.flush()
188 self.writep.flush()
189 r = self.readp.readline()
189 r = self.readp.readline()
190 if not r.startswith("Valid-requests"):
190 if not r.startswith("Valid-requests"):
191 raise util.Abort(_('unexpected response from CVS server '
191 raise util.Abort(_('unexpected response from CVS server '
192 '(expected "Valid-requests", but got %r)')
192 '(expected "Valid-requests", but got %r)')
193 % r)
193 % r)
194 if "UseUnchanged" in r:
194 if "UseUnchanged" in r:
195 self.writep.write("UseUnchanged\n")
195 self.writep.write("UseUnchanged\n")
196 self.writep.flush()
196 self.writep.flush()
197 r = self.readp.readline()
197 r = self.readp.readline()
198
198
199 def getheads(self):
199 def getheads(self):
200 self._parse()
200 self._parse()
201 return self.heads
201 return self.heads
202
202
203 def _getfile(self, name, rev):
203 def getfile(self, name, rev):
204
204
205 def chunkedread(fp, count):
205 def chunkedread(fp, count):
206 # file-objects returned by socked.makefile() do not handle
206 # file-objects returned by socked.makefile() do not handle
207 # large read() requests very well.
207 # large read() requests very well.
208 chunksize = 65536
208 chunksize = 65536
209 output = StringIO()
209 output = StringIO()
210 while count > 0:
210 while count > 0:
211 data = fp.read(min(count, chunksize))
211 data = fp.read(min(count, chunksize))
212 if not data:
212 if not data:
213 raise util.Abort(_("%d bytes missing from remote file")
213 raise util.Abort(_("%d bytes missing from remote file")
214 % count)
214 % count)
215 count -= len(data)
215 count -= len(data)
216 output.write(data)
216 output.write(data)
217 return output.getvalue()
217 return output.getvalue()
218
218
219 self._parse()
219 if rev.endswith("(DEAD)"):
220 if rev.endswith("(DEAD)"):
220 raise IOError
221 raise IOError
221
222
222 args = ("-N -P -kk -r %s --" % rev).split()
223 args = ("-N -P -kk -r %s --" % rev).split()
223 args.append(self.cvsrepo + '/' + name)
224 args.append(self.cvsrepo + '/' + name)
224 for x in args:
225 for x in args:
225 self.writep.write("Argument %s\n" % x)
226 self.writep.write("Argument %s\n" % x)
226 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
227 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
227 self.writep.flush()
228 self.writep.flush()
228
229
229 data = ""
230 data = ""
230 mode = None
231 mode = None
231 while 1:
232 while 1:
232 line = self.readp.readline()
233 line = self.readp.readline()
233 if line.startswith("Created ") or line.startswith("Updated "):
234 if line.startswith("Created ") or line.startswith("Updated "):
234 self.readp.readline() # path
235 self.readp.readline() # path
235 self.readp.readline() # entries
236 self.readp.readline() # entries
236 mode = self.readp.readline()[:-1]
237 mode = self.readp.readline()[:-1]
237 count = int(self.readp.readline()[:-1])
238 count = int(self.readp.readline()[:-1])
238 data = chunkedread(self.readp, count)
239 data = chunkedread(self.readp, count)
239 elif line.startswith(" "):
240 elif line.startswith(" "):
240 data += line[1:]
241 data += line[1:]
241 elif line.startswith("M "):
242 elif line.startswith("M "):
242 pass
243 pass
243 elif line.startswith("Mbinary "):
244 elif line.startswith("Mbinary "):
244 count = int(self.readp.readline()[:-1])
245 count = int(self.readp.readline()[:-1])
245 data = chunkedread(self.readp, count)
246 data = chunkedread(self.readp, count)
246 else:
247 else:
247 if line == "ok\n":
248 if line == "ok\n":
248 if mode is None:
249 if mode is None:
249 raise util.Abort(_('malformed response from CVS'))
250 raise util.Abort(_('malformed response from CVS'))
250 return (data, "x" in mode and "x" or "")
251 return (data, "x" in mode and "x" or "")
251 elif line.startswith("E "):
252 elif line.startswith("E "):
252 self.ui.warn(_("cvs server: %s\n") % line[2:])
253 self.ui.warn(_("cvs server: %s\n") % line[2:])
253 elif line.startswith("Remove"):
254 elif line.startswith("Remove"):
254 self.readp.readline()
255 self.readp.readline()
255 else:
256 else:
256 raise util.Abort(_("unknown CVS response: %s") % line)
257 raise util.Abort(_("unknown CVS response: %s") % line)
257
258
258 def getfile(self, file, rev):
259 self._parse()
260 data, mode = self._getfile(file, rev)
261 self.modecache[(file, rev)] = mode
262 return data
263
264 def getmode(self, file, rev):
265 return self.modecache[(file, rev)]
266
267 def getchanges(self, rev):
259 def getchanges(self, rev):
268 self._parse()
260 self._parse()
269 self.modecache = {}
270 return sorted(self.files[rev].iteritems()), {}
261 return sorted(self.files[rev].iteritems()), {}
271
262
272 def getcommit(self, rev):
263 def getcommit(self, rev):
273 self._parse()
264 self._parse()
274 return self.changeset[rev]
265 return self.changeset[rev]
275
266
276 def gettags(self):
267 def gettags(self):
277 self._parse()
268 self._parse()
278 return self.tags
269 return self.tags
279
270
280 def getchangedfiles(self, rev, i):
271 def getchangedfiles(self, rev, i):
281 self._parse()
272 self._parse()
282 return sorted(self.files[rev])
273 return sorted(self.files[rev])
@@ -1,167 +1,167 b''
1 # darcs.py - darcs support for the convert extension
1 # darcs.py - darcs support for the convert extension
2 #
2 #
3 # Copyright 2007-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2007-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from common import NoRepo, checktool, commandline, commit, converter_source
8 from common import NoRepo, checktool, commandline, commit, converter_source
9 from mercurial.i18n import _
9 from mercurial.i18n import _
10 from mercurial import util
10 from mercurial import util
11 import os, shutil, tempfile
11 import os, shutil, tempfile
12
12
13 # The naming drift of ElementTree is fun!
13 # The naming drift of ElementTree is fun!
14
14
15 try:
15 try:
16 from xml.etree.cElementTree import ElementTree
16 from xml.etree.cElementTree import ElementTree
17 except ImportError:
17 except ImportError:
18 try:
18 try:
19 from xml.etree.ElementTree import ElementTree
19 from xml.etree.ElementTree import ElementTree
20 except ImportError:
20 except ImportError:
21 try:
21 try:
22 from elementtree.cElementTree import ElementTree
22 from elementtree.cElementTree import ElementTree
23 except ImportError:
23 except ImportError:
24 try:
24 try:
25 from elementtree.ElementTree import ElementTree
25 from elementtree.ElementTree import ElementTree
26 except ImportError:
26 except ImportError:
27 ElementTree = None
27 ElementTree = None
28
28
29 class darcs_source(converter_source, commandline):
29 class darcs_source(converter_source, commandline):
30 def __init__(self, ui, path, rev=None):
30 def __init__(self, ui, path, rev=None):
31 converter_source.__init__(self, ui, path, rev=rev)
31 converter_source.__init__(self, ui, path, rev=rev)
32 commandline.__init__(self, ui, 'darcs')
32 commandline.__init__(self, ui, 'darcs')
33
33
34 # check for _darcs, ElementTree, _darcs/inventory so that we can
34 # check for _darcs, ElementTree, _darcs/inventory so that we can
35 # easily skip test-convert-darcs if ElementTree is not around
35 # easily skip test-convert-darcs if ElementTree is not around
36 if not os.path.exists(os.path.join(path, '_darcs', 'inventories')):
36 if not os.path.exists(os.path.join(path, '_darcs', 'inventories')):
37 raise NoRepo(_("%s does not look like a darcs repository") % path)
37 raise NoRepo(_("%s does not look like a darcs repository") % path)
38
38
39 if not os.path.exists(os.path.join(path, '_darcs')):
39 if not os.path.exists(os.path.join(path, '_darcs')):
40 raise NoRepo(_("%s does not look like a darcs repository") % path)
40 raise NoRepo(_("%s does not look like a darcs repository") % path)
41
41
42 checktool('darcs')
42 checktool('darcs')
43 version = self.run0('--version').splitlines()[0].strip()
43 version = self.run0('--version').splitlines()[0].strip()
44 if version < '2.1':
44 if version < '2.1':
45 raise util.Abort(_('darcs version 2.1 or newer needed (found %r)') %
45 raise util.Abort(_('darcs version 2.1 or newer needed (found %r)') %
46 version)
46 version)
47
47
48 if ElementTree is None:
48 if ElementTree is None:
49 raise util.Abort(_("Python ElementTree module is not available"))
49 raise util.Abort(_("Python ElementTree module is not available"))
50
50
51 self.path = os.path.realpath(path)
51 self.path = os.path.realpath(path)
52
52
53 self.lastrev = None
53 self.lastrev = None
54 self.changes = {}
54 self.changes = {}
55 self.parents = {}
55 self.parents = {}
56 self.tags = {}
56 self.tags = {}
57
57
58 def before(self):
58 def before(self):
59 self.tmppath = tempfile.mkdtemp(
59 self.tmppath = tempfile.mkdtemp(
60 prefix='convert-' + os.path.basename(self.path) + '-')
60 prefix='convert-' + os.path.basename(self.path) + '-')
61 output, status = self.run('init', repodir=self.tmppath)
61 output, status = self.run('init', repodir=self.tmppath)
62 self.checkexit(status)
62 self.checkexit(status)
63
63
64 tree = self.xml('changes', xml_output=True, summary=True,
64 tree = self.xml('changes', xml_output=True, summary=True,
65 repodir=self.path)
65 repodir=self.path)
66 tagname = None
66 tagname = None
67 child = None
67 child = None
68 for elt in tree.findall('patch'):
68 for elt in tree.findall('patch'):
69 node = elt.get('hash')
69 node = elt.get('hash')
70 name = elt.findtext('name', '')
70 name = elt.findtext('name', '')
71 if name.startswith('TAG '):
71 if name.startswith('TAG '):
72 tagname = name[4:].strip()
72 tagname = name[4:].strip()
73 elif tagname is not None:
73 elif tagname is not None:
74 self.tags[tagname] = node
74 self.tags[tagname] = node
75 tagname = None
75 tagname = None
76 self.changes[node] = elt
76 self.changes[node] = elt
77 self.parents[child] = [node]
77 self.parents[child] = [node]
78 child = node
78 child = node
79 self.parents[child] = []
79 self.parents[child] = []
80
80
81 def after(self):
81 def after(self):
82 self.ui.debug('cleaning up %s\n' % self.tmppath)
82 self.ui.debug('cleaning up %s\n' % self.tmppath)
83 shutil.rmtree(self.tmppath, ignore_errors=True)
83 shutil.rmtree(self.tmppath, ignore_errors=True)
84
84
85 def xml(self, cmd, **kwargs):
85 def xml(self, cmd, **kwargs):
86 etree = ElementTree()
86 etree = ElementTree()
87 fp = self._run(cmd, **kwargs)
87 fp = self._run(cmd, **kwargs)
88 etree.parse(fp)
88 etree.parse(fp)
89 self.checkexit(fp.close())
89 self.checkexit(fp.close())
90 return etree.getroot()
90 return etree.getroot()
91
91
92 def manifest(self):
92 def manifest(self):
93 man = []
93 man = []
94 output, status = self.run('show', 'files', no_directories=True,
94 output, status = self.run('show', 'files', no_directories=True,
95 repodir=self.tmppath)
95 repodir=self.tmppath)
96 self.checkexit(status)
96 self.checkexit(status)
97 for line in output.split('\n'):
97 for line in output.split('\n'):
98 path = line[2:]
98 path = line[2:]
99 if path:
99 if path:
100 man.append(path)
100 man.append(path)
101 return man
101 return man
102
102
103 def getheads(self):
103 def getheads(self):
104 return self.parents[None]
104 return self.parents[None]
105
105
106 def getcommit(self, rev):
106 def getcommit(self, rev):
107 elt = self.changes[rev]
107 elt = self.changes[rev]
108 date = util.strdate(elt.get('local_date'), '%a %b %d %H:%M:%S %Z %Y')
108 date = util.strdate(elt.get('local_date'), '%a %b %d %H:%M:%S %Z %Y')
109 desc = elt.findtext('name') + '\n' + elt.findtext('comment', '')
109 desc = elt.findtext('name') + '\n' + elt.findtext('comment', '')
110 return commit(author=elt.get('author'), date=util.datestr(date),
110 return commit(author=elt.get('author'), date=util.datestr(date),
111 desc=desc.strip(), parents=self.parents[rev])
111 desc=desc.strip(), parents=self.parents[rev])
112
112
113 def pull(self, rev):
113 def pull(self, rev):
114 output, status = self.run('pull', self.path, all=True,
114 output, status = self.run('pull', self.path, all=True,
115 match='hash %s' % rev,
115 match='hash %s' % rev,
116 no_test=True, no_posthook=True,
116 no_test=True, no_posthook=True,
117 external_merge='/bin/false',
117 external_merge='/bin/false',
118 repodir=self.tmppath)
118 repodir=self.tmppath)
119 if status:
119 if status:
120 if output.find('We have conflicts in') == -1:
120 if output.find('We have conflicts in') == -1:
121 self.checkexit(status, output)
121 self.checkexit(status, output)
122 output, status = self.run('revert', all=True, repodir=self.tmppath)
122 output, status = self.run('revert', all=True, repodir=self.tmppath)
123 self.checkexit(status, output)
123 self.checkexit(status, output)
124
124
125 def getchanges(self, rev):
125 def getchanges(self, rev):
126 copies = {}
126 copies = {}
127 changes = []
127 changes = []
128 man = None
128 man = None
129 for elt in self.changes[rev].find('summary').getchildren():
129 for elt in self.changes[rev].find('summary').getchildren():
130 if elt.tag in ('add_directory', 'remove_directory'):
130 if elt.tag in ('add_directory', 'remove_directory'):
131 continue
131 continue
132 if elt.tag == 'move':
132 if elt.tag == 'move':
133 if man is None:
133 if man is None:
134 man = self.manifest()
134 man = self.manifest()
135 source, dest = elt.get('from'), elt.get('to')
135 source, dest = elt.get('from'), elt.get('to')
136 if source in man:
136 if source in man:
137 # File move
137 # File move
138 changes.append((source, rev))
138 changes.append((source, rev))
139 changes.append((dest, rev))
139 changes.append((dest, rev))
140 copies[dest] = source
140 copies[dest] = source
141 else:
141 else:
142 # Directory move, deduce file moves from manifest
142 # Directory move, deduce file moves from manifest
143 source = source + '/'
143 source = source + '/'
144 for f in man:
144 for f in man:
145 if not f.startswith(source):
145 if not f.startswith(source):
146 continue
146 continue
147 fdest = dest + '/' + f[len(source):]
147 fdest = dest + '/' + f[len(source):]
148 changes.append((f, rev))
148 changes.append((f, rev))
149 changes.append((fdest, rev))
149 changes.append((fdest, rev))
150 copies[fdest] = f
150 copies[fdest] = f
151 else:
151 else:
152 changes.append((elt.text.strip(), rev))
152 changes.append((elt.text.strip(), rev))
153 self.pull(rev)
153 self.pull(rev)
154 self.lastrev = rev
154 self.lastrev = rev
155 return sorted(changes), copies
155 return sorted(changes), copies
156
156
157 def getfile(self, name, rev):
157 def getfile(self, name, rev):
158 if rev != self.lastrev:
158 if rev != self.lastrev:
159 raise util.Abort(_('internal calling inconsistency'))
159 raise util.Abort(_('internal calling inconsistency'))
160 return open(os.path.join(self.tmppath, name), 'rb').read()
160 path = os.path.join(self.tmppath, name)
161
161 data = open(path, 'rb').read()
162 def getmode(self, name, rev):
162 mode = os.lstat(path).st_mode
163 mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
163 mode = (mode & 0111) and 'x' or ''
164 return (mode & 0111) and 'x' or ''
164 return data, mode
165
165
166 def gettags(self):
166 def gettags(self):
167 return self.tags
167 return self.tags
@@ -1,359 +1,353 b''
1 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
1 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
2 # Copyright 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
2 # Copyright 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 import shlex
7 import shlex
8 from mercurial.i18n import _
8 from mercurial.i18n import _
9 from mercurial import util
9 from mercurial import util
10 from common import SKIPREV, converter_source
10 from common import SKIPREV, converter_source
11
11
12 def rpairs(name):
12 def rpairs(name):
13 e = len(name)
13 e = len(name)
14 while e != -1:
14 while e != -1:
15 yield name[:e], name[e + 1:]
15 yield name[:e], name[e + 1:]
16 e = name.rfind('/', 0, e)
16 e = name.rfind('/', 0, e)
17 yield '.', name
17 yield '.', name
18
18
19 class filemapper(object):
19 class filemapper(object):
20 '''Map and filter filenames when importing.
20 '''Map and filter filenames when importing.
21 A name can be mapped to itself, a new name, or None (omit from new
21 A name can be mapped to itself, a new name, or None (omit from new
22 repository).'''
22 repository).'''
23
23
24 def __init__(self, ui, path=None):
24 def __init__(self, ui, path=None):
25 self.ui = ui
25 self.ui = ui
26 self.include = {}
26 self.include = {}
27 self.exclude = {}
27 self.exclude = {}
28 self.rename = {}
28 self.rename = {}
29 if path:
29 if path:
30 if self.parse(path):
30 if self.parse(path):
31 raise util.Abort(_('errors in filemap'))
31 raise util.Abort(_('errors in filemap'))
32
32
33 def parse(self, path):
33 def parse(self, path):
34 errs = 0
34 errs = 0
35 def check(name, mapping, listname):
35 def check(name, mapping, listname):
36 if name in mapping:
36 if name in mapping:
37 self.ui.warn(_('%s:%d: %r already in %s list\n') %
37 self.ui.warn(_('%s:%d: %r already in %s list\n') %
38 (lex.infile, lex.lineno, name, listname))
38 (lex.infile, lex.lineno, name, listname))
39 return 1
39 return 1
40 return 0
40 return 0
41 lex = shlex.shlex(open(path), path, True)
41 lex = shlex.shlex(open(path), path, True)
42 lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?'
42 lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?'
43 cmd = lex.get_token()
43 cmd = lex.get_token()
44 while cmd:
44 while cmd:
45 if cmd == 'include':
45 if cmd == 'include':
46 name = lex.get_token()
46 name = lex.get_token()
47 errs += check(name, self.exclude, 'exclude')
47 errs += check(name, self.exclude, 'exclude')
48 self.include[name] = name
48 self.include[name] = name
49 elif cmd == 'exclude':
49 elif cmd == 'exclude':
50 name = lex.get_token()
50 name = lex.get_token()
51 errs += check(name, self.include, 'include')
51 errs += check(name, self.include, 'include')
52 errs += check(name, self.rename, 'rename')
52 errs += check(name, self.rename, 'rename')
53 self.exclude[name] = name
53 self.exclude[name] = name
54 elif cmd == 'rename':
54 elif cmd == 'rename':
55 src = lex.get_token()
55 src = lex.get_token()
56 dest = lex.get_token()
56 dest = lex.get_token()
57 errs += check(src, self.exclude, 'exclude')
57 errs += check(src, self.exclude, 'exclude')
58 self.rename[src] = dest
58 self.rename[src] = dest
59 elif cmd == 'source':
59 elif cmd == 'source':
60 errs += self.parse(lex.get_token())
60 errs += self.parse(lex.get_token())
61 else:
61 else:
62 self.ui.warn(_('%s:%d: unknown directive %r\n') %
62 self.ui.warn(_('%s:%d: unknown directive %r\n') %
63 (lex.infile, lex.lineno, cmd))
63 (lex.infile, lex.lineno, cmd))
64 errs += 1
64 errs += 1
65 cmd = lex.get_token()
65 cmd = lex.get_token()
66 return errs
66 return errs
67
67
68 def lookup(self, name, mapping):
68 def lookup(self, name, mapping):
69 for pre, suf in rpairs(name):
69 for pre, suf in rpairs(name):
70 try:
70 try:
71 return mapping[pre], pre, suf
71 return mapping[pre], pre, suf
72 except KeyError:
72 except KeyError:
73 pass
73 pass
74 return '', name, ''
74 return '', name, ''
75
75
76 def __call__(self, name):
76 def __call__(self, name):
77 if self.include:
77 if self.include:
78 inc = self.lookup(name, self.include)[0]
78 inc = self.lookup(name, self.include)[0]
79 else:
79 else:
80 inc = name
80 inc = name
81 if self.exclude:
81 if self.exclude:
82 exc = self.lookup(name, self.exclude)[0]
82 exc = self.lookup(name, self.exclude)[0]
83 else:
83 else:
84 exc = ''
84 exc = ''
85 if (not self.include and exc) or (len(inc) <= len(exc)):
85 if (not self.include and exc) or (len(inc) <= len(exc)):
86 return None
86 return None
87 newpre, pre, suf = self.lookup(name, self.rename)
87 newpre, pre, suf = self.lookup(name, self.rename)
88 if newpre:
88 if newpre:
89 if newpre == '.':
89 if newpre == '.':
90 return suf
90 return suf
91 if suf:
91 if suf:
92 return newpre + '/' + suf
92 return newpre + '/' + suf
93 return newpre
93 return newpre
94 return name
94 return name
95
95
96 def active(self):
96 def active(self):
97 return bool(self.include or self.exclude or self.rename)
97 return bool(self.include or self.exclude or self.rename)
98
98
99 # This class does two additional things compared to a regular source:
99 # This class does two additional things compared to a regular source:
100 #
100 #
101 # - Filter and rename files. This is mostly wrapped by the filemapper
101 # - Filter and rename files. This is mostly wrapped by the filemapper
102 # class above. We hide the original filename in the revision that is
102 # class above. We hide the original filename in the revision that is
103 # returned by getchanges to be able to find things later in getfile
103 # returned by getchanges to be able to find things later in getfile.
104 # and getmode.
105 #
104 #
106 # - Return only revisions that matter for the files we're interested in.
105 # - Return only revisions that matter for the files we're interested in.
107 # This involves rewriting the parents of the original revision to
106 # This involves rewriting the parents of the original revision to
108 # create a graph that is restricted to those revisions.
107 # create a graph that is restricted to those revisions.
109 #
108 #
110 # This set of revisions includes not only revisions that directly
109 # This set of revisions includes not only revisions that directly
111 # touch files we're interested in, but also merges that merge two
110 # touch files we're interested in, but also merges that merge two
112 # or more interesting revisions.
111 # or more interesting revisions.
113
112
114 class filemap_source(converter_source):
113 class filemap_source(converter_source):
115 def __init__(self, ui, baseconverter, filemap):
114 def __init__(self, ui, baseconverter, filemap):
116 super(filemap_source, self).__init__(ui)
115 super(filemap_source, self).__init__(ui)
117 self.base = baseconverter
116 self.base = baseconverter
118 self.filemapper = filemapper(ui, filemap)
117 self.filemapper = filemapper(ui, filemap)
119 self.commits = {}
118 self.commits = {}
120 # if a revision rev has parent p in the original revision graph, then
119 # if a revision rev has parent p in the original revision graph, then
121 # rev will have parent self.parentmap[p] in the restricted graph.
120 # rev will have parent self.parentmap[p] in the restricted graph.
122 self.parentmap = {}
121 self.parentmap = {}
123 # self.wantedancestors[rev] is the set of all ancestors of rev that
122 # self.wantedancestors[rev] is the set of all ancestors of rev that
124 # are in the restricted graph.
123 # are in the restricted graph.
125 self.wantedancestors = {}
124 self.wantedancestors = {}
126 self.convertedorder = None
125 self.convertedorder = None
127 self._rebuilt = False
126 self._rebuilt = False
128 self.origparents = {}
127 self.origparents = {}
129 self.children = {}
128 self.children = {}
130 self.seenchildren = {}
129 self.seenchildren = {}
131
130
132 def before(self):
131 def before(self):
133 self.base.before()
132 self.base.before()
134
133
135 def after(self):
134 def after(self):
136 self.base.after()
135 self.base.after()
137
136
138 def setrevmap(self, revmap):
137 def setrevmap(self, revmap):
139 # rebuild our state to make things restartable
138 # rebuild our state to make things restartable
140 #
139 #
141 # To avoid calling getcommit for every revision that has already
140 # To avoid calling getcommit for every revision that has already
142 # been converted, we rebuild only the parentmap, delaying the
141 # been converted, we rebuild only the parentmap, delaying the
143 # rebuild of wantedancestors until we need it (i.e. until a
142 # rebuild of wantedancestors until we need it (i.e. until a
144 # merge).
143 # merge).
145 #
144 #
146 # We assume the order argument lists the revisions in
145 # We assume the order argument lists the revisions in
147 # topological order, so that we can infer which revisions were
146 # topological order, so that we can infer which revisions were
148 # wanted by previous runs.
147 # wanted by previous runs.
149 self._rebuilt = not revmap
148 self._rebuilt = not revmap
150 seen = {SKIPREV: SKIPREV}
149 seen = {SKIPREV: SKIPREV}
151 dummyset = set()
150 dummyset = set()
152 converted = []
151 converted = []
153 for rev in revmap.order:
152 for rev in revmap.order:
154 mapped = revmap[rev]
153 mapped = revmap[rev]
155 wanted = mapped not in seen
154 wanted = mapped not in seen
156 if wanted:
155 if wanted:
157 seen[mapped] = rev
156 seen[mapped] = rev
158 self.parentmap[rev] = rev
157 self.parentmap[rev] = rev
159 else:
158 else:
160 self.parentmap[rev] = seen[mapped]
159 self.parentmap[rev] = seen[mapped]
161 self.wantedancestors[rev] = dummyset
160 self.wantedancestors[rev] = dummyset
162 arg = seen[mapped]
161 arg = seen[mapped]
163 if arg == SKIPREV:
162 if arg == SKIPREV:
164 arg = None
163 arg = None
165 converted.append((rev, wanted, arg))
164 converted.append((rev, wanted, arg))
166 self.convertedorder = converted
165 self.convertedorder = converted
167 return self.base.setrevmap(revmap)
166 return self.base.setrevmap(revmap)
168
167
169 def rebuild(self):
168 def rebuild(self):
170 if self._rebuilt:
169 if self._rebuilt:
171 return True
170 return True
172 self._rebuilt = True
171 self._rebuilt = True
173 self.parentmap.clear()
172 self.parentmap.clear()
174 self.wantedancestors.clear()
173 self.wantedancestors.clear()
175 self.seenchildren.clear()
174 self.seenchildren.clear()
176 for rev, wanted, arg in self.convertedorder:
175 for rev, wanted, arg in self.convertedorder:
177 if rev not in self.origparents:
176 if rev not in self.origparents:
178 self.origparents[rev] = self.getcommit(rev).parents
177 self.origparents[rev] = self.getcommit(rev).parents
179 if arg is not None:
178 if arg is not None:
180 self.children[arg] = self.children.get(arg, 0) + 1
179 self.children[arg] = self.children.get(arg, 0) + 1
181
180
182 for rev, wanted, arg in self.convertedorder:
181 for rev, wanted, arg in self.convertedorder:
183 parents = self.origparents[rev]
182 parents = self.origparents[rev]
184 if wanted:
183 if wanted:
185 self.mark_wanted(rev, parents)
184 self.mark_wanted(rev, parents)
186 else:
185 else:
187 self.mark_not_wanted(rev, arg)
186 self.mark_not_wanted(rev, arg)
188 self._discard(arg, *parents)
187 self._discard(arg, *parents)
189
188
190 return True
189 return True
191
190
192 def getheads(self):
191 def getheads(self):
193 return self.base.getheads()
192 return self.base.getheads()
194
193
195 def getcommit(self, rev):
194 def getcommit(self, rev):
196 # We want to save a reference to the commit objects to be able
195 # We want to save a reference to the commit objects to be able
197 # to rewrite their parents later on.
196 # to rewrite their parents later on.
198 c = self.commits[rev] = self.base.getcommit(rev)
197 c = self.commits[rev] = self.base.getcommit(rev)
199 for p in c.parents:
198 for p in c.parents:
200 self.children[p] = self.children.get(p, 0) + 1
199 self.children[p] = self.children.get(p, 0) + 1
201 return c
200 return c
202
201
203 def _discard(self, *revs):
202 def _discard(self, *revs):
204 for r in revs:
203 for r in revs:
205 if r is None:
204 if r is None:
206 continue
205 continue
207 self.seenchildren[r] = self.seenchildren.get(r, 0) + 1
206 self.seenchildren[r] = self.seenchildren.get(r, 0) + 1
208 if self.seenchildren[r] == self.children[r]:
207 if self.seenchildren[r] == self.children[r]:
209 del self.wantedancestors[r]
208 del self.wantedancestors[r]
210 del self.parentmap[r]
209 del self.parentmap[r]
211 del self.seenchildren[r]
210 del self.seenchildren[r]
212 if self._rebuilt:
211 if self._rebuilt:
213 del self.children[r]
212 del self.children[r]
214
213
215 def wanted(self, rev, i):
214 def wanted(self, rev, i):
216 # Return True if we're directly interested in rev.
215 # Return True if we're directly interested in rev.
217 #
216 #
218 # i is an index selecting one of the parents of rev (if rev
217 # i is an index selecting one of the parents of rev (if rev
219 # has no parents, i is None). getchangedfiles will give us
218 # has no parents, i is None). getchangedfiles will give us
220 # the list of files that are different in rev and in the parent
219 # the list of files that are different in rev and in the parent
221 # indicated by i. If we're interested in any of these files,
220 # indicated by i. If we're interested in any of these files,
222 # we're interested in rev.
221 # we're interested in rev.
223 try:
222 try:
224 files = self.base.getchangedfiles(rev, i)
223 files = self.base.getchangedfiles(rev, i)
225 except NotImplementedError:
224 except NotImplementedError:
226 raise util.Abort(_("source repository doesn't support --filemap"))
225 raise util.Abort(_("source repository doesn't support --filemap"))
227 for f in files:
226 for f in files:
228 if self.filemapper(f):
227 if self.filemapper(f):
229 return True
228 return True
230 return False
229 return False
231
230
232 def mark_not_wanted(self, rev, p):
231 def mark_not_wanted(self, rev, p):
233 # Mark rev as not interesting and update data structures.
232 # Mark rev as not interesting and update data structures.
234
233
235 if p is None:
234 if p is None:
236 # A root revision. Use SKIPREV to indicate that it doesn't
235 # A root revision. Use SKIPREV to indicate that it doesn't
237 # map to any revision in the restricted graph. Put SKIPREV
236 # map to any revision in the restricted graph. Put SKIPREV
238 # in the set of wanted ancestors to simplify code elsewhere
237 # in the set of wanted ancestors to simplify code elsewhere
239 self.parentmap[rev] = SKIPREV
238 self.parentmap[rev] = SKIPREV
240 self.wantedancestors[rev] = set((SKIPREV,))
239 self.wantedancestors[rev] = set((SKIPREV,))
241 return
240 return
242
241
243 # Reuse the data from our parent.
242 # Reuse the data from our parent.
244 self.parentmap[rev] = self.parentmap[p]
243 self.parentmap[rev] = self.parentmap[p]
245 self.wantedancestors[rev] = self.wantedancestors[p]
244 self.wantedancestors[rev] = self.wantedancestors[p]
246
245
247 def mark_wanted(self, rev, parents):
246 def mark_wanted(self, rev, parents):
248 # Mark rev ss wanted and update data structures.
247 # Mark rev ss wanted and update data structures.
249
248
250 # rev will be in the restricted graph, so children of rev in
249 # rev will be in the restricted graph, so children of rev in
251 # the original graph should still have rev as a parent in the
250 # the original graph should still have rev as a parent in the
252 # restricted graph.
251 # restricted graph.
253 self.parentmap[rev] = rev
252 self.parentmap[rev] = rev
254
253
255 # The set of wanted ancestors of rev is the union of the sets
254 # The set of wanted ancestors of rev is the union of the sets
256 # of wanted ancestors of its parents. Plus rev itself.
255 # of wanted ancestors of its parents. Plus rev itself.
257 wrev = set()
256 wrev = set()
258 for p in parents:
257 for p in parents:
259 wrev.update(self.wantedancestors[p])
258 wrev.update(self.wantedancestors[p])
260 wrev.add(rev)
259 wrev.add(rev)
261 self.wantedancestors[rev] = wrev
260 self.wantedancestors[rev] = wrev
262
261
263 def getchanges(self, rev):
262 def getchanges(self, rev):
264 parents = self.commits[rev].parents
263 parents = self.commits[rev].parents
265 if len(parents) > 1:
264 if len(parents) > 1:
266 self.rebuild()
265 self.rebuild()
267
266
268 # To decide whether we're interested in rev we:
267 # To decide whether we're interested in rev we:
269 #
268 #
270 # - calculate what parents rev will have if it turns out we're
269 # - calculate what parents rev will have if it turns out we're
271 # interested in it. If it's going to have more than 1 parent,
270 # interested in it. If it's going to have more than 1 parent,
272 # we're interested in it.
271 # we're interested in it.
273 #
272 #
274 # - otherwise, we'll compare it with the single parent we found.
273 # - otherwise, we'll compare it with the single parent we found.
275 # If any of the files we're interested in is different in the
274 # If any of the files we're interested in is different in the
276 # the two revisions, we're interested in rev.
275 # the two revisions, we're interested in rev.
277
276
278 # A parent p is interesting if its mapped version (self.parentmap[p]):
277 # A parent p is interesting if its mapped version (self.parentmap[p]):
279 # - is not SKIPREV
278 # - is not SKIPREV
280 # - is still not in the list of parents (we don't want duplicates)
279 # - is still not in the list of parents (we don't want duplicates)
281 # - is not an ancestor of the mapped versions of the other parents
280 # - is not an ancestor of the mapped versions of the other parents
282 mparents = []
281 mparents = []
283 wp = None
282 wp = None
284 for i, p1 in enumerate(parents):
283 for i, p1 in enumerate(parents):
285 mp1 = self.parentmap[p1]
284 mp1 = self.parentmap[p1]
286 if mp1 == SKIPREV or mp1 in mparents:
285 if mp1 == SKIPREV or mp1 in mparents:
287 continue
286 continue
288 for p2 in parents:
287 for p2 in parents:
289 if p1 == p2 or mp1 == self.parentmap[p2]:
288 if p1 == p2 or mp1 == self.parentmap[p2]:
290 continue
289 continue
291 if mp1 in self.wantedancestors[p2]:
290 if mp1 in self.wantedancestors[p2]:
292 break
291 break
293 else:
292 else:
294 mparents.append(mp1)
293 mparents.append(mp1)
295 wp = i
294 wp = i
296
295
297 if wp is None and parents:
296 if wp is None and parents:
298 wp = 0
297 wp = 0
299
298
300 self.origparents[rev] = parents
299 self.origparents[rev] = parents
301
300
302 if len(mparents) < 2 and not self.wanted(rev, wp):
301 if len(mparents) < 2 and not self.wanted(rev, wp):
303 # We don't want this revision.
302 # We don't want this revision.
304 # Update our state and tell the convert process to map this
303 # Update our state and tell the convert process to map this
305 # revision to the same revision its parent as mapped to.
304 # revision to the same revision its parent as mapped to.
306 p = None
305 p = None
307 if parents:
306 if parents:
308 p = parents[wp]
307 p = parents[wp]
309 self.mark_not_wanted(rev, p)
308 self.mark_not_wanted(rev, p)
310 self.convertedorder.append((rev, False, p))
309 self.convertedorder.append((rev, False, p))
311 self._discard(*parents)
310 self._discard(*parents)
312 return self.parentmap[rev]
311 return self.parentmap[rev]
313
312
314 # We want this revision.
313 # We want this revision.
315 # Rewrite the parents of the commit object
314 # Rewrite the parents of the commit object
316 self.commits[rev].parents = mparents
315 self.commits[rev].parents = mparents
317 self.mark_wanted(rev, parents)
316 self.mark_wanted(rev, parents)
318 self.convertedorder.append((rev, True, None))
317 self.convertedorder.append((rev, True, None))
319 self._discard(*parents)
318 self._discard(*parents)
320
319
321 # Get the real changes and do the filtering/mapping.
320 # Get the real changes and do the filtering/mapping. To be
322 # To be able to get the files later on in getfile and getmode,
321 # able to get the files later on in getfile, we hide the
323 # we hide the original filename in the rev part of the return
322 # original filename in the rev part of the return value.
324 # value.
325 changes, copies = self.base.getchanges(rev)
323 changes, copies = self.base.getchanges(rev)
326 newnames = {}
324 newnames = {}
327 files = []
325 files = []
328 for f, r in changes:
326 for f, r in changes:
329 newf = self.filemapper(f)
327 newf = self.filemapper(f)
330 if newf:
328 if newf:
331 files.append((newf, (f, r)))
329 files.append((newf, (f, r)))
332 newnames[f] = newf
330 newnames[f] = newf
333
331
334 ncopies = {}
332 ncopies = {}
335 for c in copies:
333 for c in copies:
336 newc = self.filemapper(c)
334 newc = self.filemapper(c)
337 if newc:
335 if newc:
338 newsource = self.filemapper(copies[c])
336 newsource = self.filemapper(copies[c])
339 if newsource:
337 if newsource:
340 ncopies[newc] = newsource
338 ncopies[newc] = newsource
341
339
342 return files, ncopies
340 return files, ncopies
343
341
344 def getfile(self, name, rev):
342 def getfile(self, name, rev):
345 realname, realrev = rev
343 realname, realrev = rev
346 return self.base.getfile(realname, realrev)
344 return self.base.getfile(realname, realrev)
347
345
348 def getmode(self, name, rev):
349 realname, realrev = rev
350 return self.base.getmode(realname, realrev)
351
352 def gettags(self):
346 def gettags(self):
353 return self.base.gettags()
347 return self.base.gettags()
354
348
355 def hasnativeorder(self):
349 def hasnativeorder(self):
356 return self.base.hasnativeorder()
350 return self.base.hasnativeorder()
357
351
358 def lookuprev(self, rev):
352 def lookuprev(self, rev):
359 return self.base.lookuprev(rev)
353 return self.base.lookuprev(rev)
@@ -1,170 +1,169 b''
1 # git.py - git support for the convert extension
1 # git.py - git support for the convert extension
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import os
8 import os
9 from mercurial import util
9 from mercurial import util
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11
11
12 from common import NoRepo, commit, converter_source, checktool
12 from common import NoRepo, commit, converter_source, checktool
13
13
14 class convert_git(converter_source):
14 class convert_git(converter_source):
15 # Windows does not support GIT_DIR= construct while other systems
15 # Windows does not support GIT_DIR= construct while other systems
16 # cannot remove environment variable. Just assume none have
16 # cannot remove environment variable. Just assume none have
17 # both issues.
17 # both issues.
18 if hasattr(os, 'unsetenv'):
18 if hasattr(os, 'unsetenv'):
19 def gitopen(self, s):
19 def gitopen(self, s):
20 prevgitdir = os.environ.get('GIT_DIR')
20 prevgitdir = os.environ.get('GIT_DIR')
21 os.environ['GIT_DIR'] = self.path
21 os.environ['GIT_DIR'] = self.path
22 try:
22 try:
23 return util.popen(s, 'rb')
23 return util.popen(s, 'rb')
24 finally:
24 finally:
25 if prevgitdir is None:
25 if prevgitdir is None:
26 del os.environ['GIT_DIR']
26 del os.environ['GIT_DIR']
27 else:
27 else:
28 os.environ['GIT_DIR'] = prevgitdir
28 os.environ['GIT_DIR'] = prevgitdir
29 else:
29 else:
30 def gitopen(self, s):
30 def gitopen(self, s):
31 return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
31 return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
32
32
33 def gitread(self, s):
33 def gitread(self, s):
34 fh = self.gitopen(s)
34 fh = self.gitopen(s)
35 data = fh.read()
35 data = fh.read()
36 return data, fh.close()
36 return data, fh.close()
37
37
38 def __init__(self, ui, path, rev=None):
38 def __init__(self, ui, path, rev=None):
39 super(convert_git, self).__init__(ui, path, rev=rev)
39 super(convert_git, self).__init__(ui, path, rev=rev)
40
40
41 if os.path.isdir(path + "/.git"):
41 if os.path.isdir(path + "/.git"):
42 path += "/.git"
42 path += "/.git"
43 if not os.path.exists(path + "/objects"):
43 if not os.path.exists(path + "/objects"):
44 raise NoRepo(_("%s does not look like a Git repository") % path)
44 raise NoRepo(_("%s does not look like a Git repository") % path)
45
45
46 checktool('git', 'git')
46 checktool('git', 'git')
47
47
48 self.path = path
48 self.path = path
49
49
50 def getheads(self):
50 def getheads(self):
51 if not self.rev:
51 if not self.rev:
52 heads, ret = self.gitread('git rev-parse --branches --remotes')
52 heads, ret = self.gitread('git rev-parse --branches --remotes')
53 heads = heads.splitlines()
53 heads = heads.splitlines()
54 else:
54 else:
55 heads, ret = self.gitread("git rev-parse --verify %s" % self.rev)
55 heads, ret = self.gitread("git rev-parse --verify %s" % self.rev)
56 heads = [heads[:-1]]
56 heads = [heads[:-1]]
57 if ret:
57 if ret:
58 raise util.Abort(_('cannot retrieve git heads'))
58 raise util.Abort(_('cannot retrieve git heads'))
59 return heads
59 return heads
60
60
61 def catfile(self, rev, type):
61 def catfile(self, rev, type):
62 if rev == "0" * 40:
62 if rev == "0" * 40:
63 raise IOError()
63 raise IOError()
64 data, ret = self.gitread("git cat-file %s %s" % (type, rev))
64 data, ret = self.gitread("git cat-file %s %s" % (type, rev))
65 if ret:
65 if ret:
66 raise util.Abort(_('cannot read %r object at %s') % (type, rev))
66 raise util.Abort(_('cannot read %r object at %s') % (type, rev))
67 return data
67 return data
68
68
69 def getfile(self, name, rev):
69 def getfile(self, name, rev):
70 return self.catfile(rev, "blob")
70 data = self.catfile(rev, "blob")
71
71 mode = self.modecache[(name, rev)]
72 def getmode(self, name, rev):
72 return data, mode
73 return self.modecache[(name, rev)]
74
73
75 def getchanges(self, version):
74 def getchanges(self, version):
76 self.modecache = {}
75 self.modecache = {}
77 fh = self.gitopen("git diff-tree -z --root -m -r %s" % version)
76 fh = self.gitopen("git diff-tree -z --root -m -r %s" % version)
78 changes = []
77 changes = []
79 seen = set()
78 seen = set()
80 entry = None
79 entry = None
81 for l in fh.read().split('\x00'):
80 for l in fh.read().split('\x00'):
82 if not entry:
81 if not entry:
83 if not l.startswith(':'):
82 if not l.startswith(':'):
84 continue
83 continue
85 entry = l
84 entry = l
86 continue
85 continue
87 f = l
86 f = l
88 if f not in seen:
87 if f not in seen:
89 seen.add(f)
88 seen.add(f)
90 entry = entry.split()
89 entry = entry.split()
91 h = entry[3]
90 h = entry[3]
92 p = (entry[1] == "100755")
91 p = (entry[1] == "100755")
93 s = (entry[1] == "120000")
92 s = (entry[1] == "120000")
94 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
93 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
95 changes.append((f, h))
94 changes.append((f, h))
96 entry = None
95 entry = None
97 if fh.close():
96 if fh.close():
98 raise util.Abort(_('cannot read changes in %s') % version)
97 raise util.Abort(_('cannot read changes in %s') % version)
99 return (changes, {})
98 return (changes, {})
100
99
101 def getcommit(self, version):
100 def getcommit(self, version):
102 c = self.catfile(version, "commit") # read the commit hash
101 c = self.catfile(version, "commit") # read the commit hash
103 end = c.find("\n\n")
102 end = c.find("\n\n")
104 message = c[end + 2:]
103 message = c[end + 2:]
105 message = self.recode(message)
104 message = self.recode(message)
106 l = c[:end].splitlines()
105 l = c[:end].splitlines()
107 parents = []
106 parents = []
108 author = committer = None
107 author = committer = None
109 for e in l[1:]:
108 for e in l[1:]:
110 n, v = e.split(" ", 1)
109 n, v = e.split(" ", 1)
111 if n == "author":
110 if n == "author":
112 p = v.split()
111 p = v.split()
113 tm, tz = p[-2:]
112 tm, tz = p[-2:]
114 author = " ".join(p[:-2])
113 author = " ".join(p[:-2])
115 if author[0] == "<": author = author[1:-1]
114 if author[0] == "<": author = author[1:-1]
116 author = self.recode(author)
115 author = self.recode(author)
117 if n == "committer":
116 if n == "committer":
118 p = v.split()
117 p = v.split()
119 tm, tz = p[-2:]
118 tm, tz = p[-2:]
120 committer = " ".join(p[:-2])
119 committer = " ".join(p[:-2])
121 if committer[0] == "<": committer = committer[1:-1]
120 if committer[0] == "<": committer = committer[1:-1]
122 committer = self.recode(committer)
121 committer = self.recode(committer)
123 if n == "parent":
122 if n == "parent":
124 parents.append(v)
123 parents.append(v)
125
124
126 if committer and committer != author:
125 if committer and committer != author:
127 message += "\ncommitter: %s\n" % committer
126 message += "\ncommitter: %s\n" % committer
128 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
127 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
129 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
128 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
130 date = tm + " " + str(tz)
129 date = tm + " " + str(tz)
131
130
132 c = commit(parents=parents, date=date, author=author, desc=message,
131 c = commit(parents=parents, date=date, author=author, desc=message,
133 rev=version)
132 rev=version)
134 return c
133 return c
135
134
136 def gettags(self):
135 def gettags(self):
137 tags = {}
136 tags = {}
138 fh = self.gitopen('git ls-remote --tags "%s"' % self.path)
137 fh = self.gitopen('git ls-remote --tags "%s"' % self.path)
139 prefix = 'refs/tags/'
138 prefix = 'refs/tags/'
140 for line in fh:
139 for line in fh:
141 line = line.strip()
140 line = line.strip()
142 if not line.endswith("^{}"):
141 if not line.endswith("^{}"):
143 continue
142 continue
144 node, tag = line.split(None, 1)
143 node, tag = line.split(None, 1)
145 if not tag.startswith(prefix):
144 if not tag.startswith(prefix):
146 continue
145 continue
147 tag = tag[len(prefix):-3]
146 tag = tag[len(prefix):-3]
148 tags[tag] = node
147 tags[tag] = node
149 if fh.close():
148 if fh.close():
150 raise util.Abort(_('cannot read tags from %s') % self.path)
149 raise util.Abort(_('cannot read tags from %s') % self.path)
151
150
152 return tags
151 return tags
153
152
154 def getchangedfiles(self, version, i):
153 def getchangedfiles(self, version, i):
155 changes = []
154 changes = []
156 if i is None:
155 if i is None:
157 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
156 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
158 for l in fh:
157 for l in fh:
159 if "\t" not in l:
158 if "\t" not in l:
160 continue
159 continue
161 m, f = l[:-1].split("\t")
160 m, f = l[:-1].split("\t")
162 changes.append(f)
161 changes.append(f)
163 else:
162 else:
164 fh = self.gitopen('git diff-tree --name-only --root -r %s "%s^%s" --'
163 fh = self.gitopen('git diff-tree --name-only --root -r %s "%s^%s" --'
165 % (version, version, i + 1))
164 % (version, version, i + 1))
166 changes = [f.rstrip('\n') for f in fh]
165 changes = [f.rstrip('\n') for f in fh]
167 if fh.close():
166 if fh.close():
168 raise util.Abort(_('cannot read changes in %s') % version)
167 raise util.Abort(_('cannot read changes in %s') % version)
169
168
170 return changes
169 return changes
@@ -1,346 +1,338 b''
1 # gnuarch.py - GNU Arch support for the convert extension
1 # gnuarch.py - GNU Arch support for the convert extension
2 #
2 #
3 # Copyright 2008, 2009 Aleix Conchillo Flaque <aleix@member.fsf.org>
3 # Copyright 2008, 2009 Aleix Conchillo Flaque <aleix@member.fsf.org>
4 # and others
4 # and others
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from common import NoRepo, commandline, commit, converter_source
9 from common import NoRepo, commandline, commit, converter_source
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import util
11 from mercurial import util
12 import os, shutil, tempfile, stat, locale
12 import os, shutil, tempfile, stat, locale
13 from email.Parser import Parser
13 from email.Parser import Parser
14
14
15 class gnuarch_source(converter_source, commandline):
15 class gnuarch_source(converter_source, commandline):
16
16
17 class gnuarch_rev(object):
17 class gnuarch_rev(object):
18 def __init__(self, rev):
18 def __init__(self, rev):
19 self.rev = rev
19 self.rev = rev
20 self.summary = ''
20 self.summary = ''
21 self.date = None
21 self.date = None
22 self.author = ''
22 self.author = ''
23 self.continuationof = None
23 self.continuationof = None
24 self.add_files = []
24 self.add_files = []
25 self.mod_files = []
25 self.mod_files = []
26 self.del_files = []
26 self.del_files = []
27 self.ren_files = {}
27 self.ren_files = {}
28 self.ren_dirs = {}
28 self.ren_dirs = {}
29
29
30 def __init__(self, ui, path, rev=None):
30 def __init__(self, ui, path, rev=None):
31 super(gnuarch_source, self).__init__(ui, path, rev=rev)
31 super(gnuarch_source, self).__init__(ui, path, rev=rev)
32
32
33 if not os.path.exists(os.path.join(path, '{arch}')):
33 if not os.path.exists(os.path.join(path, '{arch}')):
34 raise NoRepo(_("%s does not look like a GNU Arch repository")
34 raise NoRepo(_("%s does not look like a GNU Arch repository")
35 % path)
35 % path)
36
36
37 # Could use checktool, but we want to check for baz or tla.
37 # Could use checktool, but we want to check for baz or tla.
38 self.execmd = None
38 self.execmd = None
39 if util.find_exe('baz'):
39 if util.find_exe('baz'):
40 self.execmd = 'baz'
40 self.execmd = 'baz'
41 else:
41 else:
42 if util.find_exe('tla'):
42 if util.find_exe('tla'):
43 self.execmd = 'tla'
43 self.execmd = 'tla'
44 else:
44 else:
45 raise util.Abort(_('cannot find a GNU Arch tool'))
45 raise util.Abort(_('cannot find a GNU Arch tool'))
46
46
47 commandline.__init__(self, ui, self.execmd)
47 commandline.__init__(self, ui, self.execmd)
48
48
49 self.path = os.path.realpath(path)
49 self.path = os.path.realpath(path)
50 self.tmppath = None
50 self.tmppath = None
51
51
52 self.treeversion = None
52 self.treeversion = None
53 self.lastrev = None
53 self.lastrev = None
54 self.changes = {}
54 self.changes = {}
55 self.parents = {}
55 self.parents = {}
56 self.tags = {}
56 self.tags = {}
57 self.modecache = {}
58 self.catlogparser = Parser()
57 self.catlogparser = Parser()
59 self.locale = locale.getpreferredencoding()
58 self.locale = locale.getpreferredencoding()
60 self.archives = []
59 self.archives = []
61
60
62 def before(self):
61 def before(self):
63 # Get registered archives
62 # Get registered archives
64 self.archives = [i.rstrip('\n')
63 self.archives = [i.rstrip('\n')
65 for i in self.runlines0('archives', '-n')]
64 for i in self.runlines0('archives', '-n')]
66
65
67 if self.execmd == 'tla':
66 if self.execmd == 'tla':
68 output = self.run0('tree-version', self.path)
67 output = self.run0('tree-version', self.path)
69 else:
68 else:
70 output = self.run0('tree-version', '-d', self.path)
69 output = self.run0('tree-version', '-d', self.path)
71 self.treeversion = output.strip()
70 self.treeversion = output.strip()
72
71
73 # Get name of temporary directory
72 # Get name of temporary directory
74 version = self.treeversion.split('/')
73 version = self.treeversion.split('/')
75 self.tmppath = os.path.join(tempfile.gettempdir(),
74 self.tmppath = os.path.join(tempfile.gettempdir(),
76 'hg-%s' % version[1])
75 'hg-%s' % version[1])
77
76
78 # Generate parents dictionary
77 # Generate parents dictionary
79 self.parents[None] = []
78 self.parents[None] = []
80 treeversion = self.treeversion
79 treeversion = self.treeversion
81 child = None
80 child = None
82 while treeversion:
81 while treeversion:
83 self.ui.status(_('analyzing tree version %s...\n') % treeversion)
82 self.ui.status(_('analyzing tree version %s...\n') % treeversion)
84
83
85 archive = treeversion.split('/')[0]
84 archive = treeversion.split('/')[0]
86 if archive not in self.archives:
85 if archive not in self.archives:
87 self.ui.status(_('tree analysis stopped because it points to '
86 self.ui.status(_('tree analysis stopped because it points to '
88 'an unregistered archive %s...\n') % archive)
87 'an unregistered archive %s...\n') % archive)
89 break
88 break
90
89
91 # Get the complete list of revisions for that tree version
90 # Get the complete list of revisions for that tree version
92 output, status = self.runlines('revisions', '-r', '-f', treeversion)
91 output, status = self.runlines('revisions', '-r', '-f', treeversion)
93 self.checkexit(status, 'failed retrieveing revisions for %s'
92 self.checkexit(status, 'failed retrieveing revisions for %s'
94 % treeversion)
93 % treeversion)
95
94
96 # No new iteration unless a revision has a continuation-of header
95 # No new iteration unless a revision has a continuation-of header
97 treeversion = None
96 treeversion = None
98
97
99 for l in output:
98 for l in output:
100 rev = l.strip()
99 rev = l.strip()
101 self.changes[rev] = self.gnuarch_rev(rev)
100 self.changes[rev] = self.gnuarch_rev(rev)
102 self.parents[rev] = []
101 self.parents[rev] = []
103
102
104 # Read author, date and summary
103 # Read author, date and summary
105 catlog, status = self.run('cat-log', '-d', self.path, rev)
104 catlog, status = self.run('cat-log', '-d', self.path, rev)
106 if status:
105 if status:
107 catlog = self.run0('cat-archive-log', rev)
106 catlog = self.run0('cat-archive-log', rev)
108 self._parsecatlog(catlog, rev)
107 self._parsecatlog(catlog, rev)
109
108
110 # Populate the parents map
109 # Populate the parents map
111 self.parents[child].append(rev)
110 self.parents[child].append(rev)
112
111
113 # Keep track of the current revision as the child of the next
112 # Keep track of the current revision as the child of the next
114 # revision scanned
113 # revision scanned
115 child = rev
114 child = rev
116
115
117 # Check if we have to follow the usual incremental history
116 # Check if we have to follow the usual incremental history
118 # or if we have to 'jump' to a different treeversion given
117 # or if we have to 'jump' to a different treeversion given
119 # by the continuation-of header.
118 # by the continuation-of header.
120 if self.changes[rev].continuationof:
119 if self.changes[rev].continuationof:
121 treeversion = '--'.join(
120 treeversion = '--'.join(
122 self.changes[rev].continuationof.split('--')[:-1])
121 self.changes[rev].continuationof.split('--')[:-1])
123 break
122 break
124
123
125 # If we reached a base-0 revision w/o any continuation-of
124 # If we reached a base-0 revision w/o any continuation-of
126 # header, it means the tree history ends here.
125 # header, it means the tree history ends here.
127 if rev[-6:] == 'base-0':
126 if rev[-6:] == 'base-0':
128 break
127 break
129
128
130 def after(self):
129 def after(self):
131 self.ui.debug('cleaning up %s\n' % self.tmppath)
130 self.ui.debug('cleaning up %s\n' % self.tmppath)
132 shutil.rmtree(self.tmppath, ignore_errors=True)
131 shutil.rmtree(self.tmppath, ignore_errors=True)
133
132
134 def getheads(self):
133 def getheads(self):
135 return self.parents[None]
134 return self.parents[None]
136
135
137 def getfile(self, name, rev):
136 def getfile(self, name, rev):
138 if rev != self.lastrev:
137 if rev != self.lastrev:
139 raise util.Abort(_('internal calling inconsistency'))
138 raise util.Abort(_('internal calling inconsistency'))
140
139
141 # Raise IOError if necessary (i.e. deleted files).
140 # Raise IOError if necessary (i.e. deleted files).
142 if not os.path.exists(os.path.join(self.tmppath, name)):
141 if not os.path.exists(os.path.join(self.tmppath, name)):
143 raise IOError
142 raise IOError
144
143
145 data, mode = self._getfile(name, rev)
144 return self._getfile(name, rev)
146 self.modecache[(name, rev)] = mode
147
148 return data
149
150 def getmode(self, name, rev):
151 return self.modecache[(name, rev)]
152
145
153 def getchanges(self, rev):
146 def getchanges(self, rev):
154 self.modecache = {}
155 self._update(rev)
147 self._update(rev)
156 changes = []
148 changes = []
157 copies = {}
149 copies = {}
158
150
159 for f in self.changes[rev].add_files:
151 for f in self.changes[rev].add_files:
160 changes.append((f, rev))
152 changes.append((f, rev))
161
153
162 for f in self.changes[rev].mod_files:
154 for f in self.changes[rev].mod_files:
163 changes.append((f, rev))
155 changes.append((f, rev))
164
156
165 for f in self.changes[rev].del_files:
157 for f in self.changes[rev].del_files:
166 changes.append((f, rev))
158 changes.append((f, rev))
167
159
168 for src in self.changes[rev].ren_files:
160 for src in self.changes[rev].ren_files:
169 to = self.changes[rev].ren_files[src]
161 to = self.changes[rev].ren_files[src]
170 changes.append((src, rev))
162 changes.append((src, rev))
171 changes.append((to, rev))
163 changes.append((to, rev))
172 copies[to] = src
164 copies[to] = src
173
165
174 for src in self.changes[rev].ren_dirs:
166 for src in self.changes[rev].ren_dirs:
175 to = self.changes[rev].ren_dirs[src]
167 to = self.changes[rev].ren_dirs[src]
176 chgs, cps = self._rendirchanges(src, to)
168 chgs, cps = self._rendirchanges(src, to)
177 changes += [(f, rev) for f in chgs]
169 changes += [(f, rev) for f in chgs]
178 copies.update(cps)
170 copies.update(cps)
179
171
180 self.lastrev = rev
172 self.lastrev = rev
181 return sorted(set(changes)), copies
173 return sorted(set(changes)), copies
182
174
183 def getcommit(self, rev):
175 def getcommit(self, rev):
184 changes = self.changes[rev]
176 changes = self.changes[rev]
185 return commit(author=changes.author, date=changes.date,
177 return commit(author=changes.author, date=changes.date,
186 desc=changes.summary, parents=self.parents[rev], rev=rev)
178 desc=changes.summary, parents=self.parents[rev], rev=rev)
187
179
188 def gettags(self):
180 def gettags(self):
189 return self.tags
181 return self.tags
190
182
191 def _execute(self, cmd, *args, **kwargs):
183 def _execute(self, cmd, *args, **kwargs):
192 cmdline = [self.execmd, cmd]
184 cmdline = [self.execmd, cmd]
193 cmdline += args
185 cmdline += args
194 cmdline = [util.shellquote(arg) for arg in cmdline]
186 cmdline = [util.shellquote(arg) for arg in cmdline]
195 cmdline += ['>', util.nulldev, '2>', util.nulldev]
187 cmdline += ['>', util.nulldev, '2>', util.nulldev]
196 cmdline = util.quotecommand(' '.join(cmdline))
188 cmdline = util.quotecommand(' '.join(cmdline))
197 self.ui.debug(cmdline, '\n')
189 self.ui.debug(cmdline, '\n')
198 return os.system(cmdline)
190 return os.system(cmdline)
199
191
200 def _update(self, rev):
192 def _update(self, rev):
201 self.ui.debug('applying revision %s...\n' % rev)
193 self.ui.debug('applying revision %s...\n' % rev)
202 changeset, status = self.runlines('replay', '-d', self.tmppath,
194 changeset, status = self.runlines('replay', '-d', self.tmppath,
203 rev)
195 rev)
204 if status:
196 if status:
205 # Something went wrong while merging (baz or tla
197 # Something went wrong while merging (baz or tla
206 # issue?), get latest revision and try from there
198 # issue?), get latest revision and try from there
207 shutil.rmtree(self.tmppath, ignore_errors=True)
199 shutil.rmtree(self.tmppath, ignore_errors=True)
208 self._obtainrevision(rev)
200 self._obtainrevision(rev)
209 else:
201 else:
210 old_rev = self.parents[rev][0]
202 old_rev = self.parents[rev][0]
211 self.ui.debug('computing changeset between %s and %s...\n'
203 self.ui.debug('computing changeset between %s and %s...\n'
212 % (old_rev, rev))
204 % (old_rev, rev))
213 self._parsechangeset(changeset, rev)
205 self._parsechangeset(changeset, rev)
214
206
215 def _getfile(self, name, rev):
207 def _getfile(self, name, rev):
216 mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
208 mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
217 if stat.S_ISLNK(mode):
209 if stat.S_ISLNK(mode):
218 data = os.readlink(os.path.join(self.tmppath, name))
210 data = os.readlink(os.path.join(self.tmppath, name))
219 mode = mode and 'l' or ''
211 mode = mode and 'l' or ''
220 else:
212 else:
221 data = open(os.path.join(self.tmppath, name), 'rb').read()
213 data = open(os.path.join(self.tmppath, name), 'rb').read()
222 mode = (mode & 0111) and 'x' or ''
214 mode = (mode & 0111) and 'x' or ''
223 return data, mode
215 return data, mode
224
216
225 def _exclude(self, name):
217 def _exclude(self, name):
226 exclude = ['{arch}', '.arch-ids', '.arch-inventory']
218 exclude = ['{arch}', '.arch-ids', '.arch-inventory']
227 for exc in exclude:
219 for exc in exclude:
228 if name.find(exc) != -1:
220 if name.find(exc) != -1:
229 return True
221 return True
230 return False
222 return False
231
223
232 def _readcontents(self, path):
224 def _readcontents(self, path):
233 files = []
225 files = []
234 contents = os.listdir(path)
226 contents = os.listdir(path)
235 while len(contents) > 0:
227 while len(contents) > 0:
236 c = contents.pop()
228 c = contents.pop()
237 p = os.path.join(path, c)
229 p = os.path.join(path, c)
238 # os.walk could be used, but here we avoid internal GNU
230 # os.walk could be used, but here we avoid internal GNU
239 # Arch files and directories, thus saving a lot time.
231 # Arch files and directories, thus saving a lot time.
240 if not self._exclude(p):
232 if not self._exclude(p):
241 if os.path.isdir(p):
233 if os.path.isdir(p):
242 contents += [os.path.join(c, f) for f in os.listdir(p)]
234 contents += [os.path.join(c, f) for f in os.listdir(p)]
243 else:
235 else:
244 files.append(c)
236 files.append(c)
245 return files
237 return files
246
238
247 def _rendirchanges(self, src, dest):
239 def _rendirchanges(self, src, dest):
248 changes = []
240 changes = []
249 copies = {}
241 copies = {}
250 files = self._readcontents(os.path.join(self.tmppath, dest))
242 files = self._readcontents(os.path.join(self.tmppath, dest))
251 for f in files:
243 for f in files:
252 s = os.path.join(src, f)
244 s = os.path.join(src, f)
253 d = os.path.join(dest, f)
245 d = os.path.join(dest, f)
254 changes.append(s)
246 changes.append(s)
255 changes.append(d)
247 changes.append(d)
256 copies[d] = s
248 copies[d] = s
257 return changes, copies
249 return changes, copies
258
250
259 def _obtainrevision(self, rev):
251 def _obtainrevision(self, rev):
260 self.ui.debug('obtaining revision %s...\n' % rev)
252 self.ui.debug('obtaining revision %s...\n' % rev)
261 output = self._execute('get', rev, self.tmppath)
253 output = self._execute('get', rev, self.tmppath)
262 self.checkexit(output)
254 self.checkexit(output)
263 self.ui.debug('analyzing revision %s...\n' % rev)
255 self.ui.debug('analyzing revision %s...\n' % rev)
264 files = self._readcontents(self.tmppath)
256 files = self._readcontents(self.tmppath)
265 self.changes[rev].add_files += files
257 self.changes[rev].add_files += files
266
258
267 def _stripbasepath(self, path):
259 def _stripbasepath(self, path):
268 if path.startswith('./'):
260 if path.startswith('./'):
269 return path[2:]
261 return path[2:]
270 return path
262 return path
271
263
272 def _parsecatlog(self, data, rev):
264 def _parsecatlog(self, data, rev):
273 try:
265 try:
274 catlog = self.catlogparser.parsestr(data)
266 catlog = self.catlogparser.parsestr(data)
275
267
276 # Commit date
268 # Commit date
277 self.changes[rev].date = util.datestr(
269 self.changes[rev].date = util.datestr(
278 util.strdate(catlog['Standard-date'],
270 util.strdate(catlog['Standard-date'],
279 '%Y-%m-%d %H:%M:%S'))
271 '%Y-%m-%d %H:%M:%S'))
280
272
281 # Commit author
273 # Commit author
282 self.changes[rev].author = self.recode(catlog['Creator'])
274 self.changes[rev].author = self.recode(catlog['Creator'])
283
275
284 # Commit description
276 # Commit description
285 self.changes[rev].summary = '\n\n'.join((catlog['Summary'],
277 self.changes[rev].summary = '\n\n'.join((catlog['Summary'],
286 catlog.get_payload()))
278 catlog.get_payload()))
287 self.changes[rev].summary = self.recode(self.changes[rev].summary)
279 self.changes[rev].summary = self.recode(self.changes[rev].summary)
288
280
289 # Commit revision origin when dealing with a branch or tag
281 # Commit revision origin when dealing with a branch or tag
290 if 'Continuation-of' in catlog:
282 if 'Continuation-of' in catlog:
291 self.changes[rev].continuationof = self.recode(
283 self.changes[rev].continuationof = self.recode(
292 catlog['Continuation-of'])
284 catlog['Continuation-of'])
293 except Exception:
285 except Exception:
294 raise util.Abort(_('could not parse cat-log of %s') % rev)
286 raise util.Abort(_('could not parse cat-log of %s') % rev)
295
287
296 def _parsechangeset(self, data, rev):
288 def _parsechangeset(self, data, rev):
297 for l in data:
289 for l in data:
298 l = l.strip()
290 l = l.strip()
299 # Added file (ignore added directory)
291 # Added file (ignore added directory)
300 if l.startswith('A') and not l.startswith('A/'):
292 if l.startswith('A') and not l.startswith('A/'):
301 file = self._stripbasepath(l[1:].strip())
293 file = self._stripbasepath(l[1:].strip())
302 if not self._exclude(file):
294 if not self._exclude(file):
303 self.changes[rev].add_files.append(file)
295 self.changes[rev].add_files.append(file)
304 # Deleted file (ignore deleted directory)
296 # Deleted file (ignore deleted directory)
305 elif l.startswith('D') and not l.startswith('D/'):
297 elif l.startswith('D') and not l.startswith('D/'):
306 file = self._stripbasepath(l[1:].strip())
298 file = self._stripbasepath(l[1:].strip())
307 if not self._exclude(file):
299 if not self._exclude(file):
308 self.changes[rev].del_files.append(file)
300 self.changes[rev].del_files.append(file)
309 # Modified binary file
301 # Modified binary file
310 elif l.startswith('Mb'):
302 elif l.startswith('Mb'):
311 file = self._stripbasepath(l[2:].strip())
303 file = self._stripbasepath(l[2:].strip())
312 if not self._exclude(file):
304 if not self._exclude(file):
313 self.changes[rev].mod_files.append(file)
305 self.changes[rev].mod_files.append(file)
314 # Modified link
306 # Modified link
315 elif l.startswith('M->'):
307 elif l.startswith('M->'):
316 file = self._stripbasepath(l[3:].strip())
308 file = self._stripbasepath(l[3:].strip())
317 if not self._exclude(file):
309 if not self._exclude(file):
318 self.changes[rev].mod_files.append(file)
310 self.changes[rev].mod_files.append(file)
319 # Modified file
311 # Modified file
320 elif l.startswith('M'):
312 elif l.startswith('M'):
321 file = self._stripbasepath(l[1:].strip())
313 file = self._stripbasepath(l[1:].strip())
322 if not self._exclude(file):
314 if not self._exclude(file):
323 self.changes[rev].mod_files.append(file)
315 self.changes[rev].mod_files.append(file)
324 # Renamed file (or link)
316 # Renamed file (or link)
325 elif l.startswith('=>'):
317 elif l.startswith('=>'):
326 files = l[2:].strip().split(' ')
318 files = l[2:].strip().split(' ')
327 if len(files) == 1:
319 if len(files) == 1:
328 files = l[2:].strip().split('\t')
320 files = l[2:].strip().split('\t')
329 src = self._stripbasepath(files[0])
321 src = self._stripbasepath(files[0])
330 dst = self._stripbasepath(files[1])
322 dst = self._stripbasepath(files[1])
331 if not self._exclude(src) and not self._exclude(dst):
323 if not self._exclude(src) and not self._exclude(dst):
332 self.changes[rev].ren_files[src] = dst
324 self.changes[rev].ren_files[src] = dst
333 # Conversion from file to link or from link to file (modified)
325 # Conversion from file to link or from link to file (modified)
334 elif l.startswith('ch'):
326 elif l.startswith('ch'):
335 file = self._stripbasepath(l[2:].strip())
327 file = self._stripbasepath(l[2:].strip())
336 if not self._exclude(file):
328 if not self._exclude(file):
337 self.changes[rev].mod_files.append(file)
329 self.changes[rev].mod_files.append(file)
338 # Renamed directory
330 # Renamed directory
339 elif l.startswith('/>'):
331 elif l.startswith('/>'):
340 dirs = l[2:].strip().split(' ')
332 dirs = l[2:].strip().split(' ')
341 if len(dirs) == 1:
333 if len(dirs) == 1:
342 dirs = l[2:].strip().split('\t')
334 dirs = l[2:].strip().split('\t')
343 src = self._stripbasepath(dirs[0])
335 src = self._stripbasepath(dirs[0])
344 dst = self._stripbasepath(dirs[1])
336 dst = self._stripbasepath(dirs[1])
345 if not self._exclude(src) and not self._exclude(dst):
337 if not self._exclude(src) and not self._exclude(dst):
346 self.changes[rev].ren_dirs[src] = dst
338 self.changes[rev].ren_dirs[src] = dst
@@ -1,377 +1,375 b''
1 # hg.py - hg backend for convert extension
1 # hg.py - hg backend for convert extension
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 # Notes for hg->hg conversion:
8 # Notes for hg->hg conversion:
9 #
9 #
10 # * Old versions of Mercurial didn't trim the whitespace from the ends
10 # * Old versions of Mercurial didn't trim the whitespace from the ends
11 # of commit messages, but new versions do. Changesets created by
11 # of commit messages, but new versions do. Changesets created by
12 # those older versions, then converted, may thus have different
12 # those older versions, then converted, may thus have different
13 # hashes for changesets that are otherwise identical.
13 # hashes for changesets that are otherwise identical.
14 #
14 #
15 # * Using "--config convert.hg.saverev=true" will make the source
15 # * Using "--config convert.hg.saverev=true" will make the source
16 # identifier to be stored in the converted revision. This will cause
16 # identifier to be stored in the converted revision. This will cause
17 # the converted revision to have a different identity than the
17 # the converted revision to have a different identity than the
18 # source.
18 # source.
19
19
20
20
21 import os, time, cStringIO
21 import os, time, cStringIO
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 from mercurial.node import bin, hex, nullid
23 from mercurial.node import bin, hex, nullid
24 from mercurial import hg, util, context, error
24 from mercurial import hg, util, context, error
25
25
26 from common import NoRepo, commit, converter_source, converter_sink
26 from common import NoRepo, commit, converter_source, converter_sink
27
27
28 class mercurial_sink(converter_sink):
28 class mercurial_sink(converter_sink):
29 def __init__(self, ui, path):
29 def __init__(self, ui, path):
30 converter_sink.__init__(self, ui, path)
30 converter_sink.__init__(self, ui, path)
31 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
31 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
32 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
32 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
33 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
33 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
34 self.lastbranch = None
34 self.lastbranch = None
35 if os.path.isdir(path) and len(os.listdir(path)) > 0:
35 if os.path.isdir(path) and len(os.listdir(path)) > 0:
36 try:
36 try:
37 self.repo = hg.repository(self.ui, path)
37 self.repo = hg.repository(self.ui, path)
38 if not self.repo.local():
38 if not self.repo.local():
39 raise NoRepo(_('%s is not a local Mercurial repository')
39 raise NoRepo(_('%s is not a local Mercurial repository')
40 % path)
40 % path)
41 except error.RepoError, err:
41 except error.RepoError, err:
42 ui.traceback()
42 ui.traceback()
43 raise NoRepo(err.args[0])
43 raise NoRepo(err.args[0])
44 else:
44 else:
45 try:
45 try:
46 ui.status(_('initializing destination %s repository\n') % path)
46 ui.status(_('initializing destination %s repository\n') % path)
47 self.repo = hg.repository(self.ui, path, create=True)
47 self.repo = hg.repository(self.ui, path, create=True)
48 if not self.repo.local():
48 if not self.repo.local():
49 raise NoRepo(_('%s is not a local Mercurial repository')
49 raise NoRepo(_('%s is not a local Mercurial repository')
50 % path)
50 % path)
51 self.created.append(path)
51 self.created.append(path)
52 except error.RepoError:
52 except error.RepoError:
53 ui.traceback()
53 ui.traceback()
54 raise NoRepo(_("could not create hg repository %s as sink")
54 raise NoRepo(_("could not create hg repository %s as sink")
55 % path)
55 % path)
56 self.lock = None
56 self.lock = None
57 self.wlock = None
57 self.wlock = None
58 self.filemapmode = False
58 self.filemapmode = False
59
59
60 def before(self):
60 def before(self):
61 self.ui.debug('run hg sink pre-conversion action\n')
61 self.ui.debug('run hg sink pre-conversion action\n')
62 self.wlock = self.repo.wlock()
62 self.wlock = self.repo.wlock()
63 self.lock = self.repo.lock()
63 self.lock = self.repo.lock()
64
64
65 def after(self):
65 def after(self):
66 self.ui.debug('run hg sink post-conversion action\n')
66 self.ui.debug('run hg sink post-conversion action\n')
67 if self.lock:
67 if self.lock:
68 self.lock.release()
68 self.lock.release()
69 if self.wlock:
69 if self.wlock:
70 self.wlock.release()
70 self.wlock.release()
71
71
72 def revmapfile(self):
72 def revmapfile(self):
73 return os.path.join(self.path, ".hg", "shamap")
73 return os.path.join(self.path, ".hg", "shamap")
74
74
75 def authorfile(self):
75 def authorfile(self):
76 return os.path.join(self.path, ".hg", "authormap")
76 return os.path.join(self.path, ".hg", "authormap")
77
77
78 def getheads(self):
78 def getheads(self):
79 h = self.repo.changelog.heads()
79 h = self.repo.changelog.heads()
80 return [hex(x) for x in h]
80 return [hex(x) for x in h]
81
81
82 def setbranch(self, branch, pbranches):
82 def setbranch(self, branch, pbranches):
83 if not self.clonebranches:
83 if not self.clonebranches:
84 return
84 return
85
85
86 setbranch = (branch != self.lastbranch)
86 setbranch = (branch != self.lastbranch)
87 self.lastbranch = branch
87 self.lastbranch = branch
88 if not branch:
88 if not branch:
89 branch = 'default'
89 branch = 'default'
90 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
90 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
91 pbranch = pbranches and pbranches[0][1] or 'default'
91 pbranch = pbranches and pbranches[0][1] or 'default'
92
92
93 branchpath = os.path.join(self.path, branch)
93 branchpath = os.path.join(self.path, branch)
94 if setbranch:
94 if setbranch:
95 self.after()
95 self.after()
96 try:
96 try:
97 self.repo = hg.repository(self.ui, branchpath)
97 self.repo = hg.repository(self.ui, branchpath)
98 except:
98 except:
99 self.repo = hg.repository(self.ui, branchpath, create=True)
99 self.repo = hg.repository(self.ui, branchpath, create=True)
100 self.before()
100 self.before()
101
101
102 # pbranches may bring revisions from other branches (merge parents)
102 # pbranches may bring revisions from other branches (merge parents)
103 # Make sure we have them, or pull them.
103 # Make sure we have them, or pull them.
104 missings = {}
104 missings = {}
105 for b in pbranches:
105 for b in pbranches:
106 try:
106 try:
107 self.repo.lookup(b[0])
107 self.repo.lookup(b[0])
108 except:
108 except:
109 missings.setdefault(b[1], []).append(b[0])
109 missings.setdefault(b[1], []).append(b[0])
110
110
111 if missings:
111 if missings:
112 self.after()
112 self.after()
113 for pbranch, heads in missings.iteritems():
113 for pbranch, heads in missings.iteritems():
114 pbranchpath = os.path.join(self.path, pbranch)
114 pbranchpath = os.path.join(self.path, pbranch)
115 prepo = hg.repository(self.ui, pbranchpath)
115 prepo = hg.repository(self.ui, pbranchpath)
116 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
116 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
117 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
117 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
118 self.before()
118 self.before()
119
119
120 def _rewritetags(self, source, revmap, data):
120 def _rewritetags(self, source, revmap, data):
121 fp = cStringIO.StringIO()
121 fp = cStringIO.StringIO()
122 for line in data.splitlines():
122 for line in data.splitlines():
123 s = line.split(' ', 1)
123 s = line.split(' ', 1)
124 if len(s) != 2:
124 if len(s) != 2:
125 continue
125 continue
126 revid = revmap.get(source.lookuprev(s[0]))
126 revid = revmap.get(source.lookuprev(s[0]))
127 if not revid:
127 if not revid:
128 continue
128 continue
129 fp.write('%s %s\n' % (revid, s[1]))
129 fp.write('%s %s\n' % (revid, s[1]))
130 return fp.getvalue()
130 return fp.getvalue()
131
131
132 def putcommit(self, files, copies, parents, commit, source, revmap):
132 def putcommit(self, files, copies, parents, commit, source, revmap):
133
133
134 files = dict(files)
134 files = dict(files)
135 def getfilectx(repo, memctx, f):
135 def getfilectx(repo, memctx, f):
136 v = files[f]
136 v = files[f]
137 data = source.getfile(f, v)
137 data, mode = source.getfile(f, v)
138 e = source.getmode(f, v)
139 if f == '.hgtags':
138 if f == '.hgtags':
140 data = self._rewritetags(source, revmap, data)
139 data = self._rewritetags(source, revmap, data)
141 return context.memfilectx(f, data, 'l' in e, 'x' in e, copies.get(f))
140 return context.memfilectx(f, data, 'l' in mode, 'x' in mode,
141 copies.get(f))
142
142
143 pl = []
143 pl = []
144 for p in parents:
144 for p in parents:
145 if p not in pl:
145 if p not in pl:
146 pl.append(p)
146 pl.append(p)
147 parents = pl
147 parents = pl
148 nparents = len(parents)
148 nparents = len(parents)
149 if self.filemapmode and nparents == 1:
149 if self.filemapmode and nparents == 1:
150 m1node = self.repo.changelog.read(bin(parents[0]))[0]
150 m1node = self.repo.changelog.read(bin(parents[0]))[0]
151 parent = parents[0]
151 parent = parents[0]
152
152
153 if len(parents) < 2:
153 if len(parents) < 2:
154 parents.append(nullid)
154 parents.append(nullid)
155 if len(parents) < 2:
155 if len(parents) < 2:
156 parents.append(nullid)
156 parents.append(nullid)
157 p2 = parents.pop(0)
157 p2 = parents.pop(0)
158
158
159 text = commit.desc
159 text = commit.desc
160 extra = commit.extra.copy()
160 extra = commit.extra.copy()
161 if self.branchnames and commit.branch:
161 if self.branchnames and commit.branch:
162 extra['branch'] = commit.branch
162 extra['branch'] = commit.branch
163 if commit.rev:
163 if commit.rev:
164 extra['convert_revision'] = commit.rev
164 extra['convert_revision'] = commit.rev
165
165
166 while parents:
166 while parents:
167 p1 = p2
167 p1 = p2
168 p2 = parents.pop(0)
168 p2 = parents.pop(0)
169 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(),
169 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(),
170 getfilectx, commit.author, commit.date, extra)
170 getfilectx, commit.author, commit.date, extra)
171 self.repo.commitctx(ctx)
171 self.repo.commitctx(ctx)
172 text = "(octopus merge fixup)\n"
172 text = "(octopus merge fixup)\n"
173 p2 = hex(self.repo.changelog.tip())
173 p2 = hex(self.repo.changelog.tip())
174
174
175 if self.filemapmode and nparents == 1:
175 if self.filemapmode and nparents == 1:
176 man = self.repo.manifest
176 man = self.repo.manifest
177 mnode = self.repo.changelog.read(bin(p2))[0]
177 mnode = self.repo.changelog.read(bin(p2))[0]
178 if not man.cmp(m1node, man.revision(mnode)):
178 if not man.cmp(m1node, man.revision(mnode)):
179 self.ui.status(_("filtering out empty revision\n"))
179 self.ui.status(_("filtering out empty revision\n"))
180 self.repo.rollback()
180 self.repo.rollback()
181 return parent
181 return parent
182 return p2
182 return p2
183
183
184 def puttags(self, tags):
184 def puttags(self, tags):
185 try:
185 try:
186 parentctx = self.repo[self.tagsbranch]
186 parentctx = self.repo[self.tagsbranch]
187 tagparent = parentctx.node()
187 tagparent = parentctx.node()
188 except error.RepoError:
188 except error.RepoError:
189 parentctx = None
189 parentctx = None
190 tagparent = nullid
190 tagparent = nullid
191
191
192 try:
192 try:
193 oldlines = sorted(parentctx['.hgtags'].data().splitlines(True))
193 oldlines = sorted(parentctx['.hgtags'].data().splitlines(True))
194 except:
194 except:
195 oldlines = []
195 oldlines = []
196
196
197 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
197 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
198 if newlines == oldlines:
198 if newlines == oldlines:
199 return None, None
199 return None, None
200 data = "".join(newlines)
200 data = "".join(newlines)
201 def getfilectx(repo, memctx, f):
201 def getfilectx(repo, memctx, f):
202 return context.memfilectx(f, data, False, False, None)
202 return context.memfilectx(f, data, False, False, None)
203
203
204 self.ui.status(_("updating tags\n"))
204 self.ui.status(_("updating tags\n"))
205 date = "%s 0" % int(time.mktime(time.gmtime()))
205 date = "%s 0" % int(time.mktime(time.gmtime()))
206 extra = {'branch': self.tagsbranch}
206 extra = {'branch': self.tagsbranch}
207 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
207 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
208 [".hgtags"], getfilectx, "convert-repo", date,
208 [".hgtags"], getfilectx, "convert-repo", date,
209 extra)
209 extra)
210 self.repo.commitctx(ctx)
210 self.repo.commitctx(ctx)
211 return hex(self.repo.changelog.tip()), hex(tagparent)
211 return hex(self.repo.changelog.tip()), hex(tagparent)
212
212
213 def setfilemapmode(self, active):
213 def setfilemapmode(self, active):
214 self.filemapmode = active
214 self.filemapmode = active
215
215
216 class mercurial_source(converter_source):
216 class mercurial_source(converter_source):
217 def __init__(self, ui, path, rev=None):
217 def __init__(self, ui, path, rev=None):
218 converter_source.__init__(self, ui, path, rev)
218 converter_source.__init__(self, ui, path, rev)
219 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
219 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
220 self.ignored = set()
220 self.ignored = set()
221 self.saverev = ui.configbool('convert', 'hg.saverev', False)
221 self.saverev = ui.configbool('convert', 'hg.saverev', False)
222 try:
222 try:
223 self.repo = hg.repository(self.ui, path)
223 self.repo = hg.repository(self.ui, path)
224 # try to provoke an exception if this isn't really a hg
224 # try to provoke an exception if this isn't really a hg
225 # repo, but some other bogus compatible-looking url
225 # repo, but some other bogus compatible-looking url
226 if not self.repo.local():
226 if not self.repo.local():
227 raise error.RepoError()
227 raise error.RepoError()
228 except error.RepoError:
228 except error.RepoError:
229 ui.traceback()
229 ui.traceback()
230 raise NoRepo(_("%s is not a local Mercurial repository") % path)
230 raise NoRepo(_("%s is not a local Mercurial repository") % path)
231 self.lastrev = None
231 self.lastrev = None
232 self.lastctx = None
232 self.lastctx = None
233 self._changescache = None
233 self._changescache = None
234 self.convertfp = None
234 self.convertfp = None
235 # Restrict converted revisions to startrev descendants
235 # Restrict converted revisions to startrev descendants
236 startnode = ui.config('convert', 'hg.startrev')
236 startnode = ui.config('convert', 'hg.startrev')
237 if startnode is not None:
237 if startnode is not None:
238 try:
238 try:
239 startnode = self.repo.lookup(startnode)
239 startnode = self.repo.lookup(startnode)
240 except error.RepoError:
240 except error.RepoError:
241 raise util.Abort(_('%s is not a valid start revision')
241 raise util.Abort(_('%s is not a valid start revision')
242 % startnode)
242 % startnode)
243 startrev = self.repo.changelog.rev(startnode)
243 startrev = self.repo.changelog.rev(startnode)
244 children = {startnode: 1}
244 children = {startnode: 1}
245 for rev in self.repo.changelog.descendants(startrev):
245 for rev in self.repo.changelog.descendants(startrev):
246 children[self.repo.changelog.node(rev)] = 1
246 children[self.repo.changelog.node(rev)] = 1
247 self.keep = children.__contains__
247 self.keep = children.__contains__
248 else:
248 else:
249 self.keep = util.always
249 self.keep = util.always
250
250
251 def changectx(self, rev):
251 def changectx(self, rev):
252 if self.lastrev != rev:
252 if self.lastrev != rev:
253 self.lastctx = self.repo[rev]
253 self.lastctx = self.repo[rev]
254 self.lastrev = rev
254 self.lastrev = rev
255 return self.lastctx
255 return self.lastctx
256
256
257 def parents(self, ctx):
257 def parents(self, ctx):
258 return [p for p in ctx.parents() if p and self.keep(p.node())]
258 return [p for p in ctx.parents() if p and self.keep(p.node())]
259
259
260 def getheads(self):
260 def getheads(self):
261 if self.rev:
261 if self.rev:
262 heads = [self.repo[self.rev].node()]
262 heads = [self.repo[self.rev].node()]
263 else:
263 else:
264 heads = self.repo.heads()
264 heads = self.repo.heads()
265 return [hex(h) for h in heads if self.keep(h)]
265 return [hex(h) for h in heads if self.keep(h)]
266
266
267 def getfile(self, name, rev):
267 def getfile(self, name, rev):
268 try:
268 try:
269 return self.changectx(rev)[name].data()
269 fctx = self.changectx(rev)[name]
270 return fctx.data(), fctx.flags()
270 except error.LookupError, err:
271 except error.LookupError, err:
271 raise IOError(err)
272 raise IOError(err)
272
273
273 def getmode(self, name, rev):
274 return self.changectx(rev).manifest().flags(name)
275
276 def getchanges(self, rev):
274 def getchanges(self, rev):
277 ctx = self.changectx(rev)
275 ctx = self.changectx(rev)
278 parents = self.parents(ctx)
276 parents = self.parents(ctx)
279 if not parents:
277 if not parents:
280 files = sorted(ctx.manifest())
278 files = sorted(ctx.manifest())
281 if self.ignoreerrors:
279 if self.ignoreerrors:
282 # calling getcopies() is a simple way to detect missing
280 # calling getcopies() is a simple way to detect missing
283 # revlogs and populate self.ignored
281 # revlogs and populate self.ignored
284 self.getcopies(ctx, parents, files)
282 self.getcopies(ctx, parents, files)
285 return [(f, rev) for f in files if f not in self.ignored], {}
283 return [(f, rev) for f in files if f not in self.ignored], {}
286 if self._changescache and self._changescache[0] == rev:
284 if self._changescache and self._changescache[0] == rev:
287 m, a, r = self._changescache[1]
285 m, a, r = self._changescache[1]
288 else:
286 else:
289 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
287 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
290 # getcopies() detects missing revlogs early, run it before
288 # getcopies() detects missing revlogs early, run it before
291 # filtering the changes.
289 # filtering the changes.
292 copies = self.getcopies(ctx, parents, m + a)
290 copies = self.getcopies(ctx, parents, m + a)
293 changes = [(name, rev) for name in m + a + r
291 changes = [(name, rev) for name in m + a + r
294 if name not in self.ignored]
292 if name not in self.ignored]
295 return sorted(changes), copies
293 return sorted(changes), copies
296
294
297 def getcopies(self, ctx, parents, files):
295 def getcopies(self, ctx, parents, files):
298 copies = {}
296 copies = {}
299 for name in files:
297 for name in files:
300 if name in self.ignored:
298 if name in self.ignored:
301 continue
299 continue
302 try:
300 try:
303 copysource, copynode = ctx.filectx(name).renamed()
301 copysource, copynode = ctx.filectx(name).renamed()
304 if copysource in self.ignored or not self.keep(copynode):
302 if copysource in self.ignored or not self.keep(copynode):
305 continue
303 continue
306 # Ignore copy sources not in parent revisions
304 # Ignore copy sources not in parent revisions
307 found = False
305 found = False
308 for p in parents:
306 for p in parents:
309 if copysource in p:
307 if copysource in p:
310 found = True
308 found = True
311 break
309 break
312 if not found:
310 if not found:
313 continue
311 continue
314 copies[name] = copysource
312 copies[name] = copysource
315 except TypeError:
313 except TypeError:
316 pass
314 pass
317 except error.LookupError, e:
315 except error.LookupError, e:
318 if not self.ignoreerrors:
316 if not self.ignoreerrors:
319 raise
317 raise
320 self.ignored.add(name)
318 self.ignored.add(name)
321 self.ui.warn(_('ignoring: %s\n') % e)
319 self.ui.warn(_('ignoring: %s\n') % e)
322 return copies
320 return copies
323
321
324 def getcommit(self, rev):
322 def getcommit(self, rev):
325 ctx = self.changectx(rev)
323 ctx = self.changectx(rev)
326 parents = [p.hex() for p in self.parents(ctx)]
324 parents = [p.hex() for p in self.parents(ctx)]
327 if self.saverev:
325 if self.saverev:
328 crev = rev
326 crev = rev
329 else:
327 else:
330 crev = None
328 crev = None
331 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
329 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
332 desc=ctx.description(), rev=crev, parents=parents,
330 desc=ctx.description(), rev=crev, parents=parents,
333 branch=ctx.branch(), extra=ctx.extra(),
331 branch=ctx.branch(), extra=ctx.extra(),
334 sortkey=ctx.rev())
332 sortkey=ctx.rev())
335
333
336 def gettags(self):
334 def gettags(self):
337 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
335 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
338 return dict([(name, hex(node)) for name, node in tags
336 return dict([(name, hex(node)) for name, node in tags
339 if self.keep(node)])
337 if self.keep(node)])
340
338
341 def getchangedfiles(self, rev, i):
339 def getchangedfiles(self, rev, i):
342 ctx = self.changectx(rev)
340 ctx = self.changectx(rev)
343 parents = self.parents(ctx)
341 parents = self.parents(ctx)
344 if not parents and i is None:
342 if not parents and i is None:
345 i = 0
343 i = 0
346 changes = [], ctx.manifest().keys(), []
344 changes = [], ctx.manifest().keys(), []
347 else:
345 else:
348 i = i or 0
346 i = i or 0
349 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
347 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
350 changes = [[f for f in l if f not in self.ignored] for l in changes]
348 changes = [[f for f in l if f not in self.ignored] for l in changes]
351
349
352 if i == 0:
350 if i == 0:
353 self._changescache = (rev, changes)
351 self._changescache = (rev, changes)
354
352
355 return changes[0] + changes[1] + changes[2]
353 return changes[0] + changes[1] + changes[2]
356
354
357 def converted(self, rev, destrev):
355 def converted(self, rev, destrev):
358 if self.convertfp is None:
356 if self.convertfp is None:
359 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
357 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
360 'a')
358 'a')
361 self.convertfp.write('%s %s\n' % (destrev, rev))
359 self.convertfp.write('%s %s\n' % (destrev, rev))
362 self.convertfp.flush()
360 self.convertfp.flush()
363
361
364 def before(self):
362 def before(self):
365 self.ui.debug('run hg source pre-conversion action\n')
363 self.ui.debug('run hg source pre-conversion action\n')
366
364
367 def after(self):
365 def after(self):
368 self.ui.debug('run hg source post-conversion action\n')
366 self.ui.debug('run hg source post-conversion action\n')
369
367
370 def hasnativeorder(self):
368 def hasnativeorder(self):
371 return True
369 return True
372
370
373 def lookuprev(self, rev):
371 def lookuprev(self, rev):
374 try:
372 try:
375 return hex(self.repo.lookup(rev))
373 return hex(self.repo.lookup(rev))
376 except error.RepoError:
374 except error.RepoError:
377 return None
375 return None
@@ -1,229 +1,227 b''
1 # monotone.py - monotone support for the convert extension
1 # monotone.py - monotone support for the convert extension
2 #
2 #
3 # Copyright 2008, 2009 Mikkel Fahnoe Jorgensen <mikkel@dvide.com> and
3 # Copyright 2008, 2009 Mikkel Fahnoe Jorgensen <mikkel@dvide.com> and
4 # others
4 # others
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import os, re
9 import os, re
10 from mercurial import util
10 from mercurial import util
11 from common import NoRepo, commit, converter_source, checktool
11 from common import NoRepo, commit, converter_source, checktool
12 from common import commandline
12 from common import commandline
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14
14
15 class monotone_source(converter_source, commandline):
15 class monotone_source(converter_source, commandline):
16 def __init__(self, ui, path=None, rev=None):
16 def __init__(self, ui, path=None, rev=None):
17 converter_source.__init__(self, ui, path, rev)
17 converter_source.__init__(self, ui, path, rev)
18 commandline.__init__(self, ui, 'mtn')
18 commandline.__init__(self, ui, 'mtn')
19
19
20 self.ui = ui
20 self.ui = ui
21 self.path = path
21 self.path = path
22
22
23 norepo = NoRepo(_("%s does not look like a monotone repository")
23 norepo = NoRepo(_("%s does not look like a monotone repository")
24 % path)
24 % path)
25 if not os.path.exists(os.path.join(path, '_MTN')):
25 if not os.path.exists(os.path.join(path, '_MTN')):
26 # Could be a monotone repository (SQLite db file)
26 # Could be a monotone repository (SQLite db file)
27 try:
27 try:
28 header = file(path, 'rb').read(16)
28 header = file(path, 'rb').read(16)
29 except:
29 except:
30 header = ''
30 header = ''
31 if header != 'SQLite format 3\x00':
31 if header != 'SQLite format 3\x00':
32 raise norepo
32 raise norepo
33
33
34 # regular expressions for parsing monotone output
34 # regular expressions for parsing monotone output
35 space = r'\s*'
35 space = r'\s*'
36 name = r'\s+"((?:\\"|[^"])*)"\s*'
36 name = r'\s+"((?:\\"|[^"])*)"\s*'
37 value = name
37 value = name
38 revision = r'\s+\[(\w+)\]\s*'
38 revision = r'\s+\[(\w+)\]\s*'
39 lines = r'(?:.|\n)+'
39 lines = r'(?:.|\n)+'
40
40
41 self.dir_re = re.compile(space + "dir" + name)
41 self.dir_re = re.compile(space + "dir" + name)
42 self.file_re = re.compile(space + "file" + name +
42 self.file_re = re.compile(space + "file" + name +
43 "content" + revision)
43 "content" + revision)
44 self.add_file_re = re.compile(space + "add_file" + name +
44 self.add_file_re = re.compile(space + "add_file" + name +
45 "content" + revision)
45 "content" + revision)
46 self.patch_re = re.compile(space + "patch" + name +
46 self.patch_re = re.compile(space + "patch" + name +
47 "from" + revision + "to" + revision)
47 "from" + revision + "to" + revision)
48 self.rename_re = re.compile(space + "rename" + name + "to" + name)
48 self.rename_re = re.compile(space + "rename" + name + "to" + name)
49 self.delete_re = re.compile(space + "delete" + name)
49 self.delete_re = re.compile(space + "delete" + name)
50 self.tag_re = re.compile(space + "tag" + name + "revision" +
50 self.tag_re = re.compile(space + "tag" + name + "revision" +
51 revision)
51 revision)
52 self.cert_re = re.compile(lines + space + "name" + name +
52 self.cert_re = re.compile(lines + space + "name" + name +
53 "value" + value)
53 "value" + value)
54
54
55 attr = space + "file" + lines + space + "attr" + space
55 attr = space + "file" + lines + space + "attr" + space
56 self.attr_execute_re = re.compile(attr + '"mtn:execute"' +
56 self.attr_execute_re = re.compile(attr + '"mtn:execute"' +
57 space + '"true"')
57 space + '"true"')
58
58
59 # cached data
59 # cached data
60 self.manifest_rev = None
60 self.manifest_rev = None
61 self.manifest = None
61 self.manifest = None
62 self.files = None
62 self.files = None
63 self.dirs = None
63 self.dirs = None
64
64
65 checktool('mtn', abort=False)
65 checktool('mtn', abort=False)
66
66
67 # test if there are any revisions
67 # test if there are any revisions
68 self.rev = None
68 self.rev = None
69 try:
69 try:
70 self.getheads()
70 self.getheads()
71 except:
71 except:
72 raise norepo
72 raise norepo
73 self.rev = rev
73 self.rev = rev
74
74
75 def mtnrun(self, *args, **kwargs):
75 def mtnrun(self, *args, **kwargs):
76 kwargs['d'] = self.path
76 kwargs['d'] = self.path
77 return self.run0('automate', *args, **kwargs)
77 return self.run0('automate', *args, **kwargs)
78
78
79 def mtnloadmanifest(self, rev):
79 def mtnloadmanifest(self, rev):
80 if self.manifest_rev == rev:
80 if self.manifest_rev == rev:
81 return
81 return
82 self.manifest = self.mtnrun("get_manifest_of", rev).split("\n\n")
82 self.manifest = self.mtnrun("get_manifest_of", rev).split("\n\n")
83 self.manifest_rev = rev
83 self.manifest_rev = rev
84 self.files = {}
84 self.files = {}
85 self.dirs = {}
85 self.dirs = {}
86
86
87 for e in self.manifest:
87 for e in self.manifest:
88 m = self.file_re.match(e)
88 m = self.file_re.match(e)
89 if m:
89 if m:
90 attr = ""
90 attr = ""
91 name = m.group(1)
91 name = m.group(1)
92 node = m.group(2)
92 node = m.group(2)
93 if self.attr_execute_re.match(e):
93 if self.attr_execute_re.match(e):
94 attr += "x"
94 attr += "x"
95 self.files[name] = (node, attr)
95 self.files[name] = (node, attr)
96 m = self.dir_re.match(e)
96 m = self.dir_re.match(e)
97 if m:
97 if m:
98 self.dirs[m.group(1)] = True
98 self.dirs[m.group(1)] = True
99
99
100 def mtnisfile(self, name, rev):
100 def mtnisfile(self, name, rev):
101 # a non-file could be a directory or a deleted or renamed file
101 # a non-file could be a directory or a deleted or renamed file
102 self.mtnloadmanifest(rev)
102 self.mtnloadmanifest(rev)
103 return name in self.files
103 return name in self.files
104
104
105 def mtnisdir(self, name, rev):
105 def mtnisdir(self, name, rev):
106 self.mtnloadmanifest(rev)
106 self.mtnloadmanifest(rev)
107 return name in self.dirs
107 return name in self.dirs
108
108
109 def mtngetcerts(self, rev):
109 def mtngetcerts(self, rev):
110 certs = {"author":"<missing>", "date":"<missing>",
110 certs = {"author":"<missing>", "date":"<missing>",
111 "changelog":"<missing>", "branch":"<missing>"}
111 "changelog":"<missing>", "branch":"<missing>"}
112 certlist = self.mtnrun("certs", rev)
112 certlist = self.mtnrun("certs", rev)
113 # mtn < 0.45:
113 # mtn < 0.45:
114 # key "test@selenic.com"
114 # key "test@selenic.com"
115 # mtn >= 0.45:
115 # mtn >= 0.45:
116 # key [ff58a7ffb771907c4ff68995eada1c4da068d328]
116 # key [ff58a7ffb771907c4ff68995eada1c4da068d328]
117 certlist = re.split('\n\n key ["\[]', certlist)
117 certlist = re.split('\n\n key ["\[]', certlist)
118 for e in certlist:
118 for e in certlist:
119 m = self.cert_re.match(e)
119 m = self.cert_re.match(e)
120 if m:
120 if m:
121 name, value = m.groups()
121 name, value = m.groups()
122 value = value.replace(r'\"', '"')
122 value = value.replace(r'\"', '"')
123 value = value.replace(r'\\', '\\')
123 value = value.replace(r'\\', '\\')
124 certs[name] = value
124 certs[name] = value
125 # Monotone may have subsecond dates: 2005-02-05T09:39:12.364306
125 # Monotone may have subsecond dates: 2005-02-05T09:39:12.364306
126 # and all times are stored in UTC
126 # and all times are stored in UTC
127 certs["date"] = certs["date"].split('.')[0] + " UTC"
127 certs["date"] = certs["date"].split('.')[0] + " UTC"
128 return certs
128 return certs
129
129
130 # implement the converter_source interface:
130 # implement the converter_source interface:
131
131
132 def getheads(self):
132 def getheads(self):
133 if not self.rev:
133 if not self.rev:
134 return self.mtnrun("leaves").splitlines()
134 return self.mtnrun("leaves").splitlines()
135 else:
135 else:
136 return [self.rev]
136 return [self.rev]
137
137
138 def getchanges(self, rev):
138 def getchanges(self, rev):
139 #revision = self.mtncmd("get_revision %s" % rev).split("\n\n")
139 #revision = self.mtncmd("get_revision %s" % rev).split("\n\n")
140 revision = self.mtnrun("get_revision", rev).split("\n\n")
140 revision = self.mtnrun("get_revision", rev).split("\n\n")
141 files = {}
141 files = {}
142 ignoremove = {}
142 ignoremove = {}
143 renameddirs = []
143 renameddirs = []
144 copies = {}
144 copies = {}
145 for e in revision:
145 for e in revision:
146 m = self.add_file_re.match(e)
146 m = self.add_file_re.match(e)
147 if m:
147 if m:
148 files[m.group(1)] = rev
148 files[m.group(1)] = rev
149 ignoremove[m.group(1)] = rev
149 ignoremove[m.group(1)] = rev
150 m = self.patch_re.match(e)
150 m = self.patch_re.match(e)
151 if m:
151 if m:
152 files[m.group(1)] = rev
152 files[m.group(1)] = rev
153 # Delete/rename is handled later when the convert engine
153 # Delete/rename is handled later when the convert engine
154 # discovers an IOError exception from getfile,
154 # discovers an IOError exception from getfile,
155 # but only if we add the "from" file to the list of changes.
155 # but only if we add the "from" file to the list of changes.
156 m = self.delete_re.match(e)
156 m = self.delete_re.match(e)
157 if m:
157 if m:
158 files[m.group(1)] = rev
158 files[m.group(1)] = rev
159 m = self.rename_re.match(e)
159 m = self.rename_re.match(e)
160 if m:
160 if m:
161 toname = m.group(2)
161 toname = m.group(2)
162 fromname = m.group(1)
162 fromname = m.group(1)
163 if self.mtnisfile(toname, rev):
163 if self.mtnisfile(toname, rev):
164 ignoremove[toname] = 1
164 ignoremove[toname] = 1
165 copies[toname] = fromname
165 copies[toname] = fromname
166 files[toname] = rev
166 files[toname] = rev
167 files[fromname] = rev
167 files[fromname] = rev
168 elif self.mtnisdir(toname, rev):
168 elif self.mtnisdir(toname, rev):
169 renameddirs.append((fromname, toname))
169 renameddirs.append((fromname, toname))
170
170
171 # Directory renames can be handled only once we have recorded
171 # Directory renames can be handled only once we have recorded
172 # all new files
172 # all new files
173 for fromdir, todir in renameddirs:
173 for fromdir, todir in renameddirs:
174 renamed = {}
174 renamed = {}
175 for tofile in self.files:
175 for tofile in self.files:
176 if tofile in ignoremove:
176 if tofile in ignoremove:
177 continue
177 continue
178 if tofile.startswith(todir + '/'):
178 if tofile.startswith(todir + '/'):
179 renamed[tofile] = fromdir + tofile[len(todir):]
179 renamed[tofile] = fromdir + tofile[len(todir):]
180 # Avoid chained moves like:
180 # Avoid chained moves like:
181 # d1(/a) => d3/d1(/a)
181 # d1(/a) => d3/d1(/a)
182 # d2 => d3
182 # d2 => d3
183 ignoremove[tofile] = 1
183 ignoremove[tofile] = 1
184 for tofile, fromfile in renamed.items():
184 for tofile, fromfile in renamed.items():
185 self.ui.debug (_("copying file in renamed directory "
185 self.ui.debug (_("copying file in renamed directory "
186 "from '%s' to '%s'")
186 "from '%s' to '%s'")
187 % (fromfile, tofile), '\n')
187 % (fromfile, tofile), '\n')
188 files[tofile] = rev
188 files[tofile] = rev
189 copies[tofile] = fromfile
189 copies[tofile] = fromfile
190 for fromfile in renamed.values():
190 for fromfile in renamed.values():
191 files[fromfile] = rev
191 files[fromfile] = rev
192
192
193 return (files.items(), copies)
193 return (files.items(), copies)
194
194
195 def getmode(self, name, rev):
196 self.mtnloadmanifest(rev)
197 node, attr = self.files.get(name, (None, ""))
198 return attr
199
200 def getfile(self, name, rev):
195 def getfile(self, name, rev):
201 if not self.mtnisfile(name, rev):
196 if not self.mtnisfile(name, rev):
202 raise IOError() # file was deleted or renamed
197 raise IOError() # file was deleted or renamed
203 try:
198 try:
204 return self.mtnrun("get_file_of", name, r=rev)
199 data = self.mtnrun("get_file_of", name, r=rev)
205 except:
200 except:
206 raise IOError() # file was deleted or renamed
201 raise IOError() # file was deleted or renamed
202 self.mtnloadmanifest(rev)
203 node, attr = self.files.get(name, (None, ""))
204 return data, attr
207
205
208 def getcommit(self, rev):
206 def getcommit(self, rev):
209 certs = self.mtngetcerts(rev)
207 certs = self.mtngetcerts(rev)
210 return commit(
208 return commit(
211 author=certs["author"],
209 author=certs["author"],
212 date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")),
210 date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")),
213 desc=certs["changelog"],
211 desc=certs["changelog"],
214 rev=rev,
212 rev=rev,
215 parents=self.mtnrun("parents", rev).splitlines(),
213 parents=self.mtnrun("parents", rev).splitlines(),
216 branch=certs["branch"])
214 branch=certs["branch"])
217
215
218 def gettags(self):
216 def gettags(self):
219 tags = {}
217 tags = {}
220 for e in self.mtnrun("tags").split("\n\n"):
218 for e in self.mtnrun("tags").split("\n\n"):
221 m = self.tag_re.match(e)
219 m = self.tag_re.match(e)
222 if m:
220 if m:
223 tags[m.group(1)] = m.group(2)
221 tags[m.group(1)] = m.group(2)
224 return tags
222 return tags
225
223
226 def getchangedfiles(self, rev, i):
224 def getchangedfiles(self, rev, i):
227 # This function is only needed to support --filemap
225 # This function is only needed to support --filemap
228 # ... and we don't support that
226 # ... and we don't support that
229 raise NotImplementedError()
227 raise NotImplementedError()
@@ -1,208 +1,202 b''
1 # Perforce source for convert extension.
1 # Perforce source for convert extension.
2 #
2 #
3 # Copyright 2009, Frank Kingswood <frank@kingswood-consulting.co.uk>
3 # Copyright 2009, Frank Kingswood <frank@kingswood-consulting.co.uk>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from mercurial import util
8 from mercurial import util
9 from mercurial.i18n import _
9 from mercurial.i18n import _
10
10
11 from common import commit, converter_source, checktool, NoRepo
11 from common import commit, converter_source, checktool, NoRepo
12 import marshal
12 import marshal
13 import re
13 import re
14
14
15 def loaditer(f):
15 def loaditer(f):
16 "Yield the dictionary objects generated by p4"
16 "Yield the dictionary objects generated by p4"
17 try:
17 try:
18 while True:
18 while True:
19 d = marshal.load(f)
19 d = marshal.load(f)
20 if not d:
20 if not d:
21 break
21 break
22 yield d
22 yield d
23 except EOFError:
23 except EOFError:
24 pass
24 pass
25
25
26 class p4_source(converter_source):
26 class p4_source(converter_source):
27 def __init__(self, ui, path, rev=None):
27 def __init__(self, ui, path, rev=None):
28 super(p4_source, self).__init__(ui, path, rev=rev)
28 super(p4_source, self).__init__(ui, path, rev=rev)
29
29
30 if "/" in path and not path.startswith('//'):
30 if "/" in path and not path.startswith('//'):
31 raise NoRepo(_('%s does not look like a P4 repository') % path)
31 raise NoRepo(_('%s does not look like a P4 repository') % path)
32
32
33 checktool('p4', abort=False)
33 checktool('p4', abort=False)
34
34
35 self.p4changes = {}
35 self.p4changes = {}
36 self.heads = {}
36 self.heads = {}
37 self.changeset = {}
37 self.changeset = {}
38 self.files = {}
38 self.files = {}
39 self.tags = {}
39 self.tags = {}
40 self.lastbranch = {}
40 self.lastbranch = {}
41 self.parent = {}
41 self.parent = {}
42 self.encoding = "latin_1"
42 self.encoding = "latin_1"
43 self.depotname = {} # mapping from local name to depot name
43 self.depotname = {} # mapping from local name to depot name
44 self.modecache = {}
45 self.re_type = re.compile(
44 self.re_type = re.compile(
46 "([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
45 "([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
47 "(\+\w+)?$")
46 "(\+\w+)?$")
48 self.re_keywords = re.compile(
47 self.re_keywords = re.compile(
49 r"\$(Id|Header|Date|DateTime|Change|File|Revision|Author)"
48 r"\$(Id|Header|Date|DateTime|Change|File|Revision|Author)"
50 r":[^$\n]*\$")
49 r":[^$\n]*\$")
51 self.re_keywords_old = re.compile("\$(Id|Header):[^$\n]*\$")
50 self.re_keywords_old = re.compile("\$(Id|Header):[^$\n]*\$")
52
51
53 self._parse(ui, path)
52 self._parse(ui, path)
54
53
55 def _parse_view(self, path):
54 def _parse_view(self, path):
56 "Read changes affecting the path"
55 "Read changes affecting the path"
57 cmd = 'p4 -G changes -s submitted "%s"' % path
56 cmd = 'p4 -G changes -s submitted "%s"' % path
58 stdout = util.popen(cmd, mode='rb')
57 stdout = util.popen(cmd, mode='rb')
59 for d in loaditer(stdout):
58 for d in loaditer(stdout):
60 c = d.get("change", None)
59 c = d.get("change", None)
61 if c:
60 if c:
62 self.p4changes[c] = True
61 self.p4changes[c] = True
63
62
64 def _parse(self, ui, path):
63 def _parse(self, ui, path):
65 "Prepare list of P4 filenames and revisions to import"
64 "Prepare list of P4 filenames and revisions to import"
66 ui.status(_('reading p4 views\n'))
65 ui.status(_('reading p4 views\n'))
67
66
68 # read client spec or view
67 # read client spec or view
69 if "/" in path:
68 if "/" in path:
70 self._parse_view(path)
69 self._parse_view(path)
71 if path.startswith("//") and path.endswith("/..."):
70 if path.startswith("//") and path.endswith("/..."):
72 views = {path[:-3]:""}
71 views = {path[:-3]:""}
73 else:
72 else:
74 views = {"//": ""}
73 views = {"//": ""}
75 else:
74 else:
76 cmd = 'p4 -G client -o "%s"' % path
75 cmd = 'p4 -G client -o "%s"' % path
77 clientspec = marshal.load(util.popen(cmd, mode='rb'))
76 clientspec = marshal.load(util.popen(cmd, mode='rb'))
78
77
79 views = {}
78 views = {}
80 for client in clientspec:
79 for client in clientspec:
81 if client.startswith("View"):
80 if client.startswith("View"):
82 sview, cview = clientspec[client].split()
81 sview, cview = clientspec[client].split()
83 self._parse_view(sview)
82 self._parse_view(sview)
84 if sview.endswith("...") and cview.endswith("..."):
83 if sview.endswith("...") and cview.endswith("..."):
85 sview = sview[:-3]
84 sview = sview[:-3]
86 cview = cview[:-3]
85 cview = cview[:-3]
87 cview = cview[2:]
86 cview = cview[2:]
88 cview = cview[cview.find("/") + 1:]
87 cview = cview[cview.find("/") + 1:]
89 views[sview] = cview
88 views[sview] = cview
90
89
91 # list of changes that affect our source files
90 # list of changes that affect our source files
92 self.p4changes = self.p4changes.keys()
91 self.p4changes = self.p4changes.keys()
93 self.p4changes.sort(key=int)
92 self.p4changes.sort(key=int)
94
93
95 # list with depot pathnames, longest first
94 # list with depot pathnames, longest first
96 vieworder = views.keys()
95 vieworder = views.keys()
97 vieworder.sort(key=len, reverse=True)
96 vieworder.sort(key=len, reverse=True)
98
97
99 # handle revision limiting
98 # handle revision limiting
100 startrev = self.ui.config('convert', 'p4.startrev', default=0)
99 startrev = self.ui.config('convert', 'p4.startrev', default=0)
101 self.p4changes = [x for x in self.p4changes
100 self.p4changes = [x for x in self.p4changes
102 if ((not startrev or int(x) >= int(startrev)) and
101 if ((not startrev or int(x) >= int(startrev)) and
103 (not self.rev or int(x) <= int(self.rev)))]
102 (not self.rev or int(x) <= int(self.rev)))]
104
103
105 # now read the full changelists to get the list of file revisions
104 # now read the full changelists to get the list of file revisions
106 ui.status(_('collecting p4 changelists\n'))
105 ui.status(_('collecting p4 changelists\n'))
107 lastid = None
106 lastid = None
108 for change in self.p4changes:
107 for change in self.p4changes:
109 cmd = "p4 -G describe %s" % change
108 cmd = "p4 -G describe %s" % change
110 stdout = util.popen(cmd, mode='rb')
109 stdout = util.popen(cmd, mode='rb')
111 d = marshal.load(stdout)
110 d = marshal.load(stdout)
112
111
113 desc = self.recode(d["desc"])
112 desc = self.recode(d["desc"])
114 shortdesc = desc.split("\n", 1)[0]
113 shortdesc = desc.split("\n", 1)[0]
115 t = '%s %s' % (d["change"], repr(shortdesc)[1:-1])
114 t = '%s %s' % (d["change"], repr(shortdesc)[1:-1])
116 ui.status(util.ellipsis(t, 80) + '\n')
115 ui.status(util.ellipsis(t, 80) + '\n')
117
116
118 if lastid:
117 if lastid:
119 parents = [lastid]
118 parents = [lastid]
120 else:
119 else:
121 parents = []
120 parents = []
122
121
123 date = (int(d["time"]), 0) # timezone not set
122 date = (int(d["time"]), 0) # timezone not set
124 c = commit(author=self.recode(d["user"]), date=util.datestr(date),
123 c = commit(author=self.recode(d["user"]), date=util.datestr(date),
125 parents=parents, desc=desc, branch='',
124 parents=parents, desc=desc, branch='',
126 extra={"p4": change})
125 extra={"p4": change})
127
126
128 files = []
127 files = []
129 i = 0
128 i = 0
130 while ("depotFile%d" % i) in d and ("rev%d" % i) in d:
129 while ("depotFile%d" % i) in d and ("rev%d" % i) in d:
131 oldname = d["depotFile%d" % i]
130 oldname = d["depotFile%d" % i]
132 filename = None
131 filename = None
133 for v in vieworder:
132 for v in vieworder:
134 if oldname.startswith(v):
133 if oldname.startswith(v):
135 filename = views[v] + oldname[len(v):]
134 filename = views[v] + oldname[len(v):]
136 break
135 break
137 if filename:
136 if filename:
138 files.append((filename, d["rev%d" % i]))
137 files.append((filename, d["rev%d" % i]))
139 self.depotname[filename] = oldname
138 self.depotname[filename] = oldname
140 i += 1
139 i += 1
141 self.changeset[change] = c
140 self.changeset[change] = c
142 self.files[change] = files
141 self.files[change] = files
143 lastid = change
142 lastid = change
144
143
145 if lastid:
144 if lastid:
146 self.heads = [lastid]
145 self.heads = [lastid]
147
146
148 def getheads(self):
147 def getheads(self):
149 return self.heads
148 return self.heads
150
149
151 def getfile(self, name, rev):
150 def getfile(self, name, rev):
152 cmd = 'p4 -G print "%s#%s"' % (self.depotname[name], rev)
151 cmd = 'p4 -G print "%s#%s"' % (self.depotname[name], rev)
153 stdout = util.popen(cmd, mode='rb')
152 stdout = util.popen(cmd, mode='rb')
154
153
155 mode = None
154 mode = None
156 contents = ""
155 contents = ""
157 keywords = None
156 keywords = None
158
157
159 for d in loaditer(stdout):
158 for d in loaditer(stdout):
160 code = d["code"]
159 code = d["code"]
161 data = d.get("data")
160 data = d.get("data")
162
161
163 if code == "error":
162 if code == "error":
164 raise IOError(d["generic"], data)
163 raise IOError(d["generic"], data)
165
164
166 elif code == "stat":
165 elif code == "stat":
167 p4type = self.re_type.match(d["type"])
166 p4type = self.re_type.match(d["type"])
168 if p4type:
167 if p4type:
169 mode = ""
168 mode = ""
170 flags = (p4type.group(1) or "") + (p4type.group(3) or "")
169 flags = (p4type.group(1) or "") + (p4type.group(3) or "")
171 if "x" in flags:
170 if "x" in flags:
172 mode = "x"
171 mode = "x"
173 if p4type.group(2) == "symlink":
172 if p4type.group(2) == "symlink":
174 mode = "l"
173 mode = "l"
175 if "ko" in flags:
174 if "ko" in flags:
176 keywords = self.re_keywords_old
175 keywords = self.re_keywords_old
177 elif "k" in flags:
176 elif "k" in flags:
178 keywords = self.re_keywords
177 keywords = self.re_keywords
179
178
180 elif code == "text" or code == "binary":
179 elif code == "text" or code == "binary":
181 contents += data
180 contents += data
182
181
183 if mode is None:
182 if mode is None:
184 raise IOError(0, "bad stat")
183 raise IOError(0, "bad stat")
185
184
186 self.modecache[(name, rev)] = mode
187
188 if keywords:
185 if keywords:
189 contents = keywords.sub("$\\1$", contents)
186 contents = keywords.sub("$\\1$", contents)
190 if mode == "l" and contents.endswith("\n"):
187 if mode == "l" and contents.endswith("\n"):
191 contents = contents[:-1]
188 contents = contents[:-1]
192
189
193 return contents
190 return contents, mode
194
195 def getmode(self, name, rev):
196 return self.modecache[(name, rev)]
197
191
198 def getchanges(self, rev):
192 def getchanges(self, rev):
199 return self.files[rev], {}
193 return self.files[rev], {}
200
194
201 def getcommit(self, rev):
195 def getcommit(self, rev):
202 return self.changeset[rev]
196 return self.changeset[rev]
203
197
204 def gettags(self):
198 def gettags(self):
205 return self.tags
199 return self.tags
206
200
207 def getchangedfiles(self, rev, i):
201 def getchangedfiles(self, rev, i):
208 return sorted([x[0] for x in self.files[rev]])
202 return sorted([x[0] for x in self.files[rev]])
@@ -1,1167 +1,1157 b''
1 # Subversion 1.4/1.5 Python API backend
1 # Subversion 1.4/1.5 Python API backend
2 #
2 #
3 # Copyright(C) 2007 Daniel Holth et al
3 # Copyright(C) 2007 Daniel Holth et al
4
4
5 import os
5 import os
6 import re
6 import re
7 import sys
7 import sys
8 import cPickle as pickle
8 import cPickle as pickle
9 import tempfile
9 import tempfile
10 import urllib
10 import urllib
11 import urllib2
11 import urllib2
12
12
13 from mercurial import strutil, util, encoding
13 from mercurial import strutil, util, encoding
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15
15
16 # Subversion stuff. Works best with very recent Python SVN bindings
16 # Subversion stuff. Works best with very recent Python SVN bindings
17 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
17 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
18 # these bindings.
18 # these bindings.
19
19
20 from cStringIO import StringIO
20 from cStringIO import StringIO
21
21
22 from common import NoRepo, MissingTool, commit, encodeargs, decodeargs
22 from common import NoRepo, MissingTool, commit, encodeargs, decodeargs
23 from common import commandline, converter_source, converter_sink, mapfile
23 from common import commandline, converter_source, converter_sink, mapfile
24
24
25 try:
25 try:
26 from svn.core import SubversionException, Pool
26 from svn.core import SubversionException, Pool
27 import svn
27 import svn
28 import svn.client
28 import svn.client
29 import svn.core
29 import svn.core
30 import svn.ra
30 import svn.ra
31 import svn.delta
31 import svn.delta
32 import transport
32 import transport
33 import warnings
33 import warnings
34 warnings.filterwarnings('ignore',
34 warnings.filterwarnings('ignore',
35 module='svn.core',
35 module='svn.core',
36 category=DeprecationWarning)
36 category=DeprecationWarning)
37
37
38 except ImportError:
38 except ImportError:
39 pass
39 pass
40
40
41 class SvnPathNotFound(Exception):
41 class SvnPathNotFound(Exception):
42 pass
42 pass
43
43
44 def geturl(path):
44 def geturl(path):
45 try:
45 try:
46 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
46 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
47 except SubversionException:
47 except SubversionException:
48 pass
48 pass
49 if os.path.isdir(path):
49 if os.path.isdir(path):
50 path = os.path.normpath(os.path.abspath(path))
50 path = os.path.normpath(os.path.abspath(path))
51 if os.name == 'nt':
51 if os.name == 'nt':
52 path = '/' + util.normpath(path)
52 path = '/' + util.normpath(path)
53 # Module URL is later compared with the repository URL returned
53 # Module URL is later compared with the repository URL returned
54 # by svn API, which is UTF-8.
54 # by svn API, which is UTF-8.
55 path = encoding.tolocal(path)
55 path = encoding.tolocal(path)
56 return 'file://%s' % urllib.quote(path)
56 return 'file://%s' % urllib.quote(path)
57 return path
57 return path
58
58
59 def optrev(number):
59 def optrev(number):
60 optrev = svn.core.svn_opt_revision_t()
60 optrev = svn.core.svn_opt_revision_t()
61 optrev.kind = svn.core.svn_opt_revision_number
61 optrev.kind = svn.core.svn_opt_revision_number
62 optrev.value.number = number
62 optrev.value.number = number
63 return optrev
63 return optrev
64
64
65 class changedpath(object):
65 class changedpath(object):
66 def __init__(self, p):
66 def __init__(self, p):
67 self.copyfrom_path = p.copyfrom_path
67 self.copyfrom_path = p.copyfrom_path
68 self.copyfrom_rev = p.copyfrom_rev
68 self.copyfrom_rev = p.copyfrom_rev
69 self.action = p.action
69 self.action = p.action
70
70
71 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
71 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
72 strict_node_history=False):
72 strict_node_history=False):
73 protocol = -1
73 protocol = -1
74 def receiver(orig_paths, revnum, author, date, message, pool):
74 def receiver(orig_paths, revnum, author, date, message, pool):
75 if orig_paths is not None:
75 if orig_paths is not None:
76 for k, v in orig_paths.iteritems():
76 for k, v in orig_paths.iteritems():
77 orig_paths[k] = changedpath(v)
77 orig_paths[k] = changedpath(v)
78 pickle.dump((orig_paths, revnum, author, date, message),
78 pickle.dump((orig_paths, revnum, author, date, message),
79 fp, protocol)
79 fp, protocol)
80
80
81 try:
81 try:
82 # Use an ra of our own so that our parent can consume
82 # Use an ra of our own so that our parent can consume
83 # our results without confusing the server.
83 # our results without confusing the server.
84 t = transport.SvnRaTransport(url=url)
84 t = transport.SvnRaTransport(url=url)
85 svn.ra.get_log(t.ra, paths, start, end, limit,
85 svn.ra.get_log(t.ra, paths, start, end, limit,
86 discover_changed_paths,
86 discover_changed_paths,
87 strict_node_history,
87 strict_node_history,
88 receiver)
88 receiver)
89 except SubversionException, (inst, num):
89 except SubversionException, (inst, num):
90 pickle.dump(num, fp, protocol)
90 pickle.dump(num, fp, protocol)
91 except IOError:
91 except IOError:
92 # Caller may interrupt the iteration
92 # Caller may interrupt the iteration
93 pickle.dump(None, fp, protocol)
93 pickle.dump(None, fp, protocol)
94 else:
94 else:
95 pickle.dump(None, fp, protocol)
95 pickle.dump(None, fp, protocol)
96 fp.close()
96 fp.close()
97 # With large history, cleanup process goes crazy and suddenly
97 # With large history, cleanup process goes crazy and suddenly
98 # consumes *huge* amount of memory. The output file being closed,
98 # consumes *huge* amount of memory. The output file being closed,
99 # there is no need for clean termination.
99 # there is no need for clean termination.
100 os._exit(0)
100 os._exit(0)
101
101
102 def debugsvnlog(ui, **opts):
102 def debugsvnlog(ui, **opts):
103 """Fetch SVN log in a subprocess and channel them back to parent to
103 """Fetch SVN log in a subprocess and channel them back to parent to
104 avoid memory collection issues.
104 avoid memory collection issues.
105 """
105 """
106 util.set_binary(sys.stdin)
106 util.set_binary(sys.stdin)
107 util.set_binary(sys.stdout)
107 util.set_binary(sys.stdout)
108 args = decodeargs(sys.stdin.read())
108 args = decodeargs(sys.stdin.read())
109 get_log_child(sys.stdout, *args)
109 get_log_child(sys.stdout, *args)
110
110
111 class logstream(object):
111 class logstream(object):
112 """Interruptible revision log iterator."""
112 """Interruptible revision log iterator."""
113 def __init__(self, stdout):
113 def __init__(self, stdout):
114 self._stdout = stdout
114 self._stdout = stdout
115
115
116 def __iter__(self):
116 def __iter__(self):
117 while True:
117 while True:
118 try:
118 try:
119 entry = pickle.load(self._stdout)
119 entry = pickle.load(self._stdout)
120 except EOFError:
120 except EOFError:
121 raise util.Abort(_('Mercurial failed to run itself, check'
121 raise util.Abort(_('Mercurial failed to run itself, check'
122 ' hg executable is in PATH'))
122 ' hg executable is in PATH'))
123 try:
123 try:
124 orig_paths, revnum, author, date, message = entry
124 orig_paths, revnum, author, date, message = entry
125 except:
125 except:
126 if entry is None:
126 if entry is None:
127 break
127 break
128 raise SubversionException("child raised exception", entry)
128 raise SubversionException("child raised exception", entry)
129 yield entry
129 yield entry
130
130
131 def close(self):
131 def close(self):
132 if self._stdout:
132 if self._stdout:
133 self._stdout.close()
133 self._stdout.close()
134 self._stdout = None
134 self._stdout = None
135
135
136
136
137 # Check to see if the given path is a local Subversion repo. Verify this by
137 # Check to see if the given path is a local Subversion repo. Verify this by
138 # looking for several svn-specific files and directories in the given
138 # looking for several svn-specific files and directories in the given
139 # directory.
139 # directory.
140 def filecheck(ui, path, proto):
140 def filecheck(ui, path, proto):
141 for x in ('locks', 'hooks', 'format', 'db'):
141 for x in ('locks', 'hooks', 'format', 'db'):
142 if not os.path.exists(os.path.join(path, x)):
142 if not os.path.exists(os.path.join(path, x)):
143 return False
143 return False
144 return True
144 return True
145
145
146 # Check to see if a given path is the root of an svn repo over http. We verify
146 # Check to see if a given path is the root of an svn repo over http. We verify
147 # this by requesting a version-controlled URL we know can't exist and looking
147 # this by requesting a version-controlled URL we know can't exist and looking
148 # for the svn-specific "not found" XML.
148 # for the svn-specific "not found" XML.
149 def httpcheck(ui, path, proto):
149 def httpcheck(ui, path, proto):
150 try:
150 try:
151 opener = urllib2.build_opener()
151 opener = urllib2.build_opener()
152 rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path))
152 rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path))
153 data = rsp.read()
153 data = rsp.read()
154 except urllib2.HTTPError, inst:
154 except urllib2.HTTPError, inst:
155 if inst.code != 404:
155 if inst.code != 404:
156 # Except for 404 we cannot know for sure this is not an svn repo
156 # Except for 404 we cannot know for sure this is not an svn repo
157 ui.warn(_('svn: cannot probe remote repository, assume it could '
157 ui.warn(_('svn: cannot probe remote repository, assume it could '
158 'be a subversion repository. Use --source-type if you '
158 'be a subversion repository. Use --source-type if you '
159 'know better.\n'))
159 'know better.\n'))
160 return True
160 return True
161 data = inst.fp.read()
161 data = inst.fp.read()
162 except:
162 except:
163 # Could be urllib2.URLError if the URL is invalid or anything else.
163 # Could be urllib2.URLError if the URL is invalid or anything else.
164 return False
164 return False
165 return '<m:human-readable errcode="160013">' in data
165 return '<m:human-readable errcode="160013">' in data
166
166
167 protomap = {'http': httpcheck,
167 protomap = {'http': httpcheck,
168 'https': httpcheck,
168 'https': httpcheck,
169 'file': filecheck,
169 'file': filecheck,
170 }
170 }
171 def issvnurl(ui, url):
171 def issvnurl(ui, url):
172 try:
172 try:
173 proto, path = url.split('://', 1)
173 proto, path = url.split('://', 1)
174 if proto == 'file':
174 if proto == 'file':
175 path = urllib.url2pathname(path)
175 path = urllib.url2pathname(path)
176 except ValueError:
176 except ValueError:
177 proto = 'file'
177 proto = 'file'
178 path = os.path.abspath(url)
178 path = os.path.abspath(url)
179 if proto == 'file':
179 if proto == 'file':
180 path = path.replace(os.sep, '/')
180 path = path.replace(os.sep, '/')
181 check = protomap.get(proto, lambda *args: False)
181 check = protomap.get(proto, lambda *args: False)
182 while '/' in path:
182 while '/' in path:
183 if check(ui, path, proto):
183 if check(ui, path, proto):
184 return True
184 return True
185 path = path.rsplit('/', 1)[0]
185 path = path.rsplit('/', 1)[0]
186 return False
186 return False
187
187
188 # SVN conversion code stolen from bzr-svn and tailor
188 # SVN conversion code stolen from bzr-svn and tailor
189 #
189 #
190 # Subversion looks like a versioned filesystem, branches structures
190 # Subversion looks like a versioned filesystem, branches structures
191 # are defined by conventions and not enforced by the tool. First,
191 # are defined by conventions and not enforced by the tool. First,
192 # we define the potential branches (modules) as "trunk" and "branches"
192 # we define the potential branches (modules) as "trunk" and "branches"
193 # children directories. Revisions are then identified by their
193 # children directories. Revisions are then identified by their
194 # module and revision number (and a repository identifier).
194 # module and revision number (and a repository identifier).
195 #
195 #
196 # The revision graph is really a tree (or a forest). By default, a
196 # The revision graph is really a tree (or a forest). By default, a
197 # revision parent is the previous revision in the same module. If the
197 # revision parent is the previous revision in the same module. If the
198 # module directory is copied/moved from another module then the
198 # module directory is copied/moved from another module then the
199 # revision is the module root and its parent the source revision in
199 # revision is the module root and its parent the source revision in
200 # the parent module. A revision has at most one parent.
200 # the parent module. A revision has at most one parent.
201 #
201 #
202 class svn_source(converter_source):
202 class svn_source(converter_source):
203 def __init__(self, ui, url, rev=None):
203 def __init__(self, ui, url, rev=None):
204 super(svn_source, self).__init__(ui, url, rev=rev)
204 super(svn_source, self).__init__(ui, url, rev=rev)
205
205
206 if not (url.startswith('svn://') or url.startswith('svn+ssh://') or
206 if not (url.startswith('svn://') or url.startswith('svn+ssh://') or
207 (os.path.exists(url) and
207 (os.path.exists(url) and
208 os.path.exists(os.path.join(url, '.svn'))) or
208 os.path.exists(os.path.join(url, '.svn'))) or
209 issvnurl(ui, url)):
209 issvnurl(ui, url)):
210 raise NoRepo(_("%s does not look like a Subversion repository")
210 raise NoRepo(_("%s does not look like a Subversion repository")
211 % url)
211 % url)
212
212
213 try:
213 try:
214 SubversionException
214 SubversionException
215 except NameError:
215 except NameError:
216 raise MissingTool(_('Subversion python bindings could not be loaded'))
216 raise MissingTool(_('Subversion python bindings could not be loaded'))
217
217
218 try:
218 try:
219 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
219 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
220 if version < (1, 4):
220 if version < (1, 4):
221 raise MissingTool(_('Subversion python bindings %d.%d found, '
221 raise MissingTool(_('Subversion python bindings %d.%d found, '
222 '1.4 or later required') % version)
222 '1.4 or later required') % version)
223 except AttributeError:
223 except AttributeError:
224 raise MissingTool(_('Subversion python bindings are too old, 1.4 '
224 raise MissingTool(_('Subversion python bindings are too old, 1.4 '
225 'or later required'))
225 'or later required'))
226
226
227 self.lastrevs = {}
227 self.lastrevs = {}
228
228
229 latest = None
229 latest = None
230 try:
230 try:
231 # Support file://path@rev syntax. Useful e.g. to convert
231 # Support file://path@rev syntax. Useful e.g. to convert
232 # deleted branches.
232 # deleted branches.
233 at = url.rfind('@')
233 at = url.rfind('@')
234 if at >= 0:
234 if at >= 0:
235 latest = int(url[at + 1:])
235 latest = int(url[at + 1:])
236 url = url[:at]
236 url = url[:at]
237 except ValueError:
237 except ValueError:
238 pass
238 pass
239 self.url = geturl(url)
239 self.url = geturl(url)
240 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
240 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
241 try:
241 try:
242 self.transport = transport.SvnRaTransport(url=self.url)
242 self.transport = transport.SvnRaTransport(url=self.url)
243 self.ra = self.transport.ra
243 self.ra = self.transport.ra
244 self.ctx = self.transport.client
244 self.ctx = self.transport.client
245 self.baseurl = svn.ra.get_repos_root(self.ra)
245 self.baseurl = svn.ra.get_repos_root(self.ra)
246 # Module is either empty or a repository path starting with
246 # Module is either empty or a repository path starting with
247 # a slash and not ending with a slash.
247 # a slash and not ending with a slash.
248 self.module = urllib.unquote(self.url[len(self.baseurl):])
248 self.module = urllib.unquote(self.url[len(self.baseurl):])
249 self.prevmodule = None
249 self.prevmodule = None
250 self.rootmodule = self.module
250 self.rootmodule = self.module
251 self.commits = {}
251 self.commits = {}
252 self.paths = {}
252 self.paths = {}
253 self.uuid = svn.ra.get_uuid(self.ra)
253 self.uuid = svn.ra.get_uuid(self.ra)
254 except SubversionException:
254 except SubversionException:
255 ui.traceback()
255 ui.traceback()
256 raise NoRepo(_("%s does not look like a Subversion repository")
256 raise NoRepo(_("%s does not look like a Subversion repository")
257 % self.url)
257 % self.url)
258
258
259 if rev:
259 if rev:
260 try:
260 try:
261 latest = int(rev)
261 latest = int(rev)
262 except ValueError:
262 except ValueError:
263 raise util.Abort(_('svn: revision %s is not an integer') % rev)
263 raise util.Abort(_('svn: revision %s is not an integer') % rev)
264
264
265 self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
265 self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
266 try:
266 try:
267 self.startrev = int(self.startrev)
267 self.startrev = int(self.startrev)
268 if self.startrev < 0:
268 if self.startrev < 0:
269 self.startrev = 0
269 self.startrev = 0
270 except ValueError:
270 except ValueError:
271 raise util.Abort(_('svn: start revision %s is not an integer')
271 raise util.Abort(_('svn: start revision %s is not an integer')
272 % self.startrev)
272 % self.startrev)
273
273
274 self.head = self.latest(self.module, latest)
274 self.head = self.latest(self.module, latest)
275 if not self.head:
275 if not self.head:
276 raise util.Abort(_('no revision found in module %s')
276 raise util.Abort(_('no revision found in module %s')
277 % self.module)
277 % self.module)
278 self.last_changed = self.revnum(self.head)
278 self.last_changed = self.revnum(self.head)
279
279
280 self._changescache = None
280 self._changescache = None
281
281
282 if os.path.exists(os.path.join(url, '.svn/entries')):
282 if os.path.exists(os.path.join(url, '.svn/entries')):
283 self.wc = url
283 self.wc = url
284 else:
284 else:
285 self.wc = None
285 self.wc = None
286 self.convertfp = None
286 self.convertfp = None
287
287
288 def setrevmap(self, revmap):
288 def setrevmap(self, revmap):
289 lastrevs = {}
289 lastrevs = {}
290 for revid in revmap.iterkeys():
290 for revid in revmap.iterkeys():
291 uuid, module, revnum = self.revsplit(revid)
291 uuid, module, revnum = self.revsplit(revid)
292 lastrevnum = lastrevs.setdefault(module, revnum)
292 lastrevnum = lastrevs.setdefault(module, revnum)
293 if revnum > lastrevnum:
293 if revnum > lastrevnum:
294 lastrevs[module] = revnum
294 lastrevs[module] = revnum
295 self.lastrevs = lastrevs
295 self.lastrevs = lastrevs
296
296
297 def exists(self, path, optrev):
297 def exists(self, path, optrev):
298 try:
298 try:
299 svn.client.ls(self.url.rstrip('/') + '/' + urllib.quote(path),
299 svn.client.ls(self.url.rstrip('/') + '/' + urllib.quote(path),
300 optrev, False, self.ctx)
300 optrev, False, self.ctx)
301 return True
301 return True
302 except SubversionException:
302 except SubversionException:
303 return False
303 return False
304
304
305 def getheads(self):
305 def getheads(self):
306
306
307 def isdir(path, revnum):
307 def isdir(path, revnum):
308 kind = self._checkpath(path, revnum)
308 kind = self._checkpath(path, revnum)
309 return kind == svn.core.svn_node_dir
309 return kind == svn.core.svn_node_dir
310
310
311 def getcfgpath(name, rev):
311 def getcfgpath(name, rev):
312 cfgpath = self.ui.config('convert', 'svn.' + name)
312 cfgpath = self.ui.config('convert', 'svn.' + name)
313 if cfgpath is not None and cfgpath.strip() == '':
313 if cfgpath is not None and cfgpath.strip() == '':
314 return None
314 return None
315 path = (cfgpath or name).strip('/')
315 path = (cfgpath or name).strip('/')
316 if not self.exists(path, rev):
316 if not self.exists(path, rev):
317 if cfgpath:
317 if cfgpath:
318 raise util.Abort(_('expected %s to be at %r, but not found')
318 raise util.Abort(_('expected %s to be at %r, but not found')
319 % (name, path))
319 % (name, path))
320 return None
320 return None
321 self.ui.note(_('found %s at %r\n') % (name, path))
321 self.ui.note(_('found %s at %r\n') % (name, path))
322 return path
322 return path
323
323
324 rev = optrev(self.last_changed)
324 rev = optrev(self.last_changed)
325 oldmodule = ''
325 oldmodule = ''
326 trunk = getcfgpath('trunk', rev)
326 trunk = getcfgpath('trunk', rev)
327 self.tags = getcfgpath('tags', rev)
327 self.tags = getcfgpath('tags', rev)
328 branches = getcfgpath('branches', rev)
328 branches = getcfgpath('branches', rev)
329
329
330 # If the project has a trunk or branches, we will extract heads
330 # If the project has a trunk or branches, we will extract heads
331 # from them. We keep the project root otherwise.
331 # from them. We keep the project root otherwise.
332 if trunk:
332 if trunk:
333 oldmodule = self.module or ''
333 oldmodule = self.module or ''
334 self.module += '/' + trunk
334 self.module += '/' + trunk
335 self.head = self.latest(self.module, self.last_changed)
335 self.head = self.latest(self.module, self.last_changed)
336 if not self.head:
336 if not self.head:
337 raise util.Abort(_('no revision found in module %s')
337 raise util.Abort(_('no revision found in module %s')
338 % self.module)
338 % self.module)
339
339
340 # First head in the list is the module's head
340 # First head in the list is the module's head
341 self.heads = [self.head]
341 self.heads = [self.head]
342 if self.tags is not None:
342 if self.tags is not None:
343 self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags'))
343 self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags'))
344
344
345 # Check if branches bring a few more heads to the list
345 # Check if branches bring a few more heads to the list
346 if branches:
346 if branches:
347 rpath = self.url.strip('/')
347 rpath = self.url.strip('/')
348 branchnames = svn.client.ls(rpath + '/' + urllib.quote(branches),
348 branchnames = svn.client.ls(rpath + '/' + urllib.quote(branches),
349 rev, False, self.ctx)
349 rev, False, self.ctx)
350 for branch in branchnames.keys():
350 for branch in branchnames.keys():
351 module = '%s/%s/%s' % (oldmodule, branches, branch)
351 module = '%s/%s/%s' % (oldmodule, branches, branch)
352 if not isdir(module, self.last_changed):
352 if not isdir(module, self.last_changed):
353 continue
353 continue
354 brevid = self.latest(module, self.last_changed)
354 brevid = self.latest(module, self.last_changed)
355 if not brevid:
355 if not brevid:
356 self.ui.note(_('ignoring empty branch %s\n') % branch)
356 self.ui.note(_('ignoring empty branch %s\n') % branch)
357 continue
357 continue
358 self.ui.note(_('found branch %s at %d\n') %
358 self.ui.note(_('found branch %s at %d\n') %
359 (branch, self.revnum(brevid)))
359 (branch, self.revnum(brevid)))
360 self.heads.append(brevid)
360 self.heads.append(brevid)
361
361
362 if self.startrev and self.heads:
362 if self.startrev and self.heads:
363 if len(self.heads) > 1:
363 if len(self.heads) > 1:
364 raise util.Abort(_('svn: start revision is not supported '
364 raise util.Abort(_('svn: start revision is not supported '
365 'with more than one branch'))
365 'with more than one branch'))
366 revnum = self.revnum(self.heads[0])
366 revnum = self.revnum(self.heads[0])
367 if revnum < self.startrev:
367 if revnum < self.startrev:
368 raise util.Abort(
368 raise util.Abort(
369 _('svn: no revision found after start revision %d')
369 _('svn: no revision found after start revision %d')
370 % self.startrev)
370 % self.startrev)
371
371
372 return self.heads
372 return self.heads
373
373
374 def getfile(self, file, rev):
375 data, mode = self._getfile(file, rev)
376 self.modecache[(file, rev)] = mode
377 return data
378
379 def getmode(self, file, rev):
380 return self.modecache[(file, rev)]
381
382 def getchanges(self, rev):
374 def getchanges(self, rev):
383 if self._changescache and self._changescache[0] == rev:
375 if self._changescache and self._changescache[0] == rev:
384 return self._changescache[1]
376 return self._changescache[1]
385 self._changescache = None
377 self._changescache = None
386 self.modecache = {}
387 (paths, parents) = self.paths[rev]
378 (paths, parents) = self.paths[rev]
388 if parents:
379 if parents:
389 files, self.removed, copies = self.expandpaths(rev, paths, parents)
380 files, self.removed, copies = self.expandpaths(rev, paths, parents)
390 else:
381 else:
391 # Perform a full checkout on roots
382 # Perform a full checkout on roots
392 uuid, module, revnum = self.revsplit(rev)
383 uuid, module, revnum = self.revsplit(rev)
393 entries = svn.client.ls(self.baseurl + urllib.quote(module),
384 entries = svn.client.ls(self.baseurl + urllib.quote(module),
394 optrev(revnum), True, self.ctx)
385 optrev(revnum), True, self.ctx)
395 files = [n for n, e in entries.iteritems()
386 files = [n for n, e in entries.iteritems()
396 if e.kind == svn.core.svn_node_file]
387 if e.kind == svn.core.svn_node_file]
397 copies = {}
388 copies = {}
398 self.removed = set()
389 self.removed = set()
399
390
400 files.sort()
391 files.sort()
401 files = zip(files, [rev] * len(files))
392 files = zip(files, [rev] * len(files))
402
393
403 # caller caches the result, so free it here to release memory
394 # caller caches the result, so free it here to release memory
404 del self.paths[rev]
395 del self.paths[rev]
405 return (files, copies)
396 return (files, copies)
406
397
407 def getchangedfiles(self, rev, i):
398 def getchangedfiles(self, rev, i):
408 changes = self.getchanges(rev)
399 changes = self.getchanges(rev)
409 self._changescache = (rev, changes)
400 self._changescache = (rev, changes)
410 return [f[0] for f in changes[0]]
401 return [f[0] for f in changes[0]]
411
402
412 def getcommit(self, rev):
403 def getcommit(self, rev):
413 if rev not in self.commits:
404 if rev not in self.commits:
414 uuid, module, revnum = self.revsplit(rev)
405 uuid, module, revnum = self.revsplit(rev)
415 self.module = module
406 self.module = module
416 self.reparent(module)
407 self.reparent(module)
417 # We assume that:
408 # We assume that:
418 # - requests for revisions after "stop" come from the
409 # - requests for revisions after "stop" come from the
419 # revision graph backward traversal. Cache all of them
410 # revision graph backward traversal. Cache all of them
420 # down to stop, they will be used eventually.
411 # down to stop, they will be used eventually.
421 # - requests for revisions before "stop" come to get
412 # - requests for revisions before "stop" come to get
422 # isolated branches parents. Just fetch what is needed.
413 # isolated branches parents. Just fetch what is needed.
423 stop = self.lastrevs.get(module, 0)
414 stop = self.lastrevs.get(module, 0)
424 if revnum < stop:
415 if revnum < stop:
425 stop = revnum + 1
416 stop = revnum + 1
426 self._fetch_revisions(revnum, stop)
417 self._fetch_revisions(revnum, stop)
427 commit = self.commits[rev]
418 commit = self.commits[rev]
428 # caller caches the result, so free it here to release memory
419 # caller caches the result, so free it here to release memory
429 del self.commits[rev]
420 del self.commits[rev]
430 return commit
421 return commit
431
422
432 def gettags(self):
423 def gettags(self):
433 tags = {}
424 tags = {}
434 if self.tags is None:
425 if self.tags is None:
435 return tags
426 return tags
436
427
437 # svn tags are just a convention, project branches left in a
428 # svn tags are just a convention, project branches left in a
438 # 'tags' directory. There is no other relationship than
429 # 'tags' directory. There is no other relationship than
439 # ancestry, which is expensive to discover and makes them hard
430 # ancestry, which is expensive to discover and makes them hard
440 # to update incrementally. Worse, past revisions may be
431 # to update incrementally. Worse, past revisions may be
441 # referenced by tags far away in the future, requiring a deep
432 # referenced by tags far away in the future, requiring a deep
442 # history traversal on every calculation. Current code
433 # history traversal on every calculation. Current code
443 # performs a single backward traversal, tracking moves within
434 # performs a single backward traversal, tracking moves within
444 # the tags directory (tag renaming) and recording a new tag
435 # the tags directory (tag renaming) and recording a new tag
445 # everytime a project is copied from outside the tags
436 # everytime a project is copied from outside the tags
446 # directory. It also lists deleted tags, this behaviour may
437 # directory. It also lists deleted tags, this behaviour may
447 # change in the future.
438 # change in the future.
448 pendings = []
439 pendings = []
449 tagspath = self.tags
440 tagspath = self.tags
450 start = svn.ra.get_latest_revnum(self.ra)
441 start = svn.ra.get_latest_revnum(self.ra)
451 try:
442 try:
452 for entry in self._getlog([self.tags], start, self.startrev):
443 for entry in self._getlog([self.tags], start, self.startrev):
453 origpaths, revnum, author, date, message = entry
444 origpaths, revnum, author, date, message = entry
454 copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e
445 copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e
455 in origpaths.iteritems() if e.copyfrom_path]
446 in origpaths.iteritems() if e.copyfrom_path]
456 # Apply moves/copies from more specific to general
447 # Apply moves/copies from more specific to general
457 copies.sort(reverse=True)
448 copies.sort(reverse=True)
458
449
459 srctagspath = tagspath
450 srctagspath = tagspath
460 if copies and copies[-1][2] == tagspath:
451 if copies and copies[-1][2] == tagspath:
461 # Track tags directory moves
452 # Track tags directory moves
462 srctagspath = copies.pop()[0]
453 srctagspath = copies.pop()[0]
463
454
464 for source, sourcerev, dest in copies:
455 for source, sourcerev, dest in copies:
465 if not dest.startswith(tagspath + '/'):
456 if not dest.startswith(tagspath + '/'):
466 continue
457 continue
467 for tag in pendings:
458 for tag in pendings:
468 if tag[0].startswith(dest):
459 if tag[0].startswith(dest):
469 tagpath = source + tag[0][len(dest):]
460 tagpath = source + tag[0][len(dest):]
470 tag[:2] = [tagpath, sourcerev]
461 tag[:2] = [tagpath, sourcerev]
471 break
462 break
472 else:
463 else:
473 pendings.append([source, sourcerev, dest])
464 pendings.append([source, sourcerev, dest])
474
465
475 # Filter out tags with children coming from different
466 # Filter out tags with children coming from different
476 # parts of the repository like:
467 # parts of the repository like:
477 # /tags/tag.1 (from /trunk:10)
468 # /tags/tag.1 (from /trunk:10)
478 # /tags/tag.1/foo (from /branches/foo:12)
469 # /tags/tag.1/foo (from /branches/foo:12)
479 # Here/tags/tag.1 discarded as well as its children.
470 # Here/tags/tag.1 discarded as well as its children.
480 # It happens with tools like cvs2svn. Such tags cannot
471 # It happens with tools like cvs2svn. Such tags cannot
481 # be represented in mercurial.
472 # be represented in mercurial.
482 addeds = dict((p, e.copyfrom_path) for p, e
473 addeds = dict((p, e.copyfrom_path) for p, e
483 in origpaths.iteritems()
474 in origpaths.iteritems()
484 if e.action == 'A' and e.copyfrom_path)
475 if e.action == 'A' and e.copyfrom_path)
485 badroots = set()
476 badroots = set()
486 for destroot in addeds:
477 for destroot in addeds:
487 for source, sourcerev, dest in pendings:
478 for source, sourcerev, dest in pendings:
488 if (not dest.startswith(destroot + '/')
479 if (not dest.startswith(destroot + '/')
489 or source.startswith(addeds[destroot] + '/')):
480 or source.startswith(addeds[destroot] + '/')):
490 continue
481 continue
491 badroots.add(destroot)
482 badroots.add(destroot)
492 break
483 break
493
484
494 for badroot in badroots:
485 for badroot in badroots:
495 pendings = [p for p in pendings if p[2] != badroot
486 pendings = [p for p in pendings if p[2] != badroot
496 and not p[2].startswith(badroot + '/')]
487 and not p[2].startswith(badroot + '/')]
497
488
498 # Tell tag renamings from tag creations
489 # Tell tag renamings from tag creations
499 remainings = []
490 remainings = []
500 for source, sourcerev, dest in pendings:
491 for source, sourcerev, dest in pendings:
501 tagname = dest.split('/')[-1]
492 tagname = dest.split('/')[-1]
502 if source.startswith(srctagspath):
493 if source.startswith(srctagspath):
503 remainings.append([source, sourcerev, tagname])
494 remainings.append([source, sourcerev, tagname])
504 continue
495 continue
505 if tagname in tags:
496 if tagname in tags:
506 # Keep the latest tag value
497 # Keep the latest tag value
507 continue
498 continue
508 # From revision may be fake, get one with changes
499 # From revision may be fake, get one with changes
509 try:
500 try:
510 tagid = self.latest(source, sourcerev)
501 tagid = self.latest(source, sourcerev)
511 if tagid and tagname not in tags:
502 if tagid and tagname not in tags:
512 tags[tagname] = tagid
503 tags[tagname] = tagid
513 except SvnPathNotFound:
504 except SvnPathNotFound:
514 # It happens when we are following directories
505 # It happens when we are following directories
515 # we assumed were copied with their parents
506 # we assumed were copied with their parents
516 # but were really created in the tag
507 # but were really created in the tag
517 # directory.
508 # directory.
518 pass
509 pass
519 pendings = remainings
510 pendings = remainings
520 tagspath = srctagspath
511 tagspath = srctagspath
521
512
522 except SubversionException:
513 except SubversionException:
523 self.ui.note(_('no tags found at revision %d\n') % start)
514 self.ui.note(_('no tags found at revision %d\n') % start)
524 return tags
515 return tags
525
516
526 def converted(self, rev, destrev):
517 def converted(self, rev, destrev):
527 if not self.wc:
518 if not self.wc:
528 return
519 return
529 if self.convertfp is None:
520 if self.convertfp is None:
530 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
521 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
531 'a')
522 'a')
532 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
523 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
533 self.convertfp.flush()
524 self.convertfp.flush()
534
525
535 def revid(self, revnum, module=None):
526 def revid(self, revnum, module=None):
536 return 'svn:%s%s@%s' % (self.uuid, module or self.module, revnum)
527 return 'svn:%s%s@%s' % (self.uuid, module or self.module, revnum)
537
528
538 def revnum(self, rev):
529 def revnum(self, rev):
539 return int(rev.split('@')[-1])
530 return int(rev.split('@')[-1])
540
531
541 def revsplit(self, rev):
532 def revsplit(self, rev):
542 url, revnum = rev.rsplit('@', 1)
533 url, revnum = rev.rsplit('@', 1)
543 revnum = int(revnum)
534 revnum = int(revnum)
544 parts = url.split('/', 1)
535 parts = url.split('/', 1)
545 uuid = parts.pop(0)[4:]
536 uuid = parts.pop(0)[4:]
546 mod = ''
537 mod = ''
547 if parts:
538 if parts:
548 mod = '/' + parts[0]
539 mod = '/' + parts[0]
549 return uuid, mod, revnum
540 return uuid, mod, revnum
550
541
551 def latest(self, path, stop=0):
542 def latest(self, path, stop=0):
552 """Find the latest revid affecting path, up to stop. It may return
543 """Find the latest revid affecting path, up to stop. It may return
553 a revision in a different module, since a branch may be moved without
544 a revision in a different module, since a branch may be moved without
554 a change being reported. Return None if computed module does not
545 a change being reported. Return None if computed module does not
555 belong to rootmodule subtree.
546 belong to rootmodule subtree.
556 """
547 """
557 if not path.startswith(self.rootmodule):
548 if not path.startswith(self.rootmodule):
558 # Requests on foreign branches may be forbidden at server level
549 # Requests on foreign branches may be forbidden at server level
559 self.ui.debug('ignoring foreign branch %r\n' % path)
550 self.ui.debug('ignoring foreign branch %r\n' % path)
560 return None
551 return None
561
552
562 if not stop:
553 if not stop:
563 stop = svn.ra.get_latest_revnum(self.ra)
554 stop = svn.ra.get_latest_revnum(self.ra)
564 try:
555 try:
565 prevmodule = self.reparent('')
556 prevmodule = self.reparent('')
566 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
557 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
567 self.reparent(prevmodule)
558 self.reparent(prevmodule)
568 except SubversionException:
559 except SubversionException:
569 dirent = None
560 dirent = None
570 if not dirent:
561 if not dirent:
571 raise SvnPathNotFound(_('%s not found up to revision %d')
562 raise SvnPathNotFound(_('%s not found up to revision %d')
572 % (path, stop))
563 % (path, stop))
573
564
574 # stat() gives us the previous revision on this line of
565 # stat() gives us the previous revision on this line of
575 # development, but it might be in *another module*. Fetch the
566 # development, but it might be in *another module*. Fetch the
576 # log and detect renames down to the latest revision.
567 # log and detect renames down to the latest revision.
577 stream = self._getlog([path], stop, dirent.created_rev)
568 stream = self._getlog([path], stop, dirent.created_rev)
578 try:
569 try:
579 for entry in stream:
570 for entry in stream:
580 paths, revnum, author, date, message = entry
571 paths, revnum, author, date, message = entry
581 if revnum <= dirent.created_rev:
572 if revnum <= dirent.created_rev:
582 break
573 break
583
574
584 for p in paths:
575 for p in paths:
585 if not path.startswith(p) or not paths[p].copyfrom_path:
576 if not path.startswith(p) or not paths[p].copyfrom_path:
586 continue
577 continue
587 newpath = paths[p].copyfrom_path + path[len(p):]
578 newpath = paths[p].copyfrom_path + path[len(p):]
588 self.ui.debug("branch renamed from %s to %s at %d\n" %
579 self.ui.debug("branch renamed from %s to %s at %d\n" %
589 (path, newpath, revnum))
580 (path, newpath, revnum))
590 path = newpath
581 path = newpath
591 break
582 break
592 finally:
583 finally:
593 stream.close()
584 stream.close()
594
585
595 if not path.startswith(self.rootmodule):
586 if not path.startswith(self.rootmodule):
596 self.ui.debug('ignoring foreign branch %r\n' % path)
587 self.ui.debug('ignoring foreign branch %r\n' % path)
597 return None
588 return None
598 return self.revid(dirent.created_rev, path)
589 return self.revid(dirent.created_rev, path)
599
590
600 def reparent(self, module):
591 def reparent(self, module):
601 """Reparent the svn transport and return the previous parent."""
592 """Reparent the svn transport and return the previous parent."""
602 if self.prevmodule == module:
593 if self.prevmodule == module:
603 return module
594 return module
604 svnurl = self.baseurl + urllib.quote(module)
595 svnurl = self.baseurl + urllib.quote(module)
605 prevmodule = self.prevmodule
596 prevmodule = self.prevmodule
606 if prevmodule is None:
597 if prevmodule is None:
607 prevmodule = ''
598 prevmodule = ''
608 self.ui.debug("reparent to %s\n" % svnurl)
599 self.ui.debug("reparent to %s\n" % svnurl)
609 svn.ra.reparent(self.ra, svnurl)
600 svn.ra.reparent(self.ra, svnurl)
610 self.prevmodule = module
601 self.prevmodule = module
611 return prevmodule
602 return prevmodule
612
603
613 def expandpaths(self, rev, paths, parents):
604 def expandpaths(self, rev, paths, parents):
614 changed, removed = set(), set()
605 changed, removed = set(), set()
615 copies = {}
606 copies = {}
616
607
617 new_module, revnum = self.revsplit(rev)[1:]
608 new_module, revnum = self.revsplit(rev)[1:]
618 if new_module != self.module:
609 if new_module != self.module:
619 self.module = new_module
610 self.module = new_module
620 self.reparent(self.module)
611 self.reparent(self.module)
621
612
622 for path, ent in paths:
613 for path, ent in paths:
623 entrypath = self.getrelpath(path)
614 entrypath = self.getrelpath(path)
624
615
625 kind = self._checkpath(entrypath, revnum)
616 kind = self._checkpath(entrypath, revnum)
626 if kind == svn.core.svn_node_file:
617 if kind == svn.core.svn_node_file:
627 changed.add(self.recode(entrypath))
618 changed.add(self.recode(entrypath))
628 if not ent.copyfrom_path or not parents:
619 if not ent.copyfrom_path or not parents:
629 continue
620 continue
630 # Copy sources not in parent revisions cannot be
621 # Copy sources not in parent revisions cannot be
631 # represented, ignore their origin for now
622 # represented, ignore their origin for now
632 pmodule, prevnum = self.revsplit(parents[0])[1:]
623 pmodule, prevnum = self.revsplit(parents[0])[1:]
633 if ent.copyfrom_rev < prevnum:
624 if ent.copyfrom_rev < prevnum:
634 continue
625 continue
635 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
626 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
636 if not copyfrom_path:
627 if not copyfrom_path:
637 continue
628 continue
638 self.ui.debug("copied to %s from %s@%s\n" %
629 self.ui.debug("copied to %s from %s@%s\n" %
639 (entrypath, copyfrom_path, ent.copyfrom_rev))
630 (entrypath, copyfrom_path, ent.copyfrom_rev))
640 copies[self.recode(entrypath)] = self.recode(copyfrom_path)
631 copies[self.recode(entrypath)] = self.recode(copyfrom_path)
641 elif kind == 0: # gone, but had better be a deleted *file*
632 elif kind == 0: # gone, but had better be a deleted *file*
642 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
633 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
643 pmodule, prevnum = self.revsplit(parents[0])[1:]
634 pmodule, prevnum = self.revsplit(parents[0])[1:]
644 parentpath = pmodule + "/" + entrypath
635 parentpath = pmodule + "/" + entrypath
645 fromkind = self._checkpath(entrypath, prevnum, pmodule)
636 fromkind = self._checkpath(entrypath, prevnum, pmodule)
646
637
647 if fromkind == svn.core.svn_node_file:
638 if fromkind == svn.core.svn_node_file:
648 removed.add(self.recode(entrypath))
639 removed.add(self.recode(entrypath))
649 elif fromkind == svn.core.svn_node_dir:
640 elif fromkind == svn.core.svn_node_dir:
650 oroot = parentpath.strip('/')
641 oroot = parentpath.strip('/')
651 nroot = path.strip('/')
642 nroot = path.strip('/')
652 children = self._iterfiles(oroot, prevnum)
643 children = self._iterfiles(oroot, prevnum)
653 for childpath in children:
644 for childpath in children:
654 childpath = childpath.replace(oroot, nroot)
645 childpath = childpath.replace(oroot, nroot)
655 childpath = self.getrelpath("/" + childpath, pmodule)
646 childpath = self.getrelpath("/" + childpath, pmodule)
656 if childpath:
647 if childpath:
657 removed.add(self.recode(childpath))
648 removed.add(self.recode(childpath))
658 else:
649 else:
659 self.ui.debug('unknown path in revision %d: %s\n' % \
650 self.ui.debug('unknown path in revision %d: %s\n' % \
660 (revnum, path))
651 (revnum, path))
661 elif kind == svn.core.svn_node_dir:
652 elif kind == svn.core.svn_node_dir:
662 if ent.action == 'M':
653 if ent.action == 'M':
663 # If the directory just had a prop change,
654 # If the directory just had a prop change,
664 # then we shouldn't need to look for its children.
655 # then we shouldn't need to look for its children.
665 continue
656 continue
666 elif ent.action == 'R' and parents:
657 elif ent.action == 'R' and parents:
667 # If a directory is replacing a file, mark the previous
658 # If a directory is replacing a file, mark the previous
668 # file as deleted
659 # file as deleted
669 pmodule, prevnum = self.revsplit(parents[0])[1:]
660 pmodule, prevnum = self.revsplit(parents[0])[1:]
670 pkind = self._checkpath(entrypath, prevnum, pmodule)
661 pkind = self._checkpath(entrypath, prevnum, pmodule)
671 if pkind == svn.core.svn_node_file:
662 if pkind == svn.core.svn_node_file:
672 removed.add(self.recode(entrypath))
663 removed.add(self.recode(entrypath))
673
664
674 for childpath in self._iterfiles(path, revnum):
665 for childpath in self._iterfiles(path, revnum):
675 childpath = self.getrelpath("/" + childpath)
666 childpath = self.getrelpath("/" + childpath)
676 if childpath:
667 if childpath:
677 changed.add(self.recode(childpath))
668 changed.add(self.recode(childpath))
678
669
679 # Handle directory copies
670 # Handle directory copies
680 if not ent.copyfrom_path or not parents:
671 if not ent.copyfrom_path or not parents:
681 continue
672 continue
682 # Copy sources not in parent revisions cannot be
673 # Copy sources not in parent revisions cannot be
683 # represented, ignore their origin for now
674 # represented, ignore their origin for now
684 pmodule, prevnum = self.revsplit(parents[0])[1:]
675 pmodule, prevnum = self.revsplit(parents[0])[1:]
685 if ent.copyfrom_rev < prevnum:
676 if ent.copyfrom_rev < prevnum:
686 continue
677 continue
687 copyfrompath = self.getrelpath(ent.copyfrom_path, pmodule)
678 copyfrompath = self.getrelpath(ent.copyfrom_path, pmodule)
688 if not copyfrompath:
679 if not copyfrompath:
689 continue
680 continue
690 self.ui.debug("mark %s came from %s:%d\n"
681 self.ui.debug("mark %s came from %s:%d\n"
691 % (path, copyfrompath, ent.copyfrom_rev))
682 % (path, copyfrompath, ent.copyfrom_rev))
692 children = self._iterfiles(ent.copyfrom_path, ent.copyfrom_rev)
683 children = self._iterfiles(ent.copyfrom_path, ent.copyfrom_rev)
693 for childpath in children:
684 for childpath in children:
694 childpath = self.getrelpath("/" + childpath, pmodule)
685 childpath = self.getrelpath("/" + childpath, pmodule)
695 if not childpath:
686 if not childpath:
696 continue
687 continue
697 copytopath = path + childpath[len(copyfrompath):]
688 copytopath = path + childpath[len(copyfrompath):]
698 copytopath = self.getrelpath(copytopath)
689 copytopath = self.getrelpath(copytopath)
699 copies[self.recode(copytopath)] = self.recode(childpath)
690 copies[self.recode(copytopath)] = self.recode(childpath)
700
691
701 changed.update(removed)
692 changed.update(removed)
702 return (list(changed), removed, copies)
693 return (list(changed), removed, copies)
703
694
704 def _fetch_revisions(self, from_revnum, to_revnum):
695 def _fetch_revisions(self, from_revnum, to_revnum):
705 if from_revnum < to_revnum:
696 if from_revnum < to_revnum:
706 from_revnum, to_revnum = to_revnum, from_revnum
697 from_revnum, to_revnum = to_revnum, from_revnum
707
698
708 self.child_cset = None
699 self.child_cset = None
709
700
710 def parselogentry(orig_paths, revnum, author, date, message):
701 def parselogentry(orig_paths, revnum, author, date, message):
711 """Return the parsed commit object or None, and True if
702 """Return the parsed commit object or None, and True if
712 the revision is a branch root.
703 the revision is a branch root.
713 """
704 """
714 self.ui.debug("parsing revision %d (%d changes)\n" %
705 self.ui.debug("parsing revision %d (%d changes)\n" %
715 (revnum, len(orig_paths)))
706 (revnum, len(orig_paths)))
716
707
717 branched = False
708 branched = False
718 rev = self.revid(revnum)
709 rev = self.revid(revnum)
719 # branch log might return entries for a parent we already have
710 # branch log might return entries for a parent we already have
720
711
721 if rev in self.commits or revnum < to_revnum:
712 if rev in self.commits or revnum < to_revnum:
722 return None, branched
713 return None, branched
723
714
724 parents = []
715 parents = []
725 # check whether this revision is the start of a branch or part
716 # check whether this revision is the start of a branch or part
726 # of a branch renaming
717 # of a branch renaming
727 orig_paths = sorted(orig_paths.iteritems())
718 orig_paths = sorted(orig_paths.iteritems())
728 root_paths = [(p, e) for p, e in orig_paths
719 root_paths = [(p, e) for p, e in orig_paths
729 if self.module.startswith(p)]
720 if self.module.startswith(p)]
730 if root_paths:
721 if root_paths:
731 path, ent = root_paths[-1]
722 path, ent = root_paths[-1]
732 if ent.copyfrom_path:
723 if ent.copyfrom_path:
733 branched = True
724 branched = True
734 newpath = ent.copyfrom_path + self.module[len(path):]
725 newpath = ent.copyfrom_path + self.module[len(path):]
735 # ent.copyfrom_rev may not be the actual last revision
726 # ent.copyfrom_rev may not be the actual last revision
736 previd = self.latest(newpath, ent.copyfrom_rev)
727 previd = self.latest(newpath, ent.copyfrom_rev)
737 if previd is not None:
728 if previd is not None:
738 prevmodule, prevnum = self.revsplit(previd)[1:]
729 prevmodule, prevnum = self.revsplit(previd)[1:]
739 if prevnum >= self.startrev:
730 if prevnum >= self.startrev:
740 parents = [previd]
731 parents = [previd]
741 self.ui.note(
732 self.ui.note(
742 _('found parent of branch %s at %d: %s\n') %
733 _('found parent of branch %s at %d: %s\n') %
743 (self.module, prevnum, prevmodule))
734 (self.module, prevnum, prevmodule))
744 else:
735 else:
745 self.ui.debug("no copyfrom path, don't know what to do.\n")
736 self.ui.debug("no copyfrom path, don't know what to do.\n")
746
737
747 paths = []
738 paths = []
748 # filter out unrelated paths
739 # filter out unrelated paths
749 for path, ent in orig_paths:
740 for path, ent in orig_paths:
750 if self.getrelpath(path) is None:
741 if self.getrelpath(path) is None:
751 continue
742 continue
752 paths.append((path, ent))
743 paths.append((path, ent))
753
744
754 # Example SVN datetime. Includes microseconds.
745 # Example SVN datetime. Includes microseconds.
755 # ISO-8601 conformant
746 # ISO-8601 conformant
756 # '2007-01-04T17:35:00.902377Z'
747 # '2007-01-04T17:35:00.902377Z'
757 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
748 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
758
749
759 log = message and self.recode(message) or ''
750 log = message and self.recode(message) or ''
760 author = author and self.recode(author) or ''
751 author = author and self.recode(author) or ''
761 try:
752 try:
762 branch = self.module.split("/")[-1]
753 branch = self.module.split("/")[-1]
763 if branch == 'trunk':
754 if branch == 'trunk':
764 branch = ''
755 branch = ''
765 except IndexError:
756 except IndexError:
766 branch = None
757 branch = None
767
758
768 cset = commit(author=author,
759 cset = commit(author=author,
769 date=util.datestr(date),
760 date=util.datestr(date),
770 desc=log,
761 desc=log,
771 parents=parents,
762 parents=parents,
772 branch=branch,
763 branch=branch,
773 rev=rev)
764 rev=rev)
774
765
775 self.commits[rev] = cset
766 self.commits[rev] = cset
776 # The parents list is *shared* among self.paths and the
767 # The parents list is *shared* among self.paths and the
777 # commit object. Both will be updated below.
768 # commit object. Both will be updated below.
778 self.paths[rev] = (paths, cset.parents)
769 self.paths[rev] = (paths, cset.parents)
779 if self.child_cset and not self.child_cset.parents:
770 if self.child_cset and not self.child_cset.parents:
780 self.child_cset.parents[:] = [rev]
771 self.child_cset.parents[:] = [rev]
781 self.child_cset = cset
772 self.child_cset = cset
782 return cset, branched
773 return cset, branched
783
774
784 self.ui.note(_('fetching revision log for "%s" from %d to %d\n') %
775 self.ui.note(_('fetching revision log for "%s" from %d to %d\n') %
785 (self.module, from_revnum, to_revnum))
776 (self.module, from_revnum, to_revnum))
786
777
787 try:
778 try:
788 firstcset = None
779 firstcset = None
789 lastonbranch = False
780 lastonbranch = False
790 stream = self._getlog([self.module], from_revnum, to_revnum)
781 stream = self._getlog([self.module], from_revnum, to_revnum)
791 try:
782 try:
792 for entry in stream:
783 for entry in stream:
793 paths, revnum, author, date, message = entry
784 paths, revnum, author, date, message = entry
794 if revnum < self.startrev:
785 if revnum < self.startrev:
795 lastonbranch = True
786 lastonbranch = True
796 break
787 break
797 if not paths:
788 if not paths:
798 self.ui.debug('revision %d has no entries\n' % revnum)
789 self.ui.debug('revision %d has no entries\n' % revnum)
799 # If we ever leave the loop on an empty
790 # If we ever leave the loop on an empty
800 # revision, do not try to get a parent branch
791 # revision, do not try to get a parent branch
801 lastonbranch = lastonbranch or revnum == 0
792 lastonbranch = lastonbranch or revnum == 0
802 continue
793 continue
803 cset, lastonbranch = parselogentry(paths, revnum, author,
794 cset, lastonbranch = parselogentry(paths, revnum, author,
804 date, message)
795 date, message)
805 if cset:
796 if cset:
806 firstcset = cset
797 firstcset = cset
807 if lastonbranch:
798 if lastonbranch:
808 break
799 break
809 finally:
800 finally:
810 stream.close()
801 stream.close()
811
802
812 if not lastonbranch and firstcset and not firstcset.parents:
803 if not lastonbranch and firstcset and not firstcset.parents:
813 # The first revision of the sequence (the last fetched one)
804 # The first revision of the sequence (the last fetched one)
814 # has invalid parents if not a branch root. Find the parent
805 # has invalid parents if not a branch root. Find the parent
815 # revision now, if any.
806 # revision now, if any.
816 try:
807 try:
817 firstrevnum = self.revnum(firstcset.rev)
808 firstrevnum = self.revnum(firstcset.rev)
818 if firstrevnum > 1:
809 if firstrevnum > 1:
819 latest = self.latest(self.module, firstrevnum - 1)
810 latest = self.latest(self.module, firstrevnum - 1)
820 if latest:
811 if latest:
821 firstcset.parents.append(latest)
812 firstcset.parents.append(latest)
822 except SvnPathNotFound:
813 except SvnPathNotFound:
823 pass
814 pass
824 except SubversionException, (inst, num):
815 except SubversionException, (inst, num):
825 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
816 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
826 raise util.Abort(_('svn: branch has no revision %s') % to_revnum)
817 raise util.Abort(_('svn: branch has no revision %s') % to_revnum)
827 raise
818 raise
828
819
829 def _getfile(self, file, rev):
820 def getfile(self, file, rev):
830 # TODO: ra.get_file transmits the whole file instead of diffs.
821 # TODO: ra.get_file transmits the whole file instead of diffs.
831 if file in self.removed:
822 if file in self.removed:
832 raise IOError()
823 raise IOError()
833 mode = ''
824 mode = ''
834 try:
825 try:
835 new_module, revnum = self.revsplit(rev)[1:]
826 new_module, revnum = self.revsplit(rev)[1:]
836 if self.module != new_module:
827 if self.module != new_module:
837 self.module = new_module
828 self.module = new_module
838 self.reparent(self.module)
829 self.reparent(self.module)
839 io = StringIO()
830 io = StringIO()
840 info = svn.ra.get_file(self.ra, file, revnum, io)
831 info = svn.ra.get_file(self.ra, file, revnum, io)
841 data = io.getvalue()
832 data = io.getvalue()
842 # ra.get_files() seems to keep a reference on the input buffer
833 # ra.get_files() seems to keep a reference on the input buffer
843 # preventing collection. Release it explicitely.
834 # preventing collection. Release it explicitely.
844 io.close()
835 io.close()
845 if isinstance(info, list):
836 if isinstance(info, list):
846 info = info[-1]
837 info = info[-1]
847 mode = ("svn:executable" in info) and 'x' or ''
838 mode = ("svn:executable" in info) and 'x' or ''
848 mode = ("svn:special" in info) and 'l' or mode
839 mode = ("svn:special" in info) and 'l' or mode
849 except SubversionException, e:
840 except SubversionException, e:
850 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
841 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
851 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
842 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
852 if e.apr_err in notfound: # File not found
843 if e.apr_err in notfound: # File not found
853 raise IOError()
844 raise IOError()
854 raise
845 raise
855 if mode == 'l':
846 if mode == 'l':
856 link_prefix = "link "
847 link_prefix = "link "
857 if data.startswith(link_prefix):
848 if data.startswith(link_prefix):
858 data = data[len(link_prefix):]
849 data = data[len(link_prefix):]
859 return data, mode
850 return data, mode
860
851
861 def _iterfiles(self, path, revnum):
852 def _iterfiles(self, path, revnum):
862 """Enumerate all files in path at revnum, recursively."""
853 """Enumerate all files in path at revnum, recursively."""
863 path = path.strip('/')
854 path = path.strip('/')
864 pool = Pool()
855 pool = Pool()
865 rpath = '/'.join([self.baseurl, urllib.quote(path)]).strip('/')
856 rpath = '/'.join([self.baseurl, urllib.quote(path)]).strip('/')
866 entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool)
857 entries = svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool)
867 return ((path + '/' + p) for p, e in entries.iteritems()
858 return ((path + '/' + p) for p, e in entries.iteritems()
868 if e.kind == svn.core.svn_node_file)
859 if e.kind == svn.core.svn_node_file)
869
860
870 def getrelpath(self, path, module=None):
861 def getrelpath(self, path, module=None):
871 if module is None:
862 if module is None:
872 module = self.module
863 module = self.module
873 # Given the repository url of this wc, say
864 # Given the repository url of this wc, say
874 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
865 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
875 # extract the "entry" portion (a relative path) from what
866 # extract the "entry" portion (a relative path) from what
876 # svn log --xml says, ie
867 # svn log --xml says, ie
877 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
868 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
878 # that is to say "tests/PloneTestCase.py"
869 # that is to say "tests/PloneTestCase.py"
879 if path.startswith(module):
870 if path.startswith(module):
880 relative = path.rstrip('/')[len(module):]
871 relative = path.rstrip('/')[len(module):]
881 if relative.startswith('/'):
872 if relative.startswith('/'):
882 return relative[1:]
873 return relative[1:]
883 elif relative == '':
874 elif relative == '':
884 return relative
875 return relative
885
876
886 # The path is outside our tracked tree...
877 # The path is outside our tracked tree...
887 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
878 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
888 return None
879 return None
889
880
890 def _checkpath(self, path, revnum, module=None):
881 def _checkpath(self, path, revnum, module=None):
891 if module is not None:
882 if module is not None:
892 prevmodule = self.reparent('')
883 prevmodule = self.reparent('')
893 path = module + '/' + path
884 path = module + '/' + path
894 try:
885 try:
895 # ra.check_path does not like leading slashes very much, it leads
886 # ra.check_path does not like leading slashes very much, it leads
896 # to PROPFIND subversion errors
887 # to PROPFIND subversion errors
897 return svn.ra.check_path(self.ra, path.strip('/'), revnum)
888 return svn.ra.check_path(self.ra, path.strip('/'), revnum)
898 finally:
889 finally:
899 if module is not None:
890 if module is not None:
900 self.reparent(prevmodule)
891 self.reparent(prevmodule)
901
892
902 def _getlog(self, paths, start, end, limit=0, discover_changed_paths=True,
893 def _getlog(self, paths, start, end, limit=0, discover_changed_paths=True,
903 strict_node_history=False):
894 strict_node_history=False):
904 # Normalize path names, svn >= 1.5 only wants paths relative to
895 # Normalize path names, svn >= 1.5 only wants paths relative to
905 # supplied URL
896 # supplied URL
906 relpaths = []
897 relpaths = []
907 for p in paths:
898 for p in paths:
908 if not p.startswith('/'):
899 if not p.startswith('/'):
909 p = self.module + '/' + p
900 p = self.module + '/' + p
910 relpaths.append(p.strip('/'))
901 relpaths.append(p.strip('/'))
911 args = [self.baseurl, relpaths, start, end, limit, discover_changed_paths,
902 args = [self.baseurl, relpaths, start, end, limit, discover_changed_paths,
912 strict_node_history]
903 strict_node_history]
913 arg = encodeargs(args)
904 arg = encodeargs(args)
914 hgexe = util.hgexecutable()
905 hgexe = util.hgexecutable()
915 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
906 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
916 stdin, stdout = util.popen2(cmd)
907 stdin, stdout = util.popen2(cmd)
917 stdin.write(arg)
908 stdin.write(arg)
918 try:
909 try:
919 stdin.close()
910 stdin.close()
920 except IOError:
911 except IOError:
921 raise util.Abort(_('Mercurial failed to run itself, check'
912 raise util.Abort(_('Mercurial failed to run itself, check'
922 ' hg executable is in PATH'))
913 ' hg executable is in PATH'))
923 return logstream(stdout)
914 return logstream(stdout)
924
915
925 pre_revprop_change = '''#!/bin/sh
916 pre_revprop_change = '''#!/bin/sh
926
917
927 REPOS="$1"
918 REPOS="$1"
928 REV="$2"
919 REV="$2"
929 USER="$3"
920 USER="$3"
930 PROPNAME="$4"
921 PROPNAME="$4"
931 ACTION="$5"
922 ACTION="$5"
932
923
933 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
924 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
934 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
925 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
935 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
926 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
936
927
937 echo "Changing prohibited revision property" >&2
928 echo "Changing prohibited revision property" >&2
938 exit 1
929 exit 1
939 '''
930 '''
940
931
941 class svn_sink(converter_sink, commandline):
932 class svn_sink(converter_sink, commandline):
942 commit_re = re.compile(r'Committed revision (\d+).', re.M)
933 commit_re = re.compile(r'Committed revision (\d+).', re.M)
943
934
944 def prerun(self):
935 def prerun(self):
945 if self.wc:
936 if self.wc:
946 os.chdir(self.wc)
937 os.chdir(self.wc)
947
938
948 def postrun(self):
939 def postrun(self):
949 if self.wc:
940 if self.wc:
950 os.chdir(self.cwd)
941 os.chdir(self.cwd)
951
942
952 def join(self, name):
943 def join(self, name):
953 return os.path.join(self.wc, '.svn', name)
944 return os.path.join(self.wc, '.svn', name)
954
945
955 def revmapfile(self):
946 def revmapfile(self):
956 return self.join('hg-shamap')
947 return self.join('hg-shamap')
957
948
958 def authorfile(self):
949 def authorfile(self):
959 return self.join('hg-authormap')
950 return self.join('hg-authormap')
960
951
961 def __init__(self, ui, path):
952 def __init__(self, ui, path):
962 converter_sink.__init__(self, ui, path)
953 converter_sink.__init__(self, ui, path)
963 commandline.__init__(self, ui, 'svn')
954 commandline.__init__(self, ui, 'svn')
964 self.delete = []
955 self.delete = []
965 self.setexec = []
956 self.setexec = []
966 self.delexec = []
957 self.delexec = []
967 self.copies = []
958 self.copies = []
968 self.wc = None
959 self.wc = None
969 self.cwd = os.getcwd()
960 self.cwd = os.getcwd()
970
961
971 path = os.path.realpath(path)
962 path = os.path.realpath(path)
972
963
973 created = False
964 created = False
974 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
965 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
975 self.wc = path
966 self.wc = path
976 self.run0('update')
967 self.run0('update')
977 else:
968 else:
978 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
969 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
979
970
980 if os.path.isdir(os.path.dirname(path)):
971 if os.path.isdir(os.path.dirname(path)):
981 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
972 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
982 ui.status(_('initializing svn repository %r\n') %
973 ui.status(_('initializing svn repository %r\n') %
983 os.path.basename(path))
974 os.path.basename(path))
984 commandline(ui, 'svnadmin').run0('create', path)
975 commandline(ui, 'svnadmin').run0('create', path)
985 created = path
976 created = path
986 path = util.normpath(path)
977 path = util.normpath(path)
987 if not path.startswith('/'):
978 if not path.startswith('/'):
988 path = '/' + path
979 path = '/' + path
989 path = 'file://' + path
980 path = 'file://' + path
990
981
991 ui.status(_('initializing svn working copy %r\n')
982 ui.status(_('initializing svn working copy %r\n')
992 % os.path.basename(wcpath))
983 % os.path.basename(wcpath))
993 self.run0('checkout', path, wcpath)
984 self.run0('checkout', path, wcpath)
994
985
995 self.wc = wcpath
986 self.wc = wcpath
996 self.opener = util.opener(self.wc)
987 self.opener = util.opener(self.wc)
997 self.wopener = util.opener(self.wc)
988 self.wopener = util.opener(self.wc)
998 self.childmap = mapfile(ui, self.join('hg-childmap'))
989 self.childmap = mapfile(ui, self.join('hg-childmap'))
999 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
990 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
1000
991
1001 if created:
992 if created:
1002 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
993 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
1003 fp = open(hook, 'w')
994 fp = open(hook, 'w')
1004 fp.write(pre_revprop_change)
995 fp.write(pre_revprop_change)
1005 fp.close()
996 fp.close()
1006 util.set_flags(hook, False, True)
997 util.set_flags(hook, False, True)
1007
998
1008 xport = transport.SvnRaTransport(url=geturl(path))
999 xport = transport.SvnRaTransport(url=geturl(path))
1009 self.uuid = svn.ra.get_uuid(xport.ra)
1000 self.uuid = svn.ra.get_uuid(xport.ra)
1010
1001
1011 def wjoin(self, *names):
1002 def wjoin(self, *names):
1012 return os.path.join(self.wc, *names)
1003 return os.path.join(self.wc, *names)
1013
1004
1014 def putfile(self, filename, flags, data):
1005 def putfile(self, filename, flags, data):
1015 if 'l' in flags:
1006 if 'l' in flags:
1016 self.wopener.symlink(data, filename)
1007 self.wopener.symlink(data, filename)
1017 else:
1008 else:
1018 try:
1009 try:
1019 if os.path.islink(self.wjoin(filename)):
1010 if os.path.islink(self.wjoin(filename)):
1020 os.unlink(filename)
1011 os.unlink(filename)
1021 except OSError:
1012 except OSError:
1022 pass
1013 pass
1023 self.wopener(filename, 'w').write(data)
1014 self.wopener(filename, 'w').write(data)
1024
1015
1025 if self.is_exec:
1016 if self.is_exec:
1026 was_exec = self.is_exec(self.wjoin(filename))
1017 was_exec = self.is_exec(self.wjoin(filename))
1027 else:
1018 else:
1028 # On filesystems not supporting execute-bit, there is no way
1019 # On filesystems not supporting execute-bit, there is no way
1029 # to know if it is set but asking subversion. Setting it
1020 # to know if it is set but asking subversion. Setting it
1030 # systematically is just as expensive and much simpler.
1021 # systematically is just as expensive and much simpler.
1031 was_exec = 'x' not in flags
1022 was_exec = 'x' not in flags
1032
1023
1033 util.set_flags(self.wjoin(filename), False, 'x' in flags)
1024 util.set_flags(self.wjoin(filename), False, 'x' in flags)
1034 if was_exec:
1025 if was_exec:
1035 if 'x' not in flags:
1026 if 'x' not in flags:
1036 self.delexec.append(filename)
1027 self.delexec.append(filename)
1037 else:
1028 else:
1038 if 'x' in flags:
1029 if 'x' in flags:
1039 self.setexec.append(filename)
1030 self.setexec.append(filename)
1040
1031
1041 def _copyfile(self, source, dest):
1032 def _copyfile(self, source, dest):
1042 # SVN's copy command pukes if the destination file exists, but
1033 # SVN's copy command pukes if the destination file exists, but
1043 # our copyfile method expects to record a copy that has
1034 # our copyfile method expects to record a copy that has
1044 # already occurred. Cross the semantic gap.
1035 # already occurred. Cross the semantic gap.
1045 wdest = self.wjoin(dest)
1036 wdest = self.wjoin(dest)
1046 exists = os.path.exists(wdest)
1037 exists = os.path.exists(wdest)
1047 if exists:
1038 if exists:
1048 fd, tempname = tempfile.mkstemp(
1039 fd, tempname = tempfile.mkstemp(
1049 prefix='hg-copy-', dir=os.path.dirname(wdest))
1040 prefix='hg-copy-', dir=os.path.dirname(wdest))
1050 os.close(fd)
1041 os.close(fd)
1051 os.unlink(tempname)
1042 os.unlink(tempname)
1052 os.rename(wdest, tempname)
1043 os.rename(wdest, tempname)
1053 try:
1044 try:
1054 self.run0('copy', source, dest)
1045 self.run0('copy', source, dest)
1055 finally:
1046 finally:
1056 if exists:
1047 if exists:
1057 try:
1048 try:
1058 os.unlink(wdest)
1049 os.unlink(wdest)
1059 except OSError:
1050 except OSError:
1060 pass
1051 pass
1061 os.rename(tempname, wdest)
1052 os.rename(tempname, wdest)
1062
1053
1063 def dirs_of(self, files):
1054 def dirs_of(self, files):
1064 dirs = set()
1055 dirs = set()
1065 for f in files:
1056 for f in files:
1066 if os.path.isdir(self.wjoin(f)):
1057 if os.path.isdir(self.wjoin(f)):
1067 dirs.add(f)
1058 dirs.add(f)
1068 for i in strutil.rfindall(f, '/'):
1059 for i in strutil.rfindall(f, '/'):
1069 dirs.add(f[:i])
1060 dirs.add(f[:i])
1070 return dirs
1061 return dirs
1071
1062
1072 def add_dirs(self, files):
1063 def add_dirs(self, files):
1073 add_dirs = [d for d in sorted(self.dirs_of(files))
1064 add_dirs = [d for d in sorted(self.dirs_of(files))
1074 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
1065 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
1075 if add_dirs:
1066 if add_dirs:
1076 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
1067 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
1077 return add_dirs
1068 return add_dirs
1078
1069
1079 def add_files(self, files):
1070 def add_files(self, files):
1080 if files:
1071 if files:
1081 self.xargs(files, 'add', quiet=True)
1072 self.xargs(files, 'add', quiet=True)
1082 return files
1073 return files
1083
1074
1084 def tidy_dirs(self, names):
1075 def tidy_dirs(self, names):
1085 deleted = []
1076 deleted = []
1086 for d in sorted(self.dirs_of(names), reverse=True):
1077 for d in sorted(self.dirs_of(names), reverse=True):
1087 wd = self.wjoin(d)
1078 wd = self.wjoin(d)
1088 if os.listdir(wd) == '.svn':
1079 if os.listdir(wd) == '.svn':
1089 self.run0('delete', d)
1080 self.run0('delete', d)
1090 deleted.append(d)
1081 deleted.append(d)
1091 return deleted
1082 return deleted
1092
1083
1093 def addchild(self, parent, child):
1084 def addchild(self, parent, child):
1094 self.childmap[parent] = child
1085 self.childmap[parent] = child
1095
1086
1096 def revid(self, rev):
1087 def revid(self, rev):
1097 return u"svn:%s@%s" % (self.uuid, rev)
1088 return u"svn:%s@%s" % (self.uuid, rev)
1098
1089
1099 def putcommit(self, files, copies, parents, commit, source, revmap):
1090 def putcommit(self, files, copies, parents, commit, source, revmap):
1100 # Apply changes to working copy
1091 # Apply changes to working copy
1101 for f, v in files:
1092 for f, v in files:
1102 try:
1093 try:
1103 data = source.getfile(f, v)
1094 data, mode = source.getfile(f, v)
1104 except IOError:
1095 except IOError:
1105 self.delete.append(f)
1096 self.delete.append(f)
1106 else:
1097 else:
1107 e = source.getmode(f, v)
1098 self.putfile(f, mode, data)
1108 self.putfile(f, e, data)
1109 if f in copies:
1099 if f in copies:
1110 self.copies.append([copies[f], f])
1100 self.copies.append([copies[f], f])
1111 files = [f[0] for f in files]
1101 files = [f[0] for f in files]
1112
1102
1113 for parent in parents:
1103 for parent in parents:
1114 try:
1104 try:
1115 return self.revid(self.childmap[parent])
1105 return self.revid(self.childmap[parent])
1116 except KeyError:
1106 except KeyError:
1117 pass
1107 pass
1118 entries = set(self.delete)
1108 entries = set(self.delete)
1119 files = frozenset(files)
1109 files = frozenset(files)
1120 entries.update(self.add_dirs(files.difference(entries)))
1110 entries.update(self.add_dirs(files.difference(entries)))
1121 if self.copies:
1111 if self.copies:
1122 for s, d in self.copies:
1112 for s, d in self.copies:
1123 self._copyfile(s, d)
1113 self._copyfile(s, d)
1124 self.copies = []
1114 self.copies = []
1125 if self.delete:
1115 if self.delete:
1126 self.xargs(self.delete, 'delete')
1116 self.xargs(self.delete, 'delete')
1127 self.delete = []
1117 self.delete = []
1128 entries.update(self.add_files(files.difference(entries)))
1118 entries.update(self.add_files(files.difference(entries)))
1129 entries.update(self.tidy_dirs(entries))
1119 entries.update(self.tidy_dirs(entries))
1130 if self.delexec:
1120 if self.delexec:
1131 self.xargs(self.delexec, 'propdel', 'svn:executable')
1121 self.xargs(self.delexec, 'propdel', 'svn:executable')
1132 self.delexec = []
1122 self.delexec = []
1133 if self.setexec:
1123 if self.setexec:
1134 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1124 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1135 self.setexec = []
1125 self.setexec = []
1136
1126
1137 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
1127 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
1138 fp = os.fdopen(fd, 'w')
1128 fp = os.fdopen(fd, 'w')
1139 fp.write(commit.desc)
1129 fp.write(commit.desc)
1140 fp.close()
1130 fp.close()
1141 try:
1131 try:
1142 output = self.run0('commit',
1132 output = self.run0('commit',
1143 username=util.shortuser(commit.author),
1133 username=util.shortuser(commit.author),
1144 file=messagefile,
1134 file=messagefile,
1145 encoding='utf-8')
1135 encoding='utf-8')
1146 try:
1136 try:
1147 rev = self.commit_re.search(output).group(1)
1137 rev = self.commit_re.search(output).group(1)
1148 except AttributeError:
1138 except AttributeError:
1149 if not files:
1139 if not files:
1150 return parents[0]
1140 return parents[0]
1151 self.ui.warn(_('unexpected svn output:\n'))
1141 self.ui.warn(_('unexpected svn output:\n'))
1152 self.ui.warn(output)
1142 self.ui.warn(output)
1153 raise util.Abort(_('unable to cope with svn output'))
1143 raise util.Abort(_('unable to cope with svn output'))
1154 if commit.rev:
1144 if commit.rev:
1155 self.run('propset', 'hg:convert-rev', commit.rev,
1145 self.run('propset', 'hg:convert-rev', commit.rev,
1156 revprop=True, revision=rev)
1146 revprop=True, revision=rev)
1157 if commit.branch and commit.branch != 'default':
1147 if commit.branch and commit.branch != 'default':
1158 self.run('propset', 'hg:convert-branch', commit.branch,
1148 self.run('propset', 'hg:convert-branch', commit.branch,
1159 revprop=True, revision=rev)
1149 revprop=True, revision=rev)
1160 for parent in parents:
1150 for parent in parents:
1161 self.addchild(parent, rev)
1151 self.addchild(parent, rev)
1162 return self.revid(rev)
1152 return self.revid(rev)
1163 finally:
1153 finally:
1164 os.unlink(messagefile)
1154 os.unlink(messagefile)
1165
1155
1166 def puttags(self, tags):
1156 def puttags(self, tags):
1167 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
1157 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
General Comments 0
You need to be logged in to leave comments. Login now