core: make getcwd() fail-safe
[git-cola.git] / cola / widgets / clone.py
blobd265b58e154fdeb9fcae01c14c08ff633d976195
1 from __future__ import division, absolute_import, unicode_literals
2 import os
3 from functools import partial
5 from qtpy import QtCore
6 from qtpy import QtWidgets
7 from qtpy.QtCore import Qt
9 from .. import cmds
10 from .. import core
11 from .. import icons
12 from .. import utils
13 from .. import qtutils
14 from ..i18n import N_
15 from ..interaction import Interaction
16 from ..qtutils import get
17 from . import defs
18 from . import standard
19 from . import text
22 def clone(context, spawn=True, show=True, settings=None, parent=None):
23 """Clone a repository and spawn a new git-cola instance"""
24 parent = qtutils.active_window()
25 progress = standard.ProgressDialog('', '', parent)
26 return clone_repo(context, parent, show, settings,
27 progress, task_finished, spawn)
30 def clone_repo(context, parent, show, settings, progress, finish, spawn):
31 """Clone a repository asynchronously with progress animation"""
32 fn = partial(start_clone_task, context, parent, progress, finish, spawn)
33 prompt = prompt_for_clone(context, show=show, settings=settings)
34 prompt.result.connect(fn)
35 return prompt
38 def prompt_for_clone(context, show=True, settings=None):
39 """Presents a GUI for cloning a repository"""
40 view = Clone(context, settings=settings, parent=qtutils.active_window())
41 if show:
42 view.show()
43 return view
46 def task_finished(task):
47 """Report errors from the clone task if they exist"""
48 cmd = task.cmd
49 if cmd is None:
50 return
51 status = cmd.status
52 out = cmd.out
53 err = cmd.err
54 title = N_('Error: could not clone "%s"') % cmd.url
55 Interaction.command(title, 'git clone', status, out, err)
58 def start_clone_task(context, parent, progress, finish, spawn, url, destdir,
59 submodules, shallow):
60 # Use a thread to update in the background
61 runtask = context.runtask
62 progress.set_details(N_('Clone Repository'),
63 N_('Cloning repository at %s') % url)
64 task = CloneTask(context, url, destdir, submodules, shallow, spawn, parent)
65 runtask.start(task, finish=finish, progress=progress)
68 class CloneTask(qtutils.Task):
69 """Clones a Git repository"""
71 def __init__(self, context, url, destdir, submodules,
72 shallow, spawn, parent):
73 qtutils.Task.__init__(self, parent)
74 self.context = context
75 self.url = url
76 self.destdir = destdir
77 self.submodules = submodules
78 self.shallow = shallow
79 self.spawn = spawn
80 self.cmd = None
82 def task(self):
83 """Runs the model action and captures the result"""
84 self.cmd = cmds.do(
85 cmds.Clone, self.context, self.url, self.destdir,
86 self.submodules, self.shallow, spawn=self.spawn)
87 return self.cmd
90 class Clone(standard.Dialog):
92 # Signal binding for returning the input data
93 result = QtCore.Signal(object, object, bool, bool)
95 def __init__(self, context, settings=None, parent=None):
96 standard.Dialog.__init__(self, parent=parent)
97 self.context = context
98 self.model = context.model
100 self.setWindowTitle(N_('Clone Repository'))
101 if parent is not None:
102 self.setWindowModality(Qt.WindowModal)
104 # Repository location
105 self.url_label = QtWidgets.QLabel(N_('URL'))
106 hint = 'git://git.example.com/repo.git'
107 self.url = text.HintedLineEdit(context, hint, parent=self)
108 self.url.setToolTip(N_('Path or URL to clone (Env. $VARS okay)'))
110 # Initialize submodules
111 self.submodules = qtutils.checkbox(
112 text=N_('Inititalize submodules'), checked=False)
114 # Reduce commit history
115 self.shallow = qtutils.checkbox(
116 text=N_('Reduce commit history to minimum'), checked=False)
118 # Buttons
119 self.ok_button = qtutils.create_button(
120 text=N_('Clone'), icon=icons.ok(), default=True)
121 self.close_button = qtutils.close_button()
123 # Form layout for inputs
124 self.input_layout = qtutils.form(
125 defs.no_margin, defs.button_spacing,
126 (self.url_label, self.url))
128 self.button_layout = qtutils.hbox(
129 defs.margin, defs.spacing,
130 self.submodules, defs.button_spacing,
131 self.shallow, qtutils.STRETCH,
132 self.ok_button, self.close_button)
134 self.main_layout = qtutils.vbox(defs.margin, defs.spacing,
135 self.input_layout, self.button_layout)
136 self.setLayout(self.main_layout)
138 qtutils.connect_button(self.close_button, self.close)
139 qtutils.connect_button(self.ok_button, self.prepare_to_clone)
140 self.url.textChanged.connect(lambda x: self.update_actions())
142 self.init_state(settings, self.resize, 720, 200)
143 self.update_actions()
145 def update_actions(self):
146 url = get(self.url).strip()
147 enabled = bool(url)
148 self.ok_button.setEnabled(enabled)
150 def prepare_to_clone(self):
151 """Grabs and validates the input data"""
153 submodules = get(self.submodules)
154 shallow = get(self.shallow)
156 url = get(self.url)
157 url = utils.expandpath(url)
158 if not url:
159 return
160 # Pick a suitable basename by parsing the URL
161 newurl = url.replace('\\', '/').rstrip('/')
162 try:
163 default = newurl.rsplit('/', 1)[-1]
164 except IndexError:
165 default = ''
166 if default == '.git':
167 # The end of the URL is /.git, so assume it's a file path
168 default = os.path.basename(os.path.dirname(newurl))
169 if default.endswith('.git'):
170 # The URL points to a bare repo
171 default = default[:-4]
172 if url == '.':
173 # The URL is the current repo
174 default = os.path.basename(core.getcwd())
175 if not default:
176 Interaction.information(
177 N_('Error Cloning'),
178 N_('Could not parse Git URL: "%s"') % url)
179 Interaction.log(N_('Could not parse Git URL: "%s"') % url)
180 return
182 # Prompt the user for a directory to use as the parent directory
183 msg = N_('Select a parent directory for the new clone')
184 dirname = qtutils.opendir_dialog(msg, self.model.getcwd())
185 if not dirname:
186 return
187 count = 1
188 destdir = os.path.join(dirname, default)
189 olddestdir = destdir
190 if core.exists(destdir):
191 # An existing path can be specified
192 msg = (
193 N_('"%s" already exists, cola will create a new directory')
194 % destdir)
195 Interaction.information(N_('Directory Exists'), msg)
197 # Make sure the new destdir doesn't exist
198 while core.exists(destdir):
199 destdir = olddestdir + str(count)
200 count += 1
202 # Return the input data and close the dialog
203 self.result.emit(url, destdir, submodules, shallow)
204 self.close()