##// END OF EJS Templates
convert/gnuarch: follow continuation-of revisions...
Edouard Gomez -
r7585:075b7ef0 default
parent child Browse files
Show More
@@ -1,303 +1,326
1 # GNU Arch support for the convert extension
1 # GNU Arch support for the convert extension
2
2
3 from common import NoRepo, commandline, commit, converter_source
3 from common import NoRepo, commandline, commit, converter_source
4 from mercurial.i18n import _
4 from mercurial.i18n import _
5 from mercurial import util
5 from mercurial import util
6 import os, shutil, tempfile, stat, locale
6 import os, shutil, tempfile, stat, locale
7 from email.Parser import Parser
7 from email.Parser import Parser
8
8
9 class gnuarch_source(converter_source, commandline):
9 class gnuarch_source(converter_source, commandline):
10
10
11 class gnuarch_rev:
11 class gnuarch_rev:
12 def __init__(self, rev):
12 def __init__(self, rev):
13 self.rev = rev
13 self.rev = rev
14 self.summary = ''
14 self.summary = ''
15 self.date = None
15 self.date = None
16 self.author = ''
16 self.author = ''
17 self.continuationof = None
17 self.continuationof = None
18 self.add_files = []
18 self.add_files = []
19 self.mod_files = []
19 self.mod_files = []
20 self.del_files = []
20 self.del_files = []
21 self.ren_files = {}
21 self.ren_files = {}
22 self.ren_dirs = {}
22 self.ren_dirs = {}
23
23
24 def __init__(self, ui, path, rev=None):
24 def __init__(self, ui, path, rev=None):
25 super(gnuarch_source, self).__init__(ui, path, rev=rev)
25 super(gnuarch_source, self).__init__(ui, path, rev=rev)
26
26
27 if not os.path.exists(os.path.join(path, '{arch}')):
27 if not os.path.exists(os.path.join(path, '{arch}')):
28 raise NoRepo(_("%s does not look like a GNU Arch repo") % path)
28 raise NoRepo(_("%s does not look like a GNU Arch repo") % path)
29
29
30 # Could use checktool, but we want to check for baz or tla.
30 # Could use checktool, but we want to check for baz or tla.
31 self.execmd = None
31 self.execmd = None
32 if util.find_exe('baz'):
32 if util.find_exe('baz'):
33 self.execmd = 'baz'
33 self.execmd = 'baz'
34 else:
34 else:
35 if util.find_exe('tla'):
35 if util.find_exe('tla'):
36 self.execmd = 'tla'
36 self.execmd = 'tla'
37 else:
37 else:
38 raise util.Abort(_('cannot find a GNU Arch tool'))
38 raise util.Abort(_('cannot find a GNU Arch tool'))
39
39
40 commandline.__init__(self, ui, self.execmd)
40 commandline.__init__(self, ui, self.execmd)
41
41
42 self.path = os.path.realpath(path)
42 self.path = os.path.realpath(path)
43 self.tmppath = None
43 self.tmppath = None
44
44
45 self.treeversion = None
45 self.treeversion = None
46 self.lastrev = None
46 self.lastrev = None
47 self.changes = {}
47 self.changes = {}
48 self.parents = {}
48 self.parents = {}
49 self.tags = {}
49 self.tags = {}
50 self.modecache = {}
50 self.modecache = {}
51 self.catlogparser = Parser()
51 self.catlogparser = Parser()
52 self.locale = locale.getpreferredencoding()
52 self.locale = locale.getpreferredencoding()
53 self.archives = []
53 self.archives = []
54
54
55 def before(self):
55 def before(self):
56 # Get registered archives
56 # Get registered archives
57 self.archives = [i.rstrip('\n')
57 self.archives = [i.rstrip('\n')
58 for i in self.runlines0('archives', '-n')]
58 for i in self.runlines0('archives', '-n')]
59
59
60 if self.execmd == 'tla':
60 if self.execmd == 'tla':
61 output = self.run0('tree-version', self.path)
61 output = self.run0('tree-version', self.path)
62 else:
62 else:
63 output = self.run0('tree-version', '-d', self.path)
63 output = self.run0('tree-version', '-d', self.path)
64 self.treeversion = output.strip()
64 self.treeversion = output.strip()
65
65
66 self.ui.status(_('analyzing tree version %s...\n') % self.treeversion)
67
68 # Get name of temporary directory
66 # Get name of temporary directory
69 version = self.treeversion.split('/')
67 version = self.treeversion.split('/')
70 self.tmppath = os.path.join(tempfile.gettempdir(),
68 self.tmppath = os.path.join(tempfile.gettempdir(),
71 'hg-%s' % version[1])
69 'hg-%s' % version[1])
72
70
73 # Generate parents dictionary
71 # Generate parents dictionary
74 child = []
72 self.parents[None] = []
75 output, status = self.runlines('revisions', '-f', self.treeversion)
73 treeversion = self.treeversion
76 self.checkexit(status, 'archive registered?')
74 child = None
77 for l in output:
75 while treeversion:
78 rev = l.strip()
76 self.ui.status(_('analyzing tree version %s...\n') % treeversion)
79 self.changes[rev] = self.gnuarch_rev(rev)
77
78 archive = treeversion.split('/')[0]
79 if archive not in self.archives:
80 self.ui.status(_('tree analysis stopped because it points to an unregistered archive %s...\n') % archive)
81 break
82
83 # Get the complete list of revisions for that tree version
84 output, status = self.runlines('revisions', '-r', '-f', treeversion)
85 self.checkexit(status, 'failed retrieveing revisions for %s' % treeversion)
86
87 # No new iteration unless a revision has a continuation-of header
88 treeversion = None
89
90 for l in output:
91 rev = l.strip()
92 self.changes[rev] = self.gnuarch_rev(rev)
93 self.parents[rev] = []
80
94
81 # Read author, date and summary
95 # Read author, date and summary
82 catlog, status = self.run('cat-log', '-d', self.path, rev)
96 catlog, status = self.run('cat-log', '-d', self.path, rev)
83 if status:
97 if status:
84 catlog = self.run0('cat-archive-log', rev)
98 catlog = self.run0('cat-archive-log', rev)
85 self._parsecatlog(catlog, rev)
99 self._parsecatlog(catlog, rev)
100
101 # Populate the parents map
102 self.parents[child].append(rev)
86
103
87 self.parents[rev] = child
104 # Keep track of the current revision as the child of the next
88 child = [rev]
105 # revision scanned
89 if rev == self.rev:
106 child = rev
90 break
107
91 self.parents[None] = child
108 # Check if we have to follow the usual incremental history
109 # or if we have to 'jump' to a different treeversion given
110 # by the continuation-of header.
111 if self.changes[rev].continuationof:
112 treeversion = '--'.join(self.changes[rev].continuationof.split('--')[:-1])
113 break
114
115 # If we reached a base-0 revision w/o any continuation-of
116 # header, it means the tree history ends here.
117 if rev[-6:] == 'base-0':
118 break
92
119
93 def after(self):
120 def after(self):
94 self.ui.debug(_('cleaning up %s\n') % self.tmppath)
121 self.ui.debug(_('cleaning up %s\n') % self.tmppath)
95 shutil.rmtree(self.tmppath, ignore_errors=True)
122 shutil.rmtree(self.tmppath, ignore_errors=True)
96
123
97 def getheads(self):
124 def getheads(self):
98 return self.parents[None]
125 return self.parents[None]
99
126
100 def getfile(self, name, rev):
127 def getfile(self, name, rev):
101 if rev != self.lastrev:
128 if rev != self.lastrev:
102 raise util.Abort(_('internal calling inconsistency'))
129 raise util.Abort(_('internal calling inconsistency'))
103
130
104 # Raise IOError if necessary (i.e. deleted files).
131 # Raise IOError if necessary (i.e. deleted files).
105 if not os.path.exists(os.path.join(self.tmppath, name)):
132 if not os.path.exists(os.path.join(self.tmppath, name)):
106 raise IOError
133 raise IOError
107
134
108 data, mode = self._getfile(name, rev)
135 data, mode = self._getfile(name, rev)
109 self.modecache[(name, rev)] = mode
136 self.modecache[(name, rev)] = mode
110
137
111 return data
138 return data
112
139
113 def getmode(self, name, rev):
140 def getmode(self, name, rev):
114 return self.modecache[(name, rev)]
141 return self.modecache[(name, rev)]
115
142
116 def getchanges(self, rev):
143 def getchanges(self, rev):
117 self.modecache = {}
144 self.modecache = {}
118 self._update(rev)
145 self._update(rev)
119 changes = []
146 changes = []
120 copies = {}
147 copies = {}
121
148
122 for f in self.changes[rev].add_files:
149 for f in self.changes[rev].add_files:
123 changes.append((f, rev))
150 changes.append((f, rev))
124
151
125 for f in self.changes[rev].mod_files:
152 for f in self.changes[rev].mod_files:
126 changes.append((f, rev))
153 changes.append((f, rev))
127
154
128 for f in self.changes[rev].del_files:
155 for f in self.changes[rev].del_files:
129 changes.append((f, rev))
156 changes.append((f, rev))
130
157
131 for src in self.changes[rev].ren_files:
158 for src in self.changes[rev].ren_files:
132 to = self.changes[rev].ren_files[src]
159 to = self.changes[rev].ren_files[src]
133 changes.append((src, rev))
160 changes.append((src, rev))
134 changes.append((to, rev))
161 changes.append((to, rev))
135 copies[src] = to
162 copies[src] = to
136
163
137 for src in self.changes[rev].ren_dirs:
164 for src in self.changes[rev].ren_dirs:
138 to = self.changes[rev].ren_dirs[src]
165 to = self.changes[rev].ren_dirs[src]
139 chgs, cps = self._rendirchanges(src, to);
166 chgs, cps = self._rendirchanges(src, to);
140 changes += [(f, rev) for f in chgs]
167 changes += [(f, rev) for f in chgs]
141 for c in cps:
168 for c in cps:
142 copies[c] = cps[c]
169 copies[c] = cps[c]
143
170
144 self.lastrev = rev
171 self.lastrev = rev
145 return util.sort(changes), copies
172 return util.sort(changes), copies
146
173
147 def getcommit(self, rev):
174 def getcommit(self, rev):
148 changes = self.changes[rev]
175 changes = self.changes[rev]
149 return commit(author = changes.author, date = changes.date,
176 return commit(author = changes.author, date = changes.date,
150 desc = changes.summary, parents = self.parents[rev], rev=rev)
177 desc = changes.summary, parents = self.parents[rev], rev=rev)
151
178
152 def gettags(self):
179 def gettags(self):
153 return self.tags
180 return self.tags
154
181
155 def _execute(self, cmd, *args, **kwargs):
182 def _execute(self, cmd, *args, **kwargs):
156 cmdline = [self.execmd, cmd]
183 cmdline = [self.execmd, cmd]
157 cmdline += args
184 cmdline += args
158 cmdline = [util.shellquote(arg) for arg in cmdline]
185 cmdline = [util.shellquote(arg) for arg in cmdline]
159 cmdline += ['>', util.nulldev, '2>', util.nulldev]
186 cmdline += ['>', util.nulldev, '2>', util.nulldev]
160 cmdline = util.quotecommand(' '.join(cmdline))
187 cmdline = util.quotecommand(' '.join(cmdline))
161 self.ui.debug(cmdline, '\n')
188 self.ui.debug(cmdline, '\n')
162 return os.system(cmdline)
189 return os.system(cmdline)
163
190
164 def _update(self, rev):
191 def _update(self, rev):
165 if rev[-6:] == 'base-0':
192 self.ui.debug(_('applying revision %s...\n') % rev)
166 # Initialise 'base-0' revision
193 changeset, status = self.runlines('replay', '-d', self.tmppath,
194 rev)
195 if status:
196 # Something went wrong while merging (baz or tla
197 # issue?), get latest revision and try from there
198 shutil.rmtree(self.tmppath, ignore_errors=True)
167 self._obtainrevision(rev)
199 self._obtainrevision(rev)
168 else:
200 else:
169 self.ui.debug(_('applying revision %s...\n') % rev)
201 old_rev = self.parents[rev][0]
170 changeset, status = self.runlines('replay', '-d', self.tmppath,
202 self.ui.debug(_('computing changeset between %s and %s...\n')
171 rev)
203 % (old_rev, rev))
172 if status:
204 self._parsechangeset(changeset, rev)
173 # Something went wrong while merging (baz or tla
174 # issue?), get latest revision and try from there
175 shutil.rmtree(self.tmppath, ignore_errors=True)
176 self._obtainrevision(rev)
177 else:
178 old_rev = self.parents[rev][0]
179 self.ui.debug(_('computing changeset between %s and %s...\n')
180 % (old_rev, rev))
181 self._parsechangeset(changeset, rev)
182
205
183 def _getfile(self, name, rev):
206 def _getfile(self, name, rev):
184 mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
207 mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
185 if stat.S_ISLNK(mode):
208 if stat.S_ISLNK(mode):
186 data = os.readlink(os.path.join(self.tmppath, name))
209 data = os.readlink(os.path.join(self.tmppath, name))
187 mode = mode and 'l' or ''
210 mode = mode and 'l' or ''
188 else:
211 else:
189 data = open(os.path.join(self.tmppath, name), 'rb').read()
212 data = open(os.path.join(self.tmppath, name), 'rb').read()
190 mode = (mode & 0111) and 'x' or ''
213 mode = (mode & 0111) and 'x' or ''
191 return data, mode
214 return data, mode
192
215
193 def _exclude(self, name):
216 def _exclude(self, name):
194 exclude = [ '{arch}', '.arch-ids', '.arch-inventory' ]
217 exclude = [ '{arch}', '.arch-ids', '.arch-inventory' ]
195 for exc in exclude:
218 for exc in exclude:
196 if name.find(exc) != -1:
219 if name.find(exc) != -1:
197 return True
220 return True
198 return False
221 return False
199
222
200 def _readcontents(self, path):
223 def _readcontents(self, path):
201 files = []
224 files = []
202 contents = os.listdir(path)
225 contents = os.listdir(path)
203 while len(contents) > 0:
226 while len(contents) > 0:
204 c = contents.pop()
227 c = contents.pop()
205 p = os.path.join(path, c)
228 p = os.path.join(path, c)
206 # os.walk could be used, but here we avoid internal GNU
229 # os.walk could be used, but here we avoid internal GNU
207 # Arch files and directories, thus saving a lot time.
230 # Arch files and directories, thus saving a lot time.
208 if not self._exclude(p):
231 if not self._exclude(p):
209 if os.path.isdir(p):
232 if os.path.isdir(p):
210 contents += [os.path.join(c, f) for f in os.listdir(p)]
233 contents += [os.path.join(c, f) for f in os.listdir(p)]
211 else:
234 else:
212 files.append(c)
235 files.append(c)
213 return files
236 return files
214
237
215 def _rendirchanges(self, src, dest):
238 def _rendirchanges(self, src, dest):
216 changes = []
239 changes = []
217 copies = {}
240 copies = {}
218 files = self._readcontents(os.path.join(self.tmppath, dest))
241 files = self._readcontents(os.path.join(self.tmppath, dest))
219 for f in files:
242 for f in files:
220 s = os.path.join(src, f)
243 s = os.path.join(src, f)
221 d = os.path.join(dest, f)
244 d = os.path.join(dest, f)
222 changes.append(s)
245 changes.append(s)
223 changes.append(d)
246 changes.append(d)
224 copies[s] = d
247 copies[s] = d
225 return changes, copies
248 return changes, copies
226
249
227 def _obtainrevision(self, rev):
250 def _obtainrevision(self, rev):
228 self.ui.debug(_('obtaining revision %s...\n') % rev)
251 self.ui.debug(_('obtaining revision %s...\n') % rev)
229 output = self._execute('get', rev, self.tmppath)
252 output = self._execute('get', rev, self.tmppath)
230 self.checkexit(output)
253 self.checkexit(output)
231 self.ui.debug(_('analysing revision %s...\n') % rev)
254 self.ui.debug(_('analysing revision %s...\n') % rev)
232 files = self._readcontents(self.tmppath)
255 files = self._readcontents(self.tmppath)
233 self.changes[rev].add_files += files
256 self.changes[rev].add_files += files
234
257
235 def _stripbasepath(self, path):
258 def _stripbasepath(self, path):
236 if path.startswith('./'):
259 if path.startswith('./'):
237 return path[2:]
260 return path[2:]
238 return path
261 return path
239
262
240 def _parsecatlog(self, data, rev):
263 def _parsecatlog(self, data, rev):
241 try:
264 try:
242 catlog = self.catlogparser.parsestr(data)
265 catlog = self.catlogparser.parsestr(data)
243 self.changes[rev].date = util.datestr(
266 self.changes[rev].date = util.datestr(
244 util.strdate(catlog['Standard-date'],
267 util.strdate(catlog['Standard-date'],
245 '%Y-%m-%d %H:%M:%S'))
268 '%Y-%m-%d %H:%M:%S'))
246 self.changes[rev].author = catlog['Creator']
269 self.changes[rev].author = catlog['Creator']
247 self.changes[rev].summary = catlog['Summary']
270 self.changes[rev].summary = catlog['Summary']
248 if catlog.has_key('Continuation-of'):
271 if catlog.has_key('Continuation-of'):
249 self.changes[rev].continuationof = catlog['Continuation-of']
272 self.changes[rev].continuationof = catlog['Continuation-of']
250 except Exception, err:
273 except Exception, err:
251 raise util.Abort(_('could not parse cat-log of %s') % rev)
274 raise util.Abort(_('could not parse cat-log of %s') % rev)
252
275
253 def _parsechangeset(self, data, rev):
276 def _parsechangeset(self, data, rev):
254 for l in data:
277 for l in data:
255 l = l.strip()
278 l = l.strip()
256 # Added file (ignore added directory)
279 # Added file (ignore added directory)
257 if l.startswith('A') and not l.startswith('A/'):
280 if l.startswith('A') and not l.startswith('A/'):
258 file = self._stripbasepath(l[1:].strip())
281 file = self._stripbasepath(l[1:].strip())
259 if not self._exclude(file):
282 if not self._exclude(file):
260 self.changes[rev].add_files.append(file)
283 self.changes[rev].add_files.append(file)
261 # Deleted file (ignore deleted directory)
284 # Deleted file (ignore deleted directory)
262 elif l.startswith('D') and not l.startswith('D/'):
285 elif l.startswith('D') and not l.startswith('D/'):
263 file = self._stripbasepath(l[1:].strip())
286 file = self._stripbasepath(l[1:].strip())
264 if not self._exclude(file):
287 if not self._exclude(file):
265 self.changes[rev].del_files.append(file)
288 self.changes[rev].del_files.append(file)
266 # Modified binary file
289 # Modified binary file
267 elif l.startswith('Mb'):
290 elif l.startswith('Mb'):
268 file = self._stripbasepath(l[2:].strip())
291 file = self._stripbasepath(l[2:].strip())
269 if not self._exclude(file):
292 if not self._exclude(file):
270 self.changes[rev].mod_files.append(file)
293 self.changes[rev].mod_files.append(file)
271 # Modified link
294 # Modified link
272 elif l.startswith('M->'):
295 elif l.startswith('M->'):
273 file = self._stripbasepath(l[3:].strip())
296 file = self._stripbasepath(l[3:].strip())
274 if not self._exclude(file):
297 if not self._exclude(file):
275 self.changes[rev].mod_files.append(file)
298 self.changes[rev].mod_files.append(file)
276 # Modified file
299 # Modified file
277 elif l.startswith('M'):
300 elif l.startswith('M'):
278 file = self._stripbasepath(l[1:].strip())
301 file = self._stripbasepath(l[1:].strip())
279 if not self._exclude(file):
302 if not self._exclude(file):
280 self.changes[rev].mod_files.append(file)
303 self.changes[rev].mod_files.append(file)
281 # Renamed file (or link)
304 # Renamed file (or link)
282 elif l.startswith('=>'):
305 elif l.startswith('=>'):
283 files = l[2:].strip().split(' ')
306 files = l[2:].strip().split(' ')
284 if len(files) == 1:
307 if len(files) == 1:
285 files = l[2:].strip().split('\t')
308 files = l[2:].strip().split('\t')
286 src = self._stripbasepath(files[0])
309 src = self._stripbasepath(files[0])
287 dst = self._stripbasepath(files[1])
310 dst = self._stripbasepath(files[1])
288 if not self._exclude(src) and not self._exclude(dst):
311 if not self._exclude(src) and not self._exclude(dst):
289 self.changes[rev].ren_files[src] = dst
312 self.changes[rev].ren_files[src] = dst
290 # Conversion from file to link or from link to file (modified)
313 # Conversion from file to link or from link to file (modified)
291 elif l.startswith('ch'):
314 elif l.startswith('ch'):
292 file = self._stripbasepath(l[2:].strip())
315 file = self._stripbasepath(l[2:].strip())
293 if not self._exclude(file):
316 if not self._exclude(file):
294 self.changes[rev].mod_files.append(file)
317 self.changes[rev].mod_files.append(file)
295 # Renamed directory
318 # Renamed directory
296 elif l.startswith('/>'):
319 elif l.startswith('/>'):
297 dirs = l[2:].strip().split(' ')
320 dirs = l[2:].strip().split(' ')
298 if len(dirs) == 1:
321 if len(dirs) == 1:
299 dirs = l[2:].strip().split('\t')
322 dirs = l[2:].strip().split('\t')
300 src = self._stripbasepath(dirs[0])
323 src = self._stripbasepath(dirs[0])
301 dst = self._stripbasepath(dirs[1])
324 dst = self._stripbasepath(dirs[1])
302 if not self._exclude(src) and not self._exclude(dst):
325 if not self._exclude(src) and not self._exclude(dst):
303 self.changes[rev].ren_dirs[src] = dst
326 self.changes[rev].ren_dirs[src] = dst
General Comments 0
You need to be logged in to leave comments. Login now