# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: namnguyen-20070815091606-2gqalm977ys9e21w # target_branch: ../bzr.dev # testament_sha1: bd555ca89533771e69501f00b8bbda383fa243cd # timestamp: 2007-08-15 17:16:51 +0800 # base_revision_id: pqm@pqm.ubuntu.com-20070815055603-t0fwzxv6if6sr7c6 # # Begin patch === modified file 'NEWS' --- NEWS 2007-08-15 05:56:03 +0000 +++ NEWS 2007-08-15 09:16:06 +0000 @@ -2,6 +2,9 @@ IMPROVEMENTS: + * ``Branch.hooks`` now supports ``pre_commit`` hook. The hook's signature + is documented in BranchHooks constructor. (Nam T. Nguyen, #102747) + * ``pull`` and ``merge`` are much faster at installing bundle format 4. (Aaron Bentley) === modified file 'bzrlib/branch.py' --- bzrlib/branch.py 2007-07-27 13:02:00 +0000 +++ bzrlib/branch.py 2007-08-07 08:23:28 +0000 @@ -1026,6 +1026,17 @@ # is read locked and the target branches write locked. The local # branch is the low-latency branch. self['post_pull'] = [] + # invoked before a commit operation takes place. + # the api signature is + # (local, master, old_revno, old_revid, future_revno, future_revid, + # affected_ids, future_revision_tree). + # old_revid is NULL_REVISION for the first commit to a branch + # affected_ids is a dictionary of (change_type, ids) pairs where ids + # is a list of inventory entry ids and change_type is a string + # describing the change (added, deleted, renamed, etc.) + # future_revision_tree is an in-memory tree obtained from + # CommitBuilder.revision_tree() + self['pre_commit'] = [] # invoked after a commit operation completes. # the api signature is # (local, master, old_revno, old_revid, new_revno, new_revid) === modified file 'bzrlib/commit.py' --- bzrlib/commit.py 2007-08-07 22:59:45 +0000 +++ bzrlib/commit.py 2007-08-15 09:06:32 +0000 @@ -235,6 +235,7 @@ self.committer = committer self.strict = strict self.verbose = verbose + self.affected_ids = {} if reporter is None and self.reporter is None: self.reporter = NullCommitReporter() @@ -254,7 +255,7 @@ self._check_bound_branch() # Check that the working tree is up to date - old_revno,new_revno = self._check_out_of_date_tree() + old_revno, new_revno = self._check_out_of_date_tree() if self.config is None: self.config = self.branch.get_config() @@ -275,7 +276,7 @@ # information in the progress bar during the relevant stages. self.pb_stage_name = "" self.pb_stage_count = 0 - self.pb_stage_total = 4 + self.pb_stage_total = 5 if self.bound_branch: self.pb_stage_total += 1 self.pb.show_pct = False @@ -296,6 +297,7 @@ entries_title="Directory") self.builder = self.branch.get_commit_builder(self.parents, self.config, timestamp, timezone, committer, revprops, rev_id) + try: self._update_builder_with_changes() self._check_pointless() @@ -315,11 +317,14 @@ # Add revision data to the local branch self.rev_id = self.builder.commit(self.message) + except: # perhaps this should be done by the CommitBuilder ? self.work_tree.branch.repository.abort_write_group() raise + self._process_pre_hooks(old_revno, new_revno) + # Upload revision data to the master. # this will propagate merged revisions too if needed. if self.bound_branch: @@ -340,7 +345,7 @@ rev_tree = self.builder.revision_tree() self.work_tree.set_parent_trees([(self.rev_id, rev_tree)]) self.reporter.completed(new_revno, self.rev_id) - self._process_hooks(old_revno, new_revno) + self._process_post_hooks(old_revno, new_revno) finally: self._cleanup() return self.rev_id @@ -466,8 +471,13 @@ new_revno = 1 return old_revno,new_revno - def _process_hooks(self, old_revno, new_revno): - """Process any registered commit hooks.""" + def _process_pre_hooks(self, old_revno, new_revno): + """Process any registered pre commit hooks.""" + self._set_progress_stage("Running pre commit hooks") + self._process_hooks("pre_commit", old_revno, new_revno) + + def _process_post_hooks(self, old_revno, new_revno): + """Process any registered post commit hooks.""" # Process the post commit hooks, if any self._set_progress_stage("Running post commit hooks") # old style commit hooks - should be deprecated ? (obsoleted in @@ -480,6 +490,13 @@ {'branch':self.branch, 'bzrlib':bzrlib, 'rev_id':self.rev_id}) + # process new style post commit hooks + self._process_hooks("post_commit", old_revno, new_revno) + + def _process_hooks(self, hook_name, old_revno, new_revno): + if not Branch.hooks[hook_name]: + return + # new style commit hooks: if not self.bound_branch: hook_master = self.branch @@ -494,19 +511,25 @@ old_revid = self.parents[0] else: old_revid = bzrlib.revision.NULL_REVISION - for hook in Branch.hooks['post_commit']: + + for hook in Branch.hooks[hook_name]: # show the running hook in the progress bar. As hooks may # end up doing nothing (e.g. because they are not configured by # the user) this is still showing progress, not showing overall # actions - its up to each plugin to show a UI if it want's to # (such as 'Emailing diff to foo@example.com'). - self.pb_stage_name = "Running post commit hooks [%s]" % \ - Branch.hooks.get_hook_name(hook) + self.pb_stage_name = "Running %s hooks [%s]" % \ + (hook_name.replace('_', ' '), Branch.hooks.get_hook_name(hook)) self._emit_progress() if 'hooks' in debug.debug_flags: mutter("Invoking commit hook: %r", hook) - hook(hook_local, hook_master, old_revno, old_revid, new_revno, - self.rev_id) + if hook_name == "post_commit": + hook(hook_local, hook_master, old_revno, old_revid, new_revno, + self.rev_id) + elif hook_name == "pre_commit": + hook(hook_local, hook_master, + old_revno, old_revid, new_revno, self.rev_id, + self.affected_ids, self.builder.revision_tree()) def _cleanup(self): """Cleanup any open locks, progress bars etc.""" @@ -626,6 +649,7 @@ for path, ie in self.basis_inv.iter_entries(): if ie.file_id not in self.builder.new_inventory: self.reporter.deleted(path) + self.affected_ids.setdefault('deleted', []).append(ie.file_id) def _populate_from_inventory(self, specific_files): """Populate the CommitBuilder by walking the working tree inventory.""" @@ -743,6 +767,8 @@ else: basis_ie = None change = ie.describe_change(basis_ie, ie) + if change != 'unchanged' and path != '': + self.affected_ids.setdefault(change, []).append(ie.file_id) if change in (InventoryEntry.RENAMED, InventoryEntry.MODIFIED_AND_RENAMED): old_path = self.basis_inv.id2path(ie.file_id) === modified file 'bzrlib/tests/branch_implementations/test_commit.py' --- bzrlib/tests/branch_implementations/test_commit.py 2007-02-06 02:33:42 +0000 +++ bzrlib/tests/branch_implementations/test_commit.py 2007-08-15 09:06:32 +0000 @@ -59,6 +59,11 @@ ('post_commit', local_base, master.base, old_revno, old_revid, new_revno, new_revid, local_locked, master.is_locked())) + def capture_pre_commit_hook(self, local, master, old_revno, old_revid, + new_revno, new_revid, affected, tree): + self.hook_calls.append(('pre_commit', old_revno, old_revid, + new_revno, new_revid, affected)) + def test_post_commit_to_origin(self): tree = self.make_branch_and_memory_tree('branch') Branch.hooks.install_hook('post_commit', @@ -112,3 +117,87 @@ ], self.hook_calls) tree.unlock() + + def test_pre_commit_passes(self): + tree = self.make_branch_and_memory_tree('branch') + tree.lock_write() + tree.add('') + Branch.hooks.install_hook("pre_commit", self.capture_pre_commit_hook) + revid1 = tree.commit('first revision') + revid2 = tree.commit('second revision') + self.assertEqual([ + ('pre_commit', 0, NULL_REVISION, 1, revid1, {}), + ('pre_commit', 1, revid1, 2, revid2, {}) + ], + self.hook_calls) + tree.unlock() + + def test_pre_commit_fails(self): + tree = self.make_branch_and_memory_tree('branch') + tree.lock_write() + tree.add('') + class PreCommitException(Exception): pass + def hook_func(_1, _2, _3, _4, _5, new_revid, _7, _8): + raise PreCommitException(new_revid) + Branch.hooks.install_hook("pre_commit", self.capture_pre_commit_hook) + Branch.hooks.install_hook("pre_commit", hook_func) + revids = [None, None, None] + # this commit will raise exception + # so the commit is rollbacked and revno unchanged + err = self.assertRaises(PreCommitException, tree.commit, 'message') + # we have to record the revid to use in assertEqual later + revids[0] = err.message + # unregister all pre_commit hooks + Branch.hooks["pre_commit"] = [] + # and re-register the capture hook + Branch.hooks.install_hook("pre_commit", self.capture_pre_commit_hook) + # now these commits should go through + for i in range(1, 3): + revids[i] = tree.commit('message') + self.assertEqual([ + ('pre_commit', 0, NULL_REVISION, 1, revids[0], {}), + ('pre_commit', 0, NULL_REVISION, 1, revids[1], {}), + ('pre_commit', 1, revids[1], 2, revids[2], {}) + ], + self.hook_calls) + tree.unlock() + + def test_pre_commit_ids(self): + self.build_tree(['rootfile', 'dir/', 'dir/subfile']) + tree = self.make_branch_and_tree('.') + tree.lock_write() + tree.set_root_id('root_id') + tree.add('rootfile', 'rootfile_id') + tree.put_file_bytes_non_atomic('rootfile_id', 'abc') + tree.add('dir', 'dir_id') + tree.add('dir/subfile', 'dir_subfile_id') + tree.put_file_bytes_non_atomic('dir_subfile_id', 'def') + Branch.hooks.install_hook("pre_commit", self.capture_pre_commit_hook) + rev1 = tree.commit('first revision') + tree.unversion(['dir_id']) + rev2 = tree.commit('second revision') + tree.put_file_bytes_non_atomic('rootfile_id', 'ghi') + rev3 = tree.commit('third revision') + tree.unlock() + tree.lock_write() + tree.rename_one('rootfile', 'renamed') + rev4 = tree.commit('fourth revision') + tree.unlock() + tree.lock_write() + tree.put_file_bytes_non_atomic('rootfile_id', 'jkl') + tree.rename_one('renamed', 'rootfile') + rev5 = tree.commit('fifth revision') + tree.unlock() + self.assertEqual([ + ('pre_commit', 0, NULL_REVISION, 1, rev1, + {'added': ['dir_id', 'dir_subfile_id', 'rootfile_id']} ), + ('pre_commit', 1, rev1, 2, rev2, + {'deleted': ['dir_id', 'dir_subfile_id']} ), + ('pre_commit', 2, rev2, 3, rev3, + {'modified': ['rootfile_id']} ), + ('pre_commit', 3, rev3, 4, rev4, + {'renamed': ['rootfile_id']} ), + ('pre_commit', 4, rev4, 5, rev5, + {'modified and renamed': ['rootfile_id']} ) + ], + self.hook_calls) === modified file 'bzrlib/tests/test_branch.py' --- bzrlib/tests/test_branch.py 2007-07-04 14:53:53 +0000 +++ bzrlib/tests/test_branch.py 2007-08-01 06:14:14 +0000 @@ -336,6 +336,7 @@ self.assertTrue("set_rh" in hooks, "set_rh not in %s" % hooks) self.assertTrue("post_push" in hooks, "post_push not in %s" % hooks) self.assertTrue("post_commit" in hooks, "post_commit not in %s" % hooks) + self.assertTrue("pre_commit" in hooks, "pre_commit not in %s" % hooks) self.assertTrue("post_pull" in hooks, "post_pull not in %s" % hooks) self.assertTrue("post_uncommit" in hooks, "post_uncommit not in %s" % hooks) === modified file 'bzrlib/tests/workingtree_implementations/test_commit.py' --- bzrlib/tests/workingtree_implementations/test_commit.py 2007-07-13 06:22:45 +0000 +++ bzrlib/tests/workingtree_implementations/test_commit.py 2007-08-07 08:23:28 +0000 @@ -354,17 +354,18 @@ # into the factory for this test - just make the test ui factory # pun as a reporter. Then we can check the ordering is right. tree.commit('second post', specific_files=['b']) - # 4 steps, the first of which is reported 2 times, once per dir + # 5 steps, the first of which is reported 2 times, once per dir self.assertEqual( - [('update', 1, 4, 'Collecting changes [Directory 0] - Stage'), - ('update', 1, 4, 'Collecting changes [Directory 1] - Stage'), - ('update', 2, 4, 'Saving data locally - Stage'), - ('update', 3, 4, 'Updating the working tree - Stage'), - ('update', 4, 4, 'Running post commit hooks - Stage')], + [('update', 1, 5, 'Collecting changes [Directory 0] - Stage'), + ('update', 1, 5, 'Collecting changes [Directory 1] - Stage'), + ('update', 2, 5, 'Saving data locally - Stage'), + ('update', 3, 5, 'Running pre commit hooks - Stage'), + ('update', 4, 5, 'Updating the working tree - Stage'), + ('update', 5, 5, 'Running post commit hooks - Stage')], factory._calls ) - def test_commit_progress_shows_hook_names(self): + def test_commit_progress_shows_post_hook_names(self): tree = self.make_branch_and_tree('.') # set a progress bar that captures the calls so we can see what is # emitted @@ -378,15 +379,38 @@ branch.Branch.hooks.name_hook(a_hook, 'hook name') tree.commit('first post') self.assertEqual( - [('update', 1, 4, 'Collecting changes [Directory 0] - Stage'), - ('update', 1, 4, 'Collecting changes [Directory 1] - Stage'), - ('update', 2, 4, 'Saving data locally - Stage'), - ('update', 3, 4, 'Updating the working tree - Stage'), - ('update', 4, 4, 'Running post commit hooks - Stage'), - ('update', 4, 4, 'Running post commit hooks [hook name] - Stage'), - ], - factory._calls - ) - - - + [('update', 1, 5, 'Collecting changes [Directory 0] - Stage'), + ('update', 1, 5, 'Collecting changes [Directory 1] - Stage'), + ('update', 2, 5, 'Saving data locally - Stage'), + ('update', 3, 5, 'Running pre commit hooks - Stage'), + ('update', 4, 5, 'Updating the working tree - Stage'), + ('update', 5, 5, 'Running post commit hooks - Stage'), + ('update', 5, 5, 'Running post commit hooks [hook name] - Stage'), + ], + factory._calls + ) + + def test_commit_progress_shows_pre_hook_names(self): + tree = self.make_branch_and_tree('.') + # set a progress bar that captures the calls so we can see what is + # emitted + self.old_ui_factory = ui.ui_factory + self.addCleanup(self.restoreDefaults) + factory = CapturingUIFactory() + ui.ui_factory = factory + def a_hook(_, _2, _3, _4, _5, _6, _7, _8): + pass + branch.Branch.hooks.install_hook('pre_commit', a_hook) + branch.Branch.hooks.name_hook(a_hook, 'hook name') + tree.commit('first post') + self.assertEqual( + [('update', 1, 5, 'Collecting changes [Directory 0] - Stage'), + ('update', 1, 5, 'Collecting changes [Directory 1] - Stage'), + ('update', 2, 5, 'Saving data locally - Stage'), + ('update', 3, 5, 'Running pre commit hooks - Stage'), + ('update', 3, 5, 'Running pre commit hooks [hook name] - Stage'), + ('update', 4, 5, 'Updating the working tree - Stage'), + ('update', 5, 5, 'Running post commit hooks - Stage'), + ], + factory._calls + ) # Begin bundle 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