class SVNCleanup(InterfaceNonView): """ This class provides a handler to the Cleanup window view. The idea is that it displays a large folder icon with a label like "Please Wait...". Then when it finishes cleaning up, the label will change to "Finished cleaning up /path/to/folder" """ def __init__(self, path): InterfaceNonView.__init__(self) self.path = path self.vcs = rabbitvcs.vcs.VCS() self.svn = self.vcs.svn() def start(self): self.action = SVNAction( self.svn, register_gtk_quit=self.gtk_quit_is_set() ) self.action.append(self.action.set_header, _("Cleanup")) self.action.append(self.action.set_status, _("Cleaning Up...")) self.action.append(self.svn.cleanup, self.path) self.action.append(self.action.set_status, _("Completed Cleanup")) self.action.append(self.action.finish) self.action.schedule()
class SVNUpdate(InterfaceNonView): """ This class provides an interface to generate an "update". Pass it a path and it will start an update, running the notification dialog. There is no glade . """ def __init__(self, paths): self.paths = paths self.vcs = rabbitvcs.vcs.VCS() self.svn = self.vcs.svn() def start(self): self.action = SVNAction( self.svn, register_gtk_quit=self.gtk_quit_is_set(), run_in_thread=False ) self.action.append(self.action.set_header, _("Update")) self.action.append(self.action.set_status, _("Updating...")) self.action.append(self.svn.update, self.paths) self.action.append(self.action.set_status, _("Completed Update")) self.action.append(self.action.finish) self.action.schedule()
class SVNRevisionProperties(PropertiesBase): def __init__(self, path, revision=None): PropertiesBase.__init__(self, path) self.svn = self.vcs.svn() if not self.svn.is_path_repository_url(path): self.path = self.svn.get_repo_url(path) self.get_widget("path").set_text(S(self.path).display()) self.revision = revision self.revision_obj = None if revision is not None: self.revision_obj = self.svn.revision("number", revision) self.load() def load(self): self.table.clear() try: self.proplist = self.svn.revproplist( self.get_widget("path").get_text(), self.revision_obj) except Exception as e: log.exception(e) rabbitvcs.ui.dialog.MessageBox( _("Unable to retrieve properties list")) self.proplist = {} if self.proplist: for key, val in list(self.proplist.items()): self.table.append([False, key, val.rstrip()]) def save(self): delete_recurse = self.get_widget("delete_recurse").get_active() self.action = SVNAction(self.svn, notification=False, run_in_thread=False) for row in self.delete_stack: self.action.append(self.svn.revpropdel, self.path, row[1], self.revision_obj, force=True) for row in self.table.get_items(): self.action.append(self.svn.revpropset, row[1], row[2], self.path, self.revision_obj, force=True) self.action.schedule() self.close()
class Relocate(InterfaceView): """ Interface to relocate your working copy's repository location. """ def __init__(self, path): """ @type path: string @param path: A path to a local working copy """ InterfaceView.__init__(self, "relocate", "Relocate") self.path = path self.vcs = rabbitvcs.vcs.VCS() self.svn = self.vcs.svn() repo = self.svn.get_repo_url(self.path) self.get_widget("from_url").set_text(repo) self.get_widget("to_url").set_text(repo) self.repositories = rabbitvcs.ui.widget.ComboBox( self.get_widget("to_urls"), rabbitvcs.util.helper.get_repository_paths() ) def on_ok_clicked(self, widget): from_url = self.get_widget("from_url").get_text() to_url = self.get_widget("to_url").get_text() if not from_url or not to_url: MessageBox(_("The from and to url fields are both required.")) return self.hide() self.action = SVNAction( self.svn, register_gtk_quit=self.gtk_quit_is_set() ) self.action.append(self.action.set_header, _("Relocate")) self.action.append(self.action.set_status, _("Running Relocate Command...")) self.action.append( self.svn.relocate, from_url, to_url, self.path ) self.action.append(self.action.set_status, _("Completed Relocate")) self.action.append(self.action.finish) self.action.schedule()
class Relocate(InterfaceView): """ Interface to relocate your working copy's repository location. """ def __init__(self, path): """ @type path: string @param path: A path to a local working copy """ InterfaceView.__init__(self, "relocate", "Relocate") self.path = path self.vcs = rabbitvcs.vcs.VCS() self.svn = self.vcs.svn() repo = self.svn.get_repo_url(self.path) self.get_widget("from_url").set_text(repo) self.get_widget("to_url").set_text(repo) self.repositories = rabbitvcs.ui.widget.ComboBox( self.get_widget("to_urls"), rabbitvcs.util.helper.get_repository_paths()) def on_ok_clicked(self, widget): from_url = self.get_widget("from_url").get_text() to_url = self.get_widget("to_url").get_text() if not from_url or not to_url: MessageBox(_("The from and to url fields are both required.")) return self.hide() self.action = SVNAction(self.svn, register_gtk_quit=self.gtk_quit_is_set()) self.action.append(self.action.set_header, _("Relocate")) self.action.append(self.action.set_status, _("Running Relocate Command...")) self.action.append(self.svn.relocate, from_url, to_url, self.path) self.action.append(self.action.set_status, _("Completed Relocate")) self.action.append(self.action.finish) self.action.schedule()
class SVNUpdate(InterfaceNonView): """ This class provides an interface to generate an "update". Pass it a path and it will start an update, running the notification dialog. There is no glade . """ def __init__(self, paths): self.paths = paths self.vcs = rabbitvcs.vcs.VCS() self.svn = self.vcs.svn() def start(self): self.action = SVNAction(self.svn, register_gtk_quit=self.gtk_quit_is_set()) self.action.append(self.action.set_header, _("Update")) self.action.append(self.action.set_status, _("Updating...")) self.action.append(self.svn.update, self.paths) self.action.append(self.action.set_status, _("Completed Update")) self.action.append(self.action.finish) self.action.schedule()
class SVNCleanup(InterfaceNonView): """ This class provides a handler to the Cleanup window view. The idea is that it displays a large folder icon with a label like "Please Wait...". Then when it finishes cleaning up, the label will change to "Finished cleaning up /path/to/folder" """ def __init__(self, path): InterfaceNonView.__init__(self) self.path = path self.vcs = rabbitvcs.vcs.VCS() self.svn = self.vcs.svn() def start(self): self.action = SVNAction(self.svn, register_gtk_quit=self.gtk_quit_is_set()) self.action.append(self.action.set_header, _("Cleanup")) self.action.append(self.action.set_status, _("Cleaning Up...")) self.action.append(self.svn.cleanup, self.path) self.action.append(self.action.set_status, _("Completed Cleanup")) self.action.append(self.action.finish) self.action.schedule()
class SVNRevisionProperties(PropertiesBase): def __init__(self, path, revision=None): PropertiesBase.__init__(self, path) self.svn = self.vcs.svn() if not self.svn.is_path_repository_url(path): self.path = self.svn.get_repo_url(path) self.get_widget("path").set_text(self.path) self.revision = revision self.revision_obj = None if revision is not None: self.revision_obj = self.svn.revision("number", revision) self.load() def load(self): self.table.clear() try: self.proplist = self.svn.revproplist( self.get_widget("path").get_text(), self.revision_obj ) except Exception as e: log.exception(e) rabbitvcs.ui.dialog.MessageBox(_("Unable to retrieve properties list")) self.proplist = {} if self.proplist: for key,val in list(self.proplist.items()): self.table.append([False, key,val.rstrip()]) def save(self): delete_recurse = self.get_widget("delete_recurse").get_active() self.action = SVNAction( self.svn, notification=False, run_in_thread=False ) for row in self.delete_stack: self.action.append( self.svn.revpropdel, self.path, row[1], self.revision_obj, force=True ) for row in self.table.get_items(): self.action.append( self.svn.revpropset, row[1], row[2], self.path, self.revision_obj, force=True ) self.action.schedule() self.close()
class SVNAnnotate(Annotate): def __init__(self, path, revision=None): Annotate.__init__(self, path, revision) self.svn = self.vcs.svn() self.path = path self.show_revision(forceload=True) # # Helper methods # def load(self, revision): revision = revision.lower() rev = self.svn.revision("HEAD") if revision.isdigit(): rev = self.svn.revision("number", number=int(revision)) self.launch_loading() self.action = SVNAction(self.svn, notification=False) self.action.append(self.svn.annotate, self.path, to_revision=rev) if not self.log_by_order: self.action.append(self.svn.log, self.path) self.action.append(self.set_log) self.action.append(self.populate_table) self.action.append(self.enable_saveas) self.action.schedule() self.kill_loading() def blame_info(self, item): revision = item["revision"].number if revision <= 0: return ("", "", "") revision = str(revision) # remove fractional seconds and timezone information from # the end of the string provided by pysvn: # * timezone should be always "Z" (for UTC), "%Z" is not yet # yet supported by strptime # * fractional could be parsed with "%f" since python 2.6 # but this precision is not needed anyway # * the datetime module does not include strptime until python 2.4 # so this workaround is required for now datestr = item["date"][0:-8] try: date = datetime(*time.strptime(datestr, "%Y-%m-%dT%H:%M:%S")[:-2]) date = helper.format_datetime(date, self.datetime_format) except: date = "" return revision, date, S(item["author"].strip()) def populate_table(self): blamedict = self.action.get_result(0) lines = highlight(self.path, [item["line"] for item in blamedict]) self.table.clear() for i, item in enumerate(blamedict): revision, date, author = self.blame_info(item) author_color = self.author_background.get(author, "#FFFFFF") try: revision_color = self.log_by_revision[revision].background except KeyError: revision_color = "#FFFFFF" self.table.append([ revision, author, date, str(int(item["number"]) + 1), lines[i], revision_color, author_color ]) def generate_string_from_result(self): blamedict = self.action.get_result(0) text = "" for item in blamedict: revision, date, author = self.blame_info(item) text += "%s\t%s\t%s\t%s\t%s\n" % (str(int(item["number"]) + 1), revision, author, date, item["line"]) return text def short_revision(self, revision): revision = str(revision).lower() return revision if revision != "head" else "HEAD"
def merge(self, test=False): if self.type is None: return if test: startcmd = _("Running Merge Test") endcmd = _("Completed Merge Test") else: startcmd = _("Running Merge Command") endcmd = _("Completed Merge") self.hide() recursive = self.get_widget("mergeoptions_recursive").get_active() ignore_ancestry = self.get_widget( "mergeoptions_ignore_ancestry").get_active() record_only = False if self.svn.has_merge2(): record_only = self.get_widget( "mergeoptions_only_record").get_active() action = SVNAction(self.svn, register_gtk_quit=(not test)) action.append(action.set_header, _("Merge")) action.append(action.set_status, startcmd) args = () kwargs = {} if self.type == "range": url = self.mergerange_repos.get_active_text() head_revision = self.svn.get_head(self.path) revisions = self.get_widget("mergerange_revisions").get_text() if revisions == "": revisions = "head" revisions = revisions.lower().replace("head", str(head_revision)) ranges = [] for r in revisions.split(","): if r.find("-") != -1: (low, high) = [int(i) for i in r.split("-")] if low < high: low -= 1 elif r.find(":") != -1: (low, high) = [int(i) for i in r.split(":")] if low < high: low -= 1 else: high = int(r) low = high - 1 # Before pysvn v1.6.3, there was a bug that required the ranges # tuple to have three elements, even though only two were used # Fixed in Pysvn Revision 1114 if (self.svn.interface == "pysvn" and self.svn.is_version_less_than((1, 6, 3, 0))): ranges.append( (self.svn.revision("number", number=low).primitive(), self.svn.revision("number", number=high).primitive(), None)) else: ranges.append(( self.svn.revision("number", number=low).primitive(), self.svn.revision("number", number=high).primitive(), )) action.append(helper.save_repository_path, url) # Build up args and kwargs because some args are not supported # with older versions of pysvn/svn args = (self.svn.merge_ranges, url, ranges, self.svn.revision("head"), self.path) kwargs = { "notice_ancestry": (not ignore_ancestry), "dry_run": test } if record_only: kwargs["record_only"] = record_only elif self.type == "reintegrate": url = self.merge_reintegrate_repos.get_active_text() revision = self.merge_reintegrate_revision.get_revision_object() action.append(helper.save_repository_path, url) # Build up args and kwargs because some args are not supported # with older versions of pysvn/svn args = (self.svn.merge_reintegrate, url, revision, self.path) kwargs = {"dry_run": test} elif self.type == "tree": from_url = self.mergetree_from_repos.get_active_text() from_revision = self.svn.revision("head") if self.get_widget( "mergetree_from_revision_number_opt").get_active(): from_revision = self.svn.revision( "number", number=int( self.get_widget( "mergetree_from_revision_number").get_text())) to_url = self.mergetree_to_repos.get_active_text() to_revision = self.svn.revision("head") if self.get_widget( "mergetree_to_revision_number_opt").get_active(): to_revision = self.svn.revision( "number", number=int( self.get_widget( "mergetree_to_revision_number").get_text())) action.append(helper.save_repository_path, from_url) action.append(helper.save_repository_path, to_url) # Build up args and kwargs because some args are not supported # with older versions of pysvn/svn args = (self.svn.merge_trees, from_url, from_revision, to_url, to_revision, self.path) kwargs = {"recurse": recursive, "dry_run": test} if len(args) > 0: action.append(*args, **kwargs) action.append(action.set_status, endcmd) action.append(action.finish) action.schedule()
class SVNAnnotate(Annotate): def __init__(self, path, revision=None): Annotate.__init__(self, path, revision) self.svn = self.vcs.svn() if revision is None: revision = "HEAD" self.path = path self.get_widget("from").set_text(str(1)) self.get_widget("to").set_text(str(revision)) self.table = rabbitvcs.ui.widget.Table(self.get_widget("table"), [ GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING ], [_("Line"), _("Revision"), _("Author"), _("Date"), _("Text")]) self.table.allow_multiple() self.loading_dialog = None self.load() # # Helper methods # def load(self): from_rev_num = self.get_widget("from").get_text().lower() to_rev_num = self.get_widget("to").get_text().lower() if not from_rev_num.isdigit(): MessageBox(_("The from revision field must be an integer")) return from_rev = self.svn.revision("number", number=int(from_rev_num)) to_rev = self.svn.revision("head") if to_rev_num.isdigit(): to_rev = self.svn.revision("number", number=int(to_rev_num)) self.launch_loading() self.action = SVNAction(self.svn, notification=False) self.action.append(self.svn.annotate, self.path, from_rev, to_rev) self.action.append(self.populate_table) self.action.append(self.enable_saveas) self.action.schedule() self.kill_loading() @gtk_unsafe def populate_table(self): blamedict = self.action.get_result(0) self.table.clear() for item in blamedict: # remove fractional seconds and timezone information from # the end of the string provided by pysvn: # * timezone should be always "Z" (for UTC), "%Z" is not yet # yet supported by strptime # * fractional could be parsed with "%f" since python 2.6 # but this precision is not needed anyway # * the datetime module does not include strptime until python 2.4 # so this workaround is required for now datestr = item["date"][0:-8] date = datetime(*time.strptime(datestr, "%Y-%m-%dT%H:%M:%S")[:-2]) self.table.append([ str(int(item["number"]) + 1), str(item["revision"].number), item["author"], rabbitvcs.util.helper.format_datetime(date), item["line"] ]) def generate_string_from_result(self): blamedict = self.action.get_result(0) text = "" for item in blamedict: datestr = item["date"][0:-8] date = datetime(*time.strptime(datestr, "%Y-%m-%dT%H:%M:%S")[:-2]) text += "%s\t%s\t%s\t%s\t%s\n" % ( str(int(item["number"]) + 1), str( item["revision"].number), item["author"], rabbitvcs.util.helper.format_datetime(date), item["line"]) return text
class SVNExport(SVNCheckout): def __init__(self, path=None, revision=None): SVNCheckout.__init__(self, path, url=None, revision=revision) self.svn = self.vcs.svn() self.get_widget("Checkout").set_title(_("Export - %s") % path) # Determine behavior based on the given path if self.svn.is_in_a_or_a_working_copy(path): # If path is from a working copy, export FROM path and set revision # to working copy self.repositories.set_child_text(path) self.get_widget("destination").set_text("") if revision is None: self.revision_selector.set_kind_working() elif self.svn.is_path_repository_url(path): # If path is a repository, export FROM path self.repositories.set_child_text(path) self.get_widget("destination").set_text("") else: # Path is not a working copy so the user probably wants to export # TO this path self.repositories.set_child_text("") self.get_widget("destination").set_text(path) def on_ok_clicked(self, widget): url = self.repositories.get_active_text() path = self._get_path() omit_externals = self.get_widget("omit_externals").get_active() recursive = self.get_widget("recursive").get_active() if not url or not path: MessageBox(_("The repository URL and destination path are both required fields.")) return if url.startswith("file://"): url = self._parse_path(url) # Cannot do: # url = os.path.normpath(url) # ...in general, since it might be eg. an http URL. Doesn't seem to # affect pySvn though. path = os.path.normpath(path) revision = self.revision_selector.get_revision_object() self.hide() self.action = SVNAction( self.svn, register_gtk_quit=self.gtk_quit_is_set() ) self.action.append(self.action.set_header, _("Export")) self.action.append(self.action.set_status, _("Running Export Command...")) self.action.append(rabbitvcs.util.helper.save_repository_path, url) self.action.append( self.svn.export, url, path, force=True, recurse=recursive, revision=revision, ignore_externals=omit_externals ) self.action.append(self.action.set_status, _("Completed Export")) self.action.append(self.action.finish) self.action.schedule()
def merge(self, test=False): if self.type is None: return if test: startcmd = _("Running Merge Test") endcmd = _("Completed Merge Test") else: startcmd = _("Running Merge Command") endcmd = _("Completed Merge") self.hide() recursive = self.get_widget("mergeoptions_recursive").get_active() ignore_ancestry = self.get_widget("mergeoptions_ignore_ancestry").get_active() record_only = False if self.svn.has_merge2(): record_only = self.get_widget("mergeoptions_only_record").get_active() action = SVNAction(self.svn, register_gtk_quit=(not test)) action.append(action.set_header, _("Merge")) action.append(action.set_status, startcmd) args = () kwargs = {} if self.type == "range": url = self.mergerange_repos.get_active_text() head_revision = self.svn.get_head(self.path) revisions = self.get_widget("mergerange_revisions").get_text() if revisions == "": revisions = "head" revisions = revisions.lower().replace("head", str(head_revision)) ranges = [] for r in revisions.split(","): if r.find("-") != -1: (low, high) = [ int(i) for i in r.split("-") ] if low < high: low -= 1 elif r.find(":") != -1: (low, high) = [ int(i) for i in r.split(":") ] if low < high: low -= 1 else: high = int(r) low = high - 1 # Before pysvn v1.6.3, there was a bug that required the ranges # tuple to have three elements, even though only two were used # Fixed in Pysvn Revision 1114 if (self.svn.interface == "pysvn" and self.svn.is_version_less_than((1,6,3,0))): ranges.append(( self.svn.revision("number", number=low).primitive(), self.svn.revision("number", number=high).primitive(), None )) else: ranges.append(( self.svn.revision("number", number=low).primitive(), self.svn.revision("number", number=high).primitive(), )) action.append(helper.save_repository_path, url) # Build up args and kwargs because some args are not supported # with older versions of pysvn/svn args = ( self.svn.merge_ranges, url, ranges, self.svn.revision("head"), self.path ) kwargs = { "notice_ancestry": (not ignore_ancestry), "dry_run": test } if record_only: kwargs["record_only"] = record_only elif self.type == "reintegrate": url = self.merge_reintegrate_repos.get_active_text() revision = self.merge_reintegrate_revision.get_revision_object() action.append(helper.save_repository_path, url) # Build up args and kwargs because some args are not supported # with older versions of pysvn/svn args = ( self.svn.merge_reintegrate, url, revision, self.path ) kwargs = { "dry_run": test } elif self.type == "tree": from_url = self.mergetree_from_repos.get_active_text() from_revision = self.svn.revision("head") if self.get_widget("mergetree_from_revision_number_opt").get_active(): from_revision = self.svn.revision( "number", number=int(self.get_widget("mergetree_from_revision_number").get_text()) ) to_url = self.mergetree_to_repos.get_active_text() to_revision = self.svn.revision("head") if self.get_widget("mergetree_to_revision_number_opt").get_active(): to_revision = self.svn.revision( "number", number=int(self.get_widget("mergetree_to_revision_number").get_text()) ) action.append(helper.save_repository_path, from_url) action.append(helper.save_repository_path, to_url) # Build up args and kwargs because some args are not supported # with older versions of pysvn/svn args = ( self.svn.merge_trees, from_url, from_revision, to_url, to_revision, self.path ) kwargs = { "recurse": recursive, "dry_run": test } if len(args) > 0: action.append(*args, **kwargs) action.append(action.set_status, endcmd) action.append(action.finish) action.schedule()
class SVNExport(SVNCheckout): def __init__(self, path=None, revision=None): SVNCheckout.__init__(self, path, url=None, revision=revision) self.svn = self.vcs.svn() self.get_widget("Checkout").set_title(_("Export - %s") % path) # Determine behavior based on the given path if self.svn.is_in_a_or_a_working_copy(path): # If path is from a working copy, export FROM path and set revision # to working copy self.repositories.set_child_text(path) self.get_widget("destination").set_text("") if revision is None: self.revision_selector.set_kind_working() elif self.svn.is_path_repository_url(path): # If path is a repository, export FROM path self.repositories.set_child_text(path) self.get_widget("destination").set_text("") else: # Path is not a working copy so the user probably wants to export # TO this path self.repositories.set_child_text("") self.get_widget("destination").set_text(S(path).display()) def on_ok_clicked(self, widget): url = self.repositories.get_active_text() path = self._get_path() omit_externals = self.get_widget("omit_externals").get_active() recursive = self.get_widget("recursive").get_active() if not url or not path: MessageBox( _("The repository URL and destination path are both required fields." )) return if url.startswith("file://"): url = self._parse_path(url) # Cannot do: # url = os.path.normpath(url) # ...in general, since it might be eg. an http URL. Doesn't seem to # affect pySvn though. path = os.path.normpath(path) revision = self.revision_selector.get_revision_object() self.hide() self.action = SVNAction(self.svn, register_gtk_quit=self.gtk_quit_is_set()) self.action.append(self.action.set_header, _("Export")) self.action.append(self.action.set_status, _("Running Export Command...")) self.action.append(helper.save_repository_path, url) self.action.append(self.svn.export, url, path, force=True, recurse=recursive, revision=revision, ignore_externals=omit_externals) self.action.append(self.action.set_status, _("Completed Export")) self.action.append(self.action.finish) self.action.schedule()
class SVNAnnotate(Annotate): def __init__(self, path, revision=None): Annotate.__init__(self, path, revision) self.svn = self.vcs.svn() if revision is None: revision = "HEAD" self.path = path self.get_widget("from").set_text(str(1)) self.get_widget("to").set_text(str(revision)) self.table = rabbitvcs.ui.widget.Table( self.get_widget("table"), [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING], [_("Line"), _("Revision"), _("Author"), _("Date"), _("Text")] ) self.table.allow_multiple() self.loading_dialog = None self.load() # # Helper methods # def load(self): from_rev_num = self.get_widget("from").get_text().lower() to_rev_num = self.get_widget("to").get_text().lower() if not from_rev_num.isdigit(): MessageBox(_("The from revision field must be an integer")) return from_rev = self.svn.revision("number", number=int(from_rev_num)) to_rev = self.svn.revision("head") if to_rev_num.isdigit(): to_rev = self.svn.revision("number", number=int(to_rev_num)) self.launch_loading() self.action = SVNAction( self.svn, notification=False ) self.action.append( self.svn.annotate, self.path, from_rev, to_rev ) self.action.append(self.populate_table) self.action.append(self.enable_saveas) self.action.schedule() self.kill_loading() @gtk_unsafe def populate_table(self): blamedict = self.action.get_result(0) self.table.clear() for item in blamedict: # remove fractional seconds and timezone information from # the end of the string provided by pysvn: # * timezone should be always "Z" (for UTC), "%Z" is not yet # yet supported by strptime # * fractional could be parsed with "%f" since python 2.6 # but this precision is not needed anyway # * the datetime module does not include strptime until python 2.4 # so this workaround is required for now datestr = item["date"][0:-8] date = datetime(*time.strptime(datestr,"%Y-%m-%dT%H:%M:%S")[:-2]) self.table.append([ str(item["number"]), str(item["revision"].number), item["author"], rabbitvcs.util.helper.format_datetime(date), item["line"] ]) def generate_string_from_result(self): blamedict = self.action.get_result(0) text = "" for item in blamedict: datestr = item["date"][0:-8] date = datetime(*time.strptime(datestr,"%Y-%m-%dT%H:%M:%S")[:-2]) text += "%s\t%s\t%s\t%s\t%s\n" % ( item["number"], item["revision"].number, item["author"], rabbitvcs.util.helper.format_datetime(date), item["line"] ) return text