[Pkg-bazaar-commits] ./bzr/unstable r521: - Add patch to give symlink support
Martin Pool
mbp at sourcefrog.net
Fri Apr 10 08:19:12 UTC 2009
------------------------------------------------------------
revno: 521
committer: Martin Pool <mbp at sourcefrog.net>
timestamp: Tue 2005-05-17 17:01:47 +1000
message:
- Add patch to give symlink support
added:
patches/symlink-support.patch
-------------- next part --------------
=== added file 'patches/symlink-support.patch'
--- a/patches/symlink-support.patch 1970-01-01 00:00:00 +0000
+++ b/patches/symlink-support.patch 2005-05-17 07:01:47 +0000
@@ -0,0 +1,629 @@
+Return-Path: <erik at tntech.dk>
+X-Original-To: mbp at sourcefrog.net
+Delivered-To: mbp at ozlabs.org
+X-Greylist: delayed 1826 seconds by postgrey-1.21 at ozlabs; Sun, 15 May 2005 06:59:11 EST
+Received: from upstroke.tntech.dk (cpe.atm2-0-1041078.0x503eaf62.odnxx4.customer.tele.dk [80.62.175.98])
+ by ozlabs.org (Postfix) with ESMTP id B968E679EA
+ for <mbp at sourcefrog.net>; Sun, 15 May 2005 06:59:11 +1000 (EST)
+Received: by upstroke.tntech.dk (Postfix, from userid 1001)
+ id 63F83542FF; Sat, 14 May 2005 22:28:37 +0200 (CEST)
+To: Martin Pool <mbp at sourcefrog.net>
+Subject: [PATCH] symlink support patch
+From: Erik Toubro Nielsen <erik at tntech.dk>
+Date: Sat, 14 May 2005 22:28:37 +0200
+Message-ID: <86u0l57dsa.fsf at upstroke.tntech.dk>
+User-Agent: Gnus/5.1006 (Gnus v5.10.6) XEmacs/21.4 (Security Through
+ Obscurity, linux)
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="=-=-="
+X-Spam-Checker-Version: SpamAssassin 3.0.3 (2005-04-27) on ozlabs.org
+X-Spam-Level:
+X-Spam-Status: No, score=-1.4 required=3.2 tests=BAYES_00,NO_MORE_FUNN,
+ RCVD_IN_BLARS_RBL autolearn=no version=3.0.3
+Content-Length: 19688
+Lines: 604
+
+--=-=-=
+
+I'm not sending this to the list as it is pretty large.
+
+Let me know if its usefull and if I should rework anything.
+
+Erik
+
+
+Overview:
+
+ (Bugfix: in class TreeDelta I've moved kind= one level up, since
+ kind is also used in the else part)
+
+ Since both the InventoryEntry and stat cache is changed, perhaps
+ the branch format number should be increased?
+
+ Added test cases for symlinks to testbzr
+
+ Cannot use realpath since it expands path/L to path/LinkTarget
+ Cannot use exists, use new osutils.lexists
+
+ I'm overloading the text_modified to signify that a link
+ target is changed. Perhaps text_modified should be renamed
+ content_modified?
+
+ InventoryEntry has a new member "symlink_target",
+
+ The stat cache entry has been extended to contain the symlink
+ target and the st_mode. I try to ignore an old format cache
+ file.
+
+
+
+--=-=-=
+Content-Type: text/x-patch
+Content-Disposition: inline; filename=symlinksupport.patch
+
+*** modified file 'bzrlib/add.py'
+--- bzrlib/add.py
++++ bzrlib/add.py
+@@ -38,7 +38,7 @@
+ for f in file_list:
+ kind = bzrlib.osutils.file_kind(f)
+
+- if kind != 'file' and kind != 'directory':
++ if kind != 'file' and kind != 'directory' and kind != 'symlink':
+ if f not in user_list:
+ print "Skipping %s (can't add file of kind '%s')" % (f, kind)
+ continue
+@@ -56,7 +56,7 @@
+
+ kind = bzrlib.osutils.file_kind(f)
+
+- if kind != 'file' and kind != 'directory':
++ if kind != 'file' and kind != 'directory' and kind != 'symlink':
+ bailout("can't add file '%s' of kind %r" % (f, kind))
+
+ versioned = (inv.path2id(rf) != None)
+
+*** modified file 'bzrlib/branch.py'
+--- bzrlib/branch.py
++++ bzrlib/branch.py
+@@ -58,11 +58,9 @@
+ run into the root."""
+ if f == None:
+ f = os.getcwd()
+- elif hasattr(os.path, 'realpath'):
+- f = os.path.realpath(f)
+ else:
+- f = os.path.abspath(f)
+- if not os.path.exists(f):
++ f = bzrlib.osutils.normalizepath(f)
++ if not bzrlib.osutils.lexists(f):
+ raise BzrError('%r does not exist' % f)
+
+
+@@ -189,7 +187,7 @@
+ """Return path relative to this branch of something inside it.
+
+ Raises an error if path is not in this branch."""
+- rp = os.path.realpath(path)
++ rp = bzrlib.osutils.normalizepath(path)
+ # FIXME: windows
+ if not rp.startswith(self.base):
+ bailout("path %r is not within branch %r" % (rp, self.base))
+@@ -531,7 +529,9 @@
+ file_id = entry.file_id
+ mutter('commit prep file %s, id %r ' % (p, file_id))
+
+- if not os.path.exists(p):
++ # it should be enough to use os.lexists instead of exists
++ # but lexists in an 2.4 function
++ if not bzrlib.osutils.lexists(p):
+ mutter(" file is missing, removing from inventory")
+ if verbose:
+ show_status('D', entry.kind, quotefn(path))
+@@ -554,6 +554,10 @@
+ if entry.kind == 'directory':
+ if not isdir(p):
+ bailout("%s is entered as directory but not a directory" % quotefn(p))
++ elif entry.kind == 'symlink':
++ if not os.path.islink(p):
++ bailout("%s is entered as symbolic link but is not a symbolic link" % quotefn(p))
++ entry.read_symlink_target(p)
+ elif entry.kind == 'file':
+ if not isfile(p):
+ bailout("%s is entered as file but is not a file" % quotefn(p))
+
+*** modified file 'bzrlib/diff.py'
+--- bzrlib/diff.py
++++ bzrlib/diff.py
+@@ -66,6 +66,11 @@
+ print >>to_file, "\\ No newline at end of file"
+ print >>to_file
+
++def _diff_symlink(old_tree, new_tree, file_id):
++ t1 = old_tree.get_symlink_target(file_id)
++ t2 = new_tree.get_symlink_target(file_id)
++ print '*** *** target changed %r => %r' % (t1, t2)
++
+
+ def show_diff(b, revision, specific_files):
+ import sys
+@@ -112,12 +117,15 @@
+
+ for old_path, new_path, file_id, kind, text_modified in delta.renamed:
+ print '*** renamed %s %r => %r' % (kind, old_path, new_path)
+- if text_modified:
++ if kind == 'file' and text_modified:
+ _diff_one(old_tree.get_file(file_id).readlines(),
+ new_tree.get_file(file_id).readlines(),
+ sys.stdout,
+ fromfile=old_label + old_path,
+ tofile=new_label + new_path)
++
++ elif kind == 'symlink' and text_modified:
++ _diff_symlink(old_tree, new_tree, file_id)
+
+ for path, file_id, kind in delta.modified:
+ print '*** modified %s %r' % (kind, path)
+@@ -128,6 +136,8 @@
+ fromfile=old_label + path,
+ tofile=new_label + path)
+
++ elif kind == 'symlink':
++ _diff_symlink(old_tree, new_tree, file_id)
+
+
+ class TreeDelta:
+@@ -149,7 +159,9 @@
+ Each id is listed only once.
+
+ Files that are both modified and renamed are listed only in
+- renamed, with the text_modified flag true.
++ renamed, with the text_modified flag true. The text_modified
++ applies either to the the content of the file or the target of the
++ symbolic link, depending of the kind of file.
+
+ The lists are normally sorted when the delta is created.
+ """
+@@ -224,8 +236,8 @@
+ specific_files = ImmutableSet(specific_files)
+
+ for file_id in old_tree:
++ kind = old_inv.get_file_kind(file_id)
+ if file_id in new_tree:
+- kind = old_inv.get_file_kind(file_id)
+ assert kind == new_inv.get_file_kind(file_id)
+
+ assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
+@@ -246,6 +258,14 @@
+ old_sha1 = old_tree.get_file_sha1(file_id)
+ new_sha1 = new_tree.get_file_sha1(file_id)
+ text_modified = (old_sha1 != new_sha1)
++ elif kind == 'symlink':
++ t1 = old_tree.get_symlink_target(file_id)
++ t2 = new_tree.get_symlink_target(file_id)
++ if t1 != t2:
++ mutter(" symlink target changed")
++ text_modified = True
++ else:
++ text_modified = False
+ else:
+ ## mutter("no text to check for %r %r" % (file_id, kind))
+ text_modified = False
+
+*** modified file 'bzrlib/inventory.py'
+--- bzrlib/inventory.py
++++ bzrlib/inventory.py
+@@ -125,14 +125,22 @@
+ self.kind = kind
+ self.text_id = text_id
+ self.parent_id = parent_id
++ self.symlink_target = None
+ if kind == 'directory':
+ self.children = {}
+ elif kind == 'file':
+ pass
++ elif kind == 'symlink':
++ pass
+ else:
+ raise BzrError("unhandled entry kind %r" % kind)
+
+-
++ def read_symlink_target(self, path):
++ if self.kind == 'symlink':
++ try:
++ self.symlink_target = os.readlink(path)
++ except OSError,e:
++ raise BzrError("os.readlink error, %s" % e)
+
+ def sorted_children(self):
+ l = self.children.items()
+@@ -145,6 +153,7 @@
+ self.parent_id, text_id=self.text_id)
+ other.text_sha1 = self.text_sha1
+ other.text_size = self.text_size
++ other.symlink_target = self.symlink_target
+ return other
+
+
+@@ -168,7 +177,7 @@
+ if self.text_size != None:
+ e.set('text_size', '%d' % self.text_size)
+
+- for f in ['text_id', 'text_sha1']:
++ for f in ['text_id', 'text_sha1', 'symlink_target']:
+ v = getattr(self, f)
+ if v != None:
+ e.set(f, v)
+@@ -198,6 +207,7 @@
+ self = cls(elt.get('file_id'), elt.get('name'), elt.get('kind'), parent_id)
+ self.text_id = elt.get('text_id')
+ self.text_sha1 = elt.get('text_sha1')
++ self.symlink_target = elt.get('symlink_target')
+
+ ## mutter("read inventoryentry: %r" % (elt.attrib))
+
+
+*** modified file 'bzrlib/osutils.py'
+--- bzrlib/osutils.py
++++ bzrlib/osutils.py
+@@ -58,7 +58,30 @@
+ else:
+ raise BzrError("can't handle file kind with mode %o of %r" % (mode, f))
+
+-
++def lexists(f):
++ try:
++ if hasattr(os, 'lstat'):
++ os.lstat(f)
++ else:
++ os.stat(f)
++ return True
++ except OSError,e:
++ if e.errno == errno.ENOENT:
++ return False;
++ else:
++ raise BzrError("lstat/stat of (%r): %r" % (f, e))
++
++def normalizepath(f):
++ if hasattr(os.path, 'realpath'):
++ F = os.path.realpath
++ else:
++ F = os.path.abspath
++ [p,e] = os.path.split(f)
++ if e == "" or e == "." or e == "..":
++ return F(f)
++ else:
++ return os.path.join(F(p), e)
++
+
+ def isdir(f):
+ """True if f is an accessible directory."""
+
+*** modified file 'bzrlib/statcache.py'
+--- bzrlib/statcache.py
++++ bzrlib/statcache.py
+@@ -53,7 +53,7 @@
+ to use a tdb instead.
+
+ The cache is represented as a map from file_id to a tuple of (file_id,
+-sha1, path, size, mtime, ctime, ino, dev).
++sha1, path, symlink target, size, mtime, ctime, ino, dev, mode).
+ """
+
+
+@@ -62,11 +62,13 @@
+ FP_CTIME = 2
+ FP_INO = 3
+ FP_DEV = 4
+-
++FP_ST_MODE=5
+
+ SC_FILE_ID = 0
+ SC_SHA1 = 1
+-
++SC_SYMLINK_TARGET = 3
++
++CACHE_ENTRY_SIZE = 10
+
+ def fingerprint(abspath):
+ try:
+@@ -79,7 +81,7 @@
+ return None
+
+ return (fs.st_size, fs.st_mtime,
+- fs.st_ctime, fs.st_ino, fs.st_dev)
++ fs.st_ctime, fs.st_ino, fs.st_dev, fs.st_mode)
+
+
+ def _write_cache(basedir, entry_iter, dangerfiles):
+@@ -93,7 +95,9 @@
+ continue
+ outf.write(entry[0] + ' ' + entry[1] + ' ')
+ outf.write(b2a_qp(entry[2], True))
+- outf.write(' %d %d %d %d %d\n' % entry[3:])
++ outf.write(' ')
++ outf.write(b2a_qp(entry[3], True)) # symlink_target
++ outf.write(' %d %d %d %d %d %d\n' % entry[4:])
+
+ outf.commit()
+ finally:
+@@ -114,10 +118,13 @@
+
+ for l in cachefile:
+ f = l.split(' ')
++ if len(f) != CACHE_ENTRY_SIZE:
++ mutter("cache is in old format, must recreate it")
++ return {}
+ file_id = f[0]
+ if file_id in cache:
+ raise BzrError("duplicated file_id in cache: {%s}" % file_id)
+- cache[file_id] = (f[0], f[1], a2b_qp(f[2])) + tuple([long(x) for x in f[3:]])
++ cache[file_id] = (f[0], f[1], a2b_qp(f[2]), a2b_qp(f[3])) + tuple([long(x) for x in f[4:]])
+ return cache
+
+
+@@ -125,7 +132,7 @@
+
+ def _files_from_inventory(inv):
+ for path, ie in inv.iter_entries():
+- if ie.kind != 'file':
++ if ie.kind != 'file' and ie.kind != 'symlink':
+ continue
+ yield ie.file_id, path
+
+@@ -190,17 +197,24 @@
+ if (fp[FP_MTIME] >= now) or (fp[FP_CTIME] >= now):
+ dangerfiles.add(file_id)
+
+- if cacheentry and (cacheentry[3:] == fp):
++ if cacheentry and (cacheentry[4:] == fp):
+ continue # all stat fields unchanged
+
+ hardcheck += 1
+
+- dig = sha.new(file(abspath, 'rb').read()).hexdigest()
+-
++ mode = fp[FP_ST_MODE]
++ if stat.S_ISREG(mode):
++ link_target = '-' # can be anything, but must be non-empty
++ dig = sha.new(file(abspath, 'rb').read()).hexdigest()
++ elif stat.S_ISLNK(mode):
++ link_target = os.readlink(abspath)
++ dig = sha.new(link_target).hexdigest()
++ else:
++ raise BzrError("file %r: unknown file stat mode: %o"%(abspath,mode))
+ if cacheentry == None or dig != cacheentry[1]:
+ # if there was no previous entry for this file, or if the
+ # SHA has changed, then update the cache
+- cacheentry = (file_id, dig, path) + fp
++ cacheentry = (file_id, dig, path, link_target) + fp
+ cache[file_id] = cacheentry
+ change_cnt += 1
+
+
+*** modified file 'bzrlib/tree.py'
+--- bzrlib/tree.py
++++ bzrlib/tree.py
+@@ -125,6 +125,11 @@
+ os.mkdir(fullpath)
+ elif kind == 'file':
+ pumpfile(self.get_file(ie.file_id), file(fullpath, 'wb'))
++ elif kind == 'symlink':
++ try:
++ os.symlink(ie.symlink_target, fullpath)
++ except OSError,e:
++ bailout("Failed to create symlink %r -> %r, error: %s" % (fullpath, ie.symlink_target, e))
+ else:
+ bailout("don't know how to export {%s} of kind %r" % (ie.file_id, kind))
+ mutter(" export {%s} kind %s to %s" % (ie.file_id, kind, fullpath))
+@@ -167,6 +172,9 @@
+ for path, entry in self.inventory.iter_entries():
+ yield path, 'V', entry.kind, entry.file_id
+
++ def get_symlink_target(self, file_id):
++ ie = self._inventory[file_id]
++ return ie.symlink_target;
+
+ class EmptyTree(Tree):
+ def __init__(self):
+@@ -179,7 +187,8 @@
+ if False: # just to make it a generator
+ yield None
+
+-
++ def get_symlink_target(self, file_id):
++ return None
+
+ ######################################################################
+ # diff
+@@ -245,3 +254,7 @@
+ if old_name != new_name:
+ yield (old_name, new_name)
+
++
++ def get_symlink_target(self, file_id):
++ ie = self._inventory[file_id]
++ return ie.symlink_target
+
+*** modified file 'bzrlib/workingtree.py'
+--- bzrlib/workingtree.py
++++ bzrlib/workingtree.py
+@@ -53,7 +53,7 @@
+ ie = inv[file_id]
+ if ie.kind == 'file':
+ if ((file_id in self._statcache)
+- or (os.path.exists(self.abspath(inv.id2path(file_id))))):
++ or (bzrlib.osutils.lexists(self.abspath(inv.id2path(file_id))))):
+ yield file_id
+
+
+@@ -66,7 +66,7 @@
+ return os.path.join(self.basedir, filename)
+
+ def has_filename(self, filename):
+- return os.path.exists(self.abspath(filename))
++ return bzrlib.osutils.lexists(self.abspath(filename))
+
+ def get_file(self, file_id):
+ return self.get_file_byname(self.id2path(file_id))
+@@ -86,7 +86,7 @@
+ self._update_statcache()
+ if file_id in self._statcache:
+ return True
+- return os.path.exists(self.abspath(self.id2path(file_id)))
++ return bzrlib.osutils.lexists(self.abspath(self.id2path(file_id)))
+
+
+ __contains__ = has_id
+@@ -108,6 +108,12 @@
+ return self._statcache[file_id][statcache.SC_SHA1]
+
+
++ def get_symlink_target(self, file_id):
++ import statcache
++ self._update_statcache()
++ target = self._statcache[file_id][statcache.SC_SYMLINK_TARGET]
++ return target
++
+ def file_class(self, filename):
+ if self.path2id(filename):
+ return 'V'
+
+*** modified file 'testbzr'
+--- testbzr
++++ testbzr
+@@ -1,4 +1,4 @@
+-#! /usr/bin/python
++#! /usr/bin/env python
+
+ # Copyright (C) 2005 Canonical Ltd
+
+@@ -113,6 +113,17 @@
+ logfile.write(' at %s:%d\n' % stack[:2])
+
+
++def has_symlinks():
++ import os;
++ if hasattr(os, 'symlink'):
++ return True
++ else:
++ return False
++
++def listdir_sorted(dir):
++ L = os.listdir(dir)
++ L.sort()
++ return L
+
+ # prepare an empty scratch directory
+ if os.path.exists(TESTDIR):
+@@ -320,8 +331,105 @@
+ runcmd('bzr ignore *.blah')
+ assert backtick('bzr unknowns') == ''
+ assert file('.bzrignore', 'rt').read() == '*.blah\n'
+-
+-
++ cd("..")
++
++ if has_symlinks():
++ progress("symlinks")
++ mkdir('symlinks')
++ cd('symlinks')
++ runcmd('bzr init')
++ os.symlink("NOWHERE1", "link1")
++ runcmd('bzr add link1')
++ assert backtick('bzr unknowns') == ''
++ runcmd(['bzr', 'commit', '-m', '1: added symlink link1'])
++
++ mkdir('d1')
++ runcmd('bzr add d1')
++ assert backtick('bzr unknowns') == ''
++ os.symlink("NOWHERE2", "d1/link2")
++ assert backtick('bzr unknowns') == 'd1/link2\n'
++ # is d1/link2 found when adding d1
++ runcmd('bzr add d1')
++ assert backtick('bzr unknowns') == ''
++ os.symlink("NOWHERE3", "d1/link3")
++ assert backtick('bzr unknowns') == 'd1/link3\n'
++ runcmd(['bzr', 'commit', '-m', '2: added dir, symlink'])
++
++ runcmd('bzr rename d1 d2')
++ runcmd('bzr move d2/link2 .')
++ runcmd('bzr move link1 d2')
++ assert os.readlink("./link2") == "NOWHERE2"
++ assert os.readlink("d2/link1") == "NOWHERE1"
++ runcmd('bzr add d2/link3')
++ runcmd('bzr diff')
++ runcmd(['bzr', 'commit', '-m', '3: rename of dir, move symlinks, add link3'])
++
++ os.unlink("link2")
++ os.symlink("TARGET 2", "link2")
++ os.unlink("d2/link1")
++ os.symlink("TARGET 1", "d2/link1")
++ runcmd('bzr diff')
++ assert backtick("bzr relpath d2/link1") == "d2/link1\n"
++ runcmd(['bzr', 'commit', '-m', '4: retarget of two links'])
++
++ runcmd('bzr remove d2/link1')
++ assert backtick('bzr unknowns') == 'd2/link1\n'
++ runcmd(['bzr', 'commit', '-m', '5: remove d2/link1'])
++
++ os.mkdir("d1")
++ runcmd('bzr add d1')
++ runcmd('bzr rename d2/link3 d1/link3new')
++ assert backtick('bzr unknowns') == 'd2/link1\n'
++ runcmd(['bzr', 'commit', '-m', '6: remove d2/link1, move/rename link3'])
++
++ runcmd(['bzr', 'check'])
++
++ runcmd(['bzr', 'export', '-r', '1', 'exp1.tmp'])
++ cd("exp1.tmp")
++ assert listdir_sorted(".") == [ "link1" ]
++ assert os.readlink("link1") == "NOWHERE1"
++ cd("..")
++
++ runcmd(['bzr', 'export', '-r', '2', 'exp2.tmp'])
++ cd("exp2.tmp")
++ assert listdir_sorted(".") == [ "d1", "link1" ]
++ cd("..")
++
++ runcmd(['bzr', 'export', '-r', '3', 'exp3.tmp'])
++ cd("exp3.tmp")
++ assert listdir_sorted(".") == [ "d2", "link2" ]
++ assert listdir_sorted("d2") == [ "link1", "link3" ]
++ assert os.readlink("d2/link1") == "NOWHERE1"
++ assert os.readlink("link2") == "NOWHERE2"
++ cd("..")
++
++ runcmd(['bzr', 'export', '-r', '4', 'exp4.tmp'])
++ cd("exp4.tmp")
++ assert listdir_sorted(".") == [ "d2", "link2" ]
++ assert os.readlink("d2/link1") == "TARGET 1"
++ assert os.readlink("link2") == "TARGET 2"
++ assert listdir_sorted("d2") == [ "link1", "link3" ]
++ cd("..")
++
++ runcmd(['bzr', 'export', '-r', '5', 'exp5.tmp'])
++ cd("exp5.tmp")
++ assert listdir_sorted(".") == [ "d2", "link2" ]
++ assert os.path.islink("link2")
++ assert listdir_sorted("d2")== [ "link3" ]
++ cd("..")
++
++ runcmd(['bzr', 'export', '-r', '6', 'exp6.tmp'])
++ cd("exp6.tmp")
++ assert listdir_sorted(".") == [ "d1", "d2", "link2" ]
++ assert listdir_sorted("d1") == [ "link3new" ]
++ assert listdir_sorted("d2") == []
++ assert os.readlink("d1/link3new") == "NOWHERE3"
++ cd("..")
++
++ cd("..")
++ else:
++ progress("skipping symlink tests")
++
+ progress("all tests passed!")
+ except Exception, e:
+ sys.stderr.write('*' * 50 + '\n'
+
+
+--=-=-=--
More information about the Pkg-bazaar-commits
mailing list