##// END OF EJS Templates
bundlerepo: try to find containing repo on creation (issue1812)
Matt Mackall -
r16042:4b7aa1c8 stable
parent child Browse files
Show More
@@ -1,363 +1,368 b''
1 # bundlerepo.py - repository class for viewing uncompressed bundles
1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 #
2 #
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
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 """Repository class for viewing uncompressed bundles.
8 """Repository class for viewing uncompressed bundles.
9
9
10 This provides a read-only repository interface to bundles as if they
10 This provides a read-only repository interface to bundles as if they
11 were part of the actual repository.
11 were part of the actual repository.
12 """
12 """
13
13
14 from node import nullid
14 from node import nullid
15 from i18n import _
15 from i18n import _
16 import os, tempfile, shutil
16 import os, tempfile, shutil
17 import changegroup, util, mdiff, discovery
17 import changegroup, util, mdiff, discovery, cmdutil
18 import localrepo, changelog, manifest, filelog, revlog, error
18 import localrepo, changelog, manifest, filelog, revlog, error
19
19
20 class bundlerevlog(revlog.revlog):
20 class bundlerevlog(revlog.revlog):
21 def __init__(self, opener, indexfile, bundle, linkmapper):
21 def __init__(self, opener, indexfile, bundle, linkmapper):
22 # How it works:
22 # How it works:
23 # to retrieve a revision, we need to know the offset of
23 # to retrieve a revision, we need to know the offset of
24 # the revision in the bundle (an unbundle object).
24 # the revision in the bundle (an unbundle object).
25 #
25 #
26 # We store this offset in the index (start), to differentiate a
26 # We store this offset in the index (start), to differentiate a
27 # rev in the bundle and from a rev in the revlog, we check
27 # rev in the bundle and from a rev in the revlog, we check
28 # len(index[r]). If the tuple is bigger than 7, it is a bundle
28 # len(index[r]). If the tuple is bigger than 7, it is a bundle
29 # (it is bigger since we store the node to which the delta is)
29 # (it is bigger since we store the node to which the delta is)
30 #
30 #
31 revlog.revlog.__init__(self, opener, indexfile)
31 revlog.revlog.__init__(self, opener, indexfile)
32 self.bundle = bundle
32 self.bundle = bundle
33 self.basemap = {}
33 self.basemap = {}
34 n = len(self)
34 n = len(self)
35 chain = None
35 chain = None
36 while True:
36 while True:
37 chunkdata = bundle.deltachunk(chain)
37 chunkdata = bundle.deltachunk(chain)
38 if not chunkdata:
38 if not chunkdata:
39 break
39 break
40 node = chunkdata['node']
40 node = chunkdata['node']
41 p1 = chunkdata['p1']
41 p1 = chunkdata['p1']
42 p2 = chunkdata['p2']
42 p2 = chunkdata['p2']
43 cs = chunkdata['cs']
43 cs = chunkdata['cs']
44 deltabase = chunkdata['deltabase']
44 deltabase = chunkdata['deltabase']
45 delta = chunkdata['delta']
45 delta = chunkdata['delta']
46
46
47 size = len(delta)
47 size = len(delta)
48 start = bundle.tell() - size
48 start = bundle.tell() - size
49
49
50 link = linkmapper(cs)
50 link = linkmapper(cs)
51 if node in self.nodemap:
51 if node in self.nodemap:
52 # this can happen if two branches make the same change
52 # this can happen if two branches make the same change
53 chain = node
53 chain = node
54 continue
54 continue
55
55
56 for p in (p1, p2):
56 for p in (p1, p2):
57 if not p in self.nodemap:
57 if not p in self.nodemap:
58 raise error.LookupError(p, self.indexfile,
58 raise error.LookupError(p, self.indexfile,
59 _("unknown parent"))
59 _("unknown parent"))
60 # start, size, full unc. size, base (unused), link, p1, p2, node
60 # start, size, full unc. size, base (unused), link, p1, p2, node
61 e = (revlog.offset_type(start, 0), size, -1, -1, link,
61 e = (revlog.offset_type(start, 0), size, -1, -1, link,
62 self.rev(p1), self.rev(p2), node)
62 self.rev(p1), self.rev(p2), node)
63 self.basemap[n] = deltabase
63 self.basemap[n] = deltabase
64 self.index.insert(-1, e)
64 self.index.insert(-1, e)
65 self.nodemap[node] = n
65 self.nodemap[node] = n
66 chain = node
66 chain = node
67 n += 1
67 n += 1
68
68
69 def inbundle(self, rev):
69 def inbundle(self, rev):
70 """is rev from the bundle"""
70 """is rev from the bundle"""
71 if rev < 0:
71 if rev < 0:
72 return False
72 return False
73 return rev in self.basemap
73 return rev in self.basemap
74 def bundlebase(self, rev):
74 def bundlebase(self, rev):
75 return self.basemap[rev]
75 return self.basemap[rev]
76 def _chunk(self, rev):
76 def _chunk(self, rev):
77 # Warning: in case of bundle, the diff is against bundlebase,
77 # Warning: in case of bundle, the diff is against bundlebase,
78 # not against rev - 1
78 # not against rev - 1
79 # XXX: could use some caching
79 # XXX: could use some caching
80 if not self.inbundle(rev):
80 if not self.inbundle(rev):
81 return revlog.revlog._chunk(self, rev)
81 return revlog.revlog._chunk(self, rev)
82 self.bundle.seek(self.start(rev))
82 self.bundle.seek(self.start(rev))
83 return self.bundle.read(self.length(rev))
83 return self.bundle.read(self.length(rev))
84
84
85 def revdiff(self, rev1, rev2):
85 def revdiff(self, rev1, rev2):
86 """return or calculate a delta between two revisions"""
86 """return or calculate a delta between two revisions"""
87 if self.inbundle(rev1) and self.inbundle(rev2):
87 if self.inbundle(rev1) and self.inbundle(rev2):
88 # hot path for bundle
88 # hot path for bundle
89 revb = self.rev(self.bundlebase(rev2))
89 revb = self.rev(self.bundlebase(rev2))
90 if revb == rev1:
90 if revb == rev1:
91 return self._chunk(rev2)
91 return self._chunk(rev2)
92 elif not self.inbundle(rev1) and not self.inbundle(rev2):
92 elif not self.inbundle(rev1) and not self.inbundle(rev2):
93 return revlog.revlog.revdiff(self, rev1, rev2)
93 return revlog.revlog.revdiff(self, rev1, rev2)
94
94
95 return mdiff.textdiff(self.revision(self.node(rev1)),
95 return mdiff.textdiff(self.revision(self.node(rev1)),
96 self.revision(self.node(rev2)))
96 self.revision(self.node(rev2)))
97
97
98 def revision(self, node):
98 def revision(self, node):
99 """return an uncompressed revision of a given"""
99 """return an uncompressed revision of a given"""
100 if node == nullid:
100 if node == nullid:
101 return ""
101 return ""
102
102
103 text = None
103 text = None
104 chain = []
104 chain = []
105 iter_node = node
105 iter_node = node
106 rev = self.rev(iter_node)
106 rev = self.rev(iter_node)
107 # reconstruct the revision if it is from a changegroup
107 # reconstruct the revision if it is from a changegroup
108 while self.inbundle(rev):
108 while self.inbundle(rev):
109 if self._cache and self._cache[0] == iter_node:
109 if self._cache and self._cache[0] == iter_node:
110 text = self._cache[2]
110 text = self._cache[2]
111 break
111 break
112 chain.append(rev)
112 chain.append(rev)
113 iter_node = self.bundlebase(rev)
113 iter_node = self.bundlebase(rev)
114 rev = self.rev(iter_node)
114 rev = self.rev(iter_node)
115 if text is None:
115 if text is None:
116 text = revlog.revlog.revision(self, iter_node)
116 text = revlog.revlog.revision(self, iter_node)
117
117
118 while chain:
118 while chain:
119 delta = self._chunk(chain.pop())
119 delta = self._chunk(chain.pop())
120 text = mdiff.patches(text, [delta])
120 text = mdiff.patches(text, [delta])
121
121
122 p1, p2 = self.parents(node)
122 p1, p2 = self.parents(node)
123 if node != revlog.hash(text, p1, p2):
123 if node != revlog.hash(text, p1, p2):
124 raise error.RevlogError(_("integrity check failed on %s:%d")
124 raise error.RevlogError(_("integrity check failed on %s:%d")
125 % (self.datafile, self.rev(node)))
125 % (self.datafile, self.rev(node)))
126
126
127 self._cache = (node, self.rev(node), text)
127 self._cache = (node, self.rev(node), text)
128 return text
128 return text
129
129
130 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
130 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
131 raise NotImplementedError
131 raise NotImplementedError
132 def addgroup(self, revs, linkmapper, transaction):
132 def addgroup(self, revs, linkmapper, transaction):
133 raise NotImplementedError
133 raise NotImplementedError
134 def strip(self, rev, minlink):
134 def strip(self, rev, minlink):
135 raise NotImplementedError
135 raise NotImplementedError
136 def checksize(self):
136 def checksize(self):
137 raise NotImplementedError
137 raise NotImplementedError
138
138
139 class bundlechangelog(bundlerevlog, changelog.changelog):
139 class bundlechangelog(bundlerevlog, changelog.changelog):
140 def __init__(self, opener, bundle):
140 def __init__(self, opener, bundle):
141 changelog.changelog.__init__(self, opener)
141 changelog.changelog.__init__(self, opener)
142 linkmapper = lambda x: x
142 linkmapper = lambda x: x
143 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
143 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
144 linkmapper)
144 linkmapper)
145
145
146 class bundlemanifest(bundlerevlog, manifest.manifest):
146 class bundlemanifest(bundlerevlog, manifest.manifest):
147 def __init__(self, opener, bundle, linkmapper):
147 def __init__(self, opener, bundle, linkmapper):
148 manifest.manifest.__init__(self, opener)
148 manifest.manifest.__init__(self, opener)
149 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
149 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
150 linkmapper)
150 linkmapper)
151
151
152 class bundlefilelog(bundlerevlog, filelog.filelog):
152 class bundlefilelog(bundlerevlog, filelog.filelog):
153 def __init__(self, opener, path, bundle, linkmapper, repo):
153 def __init__(self, opener, path, bundle, linkmapper, repo):
154 filelog.filelog.__init__(self, opener, path)
154 filelog.filelog.__init__(self, opener, path)
155 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
155 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
156 linkmapper)
156 linkmapper)
157 self._repo = repo
157 self._repo = repo
158
158
159 def _file(self, f):
159 def _file(self, f):
160 self._repo.file(f)
160 self._repo.file(f)
161
161
162 class bundlerepository(localrepo.localrepository):
162 class bundlerepository(localrepo.localrepository):
163 def __init__(self, ui, path, bundlename):
163 def __init__(self, ui, path, bundlename):
164 self._tempparent = None
164 self._tempparent = None
165 try:
165 try:
166 localrepo.localrepository.__init__(self, ui, path)
166 localrepo.localrepository.__init__(self, ui, path)
167 except error.RepoError:
167 except error.RepoError:
168 self._tempparent = tempfile.mkdtemp()
168 self._tempparent = tempfile.mkdtemp()
169 localrepo.instance(ui, self._tempparent, 1)
169 localrepo.instance(ui, self._tempparent, 1)
170 localrepo.localrepository.__init__(self, ui, self._tempparent)
170 localrepo.localrepository.__init__(self, ui, self._tempparent)
171
171
172 if path:
172 if path:
173 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
173 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
174 else:
174 else:
175 self._url = 'bundle:' + bundlename
175 self._url = 'bundle:' + bundlename
176
176
177 self.tempfile = None
177 self.tempfile = None
178 f = util.posixfile(bundlename, "rb")
178 f = util.posixfile(bundlename, "rb")
179 self.bundle = changegroup.readbundle(f, bundlename)
179 self.bundle = changegroup.readbundle(f, bundlename)
180 if self.bundle.compressed():
180 if self.bundle.compressed():
181 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
181 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
182 suffix=".hg10un", dir=self.path)
182 suffix=".hg10un", dir=self.path)
183 self.tempfile = temp
183 self.tempfile = temp
184 fptemp = os.fdopen(fdtemp, 'wb')
184 fptemp = os.fdopen(fdtemp, 'wb')
185
185
186 try:
186 try:
187 fptemp.write("HG10UN")
187 fptemp.write("HG10UN")
188 while True:
188 while True:
189 chunk = self.bundle.read(2**18)
189 chunk = self.bundle.read(2**18)
190 if not chunk:
190 if not chunk:
191 break
191 break
192 fptemp.write(chunk)
192 fptemp.write(chunk)
193 finally:
193 finally:
194 fptemp.close()
194 fptemp.close()
195
195
196 f = util.posixfile(self.tempfile, "rb")
196 f = util.posixfile(self.tempfile, "rb")
197 self.bundle = changegroup.readbundle(f, bundlename)
197 self.bundle = changegroup.readbundle(f, bundlename)
198
198
199 # dict with the mapping 'filename' -> position in the bundle
199 # dict with the mapping 'filename' -> position in the bundle
200 self.bundlefilespos = {}
200 self.bundlefilespos = {}
201
201
202 @util.propertycache
202 @util.propertycache
203 def changelog(self):
203 def changelog(self):
204 # consume the header if it exists
204 # consume the header if it exists
205 self.bundle.changelogheader()
205 self.bundle.changelogheader()
206 c = bundlechangelog(self.sopener, self.bundle)
206 c = bundlechangelog(self.sopener, self.bundle)
207 self.manstart = self.bundle.tell()
207 self.manstart = self.bundle.tell()
208 return c
208 return c
209
209
210 @util.propertycache
210 @util.propertycache
211 def manifest(self):
211 def manifest(self):
212 self.bundle.seek(self.manstart)
212 self.bundle.seek(self.manstart)
213 # consume the header if it exists
213 # consume the header if it exists
214 self.bundle.manifestheader()
214 self.bundle.manifestheader()
215 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
215 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
216 self.filestart = self.bundle.tell()
216 self.filestart = self.bundle.tell()
217 return m
217 return m
218
218
219 @util.propertycache
219 @util.propertycache
220 def manstart(self):
220 def manstart(self):
221 self.changelog
221 self.changelog
222 return self.manstart
222 return self.manstart
223
223
224 @util.propertycache
224 @util.propertycache
225 def filestart(self):
225 def filestart(self):
226 self.manifest
226 self.manifest
227 return self.filestart
227 return self.filestart
228
228
229 def url(self):
229 def url(self):
230 return self._url
230 return self._url
231
231
232 def file(self, f):
232 def file(self, f):
233 if not self.bundlefilespos:
233 if not self.bundlefilespos:
234 self.bundle.seek(self.filestart)
234 self.bundle.seek(self.filestart)
235 while True:
235 while True:
236 chunkdata = self.bundle.filelogheader()
236 chunkdata = self.bundle.filelogheader()
237 if not chunkdata:
237 if not chunkdata:
238 break
238 break
239 fname = chunkdata['filename']
239 fname = chunkdata['filename']
240 self.bundlefilespos[fname] = self.bundle.tell()
240 self.bundlefilespos[fname] = self.bundle.tell()
241 while True:
241 while True:
242 c = self.bundle.deltachunk(None)
242 c = self.bundle.deltachunk(None)
243 if not c:
243 if not c:
244 break
244 break
245
245
246 if f[0] == '/':
246 if f[0] == '/':
247 f = f[1:]
247 f = f[1:]
248 if f in self.bundlefilespos:
248 if f in self.bundlefilespos:
249 self.bundle.seek(self.bundlefilespos[f])
249 self.bundle.seek(self.bundlefilespos[f])
250 return bundlefilelog(self.sopener, f, self.bundle,
250 return bundlefilelog(self.sopener, f, self.bundle,
251 self.changelog.rev, self)
251 self.changelog.rev, self)
252 else:
252 else:
253 return filelog.filelog(self.sopener, f)
253 return filelog.filelog(self.sopener, f)
254
254
255 def close(self):
255 def close(self):
256 """Close assigned bundle file immediately."""
256 """Close assigned bundle file immediately."""
257 self.bundle.close()
257 self.bundle.close()
258 if self.tempfile is not None:
258 if self.tempfile is not None:
259 os.unlink(self.tempfile)
259 os.unlink(self.tempfile)
260 if self._tempparent:
260 if self._tempparent:
261 shutil.rmtree(self._tempparent, True)
261 shutil.rmtree(self._tempparent, True)
262
262
263 def cancopy(self):
263 def cancopy(self):
264 return False
264 return False
265
265
266 def getcwd(self):
266 def getcwd(self):
267 return os.getcwd() # always outside the repo
267 return os.getcwd() # always outside the repo
268
268
269 def _writebranchcache(self, branches, tip, tiprev):
269 def _writebranchcache(self, branches, tip, tiprev):
270 # don't overwrite the disk cache with bundle-augmented data
270 # don't overwrite the disk cache with bundle-augmented data
271 pass
271 pass
272
272
273 def instance(ui, path, create):
273 def instance(ui, path, create):
274 if create:
274 if create:
275 raise util.Abort(_('cannot create new bundle repository'))
275 raise util.Abort(_('cannot create new bundle repository'))
276 parentpath = ui.config("bundle", "mainreporoot", "")
276 parentpath = ui.config("bundle", "mainreporoot", "")
277 if not parentpath:
278 # try to find the correct path to the working directory repo
279 parentpath = cmdutil.findrepo(os.getcwd())
280 if parentpath is None:
281 parentpath = ''
277 if parentpath:
282 if parentpath:
278 # Try to make the full path relative so we get a nice, short URL.
283 # Try to make the full path relative so we get a nice, short URL.
279 # In particular, we don't want temp dir names in test outputs.
284 # In particular, we don't want temp dir names in test outputs.
280 cwd = os.getcwd()
285 cwd = os.getcwd()
281 if parentpath == cwd:
286 if parentpath == cwd:
282 parentpath = ''
287 parentpath = ''
283 else:
288 else:
284 cwd = os.path.join(cwd,'')
289 cwd = os.path.join(cwd,'')
285 if parentpath.startswith(cwd):
290 if parentpath.startswith(cwd):
286 parentpath = parentpath[len(cwd):]
291 parentpath = parentpath[len(cwd):]
287 u = util.url(path)
292 u = util.url(path)
288 path = u.localpath()
293 path = u.localpath()
289 if u.scheme == 'bundle':
294 if u.scheme == 'bundle':
290 s = path.split("+", 1)
295 s = path.split("+", 1)
291 if len(s) == 1:
296 if len(s) == 1:
292 repopath, bundlename = parentpath, s[0]
297 repopath, bundlename = parentpath, s[0]
293 else:
298 else:
294 repopath, bundlename = s
299 repopath, bundlename = s
295 else:
300 else:
296 repopath, bundlename = parentpath, path
301 repopath, bundlename = parentpath, path
297 return bundlerepository(ui, repopath, bundlename)
302 return bundlerepository(ui, repopath, bundlename)
298
303
299 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
304 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
300 force=False):
305 force=False):
301 '''obtains a bundle of changes incoming from other
306 '''obtains a bundle of changes incoming from other
302
307
303 "onlyheads" restricts the returned changes to those reachable from the
308 "onlyheads" restricts the returned changes to those reachable from the
304 specified heads.
309 specified heads.
305 "bundlename", if given, stores the bundle to this file path permanently;
310 "bundlename", if given, stores the bundle to this file path permanently;
306 otherwise it's stored to a temp file and gets deleted again when you call
311 otherwise it's stored to a temp file and gets deleted again when you call
307 the returned "cleanupfn".
312 the returned "cleanupfn".
308 "force" indicates whether to proceed on unrelated repos.
313 "force" indicates whether to proceed on unrelated repos.
309
314
310 Returns a tuple (local, csets, cleanupfn):
315 Returns a tuple (local, csets, cleanupfn):
311
316
312 "local" is a local repo from which to obtain the actual incoming changesets; it
317 "local" is a local repo from which to obtain the actual incoming changesets; it
313 is a bundlerepo for the obtained bundle when the original "other" is remote.
318 is a bundlerepo for the obtained bundle when the original "other" is remote.
314 "csets" lists the incoming changeset node ids.
319 "csets" lists the incoming changeset node ids.
315 "cleanupfn" must be called without arguments when you're done processing the
320 "cleanupfn" must be called without arguments when you're done processing the
316 changes; it closes both the original "other" and the one returned here.
321 changes; it closes both the original "other" and the one returned here.
317 '''
322 '''
318 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads, force=force)
323 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads, force=force)
319 common, incoming, rheads = tmp
324 common, incoming, rheads = tmp
320 if not incoming:
325 if not incoming:
321 try:
326 try:
322 if bundlename:
327 if bundlename:
323 os.unlink(bundlename)
328 os.unlink(bundlename)
324 except OSError:
329 except OSError:
325 pass
330 pass
326 return other, [], other.close
331 return other, [], other.close
327
332
328 bundle = None
333 bundle = None
329 bundlerepo = None
334 bundlerepo = None
330 localrepo = other
335 localrepo = other
331 if bundlename or not other.local():
336 if bundlename or not other.local():
332 # create a bundle (uncompressed if other repo is not local)
337 # create a bundle (uncompressed if other repo is not local)
333
338
334 if other.capable('getbundle'):
339 if other.capable('getbundle'):
335 cg = other.getbundle('incoming', common=common, heads=rheads)
340 cg = other.getbundle('incoming', common=common, heads=rheads)
336 elif onlyheads is None and not other.capable('changegroupsubset'):
341 elif onlyheads is None and not other.capable('changegroupsubset'):
337 # compat with older servers when pulling all remote heads
342 # compat with older servers when pulling all remote heads
338 cg = other.changegroup(incoming, "incoming")
343 cg = other.changegroup(incoming, "incoming")
339 rheads = None
344 rheads = None
340 else:
345 else:
341 cg = other.changegroupsubset(incoming, rheads, 'incoming')
346 cg = other.changegroupsubset(incoming, rheads, 'incoming')
342 bundletype = other.local() and "HG10BZ" or "HG10UN"
347 bundletype = other.local() and "HG10BZ" or "HG10UN"
343 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
348 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
344 # keep written bundle?
349 # keep written bundle?
345 if bundlename:
350 if bundlename:
346 bundle = None
351 bundle = None
347 if not other.local():
352 if not other.local():
348 # use the created uncompressed bundlerepo
353 # use the created uncompressed bundlerepo
349 localrepo = bundlerepo = bundlerepository(ui, repo.root, fname)
354 localrepo = bundlerepo = bundlerepository(ui, repo.root, fname)
350 # this repo contains local and other now, so filter out local again
355 # this repo contains local and other now, so filter out local again
351 common = repo.heads()
356 common = repo.heads()
352
357
353 csets = localrepo.changelog.findmissing(common, rheads)
358 csets = localrepo.changelog.findmissing(common, rheads)
354
359
355 def cleanup():
360 def cleanup():
356 if bundlerepo:
361 if bundlerepo:
357 bundlerepo.close()
362 bundlerepo.close()
358 if bundle:
363 if bundle:
359 os.unlink(bundle)
364 os.unlink(bundle)
360 other.close()
365 other.close()
361
366
362 return (localrepo, csets, cleanup)
367 return (localrepo, csets, cleanup)
363
368
General Comments 0
You need to be logged in to leave comments. Login now