# Bazaar merge directive format 2 (Bazaar 0.19) # revision_id: namnguyen-20070807082534-7xw955iabtlxuayj # target_branch: ..\\bzr.dev # testament_sha1: 932fbee82a801ea9ab8dd93ac5402ba46ac1707c # timestamp: 2007-08-07 16:28:42 +0800 # base_revision_id: pqm@pqm.ubuntu.com-20070807061316-b32atzzop4r4y21g # # Begin patch === modified file 'NEWS' --- NEWS 2007-08-07 02:12:43 +0000 +++ NEWS 2007-08-07 08:25:34 +0000 @@ -40,6 +40,9 @@ IMPROVEMENTS: + * ``Branch.hooks`` now supports ``pre_commit`` hook. The hook's signature + is documented in BranchHooks constructor. (Nam T. Nguyen, #102747) + * Don't show "dots" progress indicators when run non-interactively, such as from cron. (Martin Pool) === 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-07-23 03:57:11 +0000 +++ bzrlib/commit.py 2007-08-07 08:23:28 +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 @@ -315,6 +316,8 @@ # Add revision data to the local branch self.rev_id = self.builder.commit(self.message) + 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: @@ -335,7 +338,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 @@ -461,8 +464,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 @@ -475,6 +483,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 @@ -489,19 +504,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.""" @@ -621,6 +642,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.""" @@ -738,6 +760,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-07 08:23:28 +0000 @@ -59,6 +59,18 @@ ('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): + # replacing ids with paths + # notice that we leave deleted ids in tact + for change, ids in affected.iteritems(): + if change == 'deleted': + continue + for i, id in enumerate(ids): + ids[i] = tree.id2path(id) + 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 +124,69 @@ ], 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_paths(self): + tree = self.make_branch_and_memory_tree('branch') + tree.lock_write() + tree.add('') + tree.add('file', 'bang') + tree.put_file_bytes_non_atomic('bang', 'die') + tree.mkdir('dir', 'dirid') + tree.add('dir/file', 'swoosh') + tree.put_file_bytes_non_atomic('swoosh', 'swaash') + Branch.hooks.install_hook("pre_commit", self.capture_pre_commit_hook) + rev1 = tree.commit('first revision') + tree.unversion(['dirid']) + rev2 = tree.commit('second revision') + self.assertEqual([ + ('pre_commit', 0, NULL_REVISION, 1, rev1, + {'added': ['dir', 'dir/file', 'file']} ), + ('pre_commit', 1, rev1, 2, rev2, + {'deleted': ['dirid', 'swoosh']} ) + ], + self.hook_calls) + tree.unlock() === 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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 B+n5kJ/8XckU4UJAmDWyFA==