bzr branch
http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
| 
521
by Martin Pool
 - Add patch to give symlink support  | 
1  | 
Return-Path: <erik@tntech.dk>
 | 
2  | 
X-Original-To: mbp@sourcefrog.net
 | 
|
3  | 
Delivered-To: mbp@ozlabs.org
 | 
|
4  | 
X-Greylist: delayed 1826 seconds by postgrey-1.21 at ozlabs; Sun, 15 May 2005 06:59:11 EST
 | 
|
5  | 
Received: from upstroke.tntech.dk (cpe.atm2-0-1041078.0x503eaf62.odnxx4.customer.tele.dk [80.62.175.98])
 | 
|
6  | 
	by ozlabs.org (Postfix) with ESMTP id B968E679EA
 | 
|
7  | 
	for <mbp@sourcefrog.net>; Sun, 15 May 2005 06:59:11 +1000 (EST)
 | 
|
8  | 
Received: by upstroke.tntech.dk (Postfix, from userid 1001)
 | 
|
9  | 
	id 63F83542FF; Sat, 14 May 2005 22:28:37 +0200 (CEST)
 | 
|
10  | 
To: Martin Pool <mbp@sourcefrog.net>
 | 
|
11  | 
Subject: [PATCH] symlink support patch
 | 
|
12  | 
From: Erik Toubro Nielsen <erik@tntech.dk>
 | 
|
13  | 
Date: Sat, 14 May 2005 22:28:37 +0200
 | 
|
14  | 
Message-ID: <86u0l57dsa.fsf@upstroke.tntech.dk>
 | 
|
15  | 
User-Agent: Gnus/5.1006 (Gnus v5.10.6) XEmacs/21.4 (Security Through
 | 
|
16  | 
 Obscurity, linux)
 | 
|
17  | 
MIME-Version: 1.0
 | 
|
18  | 
Content-Type: multipart/mixed; boundary="=-=-="
 | 
|
19  | 
X-Spam-Checker-Version: SpamAssassin 3.0.3 (2005-04-27) on ozlabs.org
 | 
|
20  | 
X-Spam-Level: 
 | 
|
21  | 
X-Spam-Status: No, score=-1.4 required=3.2 tests=BAYES_00,NO_MORE_FUNN,
 | 
|
22  | 
	RCVD_IN_BLARS_RBL autolearn=no version=3.0.3
 | 
|
23  | 
Content-Length: 19688
 | 
|
24  | 
Lines: 604
 | 
|
25  | 
||
26  | 
--=-=-=
 | 
|
27  | 
||
28  | 
I'm not sending this to the list as it is pretty large. 
 | 
|
29  | 
||
30  | 
Let me know if its usefull and if I should rework anything.
 | 
|
31  | 
||
32  | 
Erik
 | 
|
33  | 
||
34  | 
||
35  | 
Overview:
 | 
|
36  | 
||
37  | 
        (Bugfix: in class TreeDelta I've moved kind= one level up, since
 | 
|
38  | 
        kind is also used in the else part)
 | 
|
39  | 
||
40  | 
        Since both the InventoryEntry and stat cache is changed, perhaps
 | 
|
41  | 
        the branch format number should be increased?
 | 
|
42  | 
||
43  | 
        Added test cases for symlinks to testbzr
 | 
|
44  | 
||
45  | 
        Cannot use realpath since it expands path/L to path/LinkTarget
 | 
|
46  | 
        Cannot use exists, use new osutils.lexists
 | 
|
47  | 
||
48  | 
        I'm overloading the text_modified to signify that a link        
 | 
|
49  | 
        target is changed. Perhaps text_modified should be renamed
 | 
|
50  | 
        content_modified?
 | 
|
51  | 
||
52  | 
        InventoryEntry has a new member "symlink_target", 
 | 
|
53  | 
||
54  | 
        The stat cache entry has been extended to contain the symlink
 | 
|
55  | 
        target and the st_mode. I try to ignore an old format cache
 | 
|
56  | 
        file.
 | 
|
57  | 
||
58  | 
||
59  | 
||
60  | 
--=-=-=
 | 
|
61  | 
Content-Type: text/x-patch
 | 
|
62  | 
Content-Disposition: inline; filename=symlinksupport.patch
 | 
|
63  | 
||
64  | 
*** modified file 'bzrlib/add.py'
 | 
|
65  | 
--- bzrlib/add.py 
 | 
|
66  | 
+++ bzrlib/add.py 
 | 
|
67  | 
@@ -38,7 +38,7 @@
 | 
|
68  | 
     for f in file_list:
 | 
|
69  | 
         kind = bzrlib.osutils.file_kind(f)
 | 
|
70  | 
 
 | 
|
71  | 
-        if kind != 'file' and kind != 'directory':
 | 
|
72  | 
+        if kind != 'file' and kind != 'directory' and kind != 'symlink':
 | 
|
73  | 
             if f not in user_list:
 | 
|
74  | 
                 print "Skipping %s (can't add file of kind '%s')" % (f, kind)
 | 
|
75  | 
                 continue
 | 
|
76  | 
@@ -56,7 +56,7 @@
 | 
|
77  | 
 
 | 
|
78  | 
         kind = bzrlib.osutils.file_kind(f)
 | 
|
79  | 
 
 | 
|
80  | 
-        if kind != 'file' and kind != 'directory':
 | 
|
81  | 
+        if kind != 'file' and kind != 'directory' and kind != 'symlink':
 | 
|
82  | 
             bailout("can't add file '%s' of kind %r" % (f, kind))
 | 
|
83  | 
             
 | 
|
84  | 
         versioned = (inv.path2id(rf) != None)
 | 
|
85  | 
||
86  | 
*** modified file 'bzrlib/branch.py'
 | 
|
87  | 
--- bzrlib/branch.py 
 | 
|
88  | 
+++ bzrlib/branch.py 
 | 
|
89  | 
@@ -58,11 +58,9 @@
 | 
|
90  | 
     run into the root."""
 | 
|
91  | 
     if f == None:
 | 
|
92  | 
         f = os.getcwd()
 | 
|
93  | 
-    elif hasattr(os.path, 'realpath'):
 | 
|
94  | 
-        f = os.path.realpath(f)
 | 
|
95  | 
     else:
 | 
|
96  | 
-        f = os.path.abspath(f)
 | 
|
97  | 
-    if not os.path.exists(f):
 | 
|
98  | 
+        f = bzrlib.osutils.normalizepath(f)
 | 
|
99  | 
+    if not bzrlib.osutils.lexists(f):
 | 
|
100  | 
         raise BzrError('%r does not exist' % f)
 | 
|
101  | 
         
 | 
|
102  | 
 
 | 
|
103  | 
@@ -189,7 +187,7 @@
 | 
|
104  | 
         """Return path relative to this branch of something inside it.
 | 
|
105  | 
 
 | 
|
106  | 
         Raises an error if path is not in this branch."""
 | 
|
107  | 
-        rp = os.path.realpath(path)
 | 
|
108  | 
+        rp = bzrlib.osutils.normalizepath(path)
 | 
|
109  | 
         # FIXME: windows
 | 
|
110  | 
         if not rp.startswith(self.base):
 | 
|
111  | 
             bailout("path %r is not within branch %r" % (rp, self.base))
 | 
|
112  | 
@@ -531,7 +529,9 @@
 | 
|
113  | 
             file_id = entry.file_id
 | 
|
114  | 
             mutter('commit prep file %s, id %r ' % (p, file_id))
 | 
|
115  | 
 
 | 
|
116  | 
-            if not os.path.exists(p):
 | 
|
117  | 
+            # it should be enough to use os.lexists instead of exists
 | 
|
118  | 
+            # but lexists in an 2.4 function
 | 
|
119  | 
+            if not bzrlib.osutils.lexists(p):
 | 
|
120  | 
                 mutter("    file is missing, removing from inventory")
 | 
|
121  | 
                 if verbose:
 | 
|
122  | 
                     show_status('D', entry.kind, quotefn(path))
 | 
|
123  | 
@@ -554,6 +554,10 @@
 | 
|
124  | 
             if entry.kind == 'directory':
 | 
|
125  | 
                 if not isdir(p):
 | 
|
126  | 
                     bailout("%s is entered as directory but not a directory" % quotefn(p))
 | 
|
127  | 
+            elif entry.kind == 'symlink':
 | 
|
128  | 
+                if not os.path.islink(p):
 | 
|
129  | 
+                    bailout("%s is entered as symbolic link but is not a symbolic link" % quotefn(p))
 | 
|
130  | 
+                entry.read_symlink_target(p)
 | 
|
131  | 
             elif entry.kind == 'file':
 | 
|
132  | 
                 if not isfile(p):
 | 
|
133  | 
                     bailout("%s is entered as file but is not a file" % quotefn(p))
 | 
|
134  | 
||
135  | 
*** modified file 'bzrlib/diff.py'
 | 
|
136  | 
--- bzrlib/diff.py 
 | 
|
137  | 
+++ bzrlib/diff.py 
 | 
|
138  | 
@@ -66,6 +66,11 @@
 | 
|
139  | 
         print >>to_file, "\\ No newline at end of file"
 | 
|
140  | 
     print >>to_file
 | 
|
141  | 
 
 | 
|
142  | 
+def _diff_symlink(old_tree, new_tree, file_id):
 | 
|
143  | 
+    t1 = old_tree.get_symlink_target(file_id)
 | 
|
144  | 
+    t2 = new_tree.get_symlink_target(file_id)
 | 
|
145  | 
+    print '*** *** target changed %r => %r' % (t1, t2)
 | 
|
146  | 
+    
 | 
|
147  | 
 
 | 
|
148  | 
 def show_diff(b, revision, specific_files):
 | 
|
149  | 
     import sys
 | 
|
150  | 
@@ -112,12 +117,15 @@
 | 
|
151  | 
 
 | 
|
152  | 
     for old_path, new_path, file_id, kind, text_modified in delta.renamed:
 | 
|
153  | 
         print '*** renamed %s %r => %r' % (kind, old_path, new_path)
 | 
|
154  | 
-        if text_modified:
 | 
|
155  | 
+        if kind == 'file' and text_modified:
 | 
|
156  | 
             _diff_one(old_tree.get_file(file_id).readlines(),
 | 
|
157  | 
                    new_tree.get_file(file_id).readlines(),
 | 
|
158  | 
                    sys.stdout,
 | 
|
159  | 
                    fromfile=old_label + old_path,
 | 
|
160  | 
                    tofile=new_label + new_path)
 | 
|
161  | 
+
 | 
|
162  | 
+        elif kind == 'symlink' and text_modified:
 | 
|
163  | 
+            _diff_symlink(old_tree, new_tree, file_id)
 | 
|
164  | 
 
 | 
|
165  | 
     for path, file_id, kind in delta.modified:
 | 
|
166  | 
         print '*** modified %s %r' % (kind, path)
 | 
|
167  | 
@@ -128,6 +136,8 @@
 | 
|
168  | 
                    fromfile=old_label + path,
 | 
|
169  | 
                    tofile=new_label + path)
 | 
|
170  | 
 
 | 
|
171  | 
+        elif kind == 'symlink':
 | 
|
172  | 
+            _diff_symlink(old_tree, new_tree, file_id)
 | 
|
173  | 
 
 | 
|
174  | 
 
 | 
|
175  | 
 class TreeDelta:
 | 
|
176  | 
@@ -149,7 +159,9 @@
 | 
|
177  | 
     Each id is listed only once.
 | 
|
178  | 
 
 | 
|
179  | 
     Files that are both modified and renamed are listed only in
 | 
|
180  | 
-    renamed, with the text_modified flag true.
 | 
|
181  | 
+    renamed, with the text_modified flag true. The text_modified
 | 
|
182  | 
+    applies either to the the content of the file or the target of the
 | 
|
183  | 
+    symbolic link, depending of the kind of file.
 | 
|
184  | 
 
 | 
|
185  | 
     The lists are normally sorted when the delta is created.
 | 
|
186  | 
     """
 | 
|
187  | 
@@ -224,8 +236,8 @@
 | 
|
188  | 
         specific_files = ImmutableSet(specific_files)
 | 
|
189  | 
 
 | 
|
190  | 
     for file_id in old_tree:
 | 
|
191  | 
+        kind = old_inv.get_file_kind(file_id)
 | 
|
192  | 
         if file_id in new_tree:
 | 
|
193  | 
-            kind = old_inv.get_file_kind(file_id)
 | 
|
194  | 
             assert kind == new_inv.get_file_kind(file_id)
 | 
|
195  | 
             
 | 
|
196  | 
             assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
 | 
|
197  | 
@@ -246,6 +258,14 @@
 | 
|
198  | 
                 old_sha1 = old_tree.get_file_sha1(file_id)
 | 
|
199  | 
                 new_sha1 = new_tree.get_file_sha1(file_id)
 | 
|
200  | 
                 text_modified = (old_sha1 != new_sha1)
 | 
|
201  | 
+            elif kind == 'symlink':
 | 
|
202  | 
+                t1 = old_tree.get_symlink_target(file_id)
 | 
|
203  | 
+                t2 = new_tree.get_symlink_target(file_id)
 | 
|
204  | 
+                if t1 != t2:
 | 
|
205  | 
+                    mutter("    symlink target changed")
 | 
|
206  | 
+                    text_modified = True
 | 
|
207  | 
+                else:
 | 
|
208  | 
+                    text_modified = False
 | 
|
209  | 
             else:
 | 
|
210  | 
                 ## mutter("no text to check for %r %r" % (file_id, kind))
 | 
|
211  | 
                 text_modified = False
 | 
|
212  | 
||
213  | 
*** modified file 'bzrlib/inventory.py'
 | 
|
214  | 
--- bzrlib/inventory.py 
 | 
|
215  | 
+++ bzrlib/inventory.py 
 | 
|
216  | 
@@ -125,14 +125,22 @@
 | 
|
217  | 
         self.kind = kind
 | 
|
218  | 
         self.text_id = text_id
 | 
|
219  | 
         self.parent_id = parent_id
 | 
|
220  | 
+        self.symlink_target = None
 | 
|
221  | 
         if kind == 'directory':
 | 
|
222  | 
             self.children = {}
 | 
|
223  | 
         elif kind == 'file':
 | 
|
224  | 
             pass
 | 
|
225  | 
+        elif kind == 'symlink':
 | 
|
226  | 
+            pass
 | 
|
227  | 
         else:
 | 
|
228  | 
             raise BzrError("unhandled entry kind %r" % kind)
 | 
|
229  | 
 
 | 
|
230  | 
-
 | 
|
231  | 
+    def read_symlink_target(self, path):
 | 
|
232  | 
+        if self.kind == 'symlink':
 | 
|
233  | 
+            try:
 | 
|
234  | 
+                self.symlink_target = os.readlink(path)
 | 
|
235  | 
+            except OSError,e:
 | 
|
236  | 
+                raise BzrError("os.readlink error, %s" % e)
 | 
|
237  | 
 
 | 
|
238  | 
     def sorted_children(self):
 | 
|
239  | 
         l = self.children.items()
 | 
|
240  | 
@@ -145,6 +153,7 @@
 | 
|
241  | 
                                self.parent_id, text_id=self.text_id)
 | 
|
242  | 
         other.text_sha1 = self.text_sha1
 | 
|
243  | 
         other.text_size = self.text_size
 | 
|
244  | 
+        other.symlink_target = self.symlink_target
 | 
|
245  | 
         return other
 | 
|
246  | 
 
 | 
|
247  | 
 
 | 
|
248  | 
@@ -168,7 +177,7 @@
 | 
|
249  | 
         if self.text_size != None:
 | 
|
250  | 
             e.set('text_size', '%d' % self.text_size)
 | 
|
251  | 
             
 | 
|
252  | 
-        for f in ['text_id', 'text_sha1']:
 | 
|
253  | 
+        for f in ['text_id', 'text_sha1', 'symlink_target']:
 | 
|
254  | 
             v = getattr(self, f)
 | 
|
255  | 
             if v != None:
 | 
|
256  | 
                 e.set(f, v)
 | 
|
257  | 
@@ -198,6 +207,7 @@
 | 
|
258  | 
         self = cls(elt.get('file_id'), elt.get('name'), elt.get('kind'), parent_id)
 | 
|
259  | 
         self.text_id = elt.get('text_id')
 | 
|
260  | 
         self.text_sha1 = elt.get('text_sha1')
 | 
|
261  | 
+        self.symlink_target = elt.get('symlink_target')
 | 
|
262  | 
         
 | 
|
263  | 
         ## mutter("read inventoryentry: %r" % (elt.attrib))
 | 
|
264  | 
 
 | 
|
265  | 
||
266  | 
*** modified file 'bzrlib/osutils.py'
 | 
|
267  | 
--- bzrlib/osutils.py 
 | 
|
268  | 
+++ bzrlib/osutils.py 
 | 
|
269  | 
@@ -58,7 +58,30 @@
 | 
|
270  | 
     else:
 | 
|
271  | 
         raise BzrError("can't handle file kind with mode %o of %r" % (mode, f)) 
 | 
|
272  | 
 
 | 
|
273  | 
-
 | 
|
274  | 
+def lexists(f):
 | 
|
275  | 
+    try:
 | 
|
276  | 
+        if hasattr(os, 'lstat'):
 | 
|
277  | 
+            os.lstat(f)
 | 
|
278  | 
+        else:
 | 
|
279  | 
+            os.stat(f)
 | 
|
280  | 
+        return True
 | 
|
281  | 
+    except OSError,e:
 | 
|
282  | 
+        if e.errno == errno.ENOENT:
 | 
|
283  | 
+            return False;
 | 
|
284  | 
+        else:
 | 
|
285  | 
+            raise BzrError("lstat/stat of (%r): %r" % (f, e))
 | 
|
286  | 
+
 | 
|
287  | 
+def normalizepath(f):
 | 
|
288  | 
+    if hasattr(os.path, 'realpath'):
 | 
|
289  | 
+        F = os.path.realpath
 | 
|
290  | 
+    else:
 | 
|
291  | 
+        F = os.path.abspath
 | 
|
292  | 
+    [p,e] = os.path.split(f)
 | 
|
293  | 
+    if e == "" or e == "." or e == "..":
 | 
|
294  | 
+        return F(f)
 | 
|
295  | 
+    else:
 | 
|
296  | 
+        return os.path.join(F(p), e)
 | 
|
297  | 
+    
 | 
|
298  | 
 
 | 
|
299  | 
 def isdir(f):
 | 
|
300  | 
     """True if f is an accessible directory."""
 | 
|
301  | 
||
302  | 
*** modified file 'bzrlib/statcache.py'
 | 
|
303  | 
--- bzrlib/statcache.py 
 | 
|
304  | 
+++ bzrlib/statcache.py 
 | 
|
305  | 
@@ -53,7 +53,7 @@
 | 
|
306  | 
 to use a tdb instead.
 | 
|
307  | 
 
 | 
|
308  | 
 The cache is represented as a map from file_id to a tuple of (file_id,
 | 
|
309  | 
-sha1, path, size, mtime, ctime, ino, dev).
 | 
|
310  | 
+sha1, path, symlink target, size, mtime, ctime, ino, dev, mode).
 | 
|
311  | 
 """
 | 
|
312  | 
 
 | 
|
313  | 
 
 | 
|
314  | 
@@ -62,11 +62,13 @@
 | 
|
315  | 
 FP_CTIME = 2
 | 
|
316  | 
 FP_INO   = 3
 | 
|
317  | 
 FP_DEV   = 4
 | 
|
318  | 
-
 | 
|
319  | 
+FP_ST_MODE=5
 | 
|
320  | 
 
 | 
|
321  | 
 SC_FILE_ID = 0
 | 
|
322  | 
 SC_SHA1    = 1 
 | 
|
323  | 
-
 | 
|
324  | 
+SC_SYMLINK_TARGET = 3
 | 
|
325  | 
+
 | 
|
326  | 
+CACHE_ENTRY_SIZE = 10
 | 
|
327  | 
 
 | 
|
328  | 
 def fingerprint(abspath):
 | 
|
329  | 
     try:
 | 
|
330  | 
@@ -79,7 +81,7 @@
 | 
|
331  | 
         return None
 | 
|
332  | 
 
 | 
|
333  | 
     return (fs.st_size, fs.st_mtime,
 | 
|
334  | 
-            fs.st_ctime, fs.st_ino, fs.st_dev)
 | 
|
335  | 
+            fs.st_ctime, fs.st_ino, fs.st_dev, fs.st_mode)
 | 
|
336  | 
 
 | 
|
337  | 
 
 | 
|
338  | 
 def _write_cache(basedir, entry_iter, dangerfiles):
 | 
|
339  | 
@@ -93,7 +95,9 @@
 | 
|
340  | 
                 continue
 | 
|
341  | 
             outf.write(entry[0] + ' ' + entry[1] + ' ')
 | 
|
342  | 
             outf.write(b2a_qp(entry[2], True))
 | 
|
343  | 
-            outf.write(' %d %d %d %d %d\n' % entry[3:])
 | 
|
344  | 
+            outf.write(' ')
 | 
|
345  | 
+            outf.write(b2a_qp(entry[3], True)) # symlink_target
 | 
|
346  | 
+            outf.write(' %d %d %d %d %d %d\n' % entry[4:])
 | 
|
347  | 
 
 | 
|
348  | 
         outf.commit()
 | 
|
349  | 
     finally:
 | 
|
350  | 
@@ -114,10 +118,13 @@
 | 
|
351  | 
     
 | 
|
352  | 
     for l in cachefile:
 | 
|
353  | 
         f = l.split(' ')
 | 
|
354  | 
+        if len(f) != CACHE_ENTRY_SIZE:
 | 
|
355  | 
+            mutter("cache is in old format, must recreate it")
 | 
|
356  | 
+            return {}
 | 
|
357  | 
         file_id = f[0]
 | 
|
358  | 
         if file_id in cache:
 | 
|
359  | 
             raise BzrError("duplicated file_id in cache: {%s}" % file_id)
 | 
|
360  | 
-        cache[file_id] = (f[0], f[1], a2b_qp(f[2])) + tuple([long(x) for x in f[3:]])
 | 
|
361  | 
+        cache[file_id] = (f[0], f[1], a2b_qp(f[2]), a2b_qp(f[3])) + tuple([long(x) for x in f[4:]])
 | 
|
362  | 
     return cache
 | 
|
363  | 
 
 | 
|
364  | 
 
 | 
|
365  | 
@@ -125,7 +132,7 @@
 | 
|
366  | 
 
 | 
|
367  | 
 def _files_from_inventory(inv):
 | 
|
368  | 
     for path, ie in inv.iter_entries():
 | 
|
369  | 
-        if ie.kind != 'file':
 | 
|
370  | 
+        if ie.kind != 'file' and ie.kind != 'symlink':
 | 
|
371  | 
             continue
 | 
|
372  | 
         yield ie.file_id, path
 | 
|
373  | 
     
 | 
|
374  | 
@@ -190,17 +197,24 @@
 | 
|
375  | 
         if (fp[FP_MTIME] >= now) or (fp[FP_CTIME] >= now):
 | 
|
376  | 
             dangerfiles.add(file_id)
 | 
|
377  | 
 
 | 
|
378  | 
-        if cacheentry and (cacheentry[3:] == fp):
 | 
|
379  | 
+        if cacheentry and (cacheentry[4:] == fp):
 | 
|
380  | 
             continue                    # all stat fields unchanged
 | 
|
381  | 
 
 | 
|
382  | 
         hardcheck += 1
 | 
|
383  | 
 
 | 
|
384  | 
-        dig = sha.new(file(abspath, 'rb').read()).hexdigest()
 | 
|
385  | 
-
 | 
|
386  | 
+        mode = fp[FP_ST_MODE]
 | 
|
387  | 
+        if stat.S_ISREG(mode):
 | 
|
388  | 
+            link_target = '-' # can be anything, but must be non-empty
 | 
|
389  | 
+            dig = sha.new(file(abspath, 'rb').read()).hexdigest()
 | 
|
390  | 
+        elif stat.S_ISLNK(mode):
 | 
|
391  | 
+            link_target = os.readlink(abspath)
 | 
|
392  | 
+            dig = sha.new(link_target).hexdigest()
 | 
|
393  | 
+        else:
 | 
|
394  | 
+            raise BzrError("file %r: unknown file stat mode: %o"%(abspath,mode))
 | 
|
395  | 
         if cacheentry == None or dig != cacheentry[1]: 
 | 
|
396  | 
             # if there was no previous entry for this file, or if the
 | 
|
397  | 
             # SHA has changed, then update the cache
 | 
|
398  | 
-            cacheentry = (file_id, dig, path) + fp
 | 
|
399  | 
+            cacheentry = (file_id, dig, path, link_target) + fp
 | 
|
400  | 
             cache[file_id] = cacheentry
 | 
|
401  | 
             change_cnt += 1
 | 
|
402  | 
 
 | 
|
403  | 
||
404  | 
*** modified file 'bzrlib/tree.py'
 | 
|
405  | 
--- bzrlib/tree.py 
 | 
|
406  | 
+++ bzrlib/tree.py 
 | 
|
407  | 
@@ -125,6 +125,11 @@
 | 
|
408  | 
                 os.mkdir(fullpath)
 | 
|
409  | 
             elif kind == 'file':
 | 
|
410  | 
                 pumpfile(self.get_file(ie.file_id), file(fullpath, 'wb'))
 | 
|
411  | 
+            elif kind == 'symlink':
 | 
|
412  | 
+                try:
 | 
|
413  | 
+                    os.symlink(ie.symlink_target, fullpath)
 | 
|
414  | 
+                except OSError,e:
 | 
|
415  | 
+                    bailout("Failed to create symlink %r -> %r, error: %s" % (fullpath, ie.symlink_target, e))
 | 
|
416  | 
             else:
 | 
|
417  | 
                 bailout("don't know how to export {%s} of kind %r" % (ie.file_id, kind))
 | 
|
418  | 
             mutter("  export {%s} kind %s to %s" % (ie.file_id, kind, fullpath))
 | 
|
419  | 
@@ -167,6 +172,9 @@
 | 
|
420  | 
         for path, entry in self.inventory.iter_entries():
 | 
|
421  | 
             yield path, 'V', entry.kind, entry.file_id
 | 
|
422  | 
 
 | 
|
423  | 
+    def get_symlink_target(self, file_id):
 | 
|
424  | 
+        ie = self._inventory[file_id]
 | 
|
425  | 
+        return ie.symlink_target;
 | 
|
426  | 
 
 | 
|
427  | 
 class EmptyTree(Tree):
 | 
|
428  | 
     def __init__(self):
 | 
|
429  | 
@@ -179,7 +187,8 @@
 | 
|
430  | 
         if False:  # just to make it a generator
 | 
|
431  | 
             yield None
 | 
|
432  | 
     
 | 
|
433  | 
-
 | 
|
434  | 
+    def get_symlink_target(self, file_id):
 | 
|
435  | 
+        return None
 | 
|
436  | 
 
 | 
|
437  | 
 ######################################################################
 | 
|
438  | 
 # diff
 | 
|
439  | 
@@ -245,3 +254,7 @@
 | 
|
440  | 
         if old_name != new_name:
 | 
|
441  | 
             yield (old_name, new_name)
 | 
|
442  | 
             
 | 
|
443  | 
+
 | 
|
444  | 
+    def get_symlink_target(self, file_id):
 | 
|
445  | 
+        ie = self._inventory[file_id]        
 | 
|
446  | 
+        return ie.symlink_target
 | 
|
447  | 
||
448  | 
*** modified file 'bzrlib/workingtree.py'
 | 
|
449  | 
--- bzrlib/workingtree.py 
 | 
|
450  | 
+++ bzrlib/workingtree.py 
 | 
|
451  | 
@@ -53,7 +53,7 @@
 | 
|
452  | 
             ie = inv[file_id]
 | 
|
453  | 
             if ie.kind == 'file':
 | 
|
454  | 
                 if ((file_id in self._statcache)
 | 
|
455  | 
-                    or (os.path.exists(self.abspath(inv.id2path(file_id))))):
 | 
|
456  | 
+                    or (bzrlib.osutils.lexists(self.abspath(inv.id2path(file_id))))):
 | 
|
457  | 
                     yield file_id
 | 
|
458  | 
 
 | 
|
459  | 
 
 | 
|
460  | 
@@ -66,7 +66,7 @@
 | 
|
461  | 
         return os.path.join(self.basedir, filename)
 | 
|
462  | 
 
 | 
|
463  | 
     def has_filename(self, filename):
 | 
|
464  | 
-        return os.path.exists(self.abspath(filename))
 | 
|
465  | 
+        return bzrlib.osutils.lexists(self.abspath(filename))
 | 
|
466  | 
 
 | 
|
467  | 
     def get_file(self, file_id):
 | 
|
468  | 
         return self.get_file_byname(self.id2path(file_id))
 | 
|
469  | 
@@ -86,7 +86,7 @@
 | 
|
470  | 
         self._update_statcache()
 | 
|
471  | 
         if file_id in self._statcache:
 | 
|
472  | 
             return True
 | 
|
473  | 
-        return os.path.exists(self.abspath(self.id2path(file_id)))
 | 
|
474  | 
+        return bzrlib.osutils.lexists(self.abspath(self.id2path(file_id)))
 | 
|
475  | 
 
 | 
|
476  | 
 
 | 
|
477  | 
     __contains__ = has_id
 | 
|
478  | 
@@ -108,6 +108,12 @@
 | 
|
479  | 
         return self._statcache[file_id][statcache.SC_SHA1]
 | 
|
480  | 
 
 | 
|
481  | 
 
 | 
|
482  | 
+    def get_symlink_target(self, file_id):
 | 
|
483  | 
+        import statcache
 | 
|
484  | 
+        self._update_statcache()
 | 
|
485  | 
+        target = self._statcache[file_id][statcache.SC_SYMLINK_TARGET]
 | 
|
486  | 
+        return target
 | 
|
487  | 
+        
 | 
|
488  | 
     def file_class(self, filename):
 | 
|
489  | 
         if self.path2id(filename):
 | 
|
490  | 
             return 'V'
 | 
|
491  | 
||
492  | 
*** modified file 'testbzr'
 | 
|
493  | 
--- testbzr 
 | 
|
494  | 
+++ testbzr 
 | 
|
495  | 
@@ -1,4 +1,4 @@
 | 
|
496  | 
-#! /usr/bin/python
 | 
|
497  | 
+#! /usr/bin/env python
 | 
|
498  | 
 
 | 
|
499  | 
 # Copyright (C) 2005 Canonical Ltd
 | 
|
500  | 
 
 | 
|
501  | 
@@ -113,6 +113,17 @@
 | 
|
502  | 
     logfile.write('   at %s:%d\n' % stack[:2])
 | 
|
503  | 
 
 | 
|
504  | 
 
 | 
|
505  | 
+def has_symlinks():
 | 
|
506  | 
+    import os;
 | 
|
507  | 
+    if hasattr(os, 'symlink'):
 | 
|
508  | 
+        return True
 | 
|
509  | 
+    else:
 | 
|
510  | 
+        return False
 | 
|
511  | 
+
 | 
|
512  | 
+def listdir_sorted(dir):
 | 
|
513  | 
+    L = os.listdir(dir)
 | 
|
514  | 
+    L.sort()
 | 
|
515  | 
+    return L
 | 
|
516  | 
 
 | 
|
517  | 
 # prepare an empty scratch directory
 | 
|
518  | 
 if os.path.exists(TESTDIR):
 | 
|
519  | 
@@ -320,8 +331,105 @@
 | 
|
520  | 
     runcmd('bzr ignore *.blah')
 | 
|
521  | 
     assert backtick('bzr unknowns') == ''
 | 
|
522  | 
     assert file('.bzrignore', 'rt').read() == '*.blah\n'
 | 
|
523  | 
-
 | 
|
524  | 
-
 | 
|
525  | 
+    cd("..")
 | 
|
526  | 
+    
 | 
|
527  | 
+    if has_symlinks():
 | 
|
528  | 
+        progress("symlinks")
 | 
|
529  | 
+        mkdir('symlinks')
 | 
|
530  | 
+        cd('symlinks')
 | 
|
531  | 
+        runcmd('bzr init')
 | 
|
532  | 
+        os.symlink("NOWHERE1", "link1")
 | 
|
533  | 
+        runcmd('bzr add link1')
 | 
|
534  | 
+        assert backtick('bzr unknowns') == ''
 | 
|
535  | 
+        runcmd(['bzr', 'commit', '-m', '1: added symlink link1'])
 | 
|
536  | 
+
 | 
|
537  | 
+        mkdir('d1')
 | 
|
538  | 
+        runcmd('bzr add d1')
 | 
|
539  | 
+        assert backtick('bzr unknowns') == ''
 | 
|
540  | 
+        os.symlink("NOWHERE2", "d1/link2")
 | 
|
541  | 
+        assert backtick('bzr unknowns') == 'd1/link2\n'
 | 
|
542  | 
+        # is d1/link2 found when adding d1
 | 
|
543  | 
+        runcmd('bzr add d1')
 | 
|
544  | 
+        assert backtick('bzr unknowns') == ''
 | 
|
545  | 
+        os.symlink("NOWHERE3", "d1/link3")
 | 
|
546  | 
+        assert backtick('bzr unknowns') == 'd1/link3\n'
 | 
|
547  | 
+        runcmd(['bzr', 'commit', '-m', '2: added dir, symlink'])
 | 
|
548  | 
+
 | 
|
549  | 
+        runcmd('bzr rename d1 d2')
 | 
|
550  | 
+        runcmd('bzr move d2/link2 .')
 | 
|
551  | 
+        runcmd('bzr move link1 d2')
 | 
|
552  | 
+        assert os.readlink("./link2") == "NOWHERE2"
 | 
|
553  | 
+        assert os.readlink("d2/link1") == "NOWHERE1"
 | 
|
554  | 
+        runcmd('bzr add d2/link3')
 | 
|
555  | 
+        runcmd('bzr diff')
 | 
|
556  | 
+        runcmd(['bzr', 'commit', '-m', '3: rename of dir, move symlinks, add link3'])
 | 
|
557  | 
+
 | 
|
558  | 
+        os.unlink("link2")
 | 
|
559  | 
+        os.symlink("TARGET 2", "link2")
 | 
|
560  | 
+        os.unlink("d2/link1")
 | 
|
561  | 
+        os.symlink("TARGET 1", "d2/link1")
 | 
|
562  | 
+        runcmd('bzr diff')
 | 
|
563  | 
+        assert backtick("bzr relpath d2/link1") == "d2/link1\n"
 | 
|
564  | 
+        runcmd(['bzr', 'commit', '-m', '4: retarget of two links'])
 | 
|
565  | 
+
 | 
|
566  | 
+        runcmd('bzr remove d2/link1')
 | 
|
567  | 
+        assert backtick('bzr unknowns') == 'd2/link1\n'
 | 
|
568  | 
+        runcmd(['bzr', 'commit', '-m', '5: remove d2/link1'])
 | 
|
569  | 
+
 | 
|
570  | 
+        os.mkdir("d1")
 | 
|
571  | 
+        runcmd('bzr add d1')
 | 
|
572  | 
+        runcmd('bzr rename d2/link3 d1/link3new')
 | 
|
573  | 
+        assert backtick('bzr unknowns') == 'd2/link1\n'
 | 
|
574  | 
+        runcmd(['bzr', 'commit', '-m', '6: remove d2/link1, move/rename link3'])
 | 
|
575  | 
+        
 | 
|
576  | 
+        runcmd(['bzr', 'check'])
 | 
|
577  | 
+        
 | 
|
578  | 
+        runcmd(['bzr', 'export', '-r', '1', 'exp1.tmp'])
 | 
|
579  | 
+        cd("exp1.tmp")
 | 
|
580  | 
+        assert listdir_sorted(".") == [ "link1" ]
 | 
|
581  | 
+        assert os.readlink("link1") == "NOWHERE1"
 | 
|
582  | 
+        cd("..")
 | 
|
583  | 
+        
 | 
|
584  | 
+        runcmd(['bzr', 'export', '-r', '2', 'exp2.tmp'])
 | 
|
585  | 
+        cd("exp2.tmp")
 | 
|
586  | 
+        assert listdir_sorted(".") == [ "d1", "link1" ]
 | 
|
587  | 
+        cd("..")
 | 
|
588  | 
+        
 | 
|
589  | 
+        runcmd(['bzr', 'export', '-r', '3', 'exp3.tmp'])
 | 
|
590  | 
+        cd("exp3.tmp")
 | 
|
591  | 
+        assert listdir_sorted(".") == [ "d2", "link2" ]
 | 
|
592  | 
+        assert listdir_sorted("d2") == [ "link1", "link3" ]
 | 
|
593  | 
+        assert os.readlink("d2/link1") == "NOWHERE1"
 | 
|
594  | 
+        assert os.readlink("link2")    == "NOWHERE2"
 | 
|
595  | 
+        cd("..")
 | 
|
596  | 
+        
 | 
|
597  | 
+        runcmd(['bzr', 'export', '-r', '4', 'exp4.tmp'])
 | 
|
598  | 
+        cd("exp4.tmp")
 | 
|
599  | 
+        assert listdir_sorted(".") == [ "d2", "link2" ]
 | 
|
600  | 
+        assert os.readlink("d2/link1") == "TARGET 1"
 | 
|
601  | 
+        assert os.readlink("link2")    == "TARGET 2"
 | 
|
602  | 
+        assert listdir_sorted("d2") == [ "link1", "link3" ]
 | 
|
603  | 
+        cd("..")
 | 
|
604  | 
+        
 | 
|
605  | 
+        runcmd(['bzr', 'export', '-r', '5', 'exp5.tmp'])
 | 
|
606  | 
+        cd("exp5.tmp")
 | 
|
607  | 
+        assert listdir_sorted(".") == [ "d2", "link2" ]
 | 
|
608  | 
+        assert os.path.islink("link2")
 | 
|
609  | 
+        assert listdir_sorted("d2")== [ "link3" ]
 | 
|
610  | 
+        cd("..")
 | 
|
611  | 
+        
 | 
|
612  | 
+        runcmd(['bzr', 'export', '-r', '6', 'exp6.tmp'])
 | 
|
613  | 
+        cd("exp6.tmp")
 | 
|
614  | 
+        assert listdir_sorted(".") == [ "d1", "d2", "link2" ]
 | 
|
615  | 
+        assert listdir_sorted("d1") == [ "link3new" ]
 | 
|
616  | 
+        assert listdir_sorted("d2") == []
 | 
|
617  | 
+        assert os.readlink("d1/link3new") == "NOWHERE3"
 | 
|
618  | 
+        cd("..")
 | 
|
619  | 
+
 | 
|
620  | 
+        cd("..")
 | 
|
621  | 
+    else:
 | 
|
622  | 
+        progress("skipping symlink tests")
 | 
|
623  | 
+        
 | 
|
624  | 
     progress("all tests passed!")
 | 
|
625  | 
 except Exception, e:
 | 
|
626  | 
     sys.stderr.write('*' * 50 + '\n'
 | 
|
627  | 
||
628  | 
||
629  | 
--=-=-=--
 |