Merge branch 'merge-no-commit'
[git-cola.git] / cola / guicmds.py
blobaca02a5543c1553b606d910ddbab7cbe49254107
1 from __future__ import division, absolute_import, unicode_literals
3 import os
4 import re
6 from PyQt4 import QtGui
8 from cola import cmds
9 from cola import core
10 from cola import difftool
11 from cola import gitcmds
12 from cola import icons
13 from cola import qtutils
14 from cola import utils
15 from cola.git import git
16 from cola.i18n import N_
17 from cola.interaction import Interaction
18 from cola.models import main
19 from cola.widgets import completion
20 from cola.widgets.browse import BrowseDialog
21 from cola.widgets.selectcommits import select_commits
22 from cola.compat import ustr
25 def delete_branch():
26 """Launch the 'Delete Branch' dialog."""
27 icon = icons.discard()
28 branch = choose_branch(N_('Delete Branch'), N_('Delete'), icon=icon)
29 if not branch:
30 return
31 cmds.do(cmds.DeleteBranch, branch)
34 def delete_remote_branch():
35 """Launch the 'Delete Remote Branch' dialog."""
36 branch = choose_remote_branch(N_('Delete Remote Branch'), N_('Delete'),
37 icon=icons.discard())
38 if not branch:
39 return
40 rgx = re.compile(r'^(?P<remote>[^/]+)/(?P<branch>.+)$')
41 match = rgx.match(branch)
42 if match:
43 remote = match.group('remote')
44 branch = match.group('branch')
45 cmds.do(cmds.DeleteRemoteBranch, remote, branch)
48 def browse_current():
49 """Launch the 'Browse Current Branch' dialog."""
50 branch = gitcmds.current_branch()
51 BrowseDialog.browse(branch)
54 def browse_other():
55 """Prompt for a branch and inspect content at that point in time."""
56 # Prompt for a branch to browse
57 branch = choose_ref(N_('Browse Commits...'), N_('Browse'))
58 if not branch:
59 return
60 BrowseDialog.browse(branch)
63 def checkout_branch():
64 """Launch the 'Checkout Branch' dialog."""
65 branch = choose_potential_branch(N_('Checkout Branch'), N_('Checkout'))
66 if not branch:
67 return
68 cmds.do(cmds.CheckoutBranch, branch)
71 def cherry_pick():
72 """Launch the 'Cherry-Pick' dialog."""
73 revs, summaries = gitcmds.log_helper(all=True)
74 commits = select_commits(N_('Cherry-Pick Commit'),
75 revs, summaries, multiselect=False)
76 if not commits:
77 return
78 cmds.do(cmds.CherryPick, commits)
81 def new_repo():
82 """Prompt for a new directory and create a new Git repository
84 :returns str: repository path or None if no repository was created.
86 """
87 dlg = QtGui.QFileDialog()
88 dlg.setFileMode(QtGui.QFileDialog.Directory)
89 dlg.setOption(QtGui.QFileDialog.ShowDirsOnly)
90 dlg.show()
91 dlg.raise_()
92 if dlg.exec_() != QtGui.QFileDialog.Accepted:
93 return None
94 paths = dlg.selectedFiles()
95 if not paths:
96 return None
97 path = ustr(paths[0])
98 if not path:
99 return None
100 # Avoid needlessly calling `git init`.
101 if git.is_git_dir(path):
102 # We could prompt here and confirm that they really didn't
103 # mean to open an existing repository, but I think
104 # treating it like an "Open" is a sensible DWIM answer.
105 return path
107 status, out, err = core.run_command(['git', 'init', path])
108 if status == 0:
109 return path
110 else:
111 title = N_('Error Creating Repository')
112 msg = (N_('"%(command)s" returned exit status %(status)d') %
113 dict(command='git init %s' % path, status=status))
114 details = N_('Output:\n%s') % out
115 if err:
116 details += '\n\n'
117 details += N_('Errors: %s') % err
118 qtutils.critical(title, msg, details)
119 return None
122 def open_new_repo():
123 dirname = new_repo()
124 if not dirname:
125 return
126 cmds.do(cmds.OpenRepo, dirname)
129 def prompt_for_clone():
131 Present a GUI for cloning a repository.
133 Returns the target directory and URL
136 url, ok = qtutils.prompt(N_('Path or URL to clone (Env. $VARS okay)'))
137 url = utils.expandpath(url)
138 if not ok or not url:
139 return None
140 try:
141 # Pick a suitable basename by parsing the URL
142 newurl = url.replace('\\', '/').rstrip('/')
143 default = newurl.rsplit('/', 1)[-1]
144 if default == '.git':
145 # The end of the URL is /.git, so assume it's a file path
146 default = os.path.basename(os.path.dirname(newurl))
147 if default.endswith('.git'):
148 # The URL points to a bare repo
149 default = default[:-4]
150 if url == '.':
151 # The URL is the current repo
152 default = os.path.basename(core.getcwd())
153 if not default:
154 raise
155 except:
156 Interaction.information(
157 N_('Error Cloning'),
158 N_('Could not parse Git URL: "%s"') % url)
159 Interaction.log(N_('Could not parse Git URL: "%s"') % url)
160 return None
162 # Prompt the user for a directory to use as the parent directory
163 msg = N_('Select a parent directory for the new clone')
164 dirname = qtutils.opendir_dialog(msg, main.model().getcwd())
165 if not dirname:
166 return None
167 count = 1
168 destdir = os.path.join(dirname, default)
169 olddestdir = destdir
170 if core.exists(destdir):
171 # An existing path can be specified
172 msg = (N_('"%s" already exists, cola will create a new directory') %
173 destdir)
174 Interaction.information(N_('Directory Exists'), msg)
176 # Make sure the new destdir doesn't exist
177 while core.exists(destdir):
178 destdir = olddestdir + str(count)
179 count += 1
181 return url, destdir
184 def export_patches():
185 """Run 'git format-patch' on a list of commits."""
186 revs, summaries = gitcmds.log_helper()
187 to_export = select_commits(N_('Export Patches'), revs, summaries)
188 if not to_export:
189 return
190 cmds.do(cmds.FormatPatch, reversed(to_export), reversed(revs))
193 def diff_expression():
194 """Diff using an arbitrary expression."""
195 tracked = gitcmds.tracked_branch()
196 current = gitcmds.current_branch()
197 if tracked and current:
198 ref = tracked + '..' + current
199 else:
200 ref = 'origin/master..'
201 difftool.diff_expression(qtutils.active_window(), ref)
204 def open_repo():
205 dirname = qtutils.opendir_dialog(N_('Open Git Repository...'),
206 main.model().getcwd())
207 if not dirname:
208 return
209 cmds.do(cmds.OpenRepo, dirname)
212 def open_repo_in_new_window():
213 """Spawn a new cola session."""
214 dirname = qtutils.opendir_dialog(N_('Open Git Repository...'),
215 main.model().getcwd())
216 if not dirname:
217 return
218 cmds.do(cmds.OpenNewRepo, dirname)
221 def load_commitmsg():
222 """Load a commit message from a file."""
223 filename = qtutils.open_file(N_('Load Commit Message'),
224 directory=main.model().getcwd())
225 if filename:
226 cmds.do(cmds.LoadCommitMessageFromFile, filename)
229 def choose_from_dialog(get, title, button_text, default, icon=None):
230 parent = qtutils.active_window()
231 return get(title, button_text, parent, default=default, icon=icon)
234 def choose_ref(title, button_text, default=None, icon=None):
235 return choose_from_dialog(completion.GitRefDialog.get,
236 title, button_text, default, icon=icon)
239 def choose_branch(title, button_text, default=None, icon=None):
240 return choose_from_dialog(completion.GitBranchDialog.get,
241 title, button_text, default, icon=icon)
244 def choose_potential_branch(title, button_text, default=None, icon=None):
245 return choose_from_dialog(completion.GitPotentialBranchDialog.get,
246 title, button_text, default, icon=icon)
249 def choose_remote_branch(title, button_text, default=None, icon=None):
250 return choose_from_dialog(completion.GitRemoteBranchDialog.get,
251 title, button_text, default, icon=icon)
254 def review_branch():
255 """Diff against an arbitrary revision, branch, tag, etc."""
256 branch = choose_ref(N_('Select Branch to Review'), N_('Review'))
257 if not branch:
258 return
259 merge_base = gitcmds.merge_base_parent(branch)
260 difftool.diff_commits(qtutils.active_window(), merge_base, branch)
263 class CloneTask(qtutils.Task):
264 """Clones a Git repository"""
266 def __init__(self, url, destdir, spawn, parent):
267 qtutils.Task.__init__(self, parent)
268 self.url = url
269 self.destdir = destdir
270 self.spawn = spawn
271 self.cmd = None
273 def task(self):
274 """Runs the model action and captures the result"""
275 self.cmd = cmds.do(cmds.Clone, self.url, self.destdir, spawn=self.spawn)
276 return self.cmd
279 def clone_repo(parent, runtask, progress, finish, spawn):
280 """Clone a repostiory asynchronously with progress animation"""
281 result = prompt_for_clone()
282 if result is None:
283 return
284 # Use a thread to update in the background
285 url, destdir = result
286 progress.set_details(N_('Clone Repository'),
287 N_('Cloning repository at %s') % url)
288 task = CloneTask(url, destdir, spawn, parent)
289 runtask.start(task, finish=finish, progress=progress)
292 def report_clone_repo_errors(task):
293 """Report errors from the clone task if they exist"""
294 if task.cmd is None or task.cmd.ok:
295 return
296 Interaction.critical(task.cmd.error_message,
297 message=task.cmd.error_message,
298 details=task.cmd.error_details)
300 def rename_branch():
301 """Launch the 'Rename Branch' dialogs."""
302 branch = choose_branch(N_('Rename Existing Branch'), N_('Select'))
303 if not branch:
304 return
305 new_branch = choose_branch(N_('Enter Branch New Name'), N_('Rename'))
306 if not new_branch:
307 return
308 cmds.do(cmds.RenameBranch, branch, new_branch)