~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/sftp.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-09-16 14:03:54 UTC
  • mfrom: (2017.1.1 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060916140354-1a9932f525bb7182
(robertc) Add MemoryTree and TreeBuilder test helpers. Also test behavior of transport.has('/') which caused failures in this when merging, and as a result cleanup the sftp path normalisation logic.

Show diffs side-by-side

added added

removed removed

Lines of Context:
251
251
        :return: a path prefixed with / for regular abspath-based urls, or a
252
252
            path that does not begin with / for urls which begin with /~/.
253
253
        """
254
 
        # FIXME: share the common code across transports
 
254
        # how does this work? 
 
255
        # it processes relpath with respect to 
 
256
        # our state:
 
257
        # firstly we create a path to evaluate: 
 
258
        # if relpath is an abspath or homedir path, its the entire thing
 
259
        # otherwise we join our base with relpath
 
260
        # then we eliminate all empty segments (double //'s) outside the first
 
261
        # two elements of the list. This avoids problems with trailing 
 
262
        # slashes, or other abnormalities.
 
263
        # finally we evaluate the entire path in a single pass
 
264
        # '.'s are stripped,
 
265
        # '..' result in popping the left most already 
 
266
        # processed path (which can never be empty because of the check for
 
267
        # abspath and homedir meaning that its not, or that we've used our
 
268
        # path. If the pop would pop the root, we ignore it.
 
269
 
 
270
        # Specific case examinations:
 
271
        # remove the special casefor ~: if the current root is ~/ popping of it
 
272
        # = / thus our seed for a ~ based path is ['', '~']
 
273
        # and if we end up with [''] then we had basically ('', '..') (which is
 
274
        # '/..' so we append '' if the length is one, and assert that the first
 
275
        # element is still ''. Lastly, if we end with ['', '~'] as a prefix for
 
276
        # the output, we've got a homedir path, so we strip that prefix before
 
277
        # '/' joining the resulting list.
 
278
        #
 
279
        # case one: '/' -> ['', ''] cannot shrink
 
280
        # case two: '/' + '../foo' -> ['', 'foo'] (take '', '', '..', 'foo')
 
281
        #           and pop the second '' for the '..', append 'foo'
 
282
        # case three: '/~/' -> ['', '~', ''] 
 
283
        # case four: '/~/' + '../foo' -> ['', '~', '', '..', 'foo'],
 
284
        #           and we want to get '/foo' - the empty path in the middle
 
285
        #           needs to be stripped, then normal path manipulation will 
 
286
        #           work.
 
287
        # case five: '/..' ['', '..'], we want ['', '']
 
288
        #            stripping '' outside the first two is ok
 
289
        #            ignore .. if its too high up
 
290
        #
 
291
        # lastly this code is possibly reusable by FTP, but not reusable by
 
292
        # local paths: ~ is resolvable correctly, nor by HTTP or the smart
 
293
        # server: ~ is resolved remotely.
 
294
        # 
 
295
        # however, a version of this that acts on self.base is possible to be
 
296
        # written which manipulates the URL in canonical form, and would be
 
297
        # reusable for all transports, if a flag for allowing ~/ at all was
 
298
        # provided.
255
299
        assert isinstance(relpath, basestring)
256
 
        basepath = self._path.split('/')
 
300
        relpath = urlutils.unescape(relpath)
 
301
 
 
302
        # case 1)
257
303
        if relpath.startswith('/'):
258
 
            basepath = ['', '']
259
 
        relpath = urlutils.unescape(relpath).split('/')
260
 
        if len(basepath) > 0 and basepath[-1] == '':
261
 
            basepath = basepath[:-1]
262
 
 
263
 
        for p in relpath:
264
 
            if p == '..':
265
 
                if len(basepath) == 0:
266
 
                    # In most filesystems, a request for the parent
267
 
                    # of root, just returns root.
268
 
                    continue
269
 
                basepath.pop()
270
 
            elif p == '.':
271
 
                continue # No-op
272
 
            elif p != '':
273
 
                basepath.append(p)
274
 
 
275
 
        path = '/'.join(basepath)
276
 
        # mutter('relpath => remotepath %s => %s', relpath, path)
 
304
            # abspath - normal split is fine.
 
305
            current_path = relpath.split('/')
 
306
        elif relpath.startswith('~/'):
 
307
            # root is homedir based: normal split and prefix '' to remote the
 
308
            # special case
 
309
            current_path = [''].extend(relpath.split('/'))
 
310
        else:
 
311
            # root is from the current directory:
 
312
            if self._path.startswith('/'):
 
313
                # abspath, take the regular split
 
314
                current_path = []
 
315
            else:
 
316
                # homedir based, add the '', '~' not present in self._path
 
317
                current_path = ['', '~']
 
318
            # add our current dir
 
319
            current_path.extend(self._path.split('/'))
 
320
            # add the users relpath
 
321
            current_path.extend(relpath.split('/'))
 
322
        # strip '' segments that are not in the first one - the leading /.
 
323
        to_process = current_path[:1]
 
324
        for segment in current_path[1:]:
 
325
            if segment != '':
 
326
                to_process.append(segment)
 
327
 
 
328
        # process '.' and '..' segments into output_path.
 
329
        output_path = []
 
330
        for segment in to_process:
 
331
            if segment == '..':
 
332
                # directory pop. Remove a directory 
 
333
                # as long as we are not at the root
 
334
                if len(output_path) > 1:
 
335
                    output_path.pop()
 
336
                # else: pass
 
337
                # cannot pop beyond the root, so do nothing
 
338
            elif segment == '.':
 
339
                continue # strip the '.' from the output.
 
340
            else:
 
341
                # this will append '' to output_path for the root elements,
 
342
                # which is appropriate: its why we strip '' in the first pass.
 
343
                output_path.append(segment)
 
344
 
 
345
        # check output special cases:
 
346
        if output_path == ['']:
 
347
            # [''] -> ['', '']
 
348
            output_path = ['', '']
 
349
        elif output_path[:2] == ['', '~']:
 
350
            # ['', '~', ...] -> ...
 
351
            output_path = output_path[2:]
 
352
        path = '/'.join(output_path)
277
353
        return path
278
354
 
279
355
    def relpath(self, abspath):