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
=== modified file 'bzrlib/commit.py'
--- bzrlib/commit.py 2012-02-17 16:48:41 +0000
+++ bzrlib/commit.py 2012-03-03 06:20:23 +0000
@@ -64,12 +64,14 @@
64 ConflictsInTree,64 ConflictsInTree,
65 StrictCommitFailed65 StrictCommitFailed
66 )66 )
67from bzrlib.osutils import (get_user_encoding,67from bzrlib.osutils import (
68 is_inside_any,68 get_user_encoding,
69 minimum_path_selection,69 has_symlinks,
70 splitpath,70 is_inside_any,
71 )71 minimum_path_selection,
72from bzrlib.trace import mutter, note, is_quiet72 splitpath,
73 )
74from bzrlib.trace import mutter, note, is_quiet, warning
73from bzrlib.inventory import Inventory, InventoryEntry, make_entry75from bzrlib.inventory import Inventory, InventoryEntry, make_entry
74from bzrlib import symbol_versioning76from bzrlib import symbol_versioning
75from bzrlib.urlutils import unescape_for_display77from bzrlib.urlutils import unescape_for_display
@@ -722,6 +724,10 @@
722 # 'missing' path724 # 'missing' path
723 if report_changes:725 if report_changes:
724 reporter.missing(new_path)726 reporter.missing(new_path)
727 if change[6][0] == 'symlink' and not has_symlinks():
728 warning('bzr: warning: Ignoring "%s" as symlinks are not '
729 'supported on this platform.' % (change[1][0],))
730 continue
725 deleted_ids.append(change[0])731 deleted_ids.append(change[0])
726 # Reset the new path (None) and new versioned flag (False)732 # Reset the new path (None) and new versioned flag (False)
727 change = (change[0], (change[1][0], None), change[2],733 change = (change[0], (change[1][0], None), change[2],
728734
=== modified file 'bzrlib/delta.py'
--- bzrlib/delta.py 2011-12-18 12:46:49 +0000
+++ bzrlib/delta.py 2012-03-03 06:20:23 +0000
@@ -18,8 +18,8 @@
1818
19from bzrlib import (19from bzrlib import (
20 osutils,20 osutils,
21 trace,
21 )22 )
22from bzrlib.trace import is_quiet
2323
2424
25class TreeDelta(object):25class TreeDelta(object):
@@ -138,7 +138,11 @@
138 if fully_present[1] is True:138 if fully_present[1] is True:
139 delta.added.append((path[1], file_id, kind[1]))139 delta.added.append((path[1], file_id, kind[1]))
140 else:140 else:
141 delta.removed.append((path[0], file_id, kind[0]))141 if kind[0] == 'symlink' and not osutils.has_symlinks():
142 trace.warning('bzr: warning: Ignoring "%s" as symlinks '
143 'are not supported on this platform.' % (path[0],))
144 else:
145 delta.removed.append((path[0], file_id, kind[0]))
142 elif fully_present[0] is False:146 elif fully_present[0] is False:
143 delta.missing.append((path[1], file_id, kind[1]))147 delta.missing.append((path[1], file_id, kind[1]))
144 elif name[0] != name[1] or parent_id[0] != parent_id[1]:148 elif name[0] != name[1] or parent_id[0] != parent_id[1]:
@@ -245,7 +249,7 @@
245 :param kind: A pair of file kinds, as generated by Tree.iter_changes.249 :param kind: A pair of file kinds, as generated by Tree.iter_changes.
246 None indicates no file present.250 None indicates no file present.
247 """251 """
248 if is_quiet():252 if trace.is_quiet():
249 return253 return
250 if paths[1] == '' and versioned == 'added' and self.suppress_root_add:254 if paths[1] == '' and versioned == 'added' and self.suppress_root_add:
251 return255 return
252256
=== modified file 'bzrlib/diff.py'
--- bzrlib/diff.py 2011-12-18 12:46:49 +0000
+++ bzrlib/diff.py 2012-03-03 06:20:23 +0000
@@ -973,6 +973,10 @@
973 # is, missing) in both trees are skipped as well.973 # is, missing) in both trees are skipped as well.
974 if parent == (None, None) or kind == (None, None):974 if parent == (None, None) or kind == (None, None):
975 continue975 continue
976 if kind[0] == 'symlink' and not osutils.has_symlinks():
977 warning('bzr: warning: Ignoring "%s" as symlinks are not '
978 'supported on this platform.' % (paths[0],))
979 continue
976 oldpath, newpath = paths980 oldpath, newpath = paths
977 oldpath_encoded = get_encoded_path(paths[0])981 oldpath_encoded = get_encoded_path(paths[0])
978 newpath_encoded = get_encoded_path(paths[1])982 newpath_encoded = get_encoded_path(paths[1])
979983
=== modified file 'bzrlib/errors.py'
--- bzrlib/errors.py 2012-02-02 12:10:04 +0000
+++ bzrlib/errors.py 2012-03-03 06:20:23 +0000
@@ -2903,21 +2903,6 @@
2903 _fmt = 'No template specified.'2903 _fmt = 'No template specified.'
29042904
29052905
2906class UnableCreateSymlink(BzrError):
2907
2908 _fmt = 'Unable to create symlink %(path_str)son this platform'
2909
2910 def __init__(self, path=None):
2911 path_str = ''
2912 if path:
2913 try:
2914 path_str = repr(str(path))
2915 except UnicodeEncodeError:
2916 path_str = repr(path)
2917 path_str += ' '
2918 self.path_str = path_str
2919
2920
2921class UnsupportedTimezoneFormat(BzrError):2906class UnsupportedTimezoneFormat(BzrError):
29222907
2923 _fmt = ('Unsupported timezone format "%(timezone)s", '2908 _fmt = ('Unsupported timezone format "%(timezone)s", '
29242909
=== modified file 'bzrlib/mutabletree.py'
--- bzrlib/mutabletree.py 2012-01-24 16:19:04 +0000
+++ bzrlib/mutabletree.py 2012-03-03 06:20:23 +0000
@@ -233,14 +233,32 @@
233 if _from_tree is None:233 if _from_tree is None:
234 _from_tree = self.basis_tree()234 _from_tree = self.basis_tree()
235 changes = self.iter_changes(_from_tree)235 changes = self.iter_changes(_from_tree)
236 try:236 if osutils.has_symlinks():
237 change = changes.next()237 # Fast path for has_changes.
238 # Exclude root (talk about black magic... --vila 20090629)238 try:
239 if change[4] == (None, None):
240 change = changes.next()239 change = changes.next()
241 return True240 # Exclude root (talk about black magic... --vila 20090629)
242 except StopIteration:241 if change[4] == (None, None):
243 # No changes242 change = changes.next()
243 return True
244 except StopIteration:
245 # No changes
246 return False
247 else:
248 # Slow path for has_changes.
249 # Handle platforms that do not support symlinks in the
250 # conditional below. This is slower than the try/except
251 # approach below that but we don't have a choice as we
252 # need to be sure that all symlinks are removed from the
253 # entire changeset. This is because in plantforms that
254 # do not support symlinks, they show up as None in the
255 # working copy as compared to the repository.
256 # Also, exclude root as mention in the above fast path.
257 changes = filter(
258 lambda c: c[6][0] != 'symlink' and c[4] != (None, None),
259 changes)
260 if len(changes) > 0:
261 return True
244 return False262 return False
245263
246 @needs_read_lock264 @needs_read_lock
247265
=== modified file 'bzrlib/tests/test_commit.py'
--- bzrlib/tests/test_commit.py 2012-02-14 17:22:37 +0000
+++ bzrlib/tests/test_commit.py 2012-03-03 06:20:23 +0000
@@ -16,12 +16,14 @@
1616
1717
18import os18import os
19from StringIO import StringIO
1920
20import bzrlib21import bzrlib
21from bzrlib import (22from bzrlib import (
22 bzrdir,23 bzrdir,
23 config,24 config,
24 errors,25 errors,
26 trace,
25 )27 )
26from bzrlib.branch import Branch28from bzrlib.branch import Branch
27from bzrlib.bzrdir import BzrDirMetaFormat129from bzrlib.bzrdir import BzrDirMetaFormat1
@@ -652,6 +654,35 @@
652 finally:654 finally:
653 basis.unlock()655 basis.unlock()
654656
657 def test_unsupported_symlink_commit(self):
658 self.requireFeature(SymlinkFeature)
659 tree = self.make_branch_and_tree('.')
660 self.build_tree(['hello'])
661 tree.add('hello')
662 tree.commit('added hello', rev_id='hello_id')
663 os.symlink('hello', 'foo')
664 tree.add('foo')
665 tree.commit('added foo', rev_id='foo_id')
666 log = StringIO()
667 trace.push_log_file(log)
668 os_symlink = getattr(os, 'symlink', None)
669 os.symlink = None
670 try:
671 # At this point as bzr thinks symlinks are not supported
672 # we should get a warning about symlink foo and bzr should
673 # not think its removed.
674 os.unlink('foo')
675 self.build_tree(['world'])
676 tree.add('world')
677 tree.commit('added world', rev_id='world_id')
678 finally:
679 if os_symlink:
680 os.symlink = os_symlink
681 self.assertContainsRe(
682 log.getvalue(),
683 'bzr: warning: Ignoring "foo" as symlinks are not '
684 'supported on this platform.')
685
655 def test_commit_kind_changes(self):686 def test_commit_kind_changes(self):
656 self.requireFeature(SymlinkFeature)687 self.requireFeature(SymlinkFeature)
657 tree = self.make_branch_and_tree('.')688 tree = self.make_branch_and_tree('.')
658689
=== modified file 'bzrlib/tests/test_errors.py'
--- bzrlib/tests/test_errors.py 2011-11-28 17:15:29 +0000
+++ bzrlib/tests/test_errors.py 2012-03-03 06:20:23 +0000
@@ -523,20 +523,6 @@
523 "you wish to keep, and delete it when you are done.",523 "you wish to keep, and delete it when you are done.",
524 str(err))524 str(err))
525525
526 def test_unable_create_symlink(self):
527 err = errors.UnableCreateSymlink()
528 self.assertEquals(
529 "Unable to create symlink on this platform",
530 str(err))
531 err = errors.UnableCreateSymlink(path=u'foo')
532 self.assertEquals(
533 "Unable to create symlink 'foo' on this platform",
534 str(err))
535 err = errors.UnableCreateSymlink(path=u'\xb5')
536 self.assertEquals(
537 "Unable to create symlink u'\\xb5' on this platform",
538 str(err))
539
540 def test_invalid_url_join(self):526 def test_invalid_url_join(self):
541 """Test the formatting of InvalidURLJoin."""527 """Test the formatting of InvalidURLJoin."""
542 e = errors.InvalidURLJoin('Reason', 'base path', ('args',))528 e = errors.InvalidURLJoin('Reason', 'base path', ('args',))
543529
=== modified file 'bzrlib/tests/test_transform.py'
--- bzrlib/tests/test_transform.py 2012-02-06 23:38:33 +0000
+++ bzrlib/tests/test_transform.py 2012-03-03 06:20:23 +0000
@@ -788,22 +788,18 @@
788 u'\N{Euro Sign}wizard2',788 u'\N{Euro Sign}wizard2',
789 u'b\N{Euro Sign}hind_curtain')789 u'b\N{Euro Sign}hind_curtain')
790790
791 def test_unable_create_symlink(self):791 def test_unsupported_symlink_no_conflict(self):
792 def tt_helper():792 def tt_helper():
793 wt = self.make_branch_and_tree('.')793 wt = self.make_branch_and_tree('.')
794 tt = TreeTransform(wt) # TreeTransform obtains write lock794 tt = TreeTransform(wt)
795 try:795 self.addCleanup(tt.finalize)
796 tt.new_symlink('foo', tt.root, 'bar')796 tt.new_symlink('foo', tt.root, 'bar')
797 tt.apply()797 result = tt.find_conflicts()
798 finally:798 self.assertEqual([], result)
799 wt.unlock()
800 os_symlink = getattr(os, 'symlink', None)799 os_symlink = getattr(os, 'symlink', None)
801 os.symlink = None800 os.symlink = None
802 try:801 try:
803 err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)802 tt_helper()
804 self.assertEquals(
805 "Unable to create symlink 'foo' on this platform",
806 str(err))
807 finally:803 finally:
808 if os_symlink:804 if os_symlink:
809 os.symlink = os_symlink805 os.symlink = os_symlink
@@ -1556,6 +1552,30 @@
1556 self.addCleanup(wt.unlock)1552 self.addCleanup(wt.unlock)
1557 self.assertEqual(wt.kind(wt.path2id("foo")), "symlink")1553 self.assertEqual(wt.kind(wt.path2id("foo")), "symlink")
15581554
1555 def test_file_to_symlink_unsupported(self):
1556 wt = self.make_branch_and_tree('.')
1557 self.build_tree(['foo'])
1558 wt.add(['foo'])
1559 wt.commit("one")
1560 tt = TreeTransform(wt)
1561 self.addCleanup(tt.finalize)
1562 foo_trans_id = tt.trans_id_tree_path("foo")
1563 tt.delete_contents(foo_trans_id)
1564 log = StringIO()
1565 trace.push_log_file(log)
1566 os_symlink = getattr(os, 'symlink', None)
1567 os.symlink = None
1568 try:
1569 tt.create_symlink("bar", foo_trans_id)
1570 tt.apply()
1571 finally:
1572 if os_symlink:
1573 os.symlink = os_symlink
1574 self.assertContainsRe(
1575 log.getvalue(),
1576 'bzr: warning: Unable to create symlink "foo" '
1577 'on this platform')
1578
1559 def test_dir_to_file(self):1579 def test_dir_to_file(self):
1560 wt = self.make_branch_and_tree('.')1580 wt = self.make_branch_and_tree('.')
1561 self.build_tree(['foo/', 'foo/bar'])1581 self.build_tree(['foo/', 'foo/bar'])
@@ -2771,6 +2791,36 @@
2771 # 3 lines of diff administrivia2791 # 3 lines of diff administrivia
2772 self.assertEqual(lines[4], "+content B")2792 self.assertEqual(lines[4], "+content B")
27732793
2794 def test_unsupported_symlink_diff(self):
2795 self.requireFeature(SymlinkFeature)
2796 tree = self.make_branch_and_tree('.')
2797 self.build_tree_contents([('a', 'content 1')])
2798 tree.set_root_id('TREE_ROOT')
2799 tree.add('a', 'a-id')
2800 os.symlink('a', 'foo')
2801 tree.add('foo', 'foo-id')
2802 tree.commit('rev1', rev_id='rev1')
2803 revision_tree = tree.branch.repository.revision_tree('rev1')
2804 preview = TransformPreview(revision_tree)
2805 self.addCleanup(preview.finalize)
2806 preview.delete_versioned(preview.trans_id_tree_path('foo'))
2807 preview_tree = preview.get_preview_tree()
2808 out = StringIO()
2809 log = StringIO()
2810 trace.push_log_file(log)
2811 os_symlink = getattr(os, 'symlink', None)
2812 os.symlink = None
2813 try:
2814 show_diff_trees(revision_tree, preview_tree, out)
2815 lines = out.getvalue().splitlines()
2816 finally:
2817 if os_symlink:
2818 os.symlink = os_symlink
2819 self.assertContainsRe(
2820 log.getvalue(),
2821 'bzr: warning: Ignoring "foo" as symlinks are not supported '
2822 'on this platform')
2823
2774 def test_transform_conflicts(self):2824 def test_transform_conflicts(self):
2775 revision_tree = self.create_tree()2825 revision_tree = self.create_tree()
2776 preview = TransformPreview(revision_tree)2826 preview = TransformPreview(revision_tree)
27772827
=== modified file 'bzrlib/tests/test_workingtree.py'
--- bzrlib/tests/test_workingtree.py 2011-12-22 19:54:56 +0000
+++ bzrlib/tests/test_workingtree.py 2012-03-03 06:20:23 +0000
@@ -15,11 +15,15 @@
15# along with this program; if not, write to the Free Software15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA16# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1717
18import os
19from StringIO import StringIO
20
18from bzrlib import (21from bzrlib import (
19 bzrdir,22 bzrdir,
20 conflicts,23 conflicts,
21 errors,24 errors,
22 symbol_versioning,25 symbol_versioning,
26 trace,
23 transport,27 transport,
24 workingtree,28 workingtree,
25 workingtree_3,29 workingtree_3,
@@ -28,6 +32,7 @@
28from bzrlib.lockdir import LockDir32from bzrlib.lockdir import LockDir
29from bzrlib.mutabletree import needs_tree_write_lock33from bzrlib.mutabletree import needs_tree_write_lock
30from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped34from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
35from bzrlib.tests.features import SymlinkFeature
31from bzrlib.workingtree import (36from bzrlib.workingtree import (
32 TreeEntry,37 TreeEntry,
33 TreeDirectory,38 TreeDirectory,
@@ -501,6 +506,36 @@
501 resolved)506 resolved)
502 self.assertPathDoesNotExist('this/hello.BASE')507 self.assertPathDoesNotExist('this/hello.BASE')
503508
509 def test_unsupported_symlink_auto_resolve(self):
510 self.requireFeature(SymlinkFeature)
511 base = self.make_branch_and_tree('base')
512 self.build_tree_contents([('base/hello', 'Hello')])
513 base.add('hello', 'hello_id')
514 base.commit('commit 0')
515 other = base.bzrdir.sprout('other').open_workingtree()
516 self.build_tree_contents([('other/hello', 'Hello')])
517 os.symlink('other/hello', 'other/foo')
518 other.add('foo', 'foo_id')
519 other.commit('commit symlink')
520 this = base.bzrdir.sprout('this').open_workingtree()
521 self.assertPathExists('this/hello')
522 self.build_tree_contents([('this/hello', 'Hello')])
523 this.commit('commit 2')
524 log = StringIO()
525 trace.push_log_file(log)
526 os_symlink = getattr(os, 'symlink', None)
527 os.symlink = None
528 try:
529 this.merge_from_branch(other.branch)
530 finally:
531 if os_symlink:
532 os.symlink = os_symlink
533 self.assertContainsRe(
534 log.getvalue(),
535 'bzr: warning: Unable to create symlink "foo" '
536 'on this platform')
537
538
504 def test_auto_resolve_dir(self):539 def test_auto_resolve_dir(self):
505 tree = self.make_branch_and_tree('tree')540 tree = self.make_branch_and_tree('tree')
506 self.build_tree(['tree/hello/'])541 self.build_tree(['tree/hello/'])
507542
=== modified file 'bzrlib/transform.py'
--- bzrlib/transform.py 2012-02-06 23:38:33 +0000
+++ bzrlib/transform.py 2012-03-03 06:20:23 +0000
@@ -46,10 +46,14 @@
46 )46 )
47from bzrlib.i18n import gettext47from bzrlib.i18n import gettext
48""")48""")
49from bzrlib.errors import (DuplicateKey, MalformedTransform,49from bzrlib.errors import (
50 ReusingTransform, CantMoveRoot,50 CantMoveRoot,
51 ImmortalLimbo, NoFinalPath,51 DuplicateKey,
52 UnableCreateSymlink)52 ImmortalLimbo,
53 MalformedTransform,
54 NoFinalPath,
55 ReusingTransform,
56 )
53from bzrlib.filters import filtered_output_bytes, ContentFilterContext57from bzrlib.filters import filtered_output_bytes, ContentFilterContext
54from bzrlib.mutabletree import MutableTree58from bzrlib.mutabletree import MutableTree
55from bzrlib.osutils import (59from bzrlib.osutils import (
@@ -663,6 +667,9 @@
663 conflicts = []667 conflicts = []
664 for trans_id in self._new_id.iterkeys():668 for trans_id in self._new_id.iterkeys():
665 kind = self.final_kind(trans_id)669 kind = self.final_kind(trans_id)
670 if kind == 'symlink' and not has_symlinks():
671 # Ignore symlinks as they are not supported on this platform
672 continue
666 if kind is None:673 if kind is None:
667 conflicts.append(('versioning no contents', trans_id))674 conflicts.append(('versioning no contents', trans_id))
668 continue675 continue
@@ -1383,13 +1390,17 @@
1383 """1390 """
1384 if has_symlinks():1391 if has_symlinks():
1385 os.symlink(target, self._limbo_name(trans_id))1392 os.symlink(target, self._limbo_name(trans_id))
1386 unique_add(self._new_contents, trans_id, 'symlink')
1387 else:1393 else:
1388 try:1394 try:
1389 path = FinalPaths(self).get_path(trans_id)1395 path = FinalPaths(self).get_path(trans_id)
1390 except KeyError:1396 except KeyError:
1391 path = None1397 path = None
1392 raise UnableCreateSymlink(path=path)1398 trace.warning('bzr: warning: Unable to create symlink "%s" on '
1399 'this platform.' % (path,))
1400 # We add symlink to _new_contents even if they are unsupported
1401 # and not created. These entries are subsequently used to avoid
1402 # conflicts on platforms that don't support symlink
1403 unique_add(self._new_contents, trans_id, 'symlink')
13931404
1394 def cancel_creation(self, trans_id):1405 def cancel_creation(self, trans_id):
1395 """Cancel the creation of new file contents."""1406 """Cancel the creation of new file contents."""
13961407
=== modified file 'doc/en/release-notes/bzr-2.6.txt'
--- doc/en/release-notes/bzr-2.6.txt 2012-02-25 14:13:19 +0000
+++ doc/en/release-notes/bzr-2.6.txt 2012-03-03 06:20:23 +0000
@@ -34,6 +34,11 @@
34* Avoid 'Invalid range access' errors when whole files are retrieved with34* Avoid 'Invalid range access' errors when whole files are retrieved with
35 transport.http.get() . (Vincent Ladeuil, #924746)35 transport.http.get() . (Vincent Ladeuil, #924746)
3636
37* Branches with symlinks are now supported on Windows. Symlinks are
38 ignored by operations like branch, diff etc. with a warning as Symlinks
39 are not created on Windows.
40 (Parth Malwankar, #81689)
41
37* Two new command hooks, ``pre_command`` and ``post_command``,42* Two new command hooks, ``pre_command`` and ``post_command``,
38 provide notification before and after a command has been run.43 provide notification before and after a command has been run.
39 (Brian de Alwis, Jelmer Vernooij)44 (Brian de Alwis, Jelmer Vernooij)