13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
21
from bzrlib import (
25
28
from bzrlib.branch import Branch
26
from bzrlib.bzrdir import BzrDirMetaFormat1
29
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
27
30
from bzrlib.commit import Commit, NullCommitReporter
28
31
from bzrlib.config import BranchConfig
29
from bzrlib.errors import (
35
from bzrlib.tests import (
36
TestCaseWithTransport,
39
from bzrlib.tests.features import (
42
from bzrlib.tests.matchers import MatchesAncestry
32
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
34
from bzrlib.tests import SymlinkFeature, TestCaseWithTransport
35
from bzrlib.workingtree import WorkingTree
45
38
# TODO: Test commit with some added, and added-but-missing files
115
108
self.assertEqual('version 2', text)
117
def test_commit_lossy_native(self):
118
"""Attempt a lossy commit to a native branch."""
119
wt = self.make_branch_and_tree('.')
121
file('hello', 'w').write('hello world')
123
revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
124
self.assertEquals('revid', revid)
126
def test_commit_lossy_foreign(self):
127
"""Attempt a lossy commit to a foreign branch."""
128
test_foreign.register_dummy_foreign_for_test(self)
129
wt = self.make_branch_and_tree('.',
130
format=test_foreign.DummyForeignVcsDirFormat())
132
file('hello', 'w').write('hello world')
134
revid = wt.commit(message='add hello', lossy=True,
135
timestamp=1302659388, timezone=0)
136
self.assertEquals('dummy-v1:1302659388.0-0-UNKNOWN', revid)
138
def test_commit_bound_lossy_foreign(self):
139
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
140
test_foreign.register_dummy_foreign_for_test(self)
141
foreign_branch = self.make_branch('foreign',
142
format=test_foreign.DummyForeignVcsDirFormat())
143
wt = foreign_branch.create_checkout("local")
145
file('local/hello', 'w').write('hello world')
147
revid = wt.commit(message='add hello', lossy=True,
148
timestamp=1302659388, timezone=0)
149
self.assertEquals('dummy-v1:1302659388.0-0-0', revid)
150
self.assertEquals('dummy-v1:1302659388.0-0-0',
151
foreign_branch.last_revision())
152
self.assertEquals('dummy-v1:1302659388.0-0-0',
153
wt.branch.last_revision())
155
def test_missing_commit(self):
156
"""Test a commit with a missing file"""
110
def test_delete_commit(self):
111
"""Test a commit with a deleted file"""
157
112
wt = self.make_branch_and_tree('.')
159
114
file('hello', 'w').write('hello world')
161
116
wt.commit(message='add hello')
163
118
os.remove('hello')
164
reporter = CapturingReporter()
165
wt.commit('removed hello', rev_id='rev2', reporter=reporter)
167
[('missing', u'hello'), ('deleted', u'hello')],
119
wt.commit('removed hello', rev_id='rev2')
170
121
tree = b.repository.revision_tree('rev2')
171
122
self.assertFalse(tree.has_id('hello-id'))
173
def test_partial_commit_move(self):
174
"""Test a partial commit where a file was renamed but not committed.
176
https://bugs.launchpad.net/bzr/+bug/83039
178
If not handled properly, commit will try to snapshot
179
dialog.py with olive/ as a parent, while
180
olive/ has not been snapshotted yet.
182
wt = self.make_branch_and_tree('.')
184
self.build_tree(['annotate/', 'annotate/foo.py',
185
'olive/', 'olive/dialog.py'
187
wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
188
wt.commit(message='add files')
189
wt.rename_one("olive/dialog.py", "aaa")
190
self.build_tree_contents([('annotate/foo.py', 'modified\n')])
191
wt.commit('renamed hello', specific_files=["annotate"])
193
124
def test_pointless_commit(self):
194
125
"""Commit refuses unless there are changes or it's forced."""
195
126
wt = self.make_branch_and_tree('.')
273
204
eq(tree1.id2path('hello-id'), 'hello')
274
205
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
275
206
self.assertFalse(tree1.has_filename('fruity'))
276
self.check_tree_shape(tree1, ['hello'])
277
eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
207
self.check_inventory_shape(tree1.inventory, ['hello'])
208
ie = tree1.inventory['hello-id']
209
eq(ie.revision, 'test@rev-1')
279
211
tree2 = b.repository.revision_tree('test@rev-2')
280
212
tree2.lock_read()
281
213
self.addCleanup(tree2.unlock)
282
214
eq(tree2.id2path('hello-id'), 'fruity')
283
215
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
284
self.check_tree_shape(tree2, ['fruity'])
285
eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
216
self.check_inventory_shape(tree2.inventory, ['fruity'])
217
ie = tree2.inventory['hello-id']
218
eq(ie.revision, 'test@rev-2')
287
220
def test_reused_rev_id(self):
288
221
"""Test that a revision id cannot be reused in a branch"""
318
252
wt.commit('three', rev_id=r3, allow_pointless=False)
321
self.check_tree_shape(wt,
255
self.check_inventory_shape(wt.read_working_inventory(),
322
256
['a/', 'a/hello', 'a/b/'])
323
self.check_tree_shape(b.repository.revision_tree(r3),
257
self.check_inventory_shape(b.repository.get_revision_inventory(r3),
324
258
['a/', 'a/hello', 'a/b/'])
330
264
wt.commit('four', rev_id=r4, allow_pointless=False)
333
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
267
self.check_inventory_shape(wt.read_working_inventory(),
268
['a/', 'a/b/hello', 'a/b/'])
337
inv = b.repository.get_inventory(r4)
272
inv = b.repository.get_revision_inventory(r4)
338
273
eq(inv['hello-id'].revision, r4)
339
274
eq(inv['a-id'].revision, r1)
340
275
eq(inv['b-id'].revision, r3)
368
303
eq = self.assertEquals
369
304
eq(b.revision_history(), rev_ids)
370
305
for i in range(4):
371
self.assertThat(rev_ids[:i+1],
372
MatchesAncestry(b.repository, rev_ids[i]))
306
anc = b.repository.get_ancestry(rev_ids[i])
307
eq(anc, [None] + rev_ids[:i+1])
374
309
def test_commit_new_subdir_child_selective(self):
375
310
wt = self.make_branch_and_tree('.')
453
389
wt = self.make_branch_and_tree('.')
454
390
branch = wt.branch
455
391
wt.commit("base", allow_pointless=True, rev_id='A')
456
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
392
self.failIf(branch.repository.has_signature_for_revision_id('A'))
394
from bzrlib.testament import Testament
458
395
# monkey patch gpg signing mechanism
459
396
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
460
397
config = MustSignConfig(branch)
507
444
bound = master.sprout('bound')
508
445
wt = bound.open_workingtree()
509
446
wt.branch.set_bound_location(os.path.realpath('master'))
448
orig_default = lockdir._DEFAULT_TIMEOUT_SECONDS
510
449
master_branch.lock_write()
451
lockdir._DEFAULT_TIMEOUT_SECONDS = 1
512
452
self.assertRaises(LockContention, wt.commit, 'silly')
454
lockdir._DEFAULT_TIMEOUT_SECONDS = orig_default
514
455
master_branch.unlock()
516
457
def test_commit_bound_merge(self):
544
485
bound_tree.commit(message='commit of merge in bound tree')
546
487
def test_commit_reporting_after_merge(self):
547
# when doing a commit of a merge, the reporter needs to still
488
# when doing a commit of a merge, the reporter needs to still
548
489
# be called for each item that is added/removed/deleted.
549
490
this_tree = self.make_branch_and_tree('this')
550
491
# we need a bunch of files and dirs, to perform one action on each.
593
534
this_tree.merge_from_branch(other_tree.branch)
594
535
reporter = CapturingReporter()
595
536
this_tree.commit('do the commit', reporter=reporter)
538
('change', 'unchanged', ''),
539
('change', 'unchanged', 'dirtoleave'),
540
('change', 'unchanged', 'filetoleave'),
597
541
('change', 'modified', 'filetomodify'),
598
542
('change', 'added', 'newdir'),
599
543
('change', 'added', 'newfile'),
603
547
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
604
548
('deleted', 'dirtoremove'),
605
549
('deleted', 'filetoremove'),
607
result = set(reporter.calls)
608
missing = expected - result
609
new = result - expected
610
self.assertEqual((set(), set()), (missing, new))
612
553
def test_commit_removals_respects_filespec(self):
613
554
"""Commit respects the specified_files for removals."""
703
644
def test_commit_unversioned_specified(self):
704
645
"""Commit should raise if specified files isn't in basis or worktree"""
705
646
tree = self.make_branch_and_tree('.')
706
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
647
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
707
648
'message', specific_files=['bogus'])
709
650
class Callback(object):
711
652
def __init__(self, message, testcase):
712
653
self.called = False
713
654
self.message = message
741
682
"""Callback should not be invoked for pointless commit"""
742
683
tree = self.make_branch_and_tree('.')
743
684
cb = self.Callback(u'commit 2', self)
744
self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
685
self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
745
686
allow_pointless=False)
746
687
self.assertFalse(cb.called)
751
692
cb = self.Callback(u'commit 2', self)
752
693
repository = tree.branch.repository
753
694
# simulate network failure
754
def raise_(self, arg, arg2, arg3=None, arg4=None):
695
def raise_(self, arg, arg2):
755
696
raise errors.NoSuchFile('foo')
756
697
repository.add_inventory = raise_
757
repository.add_inventory_by_delta = raise_
758
698
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
759
699
self.assertFalse(cb.called)
791
731
rev_id = tree.commit('commit 1')
792
732
rev = tree.branch.repository.get_revision(rev_id)
793
733
self.assertFalse('author' in rev.properties)
794
self.assertFalse('authors' in rev.properties)
796
735
def test_commit_author(self):
797
736
"""Passing a non-empty author kwarg to MutableTree.commit should add
798
737
the 'author' revision property.
800
739
tree = self.make_branch_and_tree('foo')
801
rev_id = self.callDeprecated(['The parameter author was '
802
'deprecated in version 1.13. Use authors instead'],
803
tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
740
rev_id = tree.commit('commit 1', author='John Doe <jdoe@example.com>')
804
741
rev = tree.branch.repository.get_revision(rev_id)
805
742
self.assertEqual('John Doe <jdoe@example.com>',
806
rev.properties['authors'])
807
self.assertFalse('author' in rev.properties)
809
def test_commit_empty_authors_list(self):
810
"""Passing an empty list to authors shouldn't add the property."""
811
tree = self.make_branch_and_tree('foo')
812
rev_id = tree.commit('commit 1', authors=[])
813
rev = tree.branch.repository.get_revision(rev_id)
814
self.assertFalse('author' in rev.properties)
815
self.assertFalse('authors' in rev.properties)
817
def test_multiple_authors(self):
818
tree = self.make_branch_and_tree('foo')
819
rev_id = tree.commit('commit 1',
820
authors=['John Doe <jdoe@example.com>',
821
'Jane Rey <jrey@example.com>'])
822
rev = tree.branch.repository.get_revision(rev_id)
823
self.assertEqual('John Doe <jdoe@example.com>\n'
824
'Jane Rey <jrey@example.com>', rev.properties['authors'])
825
self.assertFalse('author' in rev.properties)
827
def test_author_and_authors_incompatible(self):
828
tree = self.make_branch_and_tree('foo')
829
self.assertRaises(AssertionError, tree.commit, 'commit 1',
830
authors=['John Doe <jdoe@example.com>',
831
'Jane Rey <jrey@example.com>'],
832
author="Jack Me <jme@example.com>")
834
def test_author_with_newline_rejected(self):
835
tree = self.make_branch_and_tree('foo')
836
self.assertRaises(AssertionError, tree.commit, 'commit 1',
837
authors=['John\nDoe <jdoe@example.com>'])
743
rev.properties['author'])
839
745
def test_commit_with_checkout_and_branch_sharing_repo(self):
840
746
repo = self.make_repository('repo', shared=True)