from __future__ import absolute_import # # This is an extension to the Nautilus file manager to allow better # integration with the Subversion source control system. # # Copyright (C) 2006-2008 by Jason Field # Copyright (C) 2007-2008 by Bruce van der Kooij # Copyright (C) 2008-2010 by Adam Plumb # # RabbitVCS is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # RabbitVCS is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with RabbitVCS; If not, see . # import os import six.moves._thread from time import sleep from rabbitvcs.util import helper from gi import require_version require_version("Gtk", "3.0") sa = helper.SanitizeArgv() from gi.repository import Gtk, GObject, Gdk, GLib sa.restore() from rabbitvcs.ui import InterfaceView from rabbitvcs.util.contextmenu import GtkFilesContextMenu, GtkContextMenuCaller import rabbitvcs.ui.action import rabbitvcs.ui.widget import rabbitvcs.ui.dialog import rabbitvcs.util from rabbitvcs.util.strings import S from rabbitvcs.util.log import Log from rabbitvcs.util.decorators import gtk_unsafe import rabbitvcs.vcs.status log = Log("rabbitvcs.ui.commit") from rabbitvcs import gettext _ = gettext.gettext helper.gobject_threads_init() class Commit(InterfaceView, GtkContextMenuCaller): """ Provides a user interface for the user to commit working copy changes to a repository. Pass it a list of local paths to commit. """ SETTINGS = rabbitvcs.util.settings.SettingsManager() TOGGLE_ALL = False SHOW_UNVERSIONED = SETTINGS.get("general", "show_unversioned_files") # This keeps track of any changes that the user has made to the row # selections changes = {} def __init__(self, paths, base_dir=None, message=None): """ @type paths: list of strings @param paths: A list of local paths. """ InterfaceView.__init__(self, "commit", "Commit") self.base_dir = base_dir self.vcs = rabbitvcs.vcs.VCS() self.items = [] self.files_table = rabbitvcs.ui.widget.Table( self.get_widget("files_table"), [GObject.TYPE_BOOLEAN, rabbitvcs.ui.widget.TYPE_HIDDEN_OBJECT, rabbitvcs.ui.widget.TYPE_PATH, GObject.TYPE_STRING, rabbitvcs.ui.widget.TYPE_STATUS, GObject.TYPE_STRING], [rabbitvcs.ui.widget.TOGGLE_BUTTON, "", _("Path"), _("Extension"), _("Text Status"), _("Property Status")], filters=[{ "callback": rabbitvcs.ui.widget.path_filter, "user_data": { "base_dir": base_dir, "column": 2 } }], callbacks={ "row-activated": self.on_files_table_row_activated, "mouse-event": self.on_files_table_mouse_event, "key-event": self.on_files_table_key_event, "row-toggled": self.on_files_table_toggle_event }, flags={ "sortable": True, "sort_on": 2 } ) self.files_table.allow_multiple() self.get_widget("toggle_show_unversioned").set_active(self.SHOW_UNVERSIONED) if not message: message = self.SETTINGS.get_multiline("general", "default_commit_message") self.message = rabbitvcs.ui.widget.TextView( self.get_widget("message"), message ) self.paths = [] for path in paths: if self.vcs.is_in_a_or_a_working_copy(path): self.paths.append(S(path)) # # Helper functions # def load(self): """ - Gets a listing of file items that are valid for the commit window. - Determines which items should be "activated" by default - Populates the files table with the retrieved items - Updates the status area """ self.get_widget("status").set_text(_("Loading...")) self.items = self.vcs.get_items(self.paths, self.vcs.statuses_for_commit(self.paths)) self.populate_files_table() # Overrides the GtkContextMenuCaller method def on_context_menu_command_finished(self): self.initialize_items() def should_item_be_activated(self, item): """ Determines if a file should be activated or not """ if (S(item.path) in self.paths or item.is_versioned() and item.simple_content_status() != rabbitvcs.vcs.status.status_missing): return True return False def should_item_be_visible(self, item): show_unversioned = self.SHOW_UNVERSIONED if not show_unversioned: if not item.is_versioned(): return False return True def initialize_items(self): """ Initializes the activated cache and loads the file items in a new thread """ GLib.idle_add(self.load) def show_files_table_popup_menu(self, treeview, data): paths = self.files_table.get_selected_row_items(1) GtkFilesContextMenu(self, data, self.base_dir, paths).show() def delete_items(self, widget, event): paths = self.files_table.get_selected_row_items(1) if len(paths) > 0: proc = helper.launch_ui_window("delete", paths) self.rescan_after_process_exit(proc, paths) # # Event handlers # def on_refresh_clicked(self, widget): self.initialize_items() def on_key_pressed(self, widget, event, *args): if InterfaceView.on_key_pressed(self, widget, event, *args): return True if (event.state & Gdk.ModifierType.CONTROL_MASK and Gdk.keyval_name(event.keyval) == "Return"): self.on_ok_clicked(widget) return True def on_toggle_show_all_toggled(self, widget, data=None): self.TOGGLE_ALL = not self.TOGGLE_ALL self.changes.clear() for row in self.files_table.get_items(): row[0] = self.TOGGLE_ALL self.changes[row[1]] = self.TOGGLE_ALL def on_toggle_show_unversioned_toggled(self, widget, *args): self.SHOW_UNVERSIONED = widget.get_active() self.populate_files_table() # Save this preference for future commits. if self.SETTINGS.get("general", "show_unversioned_files") != self.SHOW_UNVERSIONED: self.SETTINGS.set( "general", "show_unversioned_files", self.SHOW_UNVERSIONED ) self.SETTINGS.write() def on_files_table_row_activated(self, treeview, event, col): paths = self.files_table.get_selected_row_items(1) pathrev1 = helper.create_path_revision_string(paths[0], "base") pathrev2 = helper.create_path_revision_string(paths[0], "working") proc = helper.launch_ui_window("diff", ["-s", pathrev1, pathrev2]) self.rescan_after_process_exit(proc, paths) def on_files_table_key_event(self, treeview, event, *args): if Gdk.keyval_name(event.keyval) == "Delete": self.delete_items(treeview, event) def on_files_table_mouse_event(self, treeview, event, *args): if event.button == 3 and event.type == Gdk.EventType.BUTTON_RELEASE: self.show_files_table_popup_menu(treeview, event) def on_previous_messages_clicked(self, widget, data=None): dialog = rabbitvcs.ui.dialog.PreviousMessages() message = dialog.run() if message is not None: self.message.set_text(S(message).display()) def populate_files_table(self): """ First clears and then populates the files table based on the items retrieved in self.load() """ self.files_table.clear() n = 0 m = 0 for item in self.items: if item.path in self.changes: checked = self.changes[item.path] else: checked = self.should_item_be_activated(item) if item.is_versioned(): n += 1 else: m += 1 if not self.should_item_be_visible(item): continue self.files_table.append([ checked, S(item.path), item.path, helper.get_file_extension(item.path), item.simple_content_status(), item.simple_metadata_status() ]) self.get_widget("status").set_text(_("Found %(changed)d changed and %(unversioned)d unversioned item(s)") % { "changed": n, "unversioned": m } ) class SVNCommit(Commit): def __init__(self, paths, base_dir=None, message=None): Commit.__init__(self, paths, base_dir, message) self.get_widget("commit_to_box").show() self.get_widget("to").set_text( S(self.vcs.svn().get_repo_url(self.base_dir)).display() ) self.items = None if len(self.paths): self.initialize_items() def on_ok_clicked(self, widget, data=None): items = self.files_table.get_activated_rows(1) self.hide() if len(items) == 0: self.close() return added = 0 recurse = False for item in items: status = self.vcs.status(item, summarize=False).simple_content_status() try: if status == rabbitvcs.vcs.status.status_unversioned: self.vcs.svn().add(item) added += 1 elif status == rabbitvcs.vcs.status.status_deleted: recurse = True elif status == rabbitvcs.vcs.status.status_missing: self.vcs.svn().update(item) self.vcs.svn().remove(item) except Exception as e: log.exception(e) ticks = added + len(items)*2 self.action = rabbitvcs.ui.action.SVNAction( self.vcs.svn(), register_gtk_quit=self.gtk_quit_is_set() ) self.action.set_pbar_ticks(ticks) self.action.append(self.action.set_header, _("Commit")) self.action.append(self.action.set_status, _("Running Commit Command...")) self.action.append( helper.save_log_message, self.message.get_text() ), self.action.append(self.do_commit, items, recurse) self.action.append(self.action.finish) self.action.schedule() def do_commit(self, items, recurse): # pysvn.Revision revision = self.vcs.svn().commit(items, self.message.get_text(), recurse=recurse) self.action.set_status(_("Completed Commit") + " at Revision: " + str(revision.number)) def on_files_table_toggle_event(self, row, col): # Adds path: True/False to the dict self.changes[row[1]] = row[col] class GitCommit(Commit): def __init__(self, paths, base_dir=None, message=None): Commit.__init__(self, paths, base_dir, message) self.git = self.vcs.git(paths[0]) self.get_widget("commit_to_box").show() active_branch = self.git.get_active_branch() if active_branch: self.get_widget("to").set_text( S(active_branch.name).display() ) else: self.get_widget("to").set_text("No active branch") self.items = None if len(self.paths): self.initialize_items() def on_ok_clicked(self, widget, data=None): items = self.files_table.get_activated_rows(1) self.hide() if len(items) == 0: self.close() return staged = 0 for item in items: try: #status = self.vcs.status(item, summarize=False).simple_content_status() #if status == rabbitvcs.vcs.status.status_missing: # self.git.checkout([item]) # self.git.remove(item) #else: # self.git.stage(item) # staged += 1 self.git.stage(item) staged += 1 except Exception as e: log.exception(e) ticks = staged + len(items)*2 self.action = rabbitvcs.ui.action.GitAction( self.git, register_gtk_quit=self.gtk_quit_is_set() ) self.action.set_pbar_ticks(ticks) self.action.append(self.action.set_header, _("Commit")) self.action.append(self.action.set_status, _("Running Commit Command...")) self.action.append( helper.save_log_message, self.message.get_text() ) self.action.append( self.git.commit, self.message.get_text() ) self.action.append(self.action.set_status, _("Completed Commit")) self.action.append(self.action.finish) self.action.schedule() def on_files_table_toggle_event(self, row, col): # Adds path: True/False to the dict self.changes[row[1]] = row[col] classes_map = { rabbitvcs.vcs.VCS_SVN: SVNCommit, rabbitvcs.vcs.VCS_GIT: GitCommit } def commit_factory(paths, base_dir=None, message=None): guess = rabbitvcs.vcs.guess(paths[0]) return classes_map[guess["vcs"]](paths, base_dir, message) if __name__ == "__main__": from rabbitvcs.ui import main, BASEDIR_OPT (options, paths) = main( [BASEDIR_OPT, (["-m", "--message"], {"help":"add a commit log message"})], usage="Usage: rabbitvcs commit [path1] [path2] ..." ) window = commit_factory(paths, options.base_dir, message=options.message) window.register_gtk_quit() Gtk.main()