Merge lp:~parthm/bzr/81689-win-symlink-warning into lp:bzr
- 81689-win-symlink-warning
- Merge into bzr.dev
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Packman (community) | Needs Fixing | ||
bzr-core | Pending | ||
Review via email: mp+93784@code.launchpad.net |
Commit message
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:/
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-
D:\ext-
hardlink hello
D:\ext-
Ignoring "softlink" as symlinks are not supported on this platform.
D:\ext-
Ignoring "softlink" as symlinks are not supported on this platform.
D:\ext-
Committing to: D:/ext-
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.
=======
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 :)
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.
Would very much appreciate the inputs on this patch.
Regards,
Parth
D:\ext-
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:/
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-
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:/
.launchpad.
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
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.
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.
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.
Parth Malwankar (parthm) wrote : | # |
Hi Martin,
I was looking into this some more and noticed that test_merge.
# Have to use a real WT, because BranchBuilder and MemoryTree don't
# have symlink support
wt = self.make_
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.
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.
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
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_
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.
('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.
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.
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
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 | 64 | ConflictsInTree, | 64 | ConflictsInTree, |
6 | 65 | StrictCommitFailed | 65 | StrictCommitFailed |
7 | 66 | ) | 66 | ) |
14 | 67 | from bzrlib.osutils import (get_user_encoding, | 67 | from bzrlib.osutils import ( |
15 | 68 | is_inside_any, | 68 | get_user_encoding, |
16 | 69 | minimum_path_selection, | 69 | has_symlinks, |
17 | 70 | splitpath, | 70 | is_inside_any, |
18 | 71 | ) | 71 | minimum_path_selection, |
19 | 72 | from bzrlib.trace import mutter, note, is_quiet | 72 | splitpath, |
20 | 73 | ) | ||
21 | 74 | from bzrlib.trace import mutter, note, is_quiet, warning | ||
22 | 73 | from bzrlib.inventory import Inventory, InventoryEntry, make_entry | 75 | from bzrlib.inventory import Inventory, InventoryEntry, make_entry |
23 | 74 | from bzrlib import symbol_versioning | 76 | from bzrlib import symbol_versioning |
24 | 75 | from bzrlib.urlutils import unescape_for_display | 77 | from bzrlib.urlutils import unescape_for_display |
25 | @@ -722,6 +724,10 @@ | |||
26 | 722 | # 'missing' path | 724 | # 'missing' path |
27 | 723 | if report_changes: | 725 | if report_changes: |
28 | 724 | reporter.missing(new_path) | 726 | reporter.missing(new_path) |
29 | 727 | if change[6][0] == 'symlink' and not has_symlinks(): | ||
30 | 728 | warning('bzr: warning: Ignoring "%s" as symlinks are not ' | ||
31 | 729 | 'supported on this platform.' % (change[1][0],)) | ||
32 | 730 | continue | ||
33 | 725 | deleted_ids.append(change[0]) | 731 | deleted_ids.append(change[0]) |
34 | 726 | # Reset the new path (None) and new versioned flag (False) | 732 | # Reset the new path (None) and new versioned flag (False) |
35 | 727 | change = (change[0], (change[1][0], None), change[2], | 733 | change = (change[0], (change[1][0], None), change[2], |
36 | 728 | 734 | ||
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 | 18 | 18 | ||
42 | 19 | from bzrlib import ( | 19 | from bzrlib import ( |
43 | 20 | osutils, | 20 | osutils, |
44 | 21 | trace, | ||
45 | 21 | ) | 22 | ) |
46 | 22 | from bzrlib.trace import is_quiet | ||
47 | 23 | 23 | ||
48 | 24 | 24 | ||
49 | 25 | class TreeDelta(object): | 25 | class TreeDelta(object): |
50 | @@ -138,7 +138,11 @@ | |||
51 | 138 | if fully_present[1] is True: | 138 | if fully_present[1] is True: |
52 | 139 | delta.added.append((path[1], file_id, kind[1])) | 139 | delta.added.append((path[1], file_id, kind[1])) |
53 | 140 | else: | 140 | else: |
55 | 141 | delta.removed.append((path[0], file_id, kind[0])) | 141 | if kind[0] == 'symlink' and not osutils.has_symlinks(): |
56 | 142 | trace.warning('bzr: warning: Ignoring "%s" as symlinks ' | ||
57 | 143 | 'are not supported on this platform.' % (path[0],)) | ||
58 | 144 | else: | ||
59 | 145 | delta.removed.append((path[0], file_id, kind[0])) | ||
60 | 142 | elif fully_present[0] is False: | 146 | elif fully_present[0] is False: |
61 | 143 | delta.missing.append((path[1], file_id, kind[1])) | 147 | delta.missing.append((path[1], file_id, kind[1])) |
62 | 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]: |
63 | @@ -245,7 +249,7 @@ | |||
64 | 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. |
65 | 246 | None indicates no file present. | 250 | None indicates no file present. |
66 | 247 | """ | 251 | """ |
68 | 248 | if is_quiet(): | 252 | if trace.is_quiet(): |
69 | 249 | return | 253 | return |
70 | 250 | if paths[1] == '' and versioned == 'added' and self.suppress_root_add: | 254 | if paths[1] == '' and versioned == 'added' and self.suppress_root_add: |
71 | 251 | return | 255 | return |
72 | 252 | 256 | ||
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 | 973 | # is, missing) in both trees are skipped as well. | 973 | # is, missing) in both trees are skipped as well. |
78 | 974 | if parent == (None, None) or kind == (None, None): | 974 | if parent == (None, None) or kind == (None, None): |
79 | 975 | continue | 975 | continue |
80 | 976 | if kind[0] == 'symlink' and not osutils.has_symlinks(): | ||
81 | 977 | warning('bzr: warning: Ignoring "%s" as symlinks are not ' | ||
82 | 978 | 'supported on this platform.' % (paths[0],)) | ||
83 | 979 | continue | ||
84 | 976 | oldpath, newpath = paths | 980 | oldpath, newpath = paths |
85 | 977 | oldpath_encoded = get_encoded_path(paths[0]) | 981 | oldpath_encoded = get_encoded_path(paths[0]) |
86 | 978 | newpath_encoded = get_encoded_path(paths[1]) | 982 | newpath_encoded = get_encoded_path(paths[1]) |
87 | 979 | 983 | ||
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 | 2903 | _fmt = 'No template specified.' | 2903 | _fmt = 'No template specified.' |
93 | 2904 | 2904 | ||
94 | 2905 | 2905 | ||
95 | 2906 | class UnableCreateSymlink(BzrError): | ||
96 | 2907 | |||
97 | 2908 | _fmt = 'Unable to create symlink %(path_str)son this platform' | ||
98 | 2909 | |||
99 | 2910 | def __init__(self, path=None): | ||
100 | 2911 | path_str = '' | ||
101 | 2912 | if path: | ||
102 | 2913 | try: | ||
103 | 2914 | path_str = repr(str(path)) | ||
104 | 2915 | except UnicodeEncodeError: | ||
105 | 2916 | path_str = repr(path) | ||
106 | 2917 | path_str += ' ' | ||
107 | 2918 | self.path_str = path_str | ||
108 | 2919 | |||
109 | 2920 | |||
110 | 2921 | class UnsupportedTimezoneFormat(BzrError): | 2906 | class UnsupportedTimezoneFormat(BzrError): |
111 | 2922 | 2907 | ||
112 | 2923 | _fmt = ('Unsupported timezone format "%(timezone)s", ' | 2908 | _fmt = ('Unsupported timezone format "%(timezone)s", ' |
113 | 2924 | 2909 | ||
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 | 233 | if _from_tree is None: | 233 | if _from_tree is None: |
119 | 234 | _from_tree = self.basis_tree() | 234 | _from_tree = self.basis_tree() |
120 | 235 | changes = self.iter_changes(_from_tree) | 235 | changes = self.iter_changes(_from_tree) |
125 | 236 | try: | 236 | if osutils.has_symlinks(): |
126 | 237 | change = changes.next() | 237 | # Fast path for has_changes. |
127 | 238 | # Exclude root (talk about black magic... --vila 20090629) | 238 | try: |
124 | 239 | if change[4] == (None, None): | ||
128 | 240 | change = changes.next() | 239 | change = changes.next() |
132 | 241 | return True | 240 | # Exclude root (talk about black magic... --vila 20090629) |
133 | 242 | except StopIteration: | 241 | if change[4] == (None, None): |
134 | 243 | # No changes | 242 | change = changes.next() |
135 | 243 | return True | ||
136 | 244 | except StopIteration: | ||
137 | 245 | # No changes | ||
138 | 246 | return False | ||
139 | 247 | else: | ||
140 | 248 | # Slow path for has_changes. | ||
141 | 249 | # Handle platforms that do not support symlinks in the | ||
142 | 250 | # conditional below. This is slower than the try/except | ||
143 | 251 | # approach below that but we don't have a choice as we | ||
144 | 252 | # need to be sure that all symlinks are removed from the | ||
145 | 253 | # entire changeset. This is because in plantforms that | ||
146 | 254 | # do not support symlinks, they show up as None in the | ||
147 | 255 | # working copy as compared to the repository. | ||
148 | 256 | # Also, exclude root as mention in the above fast path. | ||
149 | 257 | changes = filter( | ||
150 | 258 | lambda c: c[6][0] != 'symlink' and c[4] != (None, None), | ||
151 | 259 | changes) | ||
152 | 260 | if len(changes) > 0: | ||
153 | 261 | return True | ||
154 | 244 | return False | 262 | return False |
155 | 245 | 263 | ||
156 | 246 | @needs_read_lock | 264 | @needs_read_lock |
157 | 247 | 265 | ||
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 | 16 | 16 | ||
163 | 17 | 17 | ||
164 | 18 | import os | 18 | import os |
165 | 19 | from StringIO import StringIO | ||
166 | 19 | 20 | ||
167 | 20 | import bzrlib | 21 | import bzrlib |
168 | 21 | from bzrlib import ( | 22 | from bzrlib import ( |
169 | 22 | bzrdir, | 23 | bzrdir, |
170 | 23 | config, | 24 | config, |
171 | 24 | errors, | 25 | errors, |
172 | 26 | trace, | ||
173 | 25 | ) | 27 | ) |
174 | 26 | from bzrlib.branch import Branch | 28 | from bzrlib.branch import Branch |
175 | 27 | from bzrlib.bzrdir import BzrDirMetaFormat1 | 29 | from bzrlib.bzrdir import BzrDirMetaFormat1 |
176 | @@ -652,6 +654,35 @@ | |||
177 | 652 | finally: | 654 | finally: |
178 | 653 | basis.unlock() | 655 | basis.unlock() |
179 | 654 | 656 | ||
180 | 657 | def test_unsupported_symlink_commit(self): | ||
181 | 658 | self.requireFeature(SymlinkFeature) | ||
182 | 659 | tree = self.make_branch_and_tree('.') | ||
183 | 660 | self.build_tree(['hello']) | ||
184 | 661 | tree.add('hello') | ||
185 | 662 | tree.commit('added hello', rev_id='hello_id') | ||
186 | 663 | os.symlink('hello', 'foo') | ||
187 | 664 | tree.add('foo') | ||
188 | 665 | tree.commit('added foo', rev_id='foo_id') | ||
189 | 666 | log = StringIO() | ||
190 | 667 | trace.push_log_file(log) | ||
191 | 668 | os_symlink = getattr(os, 'symlink', None) | ||
192 | 669 | os.symlink = None | ||
193 | 670 | try: | ||
194 | 671 | # At this point as bzr thinks symlinks are not supported | ||
195 | 672 | # we should get a warning about symlink foo and bzr should | ||
196 | 673 | # not think its removed. | ||
197 | 674 | os.unlink('foo') | ||
198 | 675 | self.build_tree(['world']) | ||
199 | 676 | tree.add('world') | ||
200 | 677 | tree.commit('added world', rev_id='world_id') | ||
201 | 678 | finally: | ||
202 | 679 | if os_symlink: | ||
203 | 680 | os.symlink = os_symlink | ||
204 | 681 | self.assertContainsRe( | ||
205 | 682 | log.getvalue(), | ||
206 | 683 | 'bzr: warning: Ignoring "foo" as symlinks are not ' | ||
207 | 684 | 'supported on this platform.') | ||
208 | 685 | |||
209 | 655 | def test_commit_kind_changes(self): | 686 | def test_commit_kind_changes(self): |
210 | 656 | self.requireFeature(SymlinkFeature) | 687 | self.requireFeature(SymlinkFeature) |
211 | 657 | tree = self.make_branch_and_tree('.') | 688 | tree = self.make_branch_and_tree('.') |
212 | 658 | 689 | ||
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 | 523 | "you wish to keep, and delete it when you are done.", | 523 | "you wish to keep, and delete it when you are done.", |
218 | 524 | str(err)) | 524 | str(err)) |
219 | 525 | 525 | ||
220 | 526 | def test_unable_create_symlink(self): | ||
221 | 527 | err = errors.UnableCreateSymlink() | ||
222 | 528 | self.assertEquals( | ||
223 | 529 | "Unable to create symlink on this platform", | ||
224 | 530 | str(err)) | ||
225 | 531 | err = errors.UnableCreateSymlink(path=u'foo') | ||
226 | 532 | self.assertEquals( | ||
227 | 533 | "Unable to create symlink 'foo' on this platform", | ||
228 | 534 | str(err)) | ||
229 | 535 | err = errors.UnableCreateSymlink(path=u'\xb5') | ||
230 | 536 | self.assertEquals( | ||
231 | 537 | "Unable to create symlink u'\\xb5' on this platform", | ||
232 | 538 | str(err)) | ||
233 | 539 | |||
234 | 540 | def test_invalid_url_join(self): | 526 | def test_invalid_url_join(self): |
235 | 541 | """Test the formatting of InvalidURLJoin.""" | 527 | """Test the formatting of InvalidURLJoin.""" |
236 | 542 | e = errors.InvalidURLJoin('Reason', 'base path', ('args',)) | 528 | e = errors.InvalidURLJoin('Reason', 'base path', ('args',)) |
237 | 543 | 529 | ||
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 | 788 | u'\N{Euro Sign}wizard2', | 788 | u'\N{Euro Sign}wizard2', |
243 | 789 | u'b\N{Euro Sign}hind_curtain') | 789 | u'b\N{Euro Sign}hind_curtain') |
244 | 790 | 790 | ||
246 | 791 | def test_unable_create_symlink(self): | 791 | def test_unsupported_symlink_no_conflict(self): |
247 | 792 | def tt_helper(): | 792 | def tt_helper(): |
248 | 793 | wt = self.make_branch_and_tree('.') | 793 | wt = self.make_branch_and_tree('.') |
255 | 794 | tt = TreeTransform(wt) # TreeTransform obtains write lock | 794 | tt = TreeTransform(wt) |
256 | 795 | try: | 795 | self.addCleanup(tt.finalize) |
257 | 796 | tt.new_symlink('foo', tt.root, 'bar') | 796 | tt.new_symlink('foo', tt.root, 'bar') |
258 | 797 | tt.apply() | 797 | result = tt.find_conflicts() |
259 | 798 | finally: | 798 | self.assertEqual([], result) |
254 | 799 | wt.unlock() | ||
260 | 800 | os_symlink = getattr(os, 'symlink', None) | 799 | os_symlink = getattr(os, 'symlink', None) |
261 | 801 | os.symlink = None | 800 | os.symlink = None |
262 | 802 | try: | 801 | try: |
267 | 803 | err = self.assertRaises(errors.UnableCreateSymlink, tt_helper) | 802 | tt_helper() |
264 | 804 | self.assertEquals( | ||
265 | 805 | "Unable to create symlink 'foo' on this platform", | ||
266 | 806 | str(err)) | ||
268 | 807 | finally: | 803 | finally: |
269 | 808 | if os_symlink: | 804 | if os_symlink: |
270 | 809 | os.symlink = os_symlink | 805 | os.symlink = os_symlink |
271 | @@ -1556,6 +1552,30 @@ | |||
272 | 1556 | self.addCleanup(wt.unlock) | 1552 | self.addCleanup(wt.unlock) |
273 | 1557 | self.assertEqual(wt.kind(wt.path2id("foo")), "symlink") | 1553 | self.assertEqual(wt.kind(wt.path2id("foo")), "symlink") |
274 | 1558 | 1554 | ||
275 | 1555 | def test_file_to_symlink_unsupported(self): | ||
276 | 1556 | wt = self.make_branch_and_tree('.') | ||
277 | 1557 | self.build_tree(['foo']) | ||
278 | 1558 | wt.add(['foo']) | ||
279 | 1559 | wt.commit("one") | ||
280 | 1560 | tt = TreeTransform(wt) | ||
281 | 1561 | self.addCleanup(tt.finalize) | ||
282 | 1562 | foo_trans_id = tt.trans_id_tree_path("foo") | ||
283 | 1563 | tt.delete_contents(foo_trans_id) | ||
284 | 1564 | log = StringIO() | ||
285 | 1565 | trace.push_log_file(log) | ||
286 | 1566 | os_symlink = getattr(os, 'symlink', None) | ||
287 | 1567 | os.symlink = None | ||
288 | 1568 | try: | ||
289 | 1569 | tt.create_symlink("bar", foo_trans_id) | ||
290 | 1570 | tt.apply() | ||
291 | 1571 | finally: | ||
292 | 1572 | if os_symlink: | ||
293 | 1573 | os.symlink = os_symlink | ||
294 | 1574 | self.assertContainsRe( | ||
295 | 1575 | log.getvalue(), | ||
296 | 1576 | 'bzr: warning: Unable to create symlink "foo" ' | ||
297 | 1577 | 'on this platform') | ||
298 | 1578 | |||
299 | 1559 | def test_dir_to_file(self): | 1579 | def test_dir_to_file(self): |
300 | 1560 | wt = self.make_branch_and_tree('.') | 1580 | wt = self.make_branch_and_tree('.') |
301 | 1561 | self.build_tree(['foo/', 'foo/bar']) | 1581 | self.build_tree(['foo/', 'foo/bar']) |
302 | @@ -2771,6 +2791,36 @@ | |||
303 | 2771 | # 3 lines of diff administrivia | 2791 | # 3 lines of diff administrivia |
304 | 2772 | self.assertEqual(lines[4], "+content B") | 2792 | self.assertEqual(lines[4], "+content B") |
305 | 2773 | 2793 | ||
306 | 2794 | def test_unsupported_symlink_diff(self): | ||
307 | 2795 | self.requireFeature(SymlinkFeature) | ||
308 | 2796 | tree = self.make_branch_and_tree('.') | ||
309 | 2797 | self.build_tree_contents([('a', 'content 1')]) | ||
310 | 2798 | tree.set_root_id('TREE_ROOT') | ||
311 | 2799 | tree.add('a', 'a-id') | ||
312 | 2800 | os.symlink('a', 'foo') | ||
313 | 2801 | tree.add('foo', 'foo-id') | ||
314 | 2802 | tree.commit('rev1', rev_id='rev1') | ||
315 | 2803 | revision_tree = tree.branch.repository.revision_tree('rev1') | ||
316 | 2804 | preview = TransformPreview(revision_tree) | ||
317 | 2805 | self.addCleanup(preview.finalize) | ||
318 | 2806 | preview.delete_versioned(preview.trans_id_tree_path('foo')) | ||
319 | 2807 | preview_tree = preview.get_preview_tree() | ||
320 | 2808 | out = StringIO() | ||
321 | 2809 | log = StringIO() | ||
322 | 2810 | trace.push_log_file(log) | ||
323 | 2811 | os_symlink = getattr(os, 'symlink', None) | ||
324 | 2812 | os.symlink = None | ||
325 | 2813 | try: | ||
326 | 2814 | show_diff_trees(revision_tree, preview_tree, out) | ||
327 | 2815 | lines = out.getvalue().splitlines() | ||
328 | 2816 | finally: | ||
329 | 2817 | if os_symlink: | ||
330 | 2818 | os.symlink = os_symlink | ||
331 | 2819 | self.assertContainsRe( | ||
332 | 2820 | log.getvalue(), | ||
333 | 2821 | 'bzr: warning: Ignoring "foo" as symlinks are not supported ' | ||
334 | 2822 | 'on this platform') | ||
335 | 2823 | |||
336 | 2774 | def test_transform_conflicts(self): | 2824 | def test_transform_conflicts(self): |
337 | 2775 | revision_tree = self.create_tree() | 2825 | revision_tree = self.create_tree() |
338 | 2776 | preview = TransformPreview(revision_tree) | 2826 | preview = TransformPreview(revision_tree) |
339 | 2777 | 2827 | ||
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 | 15 | # along with this program; if not, write to the Free Software | 15 | # along with this program; if not, write to the Free Software |
345 | 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
346 | 17 | 17 | ||
347 | 18 | import os | ||
348 | 19 | from StringIO import StringIO | ||
349 | 20 | |||
350 | 18 | from bzrlib import ( | 21 | from bzrlib import ( |
351 | 19 | bzrdir, | 22 | bzrdir, |
352 | 20 | conflicts, | 23 | conflicts, |
353 | 21 | errors, | 24 | errors, |
354 | 22 | symbol_versioning, | 25 | symbol_versioning, |
355 | 26 | trace, | ||
356 | 23 | transport, | 27 | transport, |
357 | 24 | workingtree, | 28 | workingtree, |
358 | 25 | workingtree_3, | 29 | workingtree_3, |
359 | @@ -28,6 +32,7 @@ | |||
360 | 28 | from bzrlib.lockdir import LockDir | 32 | from bzrlib.lockdir import LockDir |
361 | 29 | from bzrlib.mutabletree import needs_tree_write_lock | 33 | from bzrlib.mutabletree import needs_tree_write_lock |
362 | 30 | from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped | 34 | from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped |
363 | 35 | from bzrlib.tests.features import SymlinkFeature | ||
364 | 31 | from bzrlib.workingtree import ( | 36 | from bzrlib.workingtree import ( |
365 | 32 | TreeEntry, | 37 | TreeEntry, |
366 | 33 | TreeDirectory, | 38 | TreeDirectory, |
367 | @@ -501,6 +506,36 @@ | |||
368 | 501 | resolved) | 506 | resolved) |
369 | 502 | self.assertPathDoesNotExist('this/hello.BASE') | 507 | self.assertPathDoesNotExist('this/hello.BASE') |
370 | 503 | 508 | ||
371 | 509 | def test_unsupported_symlink_auto_resolve(self): | ||
372 | 510 | self.requireFeature(SymlinkFeature) | ||
373 | 511 | base = self.make_branch_and_tree('base') | ||
374 | 512 | self.build_tree_contents([('base/hello', 'Hello')]) | ||
375 | 513 | base.add('hello', 'hello_id') | ||
376 | 514 | base.commit('commit 0') | ||
377 | 515 | other = base.bzrdir.sprout('other').open_workingtree() | ||
378 | 516 | self.build_tree_contents([('other/hello', 'Hello')]) | ||
379 | 517 | os.symlink('other/hello', 'other/foo') | ||
380 | 518 | other.add('foo', 'foo_id') | ||
381 | 519 | other.commit('commit symlink') | ||
382 | 520 | this = base.bzrdir.sprout('this').open_workingtree() | ||
383 | 521 | self.assertPathExists('this/hello') | ||
384 | 522 | self.build_tree_contents([('this/hello', 'Hello')]) | ||
385 | 523 | this.commit('commit 2') | ||
386 | 524 | log = StringIO() | ||
387 | 525 | trace.push_log_file(log) | ||
388 | 526 | os_symlink = getattr(os, 'symlink', None) | ||
389 | 527 | os.symlink = None | ||
390 | 528 | try: | ||
391 | 529 | this.merge_from_branch(other.branch) | ||
392 | 530 | finally: | ||
393 | 531 | if os_symlink: | ||
394 | 532 | os.symlink = os_symlink | ||
395 | 533 | self.assertContainsRe( | ||
396 | 534 | log.getvalue(), | ||
397 | 535 | 'bzr: warning: Unable to create symlink "foo" ' | ||
398 | 536 | 'on this platform') | ||
399 | 537 | |||
400 | 538 | |||
401 | 504 | def test_auto_resolve_dir(self): | 539 | def test_auto_resolve_dir(self): |
402 | 505 | tree = self.make_branch_and_tree('tree') | 540 | tree = self.make_branch_and_tree('tree') |
403 | 506 | self.build_tree(['tree/hello/']) | 541 | self.build_tree(['tree/hello/']) |
404 | 507 | 542 | ||
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 | 46 | ) | 46 | ) |
410 | 47 | from bzrlib.i18n import gettext | 47 | from bzrlib.i18n import gettext |
411 | 48 | """) | 48 | """) |
416 | 49 | from bzrlib.errors import (DuplicateKey, MalformedTransform, | 49 | from bzrlib.errors import ( |
417 | 50 | ReusingTransform, CantMoveRoot, | 50 | CantMoveRoot, |
418 | 51 | ImmortalLimbo, NoFinalPath, | 51 | DuplicateKey, |
419 | 52 | UnableCreateSymlink) | 52 | ImmortalLimbo, |
420 | 53 | MalformedTransform, | ||
421 | 54 | NoFinalPath, | ||
422 | 55 | ReusingTransform, | ||
423 | 56 | ) | ||
424 | 53 | from bzrlib.filters import filtered_output_bytes, ContentFilterContext | 57 | from bzrlib.filters import filtered_output_bytes, ContentFilterContext |
425 | 54 | from bzrlib.mutabletree import MutableTree | 58 | from bzrlib.mutabletree import MutableTree |
426 | 55 | from bzrlib.osutils import ( | 59 | from bzrlib.osutils import ( |
427 | @@ -663,6 +667,9 @@ | |||
428 | 663 | conflicts = [] | 667 | conflicts = [] |
429 | 664 | for trans_id in self._new_id.iterkeys(): | 668 | for trans_id in self._new_id.iterkeys(): |
430 | 665 | kind = self.final_kind(trans_id) | 669 | kind = self.final_kind(trans_id) |
431 | 670 | if kind == 'symlink' and not has_symlinks(): | ||
432 | 671 | # Ignore symlinks as they are not supported on this platform | ||
433 | 672 | continue | ||
434 | 666 | if kind is None: | 673 | if kind is None: |
435 | 667 | conflicts.append(('versioning no contents', trans_id)) | 674 | conflicts.append(('versioning no contents', trans_id)) |
436 | 668 | continue | 675 | continue |
437 | @@ -1383,13 +1390,17 @@ | |||
438 | 1383 | """ | 1390 | """ |
439 | 1384 | if has_symlinks(): | 1391 | if has_symlinks(): |
440 | 1385 | os.symlink(target, self._limbo_name(trans_id)) | 1392 | os.symlink(target, self._limbo_name(trans_id)) |
441 | 1386 | unique_add(self._new_contents, trans_id, 'symlink') | ||
442 | 1387 | else: | 1393 | else: |
443 | 1388 | try: | 1394 | try: |
444 | 1389 | path = FinalPaths(self).get_path(trans_id) | 1395 | path = FinalPaths(self).get_path(trans_id) |
445 | 1390 | except KeyError: | 1396 | except KeyError: |
446 | 1391 | path = None | 1397 | path = None |
448 | 1392 | raise UnableCreateSymlink(path=path) | 1398 | trace.warning('bzr: warning: Unable to create symlink "%s" on ' |
449 | 1399 | 'this platform.' % (path,)) | ||
450 | 1400 | # We add symlink to _new_contents even if they are unsupported | ||
451 | 1401 | # and not created. These entries are subsequently used to avoid | ||
452 | 1402 | # conflicts on platforms that don't support symlink | ||
453 | 1403 | unique_add(self._new_contents, trans_id, 'symlink') | ||
454 | 1393 | 1404 | ||
455 | 1394 | def cancel_creation(self, trans_id): | 1405 | def cancel_creation(self, trans_id): |
456 | 1395 | """Cancel the creation of new file contents.""" | 1406 | """Cancel the creation of new file contents.""" |
457 | 1396 | 1407 | ||
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 | 34 | * Avoid 'Invalid range access' errors when whole files are retrieved with | 34 | * Avoid 'Invalid range access' errors when whole files are retrieved with |
463 | 35 | transport.http.get() . (Vincent Ladeuil, #924746) | 35 | transport.http.get() . (Vincent Ladeuil, #924746) |
464 | 36 | 36 | ||
465 | 37 | * Branches with symlinks are now supported on Windows. Symlinks are | ||
466 | 38 | ignored by operations like branch, diff etc. with a warning as Symlinks | ||
467 | 39 | are not created on Windows. | ||
468 | 40 | (Parth Malwankar, #81689) | ||
469 | 41 | |||
470 | 37 | * Two new command hooks, ``pre_command`` and ``post_command``, | 42 | * Two new command hooks, ``pre_command`` and ``post_command``, |
471 | 38 | provide notification before and after a command has been run. | 43 | provide notification before and after a command has been run. |
472 | 39 | (Brian de Alwis, Jelmer Vernooij) | 44 | (Brian de Alwis, Jelmer Vernooij) |
Hi Parth,
Thanks for working on this. If there's anything I can help with, please let me know. :)