def serve(self, thread_name_suffix=''): self._should_terminate = False # for hooks we are letting code know that a server has started (and # later stopped). # There are three interesting urls: # The URL the server can be contacted on. (e.g. bzr://host/) # The URL that a commit done on the same machine as the server will # have within the servers space. (e.g. file:///home/user/source) # The URL that will be given to other hooks in the same process - # the URL of the backing transport itself. (e.g. chroot+:///) # We need all three because: # * other machines see the first # * local commits on this machine should be able to be mapped to # this server # * commits the server does itself need to be mapped across to this # server. # The latter two urls are different aliases to the servers url, # so we group those in a list - as there might be more aliases # in the future. backing_urls = [self.backing_transport.base] try: backing_urls.append(self.backing_transport.external_url()) except errors.InProcessTransport: pass for hook in SmartTCPServer.hooks['server_started']: hook(backing_urls, self.get_url()) self._started.set() try: try: while not self._should_terminate: try: conn, client_addr = self._server_socket.accept() except self._socket_timeout: # just check if we're asked to stop pass except self._socket_error, e: # if the socket is closed by stop_background_thread # we might get a EBADF here, any other socket errors # should get logged. if e.args[0] != errno.EBADF: trace.warning("listening socket error: %s", e) else: self.serve_conn(conn, thread_name_suffix) except KeyboardInterrupt: # dont log when CTRL-C'd. raise except Exception, e: trace.error("Unhandled smart server error.") trace.log_exception_quietly() raise finally: self._stopped.set() try: # ensure the server socket is closed. self._server_socket.close() except self._socket_error: # ignore errors on close pass for hook in SmartTCPServer.hooks['server_stopped']: hook(backing_urls, self.get_url())
def wrapped(*args, **kwargs): try: return unbound(*args, **kwargs) except errors: raise except: trace.mutter('Error suppressed by only_raises:') trace.log_exception_quietly()
def _translate_error(err): if isinstance(err, errors.NoSuchFile): return ('NoSuchFile', err.path) elif isinstance(err, errors.FileExists): return ('FileExists', err.path) elif isinstance(err, errors.DirectoryNotEmpty): return ('DirectoryNotEmpty', err.path) elif isinstance(err, errors.IncompatibleRepositories): return ('IncompatibleRepositories', str(err.source), str(err.target), str(err.details)) elif isinstance(err, errors.ShortReadvError): return ('ShortReadvError', err.path, str(err.offset), str(err.length), str(err.actual)) elif isinstance(err, errors.RevisionNotPresent): return ('RevisionNotPresent', err.revision_id, err.file_id) elif isinstance(err, errors.UnstackableRepositoryFormat): return (('UnstackableRepositoryFormat', str(err.format), err.url)) elif isinstance(err, errors.UnstackableBranchFormat): return ('UnstackableBranchFormat', str(err.format), err.url) elif isinstance(err, errors.NotStacked): return ('NotStacked',) elif isinstance(err, errors.BzrCheckError): return ('BzrCheckError', err.msg) elif isinstance(err, UnicodeError): # If it is a DecodeError, than most likely we are starting # with a plain string str_or_unicode = err.object if isinstance(str_or_unicode, unicode): # XXX: UTF-8 might have \x01 (our protocol v1 and v2 seperator # byte) in it, so this encoding could cause broken responses. # Newer clients use protocol v3, so will be fine. val = 'u:' + str_or_unicode.encode('utf-8') else: val = 's:' + str_or_unicode.encode('base64') # This handles UnicodeEncodeError or UnicodeDecodeError return (err.__class__.__name__, err.encoding, val, str(err.start), str(err.end), err.reason) elif isinstance(err, errors.TransportNotPossible): if err.msg == "readonly transport": return ('ReadOnlyError', ) elif isinstance(err, errors.ReadError): # cannot read the file return ('ReadError', err.path) elif isinstance(err, errors.PermissionDenied): return ('PermissionDenied', err.path, err.extra) elif isinstance(err, errors.TokenMismatch): return ('TokenMismatch', err.given_token, err.lock_token) elif isinstance(err, errors.LockContention): return ('LockContention',) elif isinstance(err, MemoryError): # GZ 2011-02-24: Copy bzrlib.trace -Dmem_dump functionality here? return ('MemoryError',) # Unserialisable error. Log it, and return a generic error trace.log_exception_quietly() return ('error', trace._qualified_exception_name(err.__class__, True), str(err))
def _translate_error(err): if isinstance(err, errors.NoSuchFile): return ('NoSuchFile', err.path) elif isinstance(err, errors.FileExists): return ('FileExists', err.path) elif isinstance(err, errors.DirectoryNotEmpty): return ('DirectoryNotEmpty', err.path) elif isinstance(err, errors.IncompatibleRepositories): return ('IncompatibleRepositories', str(err.source), str(err.target), str(err.details)) elif isinstance(err, errors.ShortReadvError): return ('ShortReadvError', err.path, str(err.offset), str(err.length), str(err.actual)) elif isinstance(err, errors.RevisionNotPresent): return ('RevisionNotPresent', err.revision_id, err.file_id) elif isinstance(err, errors.UnstackableRepositoryFormat): return (('UnstackableRepositoryFormat', str(err.format), err.url)) elif isinstance(err, errors.UnstackableBranchFormat): return ('UnstackableBranchFormat', str(err.format), err.url) elif isinstance(err, errors.NotStacked): return ('NotStacked', ) elif isinstance(err, errors.BzrCheckError): return ('BzrCheckError', err.msg) elif isinstance(err, UnicodeError): # If it is a DecodeError, than most likely we are starting # with a plain string str_or_unicode = err.object if isinstance(str_or_unicode, unicode): # XXX: UTF-8 might have \x01 (our protocol v1 and v2 seperator # byte) in it, so this encoding could cause broken responses. # Newer clients use protocol v3, so will be fine. val = 'u:' + str_or_unicode.encode('utf-8') else: val = 's:' + str_or_unicode.encode('base64') # This handles UnicodeEncodeError or UnicodeDecodeError return (err.__class__.__name__, err.encoding, val, str(err.start), str(err.end), err.reason) elif isinstance(err, errors.TransportNotPossible): if err.msg == "readonly transport": return ('ReadOnlyError', ) elif isinstance(err, errors.ReadError): # cannot read the file return ('ReadError', err.path) elif isinstance(err, errors.PermissionDenied): return ('PermissionDenied', err.path, err.extra) elif isinstance(err, errors.TokenMismatch): return ('TokenMismatch', err.given_token, err.lock_token) elif isinstance(err, errors.LockContention): return ('LockContention', ) elif isinstance(err, MemoryError): # GZ 2011-02-24: Copy bzrlib.trace -Dmem_dump functionality here? return ('MemoryError', ) # Unserialisable error. Log it, and return a generic error trace.log_exception_quietly() return ('error', trace._qualified_exception_name(err.__class__, True), str(err))
def _parse_json_info(self, json_info): """Parse the json response from Launchpad into objects.""" if json is None: return None try: return json.loads(json_info) except Exception: trace.mutter('Failed to parse json info: %r' % (json_info,)) trace.log_exception_quietly() return None
def load_from_dir(d): """Load the plugins in directory d.""" # Get the list of valid python suffixes for __init__.py? # this includes .py, .pyc, and .pyo (depending on if we are running -O) # but it doesn't include compiled modules (.so, .dll, etc) valid_suffixes = [suffix for suffix, mod_type, flags in imp.get_suffixes() if flags in (imp.PY_SOURCE, imp.PY_COMPILED)] package_entries = ['__init__'+suffix for suffix in valid_suffixes] plugin_names = set() for f in os.listdir(d): path = osutils.pathjoin(d, f) if os.path.isdir(path): for entry in package_entries: # This directory should be a package, and thus added to # the list if os.path.isfile(osutils.pathjoin(path, entry)): break else: # This directory is not a package continue else: for suffix_info in imp.get_suffixes(): if f.endswith(suffix_info[0]): f = f[:-len(suffix_info[0])] if suffix_info[2] == imp.C_EXTENSION and f.endswith('module'): f = f[:-len('module')] break else: continue if getattr(_mod_plugins, f, None): mutter('Plugin name %s already loaded', f) else: # mutter('add plugin name %s', f) plugin_names.add(f) for name in plugin_names: try: exec "import bzrlib.plugins.%s" % name in {} except KeyboardInterrupt: raise except Exception, e: ## import pdb; pdb.set_trace() if re.search('\.|-| ', name): sanitised_name = re.sub('[-. ]', '_', name) if sanitised_name.startswith('bzr_'): sanitised_name = sanitised_name[len('bzr_'):] warning("Unable to load %r in %r as a plugin because the " "file path isn't a valid module name; try renaming " "it to %r." % (name, d, sanitised_name)) else: warning('Unable to load plugin %r from %r' % (name, d)) log_exception_quietly() if 'error' in debug.debug_flags: trace.print_exception(sys.exc_info(), sys.stderr)
def unregister_on_hangup(identifier): """Remove a callback from being called during sighup.""" if _on_sighup is None: return try: del _on_sighup[identifier] except KeyboardInterrupt: raise except Exception: # This usually runs as a tear-down step. So we don't want to propagate # most exceptions. trace.mutter('Error occurred during unregister_on_hangup:') trace.log_exception_quietly()
def _get_lp_info(self): """Place an actual HTTP query against the Launchpad service.""" if json is None: return None query_URL = self._query_URL() try: req = urllib2.Request(query_URL) response = urllib2.urlopen(req) json_info = response.read() # TODO: We haven't tested the HTTPError except (urllib2.URLError, urllib2.HTTPError), e: trace.mutter('failed to place query to %r' % (query_URL,)) trace.log_exception_quietly() return None
def get_latest_version(self): """Get the latest published version for the given package.""" json_info = self._get_lp_info() if json_info is None: return None info = self._parse_json_info(json_info) if info is None: return None try: entries = info['entries'] if len(entries) == 0: return None return entries[0]['source_package_version'] except KeyError: trace.log_exception_quietly() return None
def _sighup_handler(signal_number, interrupted_frame): """This is the actual function that is registered for handling SIGHUP. It will call out to all the registered functions, letting them know that a graceful termination has been requested. """ if _on_sighup is None: return trace.mutter('Caught SIGHUP, sending graceful shutdown requests.') for ref in _on_sighup.valuerefs(): try: cb = ref() if cb is not None: cb() except KeyboardInterrupt: raise except Exception: trace.mutter('Error occurred while running SIGHUP handlers:') trace.log_exception_quietly()
def _do_loop(self): while not self._should_terminate.isSet(): try: conn, client_addr = self._server_socket.accept() except self._socket_timeout: pass # Run shutdown and children checks. except self._socket_error as e: if e.args[0] == errno.EINTR: pass # Run shutdown and children checks. elif e.args[0] != errno.EBADF: # We can get EBADF here while we are shutting down # So we just ignore it for now pass else: # Log any other failure mode trace.warning("listening socket error: %s", e) else: self.log(client_addr, 'connected') # TODO: We should probably trap exceptions coming out of # this and log them, so that we don't kill the service # because of an unhandled error. # Note: settimeout is used so that a malformed request # doesn't cause us to hang forever. Also note that the # particular implementation means that a malicious # client could probably send us one byte every once in a # while, and we would just keep trying to read it. # However, as a local service, we aren't worrying about # it. conn.settimeout(self.WAIT_FOR_REQUEST_TIMEOUT) try: self.serve_one_connection(conn, client_addr) except self._socket_timeout as e: trace.log_exception_quietly() self.log( client_addr, 'request timeout failure: %s' % (e,)) conn.sendall('FAILURE\nrequest timed out\n') conn.close() except Exception as e: trace.log_exception_quietly() self.log(client_addr, 'trapped a failure while handling' ' connection: %s' % (e,)) self._poll_children()
def _do_loop(self): while not self._should_terminate.isSet(): try: conn, client_addr = self._server_socket.accept() except self._socket_timeout: pass # Run shutdown and children checks. except self._socket_error as e: if e.args[0] == errno.EINTR: pass # Run shutdown and children checks. elif e.args[0] != errno.EBADF: # We can get EBADF here while we are shutting down # So we just ignore it for now pass else: # Log any other failure mode trace.warning("listening socket error: %s", e) else: self.log(client_addr, "connected") # TODO: We should probably trap exceptions coming out of # this and log them, so that we don't kill the service # because of an unhandled error. # Note: settimeout is used so that a malformed request # doesn't cause us to hang forever. Also note that the # particular implementation means that a malicious # client could probably send us one byte every once in a # while, and we would just keep trying to read it. # However, as a local service, we aren't worrying about # it. conn.settimeout(self.WAIT_FOR_REQUEST_TIMEOUT) try: self.serve_one_connection(conn, client_addr) except self._socket_timeout as e: trace.log_exception_quietly() self.log(client_addr, "request timeout failure: %s" % (e,)) conn.sendall("FAILURE\nrequest timed out\n") conn.close() except Exception as e: trace.log_exception_quietly() self.log(client_addr, "trapped a failure while handling" " connection: %s" % (e,)) self._poll_children()
def _call(self, protocol_version): """We know the protocol version. So this just sends the request, and then reads the response. This is where the code will be to retry requests if the connection is closed. """ response_handler = self._send(protocol_version) try: response_tuple = response_handler.read_response_tuple( expect_body=self.expect_response_body) except errors.ConnectionReset, e: self.client._medium.reset() if not self._is_safe_to_send_twice(): raise trace.warning('ConnectionReset reading response for %r, retrying' % (self.method,)) trace.log_exception_quietly() encoder, response_handler = self._construct_protocol( protocol_version) self._send_no_retry(encoder) response_tuple = response_handler.read_response_tuple( expect_body=self.expect_response_body)
def _send(self, protocol_version): """Encode the request, and send it to the server. This will retry a request if we get a ConnectionReset while sending the request to the server. (Unless we have a body_stream that we have already started consuming, since we can't restart body_streams) :return: response_handler as defined by _construct_protocol """ encoder, response_handler = self._construct_protocol(protocol_version) try: self._send_no_retry(encoder) except errors.ConnectionReset, e: # If we fail during the _send_no_retry phase, then we can # be confident that the server did not get our request, because we # haven't started waiting for the reply yet. So try the request # again. We only issue a single retry, because if the connection # really is down, there is no reason to loop endlessly. # Connection is dead, so close our end of it. self.client._medium.reset() if (('noretry' in debug.debug_flags) or (self.body_stream is not None and encoder.body_stream_started)): # We can't restart a body_stream that has been partially # consumed, so we don't retry. # Note: We don't have to worry about # SmartClientRequestProtocolOne or Two, because they don't # support client-side body streams. raise trace.warning('ConnectionReset calling %r, retrying' % (self.method,)) trace.log_exception_quietly() encoder, response_handler = self._construct_protocol( protocol_version) self._send_no_retry(encoder)
def logException(self, exception): """Log exception with Bazaar's exception logger.""" try: raise exception except exception.__class__: trace.log_exception_quietly()
def report_bug(exc_info, stderr): if ('no_apport' in debug.debug_flags) or \ os.environ.get('APPORT_DISABLE', None): return report_bug_legacy(exc_info, stderr) try: if report_bug_to_apport(exc_info, stderr): # wrote a file; if None then report the old way return except ImportError, e: trace.mutter("couldn't find apport bug-reporting library: %s" % e) except Exception, e: # this should only happen if apport is installed but it didn't # work, eg because of an io error writing the crash file trace.mutter("bzr: failed to report crash using apport: %r" % e) trace.log_exception_quietly() return report_bug_legacy(exc_info, stderr) def report_bug_legacy(exc_info, err_file): """Report a bug by just printing a message to the user.""" trace.print_exception(exc_info, err_file) err_file.write('\n') import textwrap def print_wrapped(l): err_file.write(textwrap.fill(l, width=78, subsequent_indent=' ') + '\n') print_wrapped('bzr %s on python %s (%s)\n' % \ (bzrlib.__version__, bzrlib._format_version_tuple(sys.version_info), platform.platform(aliased=1)))
def report_bug(exc_info, stderr): if ('no_apport' in debug.debug_flags) or \ os.environ.get('APPORT_DISABLE', None): return report_bug_legacy(exc_info, stderr) try: if report_bug_to_apport(exc_info, stderr): # wrote a file; if None then report the old way return except ImportError, e: trace.mutter("couldn't find apport bug-reporting library: %s" % e) except Exception, e: # this should only happen if apport is installed but it didn't # work, eg because of an io error writing the crash file trace.mutter("bzr: failed to report crash using apport: %r" % e) trace.log_exception_quietly() return report_bug_legacy(exc_info, stderr) def report_bug_legacy(exc_info, err_file): """Report a bug by just printing a message to the user.""" trace.print_exception(exc_info, err_file) err_file.write('\n') import textwrap def print_wrapped(l): err_file.write( textwrap.fill(l, width=78, subsequent_indent=' ') + '\n') print_wrapped('bzr %s on python %s (%s)\n' % \ (bzrlib.__version__, bzrlib._format_version_tuple(sys.version_info),
def _log_cleanup_error(exc): trace.mutter('Cleanup failed:') trace.log_exception_quietly() if 'cleanup' in debug.debug_flags: trace.warning('bzr: warning: Cleanup failed: %s', exc)
def load_from_zip(zip_name): """Load all the plugins in a zip.""" valid_suffixes = ('.py', '.pyc', '.pyo') # only python modules/packages # is allowed try: index = zip_name.rindex('.zip') except ValueError: return archive = zip_name[:index+4] prefix = zip_name[index+5:] mutter('Looking for plugins in %r', zip_name) # use zipfile to get list of files/dirs inside zip try: z = zipfile.ZipFile(archive) namelist = z.namelist() z.close() except zipfile.error: # not a valid zip return if prefix: prefix = prefix.replace('\\','/') if prefix[-1] != '/': prefix += '/' ix = len(prefix) namelist = [name[ix:] for name in namelist if name.startswith(prefix)] mutter('Names in archive: %r', namelist) for name in namelist: if not name or name.endswith('/'): continue # '/' is used to separate pathname components inside zip archives ix = name.rfind('/') if ix == -1: head, tail = '', name else: head, tail = name.rsplit('/',1) if '/' in head: # we don't need looking in subdirectories continue base, suffix = osutils.splitext(tail) if suffix not in valid_suffixes: continue if base == '__init__': # package plugin_name = head elif head == '': # module plugin_name = base else: continue if not plugin_name: continue if getattr(_mod_plugins, plugin_name, None): mutter('Plugin name %s already loaded', plugin_name) continue try: exec "import bzrlib.plugins.%s" % plugin_name in {} mutter('Load plugin %s from zip %r', plugin_name, zip_name) except KeyboardInterrupt: raise except Exception, e: ## import pdb; pdb.set_trace() warning('Unable to load plugin %r from %r' % (name, zip_name)) log_exception_quietly() if 'error' in debug.debug_flags: trace.print_exception(sys.exc_info(), sys.stderr)
def _log_cleanup_error(exc): trace.mutter("Cleanup failed:") trace.log_exception_quietly() if "cleanup" in debug.debug_flags: trace.warning("bzr: warning: Cleanup failed: %s", exc)
def _commit(self, operation, message, timestamp, timezone, committer, specific_files, rev_id, allow_pointless, strict, verbose, working_tree, local, reporter, message_callback, recursive, exclude, possible_master_transports, lossy): mutter('preparing to commit') if working_tree is None: raise BzrError("working_tree must be passed into commit().") else: self.work_tree = working_tree self.branch = self.work_tree.branch if getattr(self.work_tree, 'requires_rich_root', lambda: False)(): if not self.branch.repository.supports_rich_root(): raise errors.RootNotRich() if message_callback is None: if message is not None: if isinstance(message, str): message = message.decode(get_user_encoding()) message_callback = lambda x: message else: raise BzrError("The message or message_callback keyword" " parameter is required for commit().") self.bound_branch = None self.any_entries_deleted = False if exclude is not None: self.exclude = sorted( minimum_path_selection(exclude)) else: self.exclude = [] self.local = local self.master_branch = None self.recursive = recursive self.rev_id = None # self.specific_files is None to indicate no filter, or any iterable to # indicate a filter - [] means no files at all, as per iter_changes. if specific_files is not None: self.specific_files = sorted( minimum_path_selection(specific_files)) else: self.specific_files = None self.allow_pointless = allow_pointless self.message_callback = message_callback self.timestamp = timestamp self.timezone = timezone self.committer = committer self.strict = strict self.verbose = verbose self.work_tree.lock_write() operation.add_cleanup(self.work_tree.unlock) self.parents = self.work_tree.get_parent_ids() # We can use record_iter_changes IFF iter_changes is compatible with # the command line parameters, and the repository has fast delta # generation. See bug 347649. self.use_record_iter_changes = ( not self.exclude and not self.branch.repository._format.supports_tree_reference and (self.branch.repository._format.fast_deltas or len(self.parents) < 2)) self.pb = ui.ui_factory.nested_progress_bar() operation.add_cleanup(self.pb.finished) self.basis_revid = self.work_tree.last_revision() self.basis_tree = self.work_tree.basis_tree() self.basis_tree.lock_read() operation.add_cleanup(self.basis_tree.unlock) # Cannot commit with conflicts present. if len(self.work_tree.conflicts()) > 0: raise ConflictsInTree # Setup the bound branch variables as needed. self._check_bound_branch(operation, possible_master_transports) # Check that the working tree is up to date old_revno, old_revid, new_revno = self._check_out_of_date_tree() # Complete configuration setup if reporter is not None: self.reporter = reporter elif self.reporter is None: self.reporter = self._select_reporter() if self.config_stack is None: self.config_stack = self.work_tree.get_config_stack() self._set_specific_file_ids() # Setup the progress bar. As the number of files that need to be # committed in unknown, progress is reported as stages. # We keep track of entries separately though and include that # information in the progress bar during the relevant stages. self.pb_stage_name = "" self.pb_stage_count = 0 self.pb_stage_total = 5 if self.bound_branch: # 2 extra stages: "Uploading data to master branch" and "Merging # tags to master branch" self.pb_stage_total += 2 self.pb.show_pct = False self.pb.show_spinner = False self.pb.show_eta = False self.pb.show_count = True self.pb.show_bar = True self._gather_parents() # After a merge, a selected file commit is not supported. # See 'bzr help merge' for an explanation as to why. if len(self.parents) > 1 and self.specific_files is not None: raise errors.CannotCommitSelectedFileMerge(self.specific_files) # Excludes are a form of selected file commit. if len(self.parents) > 1 and self.exclude: raise errors.CannotCommitSelectedFileMerge(self.exclude) # Collect the changes self._set_progress_stage("Collecting changes", counter=True) self._lossy = lossy self.builder = self.branch.get_commit_builder(self.parents, self.config_stack, timestamp, timezone, committer, self.revprops, rev_id, lossy=lossy) if not self.builder.supports_record_entry_contents and self.exclude: self.builder.abort() raise errors.ExcludesUnsupported(self.branch.repository) if self.builder.updates_branch and self.bound_branch: self.builder.abort() raise AssertionError( "bound branches not supported for commit builders " "that update the branch") try: self.builder.will_record_deletes() # find the location being committed to if self.bound_branch: master_location = self.master_branch.base else: master_location = self.branch.base # report the start of the commit self.reporter.started(new_revno, self.rev_id, master_location) self._update_builder_with_changes() self._check_pointless() # TODO: Now the new inventory is known, check for conflicts. # ADHB 2006-08-08: If this is done, populate_new_inv should not add # weave lines, because nothing should be recorded until it is known # that commit will succeed. self._set_progress_stage("Saving data locally") self.builder.finish_inventory() # Prompt the user for a commit message if none provided message = message_callback(self) self.message = message # Add revision data to the local branch self.rev_id = self.builder.commit(self.message) except Exception, e: mutter("aborting commit write group because of exception:") trace.log_exception_quietly() self.builder.abort() raise
def _commit(self, operation, message, timestamp, timezone, committer, specific_files, rev_id, allow_pointless, strict, verbose, working_tree, local, reporter, message_callback, recursive, exclude, possible_master_transports, lossy): mutter('preparing to commit') if working_tree is None: raise BzrError("working_tree must be passed into commit().") else: self.work_tree = working_tree self.branch = self.work_tree.branch if getattr(self.work_tree, 'requires_rich_root', lambda: False)(): if not self.branch.repository.supports_rich_root(): raise errors.RootNotRich() if message_callback is None: if message is not None: if isinstance(message, str): message = message.decode(get_user_encoding()) message_callback = lambda x: message else: raise BzrError("The message or message_callback keyword" " parameter is required for commit().") self.bound_branch = None self.any_entries_deleted = False if exclude is not None: self.exclude = sorted(minimum_path_selection(exclude)) else: self.exclude = [] self.local = local self.master_branch = None self.recursive = recursive self.rev_id = None # self.specific_files is None to indicate no filter, or any iterable to # indicate a filter - [] means no files at all, as per iter_changes. if specific_files is not None: self.specific_files = sorted( minimum_path_selection(specific_files)) else: self.specific_files = None self.allow_pointless = allow_pointless self.message_callback = message_callback self.timestamp = timestamp self.timezone = timezone self.committer = committer self.strict = strict self.verbose = verbose self.work_tree.lock_write() operation.add_cleanup(self.work_tree.unlock) self.parents = self.work_tree.get_parent_ids() # We can use record_iter_changes IFF iter_changes is compatible with # the command line parameters, and the repository has fast delta # generation. See bug 347649. self.use_record_iter_changes = ( not self.exclude and not self.branch.repository._format.supports_tree_reference and (self.branch.repository._format.fast_deltas or len(self.parents) < 2)) self.pb = ui.ui_factory.nested_progress_bar() operation.add_cleanup(self.pb.finished) self.basis_revid = self.work_tree.last_revision() self.basis_tree = self.work_tree.basis_tree() self.basis_tree.lock_read() operation.add_cleanup(self.basis_tree.unlock) # Cannot commit with conflicts present. if len(self.work_tree.conflicts()) > 0: raise ConflictsInTree # Setup the bound branch variables as needed. self._check_bound_branch(operation, possible_master_transports) # Check that the working tree is up to date old_revno, old_revid, new_revno = self._check_out_of_date_tree() # Complete configuration setup if reporter is not None: self.reporter = reporter elif self.reporter is None: self.reporter = self._select_reporter() if self.config_stack is None: self.config_stack = self.work_tree.get_config_stack() self._set_specific_file_ids() # Setup the progress bar. As the number of files that need to be # committed in unknown, progress is reported as stages. # We keep track of entries separately though and include that # information in the progress bar during the relevant stages. self.pb_stage_name = "" self.pb_stage_count = 0 self.pb_stage_total = 5 if self.bound_branch: # 2 extra stages: "Uploading data to master branch" and "Merging # tags to master branch" self.pb_stage_total += 2 self.pb.show_pct = False self.pb.show_spinner = False self.pb.show_eta = False self.pb.show_count = True self.pb.show_bar = True self._gather_parents() # After a merge, a selected file commit is not supported. # See 'bzr help merge' for an explanation as to why. if len(self.parents) > 1 and self.specific_files is not None: raise errors.CannotCommitSelectedFileMerge(self.specific_files) # Excludes are a form of selected file commit. if len(self.parents) > 1 and self.exclude: raise errors.CannotCommitSelectedFileMerge(self.exclude) # Collect the changes self._set_progress_stage("Collecting changes", counter=True) self._lossy = lossy self.builder = self.branch.get_commit_builder(self.parents, self.config_stack, timestamp, timezone, committer, self.revprops, rev_id, lossy=lossy) if not self.builder.supports_record_entry_contents and self.exclude: self.builder.abort() raise errors.ExcludesUnsupported(self.branch.repository) if self.builder.updates_branch and self.bound_branch: self.builder.abort() raise AssertionError( "bound branches not supported for commit builders " "that update the branch") try: self.builder.will_record_deletes() # find the location being committed to if self.bound_branch: master_location = self.master_branch.base else: master_location = self.branch.base # report the start of the commit self.reporter.started(new_revno, self.rev_id, master_location) self._update_builder_with_changes() self._check_pointless() # TODO: Now the new inventory is known, check for conflicts. # ADHB 2006-08-08: If this is done, populate_new_inv should not add # weave lines, because nothing should be recorded until it is known # that commit will succeed. self._set_progress_stage("Saving data locally") self.builder.finish_inventory() # Prompt the user for a commit message if none provided message = message_callback(self) self.message = message # Add revision data to the local branch self.rev_id = self.builder.commit(self.message) except Exception, e: mutter("aborting commit write group because of exception:") trace.log_exception_quietly() self.builder.abort() raise