From cb97307237c6431196ed7760ac70d9db23f0ddb7 Mon Sep 17 00:00:00 2001 From: Tony Duckles Date: Sat, 24 Mar 2012 14:31:26 -0500 Subject: [PATCH] Prevent KeyboardInterrupt's during SVN commit * svn2svn/run/breakhandler.py: Adding * svn2svn/run/svn2svn.py (commit_from_svn_log_entry): Use BreakHandler to ensure that "svn commit" and post-commit rev-prop updating happen as an atomic unit. --- svn2svn/run/breakhandler.py | 111 ++++++++++++++++++++++++++++++++++++ svn2svn/run/svn2svn.py | 14 ++++- 2 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 svn2svn/run/breakhandler.py diff --git a/svn2svn/run/breakhandler.py b/svn2svn/run/breakhandler.py new file mode 100644 index 0000000..bb028ae --- /dev/null +++ b/svn2svn/run/breakhandler.py @@ -0,0 +1,111 @@ +''' +Trap keyboard interrupts. No rights reserved; use at your own risk. + +@author: Stacy Prowell (http://stacyprowell.com) +@url: http://stacyprowell.com/blog/2009/03/30/trapping-ctrlc-in-python/ +''' +import signal + +class BreakHandler: + ''' + Trap CTRL-C, set a flag, and keep going. This is very useful for + gracefully exiting database loops while simulating transactions. + + To use this, make an instance and then enable it. You can check + whether a break was trapped using the trapped property. + + # Create and enable a break handler. + ih = BreakHandler() + ih.enable() + for x in big_set: + complex_operation_1() + complex_operation_2() + complex_operation_3() + # Check whether there was a break. + if ih.trapped: + # Stop the loop. + break + ih.disable() + # Back to usual operation... + ''' + + def __init__(self, emphatic=9): + ''' + Create a new break handler. + + @param emphatic: This is the number of times that the user must + press break to *disable* the handler. If you press + break this number of times, the handler is automagically + disabled, and one more break will trigger an old + style keyboard interrupt. The default is nine. This + is a Good Idea, since if you happen to lose your + connection to the handler you can *still* disable it. + ''' + self._count = 0 + self._enabled = False + self._emphatic = emphatic + self._oldhandler = None + return + + def _reset(self): + ''' + Reset the trapped status and count. You should not need to use this + directly; instead you can disable the handler and then re-enable it. + This is better, in case someone presses CTRL-C during this operation. + ''' + self._count = 0 + return + + def enable(self): + ''' + Enable trapping of the break. This action also resets the + handler count and trapped properties. + ''' + if not self._enabled: + self._reset() + self._enabled = True + self._oldhandler = signal.signal(signal.SIGINT, self) + return + + def disable(self): + ''' + Disable trapping the break. You can check whether a break + was trapped using the count and trapped properties. + ''' + if self._enabled: + self._enabled = False + signal.signal(signal.SIGINT, self._oldhandler) + self._oldhandler = None + return + + def __call__(self, signame, sf): + ''' + An break just occurred. Save information about it and keep + going. + ''' + self._count += 1 + # If we've exceeded the "emphatic" count disable this handler. + if self._count >= self._emphatic: + self.disable() + return + + def __del__(self): + ''' + Python is reclaiming this object, so make sure we are disabled. + ''' + self.disable() + return + + @property + def count(self): + ''' + The number of breaks trapped. + ''' + return self._count + + @property + def trapped(self): + ''' + Whether a break was trapped. + ''' + return self._count > 0 diff --git a/svn2svn/run/svn2svn.py b/svn2svn/run/svn2svn.py index cf30f7e..a284f29 100644 --- a/svn2svn/run/svn2svn.py +++ b/svn2svn/run/svn2svn.py @@ -8,6 +8,7 @@ from .. import svnclient from ..shell import run_svn,run_shell_command from ..errors import (ExternalCommandFailed, UnsupportedSVNAction, InternalError, VerificationError) from parse import HelpFormatter +from breakhandler import BreakHandler import sys import os @@ -88,6 +89,12 @@ def commit_from_svn_log_entry(log_entry, commit_paths=None, target_revprops=None args += list(commit_paths) rev_num = None if not options.dry_run: + # Use BreakHandler class to temporarily redirect SIGINT handler, so that + # "svn commit" + post-commit rev-prop updating is a quasi-atomic unit. + # If user presses Ctrl-C during this, wait until after this full action + # has finished raising the KeyboardInterrupt exception. + bh = BreakHandler() + bh.enable() # Run the "svn commit" command, and screen-scrape the target_rev value (if any) output = run_svn(args) rev_num = parse_svn_commit_rev(output) if output else None @@ -95,6 +102,10 @@ def commit_from_svn_log_entry(log_entry, commit_paths=None, target_revprops=None ui.status("Committed revision %s.", rev_num) if options.keep_date: run_svn(["propset", "--revprop", "-r", rev_num, "svn:date", log_entry['date_raw']]) + bh.disable() + # Check if the user tried to press Ctrl-C + if bh.trapped: + raise KeyboardInterrupt return rev_num def verify_commit(source_rev, target_rev, log_entry=None): @@ -1076,9 +1087,6 @@ def real_main(args, parser): it_log_entries = svnclient.iter_svn_log_entries(source_url, source_start_rev+1, source_end_rev, get_revprops=True, ancestors=source_ancestors) if source_start_rev < source_end_rev else [] source_rev = None - # TODO: Now that commit_from_svn_log_entry() might try to do a "svn propset svn:date", - # we might want some better KeyboardInterupt handilng here, to ensure that - # commit_from_svn_log_entry() always runs as an atomic unit. try: for log_entry in it_log_entries: if options.entries_proc_limit: -- 2.43.0