Skip to content

Commit

Permalink
startup: add a progress bar when cloning
Browse files Browse the repository at this point in the history
Show a progress bar when cloning repositories so that the user knows
that the GUI is busy doing the clone.

Closes #312

Suggested-by: Raghavendra Karunanidhi <raghu.k@openly.co>
Signed-off-by: David Aguilar <davvid@gmail.com>
  • Loading branch information
davvid committed Aug 2, 2014
1 parent be86f1f commit fa980b7
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 19 deletions.
25 changes: 16 additions & 9 deletions cola/cmds.py
Expand Up @@ -807,18 +807,25 @@ def __init__(self, url, new_directory, spawn=True):
self.new_directory = new_directory
self.spawn = spawn

self.ok = False
self.error_message = ''
self.error_details = ''

def do(self):
status, out, err = self.model.git.clone(self.url, self.new_directory)
if status != 0:
Interaction.information(
N_('Error: could not clone "%s"') % self.url,
self.ok = status == 0

if self.ok:
if self.spawn:
core.fork([sys.executable, sys.argv[0],
'--repo', self.new_directory])
else:
self.error_message = N_('Error: could not clone "%s"') % self.url
self.error_details = (
(N_('git clone returned exit code %s') % status) +
((out+err) and ('\n\n' + out + err) or ''))
return False
if self.spawn:
core.fork([sys.executable, sys.argv[0],
'--repo', self.new_directory])
return True
((out+err) and ('\n\n' + out + err) or ''))

return self


class GitXBaseContext(object):
Expand Down
11 changes: 5 additions & 6 deletions cola/guicmds.py
Expand Up @@ -122,11 +122,11 @@ def open_new_repo():
cmds.do(cmds.OpenRepo, dirname)


def clone_repo(spawn=True):
def prompt_for_clone():
"""
Present GUI controls for cloning a repository
Present a GUI for cloning a repository.
A new cola session is invoked when 'spawn' is True.
Returns the target directory and URL
"""
url, ok = qtutils.prompt(N_('Path or URL to clone (Env. $VARS okay)'))
Expand Down Expand Up @@ -175,9 +175,8 @@ def clone_repo(spawn=True):
while core.exists(destdir):
destdir = olddestdir + str(count)
count += 1
if cmds.do(cmds.Clone, url, destdir, spawn=spawn):
return destdir
return None

return url, destdir


def export_patches():
Expand Down
33 changes: 32 additions & 1 deletion cola/widgets/main.py
Expand Up @@ -53,11 +53,14 @@
from cola.widgets.log import LogWidget
from cola.widgets.patch import apply_patches
from cola.widgets.prefs import preferences
from cola.widgets.progress import ProgressDialog
from cola.widgets.recent import browse_recent_files
from cola.widgets.status import StatusWidget
from cola.widgets.search import search
from cola.widgets.standard import MainWindow
from cola.widgets.stash import stash
from cola.widgets.tasks import TaskRunner
from cola.widgets.tasks import CloneTask


class MainView(MainWindow):
Expand All @@ -78,6 +81,11 @@ def __init__(self, model, parent=None, settings=None):
# Keeps track of merge messages we've seen
self.merge_message_hash = ''

# Runs asynchronous tasks
self.task_runner = TaskRunner(self)
self.clone_progress = ProgressDialog(N_('Clone Repository'),
N_('Cloning'), self)

cfg = gitcfg.instance()
self.browser_dockable = (cfg.get('cola.browserdockable') or
cfg.get('cola.classicdockable'))
Expand Down Expand Up @@ -223,7 +231,7 @@ def __init__(self, model, parent=None, settings=None):
N_('Stash...'), stash, 'Alt+Shift+S')

self.clone_repo_action = add_action(self,
N_('Clone...'), guicmds.clone_repo)
N_('Clone...'), self.clone_repo)
self.clone_repo_action.setIcon(qtutils.git_icon())

self.help_docs_action = add_action(self,
Expand Down Expand Up @@ -724,3 +732,26 @@ def rebase_skip(self):

def rebase_abort(self):
cmds.do(cmds.RebaseAbort)

def clone_repo(self):
result = guicmds.prompt_for_clone()
if result is None:
return
self.setEnabled(False)
self.clone_progress.setEnabled(True)
self.clone_progress.show()

# Use a thread to update in the background
url, destdir = result
task = CloneTask(self.task_runner, url, destdir, True)
self.task_runner.start(task, self.clone_task_done)

def clone_task_done(self, task):
"""Continuation function called when the clone is complete"""
self.setEnabled(True)
self.clone_progress.hide()
if task.cmd is None:
return
if not task.cmd.ok:
Interaction.information(task.cmd.error_message,
task.cmd.error_details)
46 changes: 43 additions & 3 deletions cola/widgets/startup.py
Expand Up @@ -7,17 +7,23 @@
"""
from __future__ import division, absolute_import, unicode_literals

from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import Qt
from PyQt4.QtCore import SIGNAL

from cola import cmds
from cola import core
from cola import guicmds
from cola import qtutils
from cola.compat import ustr
from cola.i18n import N_
from cola.interaction import Interaction
from cola.settings import Settings
from cola.widgets import defs
from cola.widgets.progress import ProgressDialog
from cola.widgets.tasks import TaskRunner
from cola.widgets.tasks import CloneTask


class StartupDialog(QtGui.QDialog):
Expand All @@ -28,6 +34,7 @@ def __init__(self, parent=None):
self.setWindowTitle(N_('git-cola'))

self.repodir = None
self.task_runner = TaskRunner(self)

self.new_button = QtGui.QPushButton(N_('New...'))
self.new_button.setIcon(qtutils.new_icon())
Expand All @@ -46,6 +53,13 @@ def __init__(self, parent=None):
self.bookmarks_label = QtGui.QLabel(N_('Select Repository...'))
self.bookmarks_label.setAlignment(Qt.AlignCenter)

# Gather widgets for easy enable/disable swapping
self.widgets = [
self.new_button,
self.open_button,
self.clone_button,
self.close_button,
]

self.bookmarks_model = QtGui.QStandardItemModel()

Expand Down Expand Up @@ -94,6 +108,8 @@ def __init__(self, parent=None):
self.main_layout.addLayout(self.button_layout)
self.setLayout(self.main_layout)

self.progress = ProgressDialog(N_('Clone Repository'),
N_('Cloning'), self)

qtutils.connect_button(self.open_button, self.open_repo)
qtutils.connect_button(self.clone_button, self.clone_repo)
Expand Down Expand Up @@ -129,10 +145,34 @@ def open_repo(self):
self.accept()

def clone_repo(self):
repodir = guicmds.clone_repo(spawn=False)
if repodir:
self.repodir = repodir
result = guicmds.prompt_for_clone()
if result is None:
return

for widget in self.widgets:
widget.setEnabled(False)

# Show a nice progress bar
self.progress.show()

# Use a thread to update in the background
url, destdir = result
task = CloneTask(self.task_runner, url, destdir, False)
self.task_runner.start(task, self.clone_task_done)

def clone_task_done(self, task):
for widget in self.widgets:
widget.setEnabled(True)
self.progress.hide()

if task.cmd is None:
return
if task.cmd.ok:
self.repodir = task.destdir
self.accept()
else:
Interaction.information(task.cmd.error_message,
task.cmd.error_details)

def new_repo(self):
repodir = guicmds.new_repo()
Expand Down
65 changes: 65 additions & 0 deletions cola/widgets/tasks.py
@@ -0,0 +1,65 @@
from PyQt4 import QtCore
from PyQt4.QtCore import SIGNAL

from cola import cmds


class TaskRunner(QtCore.QObject):
"""Runs QRunnable instances and transfers control when they finish"""

def __init__(self, parent):
QtCore.QObject.__init__(self, parent)
self.tasks = []
self.task_callbacks = {}
self.connect(self, Task.FINISHED, self.finish)

def start(self, task, callback):
"""Start the task and register a callback"""
self.tasks.append(task)
if callback is not None:
task_id = id(task)
self.task_callbacks[task_id] = callback
QtCore.QThreadPool.globalInstance().start(task)

def finish(self, task, *args, **kwargs):
task_id = id(task)
try:
self.tasks.remove(task)
except:
pass
try:
callback = self.task_callbacks[task_id]
del self.task_callbacks[task_id]
except KeyError:
return
callback(task, *args, **kwargs)


class Task(QtCore.QRunnable):
"""Base class for concrete tasks"""

FINISHED = SIGNAL('finished')

def __init__(self, sender):
QtCore.QRunnable.__init__(self)
self.sender = sender

def finish(self, *args, **kwargs):
self.sender.emit(self.FINISHED, self, *args, **kwargs)


class CloneTask(Task):
"""Clones a Git repository"""

def __init__(self, sender, url, destdir, spawn):
Task.__init__(self, sender)
self.url = url
self.destdir = destdir
self.spawn = spawn
self.cmd = None

def run(self):
"""Runs the model action and captures the result"""
self.cmd = cmds.do(cmds.Clone, self.url, self.destdir,
spawn=self.spawn)
self.finish()
4 changes: 4 additions & 0 deletions share/doc/git-cola/relnotes.rst
Expand Up @@ -9,6 +9,10 @@ Usability, bells and whistles

https://github.com/git-cola/git-cola/issue/335

* We now show a progress bar when cloning repositories.

https://github.com/git-cola/git-cola/issue/312

git-cola v2.0.5
===============
Usability, bells and whistles
Expand Down
1 change: 1 addition & 0 deletions share/doc/git-cola/thanks.rst
Expand Up @@ -60,6 +60,7 @@ Thanks
* Paolo G. Giarrusso
* Paul Hildebrandt
* Peter Júnoš
* Raghavendra Karunanidhi
* Rustam Safin
* Samsul Ma'arif
* Sergey Leschina
Expand Down

0 comments on commit fa980b7

Please sign in to comment.