diff options
Diffstat (limited to 'morphlib/branchmanager.py')
-rw-r--r-- | morphlib/branchmanager.py | 94 |
1 files changed, 80 insertions, 14 deletions
diff --git a/morphlib/branchmanager.py b/morphlib/branchmanager.py index 87a75ddc..a33b4ccb 100644 --- a/morphlib/branchmanager.py +++ b/morphlib/branchmanager.py @@ -34,23 +34,58 @@ class RefCleanupError(cliapp.AppException): class LocalRefManager(object): '''Provide atomic update over a set of refs in a set of repositories. - Any ref changes made with the update, add and delete methods will - be reversed after the end of the with statement the LocalRefManager - is used in, if an exception is raised in the aforesaid with statement. + When used in a with statement, if an exception is raised in the + body, then any ref changes are reverted, so deletes get replaced, + new branches get deleted and ref changes are changed back to the + value before the LocalRefManager was created. + + By default, changes are kept after the with statement ends. This can + be overridden to revert after the manager exits by passing True to + the construcor. + + with LocalRefManager(True) as lrm: + # Update refs with lrm.update, lrm.add or lrm.delete + # Use changed refs + # refs are back to their previous value + + There is also an explicit .close() method to clean up after the + context has exited like so: + + with LocalRefManager() as lrm: + # update refs + # Do something with altered refs + lrm.close() # Explicitly clean up + + The name .close() was chosen for the cleanup method, so the + LocalRefManager object may also be used again in a second with + statement using contextlib.closing(). + + with LocalRefManager() as lrm: + # update refs + with contextlib.closing(lrm) as lrm: + # Do something with pushed refs and clean up if there is an + # exception + + This is also useful if the LocalRefManager is nested in another + object, since the .close() method can be called in that object's + cleanup method. ''' - def __init__(self): - self._cleanup = None + def __init__(self, cleanup_on_success=False): + self._cleanup_on_success = cleanup_on_success + self._cleanup = collections.deque() def __enter__(self): - self._cleanup = collections.deque() return self def __exit__(self, etype, evalue, estack): # No exception was raised, so no cleanup is required - if (etype, evalue, estack) == (None, None, None): + if not self._cleanup_on_success and evalue is None: return + self.close(evalue) + + def close(self, primary=None): exceptions = [] d = self._cleanup while d: @@ -60,7 +95,7 @@ class LocalRefManager(object): except Exception, e: exceptions.append((op, args, e)) if exceptions: - raise RefCleanupError(evalue, exceptions) + raise RefCleanupError(primary, exceptions) def update(self, gd, ref, commit, old_commit, message=None): '''Update a git repository's ref, reverting it on failure. @@ -116,19 +151,50 @@ class LocalRefManager(object): class RemoteRefManager(object): '''Provide temporary pushes to remote repositories. - Any ref changes made with the push method will be reversed after - the end of the with statement the RemoteRefManager is used in. + When used in a with statement, if an exception is raised in the body, + then any pushed refs are reverted, so deletes get replaced and new + branches get deleted. + + By default it will also undo pushed refs when an exception is not + raised, this can be overridden by passing False to the constructor. + + There is also an explicit .close() method to clean up after the + context has exited like so: + + with RemoteRefManager(False) as rrm: + # push refs with rrm.push(...) + # Do something with pushed refs + rrm.close() # Explicitly clean up + + The name .close() was chosen for the cleanup method, so the + RemoteRefManager object may also be used again in a second with + statement using contextlib.closing(). + + with RemoteRefManager(False) as rrm: + rrm.push(...) + with contextlib.closing(rrm) as rrm: + # Do something with pushed refs and clean up if there is an + # exception + + This is also useful if the RemoteRefManager is nested in another + object, since the .close() method can be called in that object's + cleanup method. ''' - def __init__(self): - self._cleanup = None + def __init__(self, cleanup_on_success=True): + self._cleanup_on_success = cleanup_on_success + self._cleanup = collections.deque() def __enter__(self): - self._cleanup = collections.deque() return self def __exit__(self, etype, evalue, estack): + if not self._cleanup_on_success and evalue is None: + return + self.close(evalue) + + def close(self, primary=None): exceptions = [] d = self._cleanup while d: @@ -138,7 +204,7 @@ class RemoteRefManager(object): except Exception, e: exceptions.append((remote, refspecs, e)) if exceptions: - raise RefCleanupError(evalue, exceptions) + raise RefCleanupError(primary, exceptions) def push(self, remote, *refspecs): '''Push refspecs to remote and revert on failure. |