Merge lp:~vila/bzr/tree-has-changes into lp:~bzr/bzr/trunk-old

Proposed by Vincent Ladeuil
Status: Superseded
Proposed branch: lp:~vila/bzr/tree-has-changes
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 284 lines
To merge this branch: bzr merge lp:~vila/bzr/tree-has-changes
Reviewer Review Type Date Requested Status
bzr-core Pending
Review via email: mp+8125@code.launchpad.net

This proposal has been superseded by a proposal from 2009-07-08.

To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote :

More an RFC than a merge request, this patch implements a new tree.has_changes()
method as discussed during bug #284038 reviews.

The way I have to filter the root seems ugly but I couldn't find
an alternative, so feedback welcome.

I was also surprised to be able to use _has_changes() in so many different places,
that may have an impact on performances (and for once may be more in real life than
in benchmarks).

John also asked for some kind of has_changes_strictly() to share between send and push --strict.
I also like feedback on that as it seems that there are more use-cases than we discuss
and/or latent bugs (see XXX marks).

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Vincent Ladeuil wrote:
> Vincent Ladeuil has proposed merging lp:~vila/bzr/tree-has-changes into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> More an RFC than a merge request, this patch implements a new tree.has_changes()
> method as discussed during bug #284038 reviews.
>
> The way I have to filter the root seems ugly but I couldn't find
> an alternative, so feedback welcome.
>
> I was also surprised to be able to use _has_changes() in so many different places,
> that may have an impact on performances (and for once may be more in real life than
> in benchmarks).
>
> John also asked for some kind of has_changes_strictly() to share between send and push --strict.
> I also like feedback on that as it seems that there are more use-cases than we discuss
> and/or latent bugs (see XXX marks).
>

Do we want to do this as:

  wt.has_changes(other_tree)

or do we want it as simply

  wt.has_changes()

And internally it looks at "self.basis_tree()".

For example, RevisionTrees know explicitly that they can't have changes,
arguably calling it is undefined. I would consider making the function
part of the MutableTree api, and not the base Tree api.

Because of lock lifetimes, etc, I'd rather handle it internally.

I'm not sure what is going on in "merge", but I'm pretty sure it is
doing the same thing.

I think it makes the api cleaner as then we don't have to worry about
arbitrary targets for "has_changes()".

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkpM54MACgkQJdeBCYSNAAN12gCfYVFCK1VFnGvJoR6s1rV4s/4s
8s0An3XWw0FDMuKK4gb2NUtklV12HqfA
=lGw2
-----END PGP SIGNATURE-----

Revision history for this message
Robert Collins (lifeless) wrote :

On Thu, 2009-07-02 at 17:00 +0000, John A Meinel wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1

> Do we want to do this as:
>
> wt.has_changes(other_tree)
>
> or do we want it as simply
>
> wt.has_changes()

I don't have a strong opinion; if I were deciding I would look at where
it is used: if it is used in operations that take -r parameters, then
the former is probably correct. If its only ever desirable to know if
there are changes without examining them then doing it just on mutable
tree seems ok to me.

-Rob

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/builtins.py'
2--- bzrlib/builtins.py 2009-07-02 11:37:38 +0000
3+++ bzrlib/builtins.py 2009-07-08 12:36:36 +0000
4@@ -449,18 +449,18 @@
5 except errors.NoWorkingTree:
6 raise errors.BzrCommandError("No working tree to remove")
7 except errors.NotLocalUrl:
8- raise errors.BzrCommandError("You cannot remove the working tree of a "
9- "remote path")
10+ raise errors.BzrCommandError("You cannot remove the working tree"
11+ " of a remote path")
12 if not force:
13- changes = working.changes_from(working.basis_tree())
14- if changes.has_changed():
15+ # XXX: What about pending merges ? -- vila 20090629
16+ if working.has_changes(working.basis_tree()):
17 raise errors.UncommittedChanges(working)
18
19 working_path = working.bzrdir.root_transport.base
20 branch_path = working.branch.bzrdir.root_transport.base
21 if working_path != branch_path:
22- raise errors.BzrCommandError("You cannot remove the working tree from "
23- "a lightweight checkout")
24+ raise errors.BzrCommandError("You cannot remove the working tree"
25+ " from a lightweight checkout")
26
27 d.destroy_workingtree()
28
29@@ -1114,8 +1114,8 @@
30 revision_id = None
31 if (tree is not None and revision_id is None
32 and (strict is None or strict)): # Default to True:
33- changes = tree.changes_from(tree.basis_tree())
34- if changes.has_changed() or len(tree.get_parent_ids()) > 1:
35+ if (tree.has_changes(tree.basis_tree())
36+ or len(tree.get_parent_ids()) > 1):
37 raise errors.UncommittedChanges(
38 tree, more='Use --no-strict to force the push.')
39 if tree.last_revision() != tree.branch.last_revision():
40@@ -3641,8 +3641,7 @@
41 except errors.NoSuchRevision:
42 basis_tree = tree.basis_tree()
43 if not force:
44- changes = tree.changes_from(basis_tree)
45- if changes.has_changed():
46+ if tree.has_changes(basis_tree):
47 raise errors.UncommittedChanges(tree)
48
49 view_info = _get_view_info_for_change_reporter(tree)
50
51=== modified file 'bzrlib/merge.py'
52--- bzrlib/merge.py 2009-04-14 15:02:46 +0000
53+++ bzrlib/merge.py 2009-07-08 12:36:36 +0000
54@@ -243,8 +243,7 @@
55
56 if self.other_rev_id is None:
57 other_basis_tree = self.revision_tree(self.other_basis)
58- changes = other_basis_tree.changes_from(self.other_tree)
59- if changes.has_changed():
60+ if other_basis_tree.has_changes(self.other_tree):
61 raise WorkingTreeNotRevision(self.this_tree)
62 other_rev_id = self.other_basis
63 self.other_tree = other_basis_tree
64@@ -276,8 +275,7 @@
65 basis_tree = self.revision_tree(self.this_tree.last_revision())
66 except errors.NoSuchRevision:
67 basis_tree = self.this_tree.basis_tree()
68- changes = self.this_tree.changes_from(basis_tree)
69- if not changes.has_changed():
70+ if not self.this_tree.has_changes(basis_tree):
71 self.this_rev_id = self.this_basis
72
73 def set_interesting_files(self, file_list):
74
75=== modified file 'bzrlib/reconfigure.py'
76--- bzrlib/reconfigure.py 2009-07-06 06:46:30 +0000
77+++ bzrlib/reconfigure.py 2009-07-08 12:36:36 +0000
78@@ -218,8 +218,8 @@
79 def _check(self):
80 """Raise if reconfiguration would destroy local changes"""
81 if self._destroy_tree:
82- changes = self.tree.changes_from(self.tree.basis_tree())
83- if changes.has_changed():
84+ # XXX: What about pending merges ? -- vila 20090629
85+ if self.tree.has_changes(self.tree.basis_tree()):
86 raise errors.UncommittedChanges(self.tree)
87 if self._create_reference and self.local_branch is not None:
88 reference_branch = branch.Branch.open(self._select_bind_location())
89
90=== modified file 'bzrlib/send.py'
91--- bzrlib/send.py 2009-07-01 15:17:33 +0000
92+++ bzrlib/send.py 2009-07-08 12:36:36 +0000
93@@ -119,8 +119,8 @@
94 except KeyError:
95 strict = None
96 if tree is not None and (strict is None or strict):
97- changes = tree.changes_from(tree.basis_tree())
98- if changes.has_changed() or len(tree.get_parent_ids()) > 1:
99+ if (tree.has_changes(tree.basis_tree())
100+ or len(tree.get_parent_ids()) > 1):
101 raise errors.UncommittedChanges(
102 tree, more='Use --no-strict to force the send.')
103 if tree.last_revision() != tree.branch.last_revision():
104
105=== modified file 'bzrlib/tests/intertree_implementations/test_compare.py'
106--- bzrlib/tests/intertree_implementations/test_compare.py 2009-04-09 20:23:07 +0000
107+++ bzrlib/tests/intertree_implementations/test_compare.py 2009-07-08 12:36:36 +0000
108@@ -367,6 +367,15 @@
109 tree1.unlock()
110 tree2.unlock()
111
112+ def do_has_changes(self, tree1, tree2, **extra_args):
113+ tree1.lock_read()
114+ tree2.lock_read()
115+ try:
116+ return tree1.has_changes(tree2)
117+ finally:
118+ tree1.unlock()
119+ tree2.unlock()
120+
121 def mutable_trees_to_locked_test_trees(self, tree1, tree2):
122 """Convert the working trees into test trees.
123
124@@ -436,6 +445,7 @@
125 tree2 = self.get_tree_no_parents_no_content(tree2)
126 tree1, tree2 = self.mutable_trees_to_test_trees(self, tree1, tree2)
127 self.assertEqual([], self.do_iter_changes(tree1, tree2))
128+ self.assertEquals(False, self.do_has_changes(tree1, tree2))
129
130 def added(self, tree, file_id):
131 path, entry = self.get_path_entry(tree, file_id)
132@@ -519,6 +529,7 @@
133 self.added(tree2, 'c-id'),
134 self.deleted(tree1, 'empty-root-id')])
135 self.assertEqual(expected_results, self.do_iter_changes(tree1, tree2))
136+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
137
138 def test_empty_specific_files(self):
139 tree1 = self.make_branch_and_tree('1')
140@@ -542,6 +553,7 @@
141 self.added(tree2, 'c-id'),
142 self.deleted(tree1, 'empty-root-id')])
143 self.assertEqual(expected_results, self.do_iter_changes(tree1, tree2))
144+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
145
146 def test_empty_to_abc_content_a_only(self):
147 tree1 = self.make_branch_and_tree('1')
148@@ -593,6 +605,7 @@
149 self.assertEqual(
150 expected_results,
151 self.do_iter_changes(tree1, tree2))
152+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
153
154 def test_content_modification(self):
155 tree1 = self.make_branch_and_tree('1')
156@@ -605,6 +618,7 @@
157 (root_id, root_id), ('a', 'a'),
158 ('file', 'file'), (False, False))],
159 self.do_iter_changes(tree1, tree2))
160+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
161
162 def test_meta_modification(self):
163 tree1 = self.make_branch_and_tree('1')
164@@ -834,6 +848,7 @@
165 self.content_changed(tree2, 'c-id'),
166 ])
167 self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
168+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
169
170 def test_unversioned_paths_in_tree(self):
171 tree1 = self.make_branch_and_tree('tree1')
172@@ -1051,6 +1066,7 @@
173 self.assertEqual(expected,
174 self.do_iter_changes(tree1, tree2, include_unchanged=True,
175 want_unversioned=True))
176+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
177
178 def test_versioned_symlinks_specific_files(self):
179 self.requireFeature(tests.SymlinkFeature)
180@@ -1072,17 +1088,20 @@
181 self.assertEqual(expected, self.do_iter_changes(tree1, tree2,
182 specific_files=['added', 'changed', 'fromdir', 'fromfile',
183 'removed', 'unchanged', 'todir', 'tofile']))
184+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
185
186 def test_tree_with_special_names(self):
187 tree1, tree2, paths, path_ids = self.make_tree_with_special_names()
188 expected = sorted(self.added(tree2, f_id) for f_id in path_ids)
189 self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
190+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
191
192 def test_trees_with_special_names(self):
193 tree1, tree2, paths, path_ids = self.make_trees_with_special_names()
194 expected = sorted(self.content_changed(tree2, f_id) for f_id in path_ids
195 if f_id.endswith('_f-id'))
196 self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
197+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
198
199 def test_trees_with_deleted_dir(self):
200 tree1 = self.make_branch_and_tree('tree1')
201@@ -1106,6 +1125,7 @@
202 self.deleted(tree1, 'e-id'),
203 ])
204 self.assertEqual(expected, self.do_iter_changes(tree1, tree2))
205+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
206
207 def test_added_unicode(self):
208 tree1 = self.make_branch_and_tree('tree1')
209@@ -1134,6 +1154,7 @@
210 self.assertEqual([self.added(tree2, added_id)],
211 self.do_iter_changes(tree1, tree2,
212 specific_files=[u'\u03b1']))
213+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
214
215 def test_deleted_unicode(self):
216 tree1 = self.make_branch_and_tree('tree1')
217@@ -1162,6 +1183,7 @@
218 self.assertEqual([self.deleted(tree1, deleted_id)],
219 self.do_iter_changes(tree1, tree2,
220 specific_files=[u'\u03b1']))
221+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
222
223 def test_modified_unicode(self):
224 tree1 = self.make_branch_and_tree('tree1')
225@@ -1191,6 +1213,7 @@
226 self.assertEqual([self.content_changed(tree1, mod_id)],
227 self.do_iter_changes(tree1, tree2,
228 specific_files=[u'\u03b1']))
229+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
230
231 def test_renamed_unicode(self):
232 tree1 = self.make_branch_and_tree('tree1')
233@@ -1221,6 +1244,7 @@
234 self.assertEqual([self.renamed(tree1, tree2, rename_id, False)],
235 self.do_iter_changes(tree1, tree2,
236 specific_files=[u'\u03b1']))
237+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
238
239 def test_unchanged_unicode(self):
240 tree1 = self.make_branch_and_tree('tree1')
241@@ -1309,6 +1333,7 @@
242 want_unversioned=True))
243 self.assertEqual([], # Without want_unversioned we should get nothing
244 self.do_iter_changes(tree1, tree2))
245+ self.assertEquals(False, self.do_has_changes(tree1, tree2))
246
247 # We should also be able to select just a subset
248 expected = sorted([
249@@ -1389,6 +1414,7 @@
250 ])
251 self.assertEqual(expected,
252 self.do_iter_changes(tree1, tree2))
253+ self.assertEquals(True, self.do_has_changes(tree1, tree2))
254
255 def test_deleted_and_unknown(self):
256 """Test a file marked removed, but still present on disk."""
257
258=== modified file 'bzrlib/tree.py'
259--- bzrlib/tree.py 2009-07-01 10:56:49 +0000
260+++ bzrlib/tree.py 2009-07-08 12:36:36 +0000
261@@ -102,6 +102,24 @@
262 return intertree.iter_changes(include_unchanged, specific_files, pb,
263 extra_trees, require_versioned, want_unversioned=want_unversioned)
264
265+
266+ @needs_read_lock
267+ def has_changes(self, from_tree):
268+ """Quickly check that the tree contains at least one change.
269+
270+ :return: True if a change is found. False otherwise
271+ """
272+ changes = self.iter_changes(from_tree)
273+ try:
274+ change = changes.next()
275+ # Exclude root (talk about black magic... --vila 20090629)
276+ if change[4] == (None, None):
277+ change = changes.next()
278+ return True
279+ except StopIteration:
280+ # No changes
281+ return False
282+
283 def conflicts(self):
284 """Get a list of the conflicts in the tree.
285