Merge lp:~parthm/bzr/81689-win-symlink-warning into lp:bzr

Proposed by Parth Malwankar
Status: Work in progress
Proposed branch: lp:~parthm/bzr/81689-win-symlink-warning
Merge into: lp:bzr
Diff against target: 472 lines (+197/-62)
11 files modified
bzrlib/commit.py (+12/-6)
bzrlib/delta.py (+7/-3)
bzrlib/diff.py (+4/-0)
bzrlib/errors.py (+0/-15)
bzrlib/mutabletree.py (+25/-7)
bzrlib/tests/test_commit.py (+31/-0)
bzrlib/tests/test_errors.py (+0/-14)
bzrlib/tests/test_transform.py (+61/-11)
bzrlib/tests/test_workingtree.py (+35/-0)
bzrlib/transform.py (+17/-6)
doc/en/release-notes/bzr-2.6.txt (+5/-0)
To merge this branch: bzr merge lp:~parthm/bzr/81689-win-symlink-warning
Reviewer Review Type Date Requested Status
Martin Packman (community) Needs Fixing
bzr-core Pending
Review via email: mp+93784@code.launchpad.net

Description of the change

Hello,

This branch contains changes to allow checkout/branch of repositories containing symlinks on Windows platform. The approach is to simply ignore symlinks and issue a warning about unsupported symlinks when commands like branch, status, diff and commit are run. This is discussed https://bugs.launchpad.net/bzr/+bug/81689 with comments starting with #28.

The patch isn't quite ready for merge yet and I still need to write additional tests. Just wanted to put this up here so I can improve it based on review comments and the general approach is validated. Thanks.

Below is the sample command interaction on Windows.

=================================================================
D:\ext-src>brun branch lp:~parthm/+junk/project-with-symlink
Unable to creat symlink "softlink" on this platform.
Branched 2 revisions.

D:\ext-src>cd project-with-symlink

D:\ext-src\project-with-symlink>ls
hardlink hello

D:\ext-src\project-with-symlink>..\brun.bat st
Ignoring "softlink" as symlinks are not supported on this platform.

D:\ext-src\project-with-symlink>..\brun.bat diff
Ignoring "softlink" as symlinks are not supported on this platform.

D:\ext-src\project-with-symlink>..\brun.bat ci
Committing to: D:/ext-src/project-with-symlink/
missing softlink
Ignoring "softlink" as symlinks are not supported on this platform.
bzr: ERROR: No changes to commit. Please 'bzr add' the files you want to commit,
 or use --unchanged to force an empty commit.
=================================================================

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Hi Parth,

Thanks for working on this. If there's anything I can help with, please let me know. :)

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Setting to work in progress for now so it doesn't show up in the review queue. Let me know when there's something we can look at :)

Revision history for this message
Parth Malwankar (parthm) wrote :

Thanks Jelmer.
The patch is tested on Windows and Linux and seems to work well.

One change that looks somewhat risky at first glance is that mutabletree.has_changes now has a "fast path" (the original) which merely checks if any change exists in the list and a "slow path" that actually filters our symlinks on platforms that don't support windows. This was required to get merge to work correctly. As Windows, would use "slow path" with this change, I tried a basic merge benchmark with the bazaar branch. The performance for slow and fast path seems comparable at least with the small set of changes I did as a test. Logs for the benchmark are below.

Would very much appreciate the inputs on this patch.

Regards,
Parth

D:\ext-src\bzr.dev\foo>c:/python27/python.exe d:/ext-src/bzr.dev/81689-win-symlink-warning/bzr merge ..\bar
 M BRANCH.TODO
 M INSTALL
 M MANIFEST.in
 M Makefile
 M TODO
 M profile_imports.py
 M setup.py
All changes applied successfully.
bzr: warning: some compiled extensions could not be loaded; see <https://answers.launchpad.net/bzr/+faq/703>

Version Number: Windows NT 5.1 (Build 2600)
Exit Time: 9:41 pm, Friday, February 24 2012
Elapsed Time: 0:00:01.875
Process Time: 0:00:00.031
System Calls: 35955
Context Switches: 8848
Page Faults: 31238
Bytes Read: 10693159
Bytes Written: 648409
Bytes Other: 417932

D:\ext-src\bzr.dev\foo>c:/python27/python.exe d:/ext-src/bzr.dev/trunk/bzr merge ..\bar
 M BRANCH.TODO
 M INSTALL
 M MANIFEST.in
 M Makefile
 M TODO
 M profile_imports.py
 M setup.py
All changes applied successfully.
bzr: warning: some compiled extensions could not be loaded; see <https://answers
.launchpad.net/bzr/+faq/703>

Version Number: Windows NT 5.1 (Build 2600)
Exit Time: 9:42 pm, Friday, February 24 2012
Elapsed Time: 0:00:02.109
Process Time: 0:00:00.015
System Calls: 36312
Context Switches: 10706
Page Faults: 54597
Bytes Read: 11065998
Bytes Written: 638656
Bytes Other: 428054

Revision history for this message
Martin Packman (gz) wrote :

Parth, you are a star for tackling this.

One thing this branch really needs is some tests that cover the code paths for where symlinks are unsupported. I think the basic plan should be:

* Create a branch in memory that contains symlinks
* Patch out os.symlink, with the both the "doesn't exist" and "raises error" cases covered
* Run command of some sort on the branch

With the different interesting combinations exercised.

I'm failing to find a good example at the moment, but I think you can use bzrlib.branchbuilder for the first step, and can check the testcase log for your warning message and the treeshape on disk as assertions. Yell if you need any help from me.

review: Needs Fixing
Revision history for this message
Parth Malwankar (parthm) wrote :

Thanks for the ideas on the test. I have added test for commit, tranform, merge, workingtree and diff. Would appreaciate any further review comments.

Revision history for this message
Martin Packman (gz) wrote :

Thanks Parth.

The tests still seem to be constructing the tree directly on disk, and thereby need symlink support to run at all? That shouldn't be necessary, I'll look into the branchbuilder to see if I can cook up a recipe to do what we want here.

Revision history for this message
Parth Malwankar (parthm) wrote :

Hi Martin,

I was looking into this some more and noticed that test_merge.test_modified_symlink has the following comment:

        # Have to use a real WT, because BranchBuilder and MemoryTree don't
        # have symlink support
        wt = self.make_branch_and_tree('path')
        wt.lock_write()
        self.addCleanup(wt.unlock)

I am not sure if this has changed or if there is another way to approach this. Do let me know if you have any other thoughs on the tests. Thanks very much.

Revision history for this message
Martin Packman (gz) wrote :

Yes, it seems it's just not been done yet. Implementing symlink support in branchbuilder an memorytree doesn't look to hard though, I'll see where I get with that.

Revision history for this message
Parth Malwankar (parthm) wrote :

> Yes, it seems it's just not been done yet. Implementing symlink support in
> branchbuilder an memorytree doesn't look to hard though, I'll see where I get
> with that.

Hi Martin,

I have filed bug #977312 to track this. Would appreciate it if you could give some pointers on this on the bug report. I could take a stab at it in case you haven't had a chance to looking at it already.

Regards,
Parth

Revision history for this message
Martin Packman (gz) wrote :

Thanks for filing that Parth. I haven't had a chance to look into it yet, but writing some tests in bt.test_branchbuilder would be where I'd start.

A build_snapshot call with something like:
    ('add', (u'link', 'symlink-id', 'symlink', u'target'))
Should have the effect of (diff just for clarity):
=== added symlink 'link'
=== target is u'target'

I'm not seeing anything obvious preventing that from working, there may just need to be a few tweaks to some methods, like MemoryTree.path_content_summary which needs to return:
    ('symlink', None, None, target)
Rather than raising NotImplementedError as currently.

There may be other issues, which is why writing some tests for the basic functionality before trying to use it here is a good idea.

Revision history for this message
Martin Packman (gz) wrote :

Okay, it looks like the fun bit will be getting the link target into the inventory, which will either need a bunch of special casing, or perhaps adding symlink support to MemoryTransport as well.

Revision history for this message
Martin Packman (gz) wrote :

This needs some work but is in the right direction, I'll mark it as work in progress and try to get some of the blocking issues resolved.

Unmerged revisions

6491. By Parth Malwankar

update release notes

6490. By Parth Malwankar

SymlinkFeature required for test

6489. By Parth Malwankar

added commit test

6488. By Parth Malwankar

added merge test

6487. By Parth Malwankar

renamed test

6486. By Parth Malwankar

merged in trunk

6485. By Parth Malwankar

added diff test

6484. By Parth Malwankar

added test. warning now explicit

6483. By Parth Malwankar

moved release info to 2.6

6482. By Parth Malwankar

merged in trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/commit.py'
2--- bzrlib/commit.py 2012-02-17 16:48:41 +0000
3+++ bzrlib/commit.py 2012-03-03 06:20:23 +0000
4@@ -64,12 +64,14 @@
5 ConflictsInTree,
6 StrictCommitFailed
7 )
8-from bzrlib.osutils import (get_user_encoding,
9- is_inside_any,
10- minimum_path_selection,
11- splitpath,
12- )
13-from bzrlib.trace import mutter, note, is_quiet
14+from bzrlib.osutils import (
15+ get_user_encoding,
16+ has_symlinks,
17+ is_inside_any,
18+ minimum_path_selection,
19+ splitpath,
20+ )
21+from bzrlib.trace import mutter, note, is_quiet, warning
22 from bzrlib.inventory import Inventory, InventoryEntry, make_entry
23 from bzrlib import symbol_versioning
24 from bzrlib.urlutils import unescape_for_display
25@@ -722,6 +724,10 @@
26 # 'missing' path
27 if report_changes:
28 reporter.missing(new_path)
29+ if change[6][0] == 'symlink' and not has_symlinks():
30+ warning('bzr: warning: Ignoring "%s" as symlinks are not '
31+ 'supported on this platform.' % (change[1][0],))
32+ continue
33 deleted_ids.append(change[0])
34 # Reset the new path (None) and new versioned flag (False)
35 change = (change[0], (change[1][0], None), change[2],
36
37=== modified file 'bzrlib/delta.py'
38--- bzrlib/delta.py 2011-12-18 12:46:49 +0000
39+++ bzrlib/delta.py 2012-03-03 06:20:23 +0000
40@@ -18,8 +18,8 @@
41
42 from bzrlib import (
43 osutils,
44+ trace,
45 )
46-from bzrlib.trace import is_quiet
47
48
49 class TreeDelta(object):
50@@ -138,7 +138,11 @@
51 if fully_present[1] is True:
52 delta.added.append((path[1], file_id, kind[1]))
53 else:
54- delta.removed.append((path[0], file_id, kind[0]))
55+ if kind[0] == 'symlink' and not osutils.has_symlinks():
56+ trace.warning('bzr: warning: Ignoring "%s" as symlinks '
57+ 'are not supported on this platform.' % (path[0],))
58+ else:
59+ delta.removed.append((path[0], file_id, kind[0]))
60 elif fully_present[0] is False:
61 delta.missing.append((path[1], file_id, kind[1]))
62 elif name[0] != name[1] or parent_id[0] != parent_id[1]:
63@@ -245,7 +249,7 @@
64 :param kind: A pair of file kinds, as generated by Tree.iter_changes.
65 None indicates no file present.
66 """
67- if is_quiet():
68+ if trace.is_quiet():
69 return
70 if paths[1] == '' and versioned == 'added' and self.suppress_root_add:
71 return
72
73=== modified file 'bzrlib/diff.py'
74--- bzrlib/diff.py 2011-12-18 12:46:49 +0000
75+++ bzrlib/diff.py 2012-03-03 06:20:23 +0000
76@@ -973,6 +973,10 @@
77 # is, missing) in both trees are skipped as well.
78 if parent == (None, None) or kind == (None, None):
79 continue
80+ if kind[0] == 'symlink' and not osutils.has_symlinks():
81+ warning('bzr: warning: Ignoring "%s" as symlinks are not '
82+ 'supported on this platform.' % (paths[0],))
83+ continue
84 oldpath, newpath = paths
85 oldpath_encoded = get_encoded_path(paths[0])
86 newpath_encoded = get_encoded_path(paths[1])
87
88=== modified file 'bzrlib/errors.py'
89--- bzrlib/errors.py 2012-02-02 12:10:04 +0000
90+++ bzrlib/errors.py 2012-03-03 06:20:23 +0000
91@@ -2903,21 +2903,6 @@
92 _fmt = 'No template specified.'
93
94
95-class UnableCreateSymlink(BzrError):
96-
97- _fmt = 'Unable to create symlink %(path_str)son this platform'
98-
99- def __init__(self, path=None):
100- path_str = ''
101- if path:
102- try:
103- path_str = repr(str(path))
104- except UnicodeEncodeError:
105- path_str = repr(path)
106- path_str += ' '
107- self.path_str = path_str
108-
109-
110 class UnsupportedTimezoneFormat(BzrError):
111
112 _fmt = ('Unsupported timezone format "%(timezone)s", '
113
114=== modified file 'bzrlib/mutabletree.py'
115--- bzrlib/mutabletree.py 2012-01-24 16:19:04 +0000
116+++ bzrlib/mutabletree.py 2012-03-03 06:20:23 +0000
117@@ -233,14 +233,32 @@
118 if _from_tree is None:
119 _from_tree = self.basis_tree()
120 changes = self.iter_changes(_from_tree)
121- try:
122- change = changes.next()
123- # Exclude root (talk about black magic... --vila 20090629)
124- if change[4] == (None, None):
125+ if osutils.has_symlinks():
126+ # Fast path for has_changes.
127+ try:
128 change = changes.next()
129- return True
130- except StopIteration:
131- # No changes
132+ # Exclude root (talk about black magic... --vila 20090629)
133+ if change[4] == (None, None):
134+ change = changes.next()
135+ return True
136+ except StopIteration:
137+ # No changes
138+ return False
139+ else:
140+ # Slow path for has_changes.
141+ # Handle platforms that do not support symlinks in the
142+ # conditional below. This is slower than the try/except
143+ # approach below that but we don't have a choice as we
144+ # need to be sure that all symlinks are removed from the
145+ # entire changeset. This is because in plantforms that
146+ # do not support symlinks, they show up as None in the
147+ # working copy as compared to the repository.
148+ # Also, exclude root as mention in the above fast path.
149+ changes = filter(
150+ lambda c: c[6][0] != 'symlink' and c[4] != (None, None),
151+ changes)
152+ if len(changes) > 0:
153+ return True
154 return False
155
156 @needs_read_lock
157
158=== modified file 'bzrlib/tests/test_commit.py'
159--- bzrlib/tests/test_commit.py 2012-02-14 17:22:37 +0000
160+++ bzrlib/tests/test_commit.py 2012-03-03 06:20:23 +0000
161@@ -16,12 +16,14 @@
162
163
164 import os
165+from StringIO import StringIO
166
167 import bzrlib
168 from bzrlib import (
169 bzrdir,
170 config,
171 errors,
172+ trace,
173 )
174 from bzrlib.branch import Branch
175 from bzrlib.bzrdir import BzrDirMetaFormat1
176@@ -652,6 +654,35 @@
177 finally:
178 basis.unlock()
179
180+ def test_unsupported_symlink_commit(self):
181+ self.requireFeature(SymlinkFeature)
182+ tree = self.make_branch_and_tree('.')
183+ self.build_tree(['hello'])
184+ tree.add('hello')
185+ tree.commit('added hello', rev_id='hello_id')
186+ os.symlink('hello', 'foo')
187+ tree.add('foo')
188+ tree.commit('added foo', rev_id='foo_id')
189+ log = StringIO()
190+ trace.push_log_file(log)
191+ os_symlink = getattr(os, 'symlink', None)
192+ os.symlink = None
193+ try:
194+ # At this point as bzr thinks symlinks are not supported
195+ # we should get a warning about symlink foo and bzr should
196+ # not think its removed.
197+ os.unlink('foo')
198+ self.build_tree(['world'])
199+ tree.add('world')
200+ tree.commit('added world', rev_id='world_id')
201+ finally:
202+ if os_symlink:
203+ os.symlink = os_symlink
204+ self.assertContainsRe(
205+ log.getvalue(),
206+ 'bzr: warning: Ignoring "foo" as symlinks are not '
207+ 'supported on this platform.')
208+
209 def test_commit_kind_changes(self):
210 self.requireFeature(SymlinkFeature)
211 tree = self.make_branch_and_tree('.')
212
213=== modified file 'bzrlib/tests/test_errors.py'
214--- bzrlib/tests/test_errors.py 2011-11-28 17:15:29 +0000
215+++ bzrlib/tests/test_errors.py 2012-03-03 06:20:23 +0000
216@@ -523,20 +523,6 @@
217 "you wish to keep, and delete it when you are done.",
218 str(err))
219
220- def test_unable_create_symlink(self):
221- err = errors.UnableCreateSymlink()
222- self.assertEquals(
223- "Unable to create symlink on this platform",
224- str(err))
225- err = errors.UnableCreateSymlink(path=u'foo')
226- self.assertEquals(
227- "Unable to create symlink 'foo' on this platform",
228- str(err))
229- err = errors.UnableCreateSymlink(path=u'\xb5')
230- self.assertEquals(
231- "Unable to create symlink u'\\xb5' on this platform",
232- str(err))
233-
234 def test_invalid_url_join(self):
235 """Test the formatting of InvalidURLJoin."""
236 e = errors.InvalidURLJoin('Reason', 'base path', ('args',))
237
238=== modified file 'bzrlib/tests/test_transform.py'
239--- bzrlib/tests/test_transform.py 2012-02-06 23:38:33 +0000
240+++ bzrlib/tests/test_transform.py 2012-03-03 06:20:23 +0000
241@@ -788,22 +788,18 @@
242 u'\N{Euro Sign}wizard2',
243 u'b\N{Euro Sign}hind_curtain')
244
245- def test_unable_create_symlink(self):
246+ def test_unsupported_symlink_no_conflict(self):
247 def tt_helper():
248 wt = self.make_branch_and_tree('.')
249- tt = TreeTransform(wt) # TreeTransform obtains write lock
250- try:
251- tt.new_symlink('foo', tt.root, 'bar')
252- tt.apply()
253- finally:
254- wt.unlock()
255+ tt = TreeTransform(wt)
256+ self.addCleanup(tt.finalize)
257+ tt.new_symlink('foo', tt.root, 'bar')
258+ result = tt.find_conflicts()
259+ self.assertEqual([], result)
260 os_symlink = getattr(os, 'symlink', None)
261 os.symlink = None
262 try:
263- err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
264- self.assertEquals(
265- "Unable to create symlink 'foo' on this platform",
266- str(err))
267+ tt_helper()
268 finally:
269 if os_symlink:
270 os.symlink = os_symlink
271@@ -1556,6 +1552,30 @@
272 self.addCleanup(wt.unlock)
273 self.assertEqual(wt.kind(wt.path2id("foo")), "symlink")
274
275+ def test_file_to_symlink_unsupported(self):
276+ wt = self.make_branch_and_tree('.')
277+ self.build_tree(['foo'])
278+ wt.add(['foo'])
279+ wt.commit("one")
280+ tt = TreeTransform(wt)
281+ self.addCleanup(tt.finalize)
282+ foo_trans_id = tt.trans_id_tree_path("foo")
283+ tt.delete_contents(foo_trans_id)
284+ log = StringIO()
285+ trace.push_log_file(log)
286+ os_symlink = getattr(os, 'symlink', None)
287+ os.symlink = None
288+ try:
289+ tt.create_symlink("bar", foo_trans_id)
290+ tt.apply()
291+ finally:
292+ if os_symlink:
293+ os.symlink = os_symlink
294+ self.assertContainsRe(
295+ log.getvalue(),
296+ 'bzr: warning: Unable to create symlink "foo" '
297+ 'on this platform')
298+
299 def test_dir_to_file(self):
300 wt = self.make_branch_and_tree('.')
301 self.build_tree(['foo/', 'foo/bar'])
302@@ -2771,6 +2791,36 @@
303 # 3 lines of diff administrivia
304 self.assertEqual(lines[4], "+content B")
305
306+ def test_unsupported_symlink_diff(self):
307+ self.requireFeature(SymlinkFeature)
308+ tree = self.make_branch_and_tree('.')
309+ self.build_tree_contents([('a', 'content 1')])
310+ tree.set_root_id('TREE_ROOT')
311+ tree.add('a', 'a-id')
312+ os.symlink('a', 'foo')
313+ tree.add('foo', 'foo-id')
314+ tree.commit('rev1', rev_id='rev1')
315+ revision_tree = tree.branch.repository.revision_tree('rev1')
316+ preview = TransformPreview(revision_tree)
317+ self.addCleanup(preview.finalize)
318+ preview.delete_versioned(preview.trans_id_tree_path('foo'))
319+ preview_tree = preview.get_preview_tree()
320+ out = StringIO()
321+ log = StringIO()
322+ trace.push_log_file(log)
323+ os_symlink = getattr(os, 'symlink', None)
324+ os.symlink = None
325+ try:
326+ show_diff_trees(revision_tree, preview_tree, out)
327+ lines = out.getvalue().splitlines()
328+ finally:
329+ if os_symlink:
330+ os.symlink = os_symlink
331+ self.assertContainsRe(
332+ log.getvalue(),
333+ 'bzr: warning: Ignoring "foo" as symlinks are not supported '
334+ 'on this platform')
335+
336 def test_transform_conflicts(self):
337 revision_tree = self.create_tree()
338 preview = TransformPreview(revision_tree)
339
340=== modified file 'bzrlib/tests/test_workingtree.py'
341--- bzrlib/tests/test_workingtree.py 2011-12-22 19:54:56 +0000
342+++ bzrlib/tests/test_workingtree.py 2012-03-03 06:20:23 +0000
343@@ -15,11 +15,15 @@
344 # along with this program; if not, write to the Free Software
345 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
346
347+import os
348+from StringIO import StringIO
349+
350 from bzrlib import (
351 bzrdir,
352 conflicts,
353 errors,
354 symbol_versioning,
355+ trace,
356 transport,
357 workingtree,
358 workingtree_3,
359@@ -28,6 +32,7 @@
360 from bzrlib.lockdir import LockDir
361 from bzrlib.mutabletree import needs_tree_write_lock
362 from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
363+from bzrlib.tests.features import SymlinkFeature
364 from bzrlib.workingtree import (
365 TreeEntry,
366 TreeDirectory,
367@@ -501,6 +506,36 @@
368 resolved)
369 self.assertPathDoesNotExist('this/hello.BASE')
370
371+ def test_unsupported_symlink_auto_resolve(self):
372+ self.requireFeature(SymlinkFeature)
373+ base = self.make_branch_and_tree('base')
374+ self.build_tree_contents([('base/hello', 'Hello')])
375+ base.add('hello', 'hello_id')
376+ base.commit('commit 0')
377+ other = base.bzrdir.sprout('other').open_workingtree()
378+ self.build_tree_contents([('other/hello', 'Hello')])
379+ os.symlink('other/hello', 'other/foo')
380+ other.add('foo', 'foo_id')
381+ other.commit('commit symlink')
382+ this = base.bzrdir.sprout('this').open_workingtree()
383+ self.assertPathExists('this/hello')
384+ self.build_tree_contents([('this/hello', 'Hello')])
385+ this.commit('commit 2')
386+ log = StringIO()
387+ trace.push_log_file(log)
388+ os_symlink = getattr(os, 'symlink', None)
389+ os.symlink = None
390+ try:
391+ this.merge_from_branch(other.branch)
392+ finally:
393+ if os_symlink:
394+ os.symlink = os_symlink
395+ self.assertContainsRe(
396+ log.getvalue(),
397+ 'bzr: warning: Unable to create symlink "foo" '
398+ 'on this platform')
399+
400+
401 def test_auto_resolve_dir(self):
402 tree = self.make_branch_and_tree('tree')
403 self.build_tree(['tree/hello/'])
404
405=== modified file 'bzrlib/transform.py'
406--- bzrlib/transform.py 2012-02-06 23:38:33 +0000
407+++ bzrlib/transform.py 2012-03-03 06:20:23 +0000
408@@ -46,10 +46,14 @@
409 )
410 from bzrlib.i18n import gettext
411 """)
412-from bzrlib.errors import (DuplicateKey, MalformedTransform,
413- ReusingTransform, CantMoveRoot,
414- ImmortalLimbo, NoFinalPath,
415- UnableCreateSymlink)
416+from bzrlib.errors import (
417+ CantMoveRoot,
418+ DuplicateKey,
419+ ImmortalLimbo,
420+ MalformedTransform,
421+ NoFinalPath,
422+ ReusingTransform,
423+ )
424 from bzrlib.filters import filtered_output_bytes, ContentFilterContext
425 from bzrlib.mutabletree import MutableTree
426 from bzrlib.osutils import (
427@@ -663,6 +667,9 @@
428 conflicts = []
429 for trans_id in self._new_id.iterkeys():
430 kind = self.final_kind(trans_id)
431+ if kind == 'symlink' and not has_symlinks():
432+ # Ignore symlinks as they are not supported on this platform
433+ continue
434 if kind is None:
435 conflicts.append(('versioning no contents', trans_id))
436 continue
437@@ -1383,13 +1390,17 @@
438 """
439 if has_symlinks():
440 os.symlink(target, self._limbo_name(trans_id))
441- unique_add(self._new_contents, trans_id, 'symlink')
442 else:
443 try:
444 path = FinalPaths(self).get_path(trans_id)
445 except KeyError:
446 path = None
447- raise UnableCreateSymlink(path=path)
448+ trace.warning('bzr: warning: Unable to create symlink "%s" on '
449+ 'this platform.' % (path,))
450+ # We add symlink to _new_contents even if they are unsupported
451+ # and not created. These entries are subsequently used to avoid
452+ # conflicts on platforms that don't support symlink
453+ unique_add(self._new_contents, trans_id, 'symlink')
454
455 def cancel_creation(self, trans_id):
456 """Cancel the creation of new file contents."""
457
458=== modified file 'doc/en/release-notes/bzr-2.6.txt'
459--- doc/en/release-notes/bzr-2.6.txt 2012-02-25 14:13:19 +0000
460+++ doc/en/release-notes/bzr-2.6.txt 2012-03-03 06:20:23 +0000
461@@ -34,6 +34,11 @@
462 * Avoid 'Invalid range access' errors when whole files are retrieved with
463 transport.http.get() . (Vincent Ladeuil, #924746)
464
465+* Branches with symlinks are now supported on Windows. Symlinks are
466+ ignored by operations like branch, diff etc. with a warning as Symlinks
467+ are not created on Windows.
468+ (Parth Malwankar, #81689)
469+
470 * Two new command hooks, ``pre_command`` and ``post_command``,
471 provide notification before and after a command has been run.
472 (Brian de Alwis, Jelmer Vernooij)