git-cola v2.11
[git-cola.git] / cola / guicmds.py
blobe16f453e112a17b876737bfecf5e8e0f915a1126
1 from __future__ import division, absolute_import, unicode_literals
2 import os
3 import re
5 from .i18n import N_
6 from .interaction import Interaction
7 from .models import main
8 from .widgets import completion
9 from .widgets.browse import BrowseBranch
10 from .widgets.selectcommits import select_commits
11 from . import cmds
12 from . import core
13 from . import difftool
14 from . import gitcmds
15 from . import icons
16 from . import qtutils
17 from . import utils
18 from . import git
21 def delete_branch():
22 """Launch the 'Delete Branch' dialog."""
23 icon = icons.discard()
24 branch = choose_branch(N_('Delete Branch'), N_('Delete'), icon=icon)
25 if not branch:
26 return
27 cmds.do(cmds.DeleteBranch, branch)
30 def delete_remote_branch():
31 """Launch the 'Delete Remote Branch' dialog."""
32 branch = choose_remote_branch(N_('Delete Remote Branch'), N_('Delete'),
33 icon=icons.discard())
34 if not branch:
35 return
36 rgx = re.compile(r'^(?P<remote>[^/]+)/(?P<branch>.+)$')
37 match = rgx.match(branch)
38 if match:
39 remote = match.group('remote')
40 branch = match.group('branch')
41 cmds.do(cmds.DeleteRemoteBranch, remote, branch)
44 def browse_current():
45 """Launch the 'Browse Current Branch' dialog."""
46 branch = gitcmds.current_branch()
47 BrowseBranch.browse(branch)
50 def browse_other():
51 """Prompt for a branch and inspect content at that point in time."""
52 # Prompt for a branch to browse
53 branch = choose_ref(N_('Browse Commits...'), N_('Browse'))
54 if not branch:
55 return
56 BrowseBranch.browse(branch)
59 def checkout_branch():
60 """Launch the 'Checkout Branch' dialog."""
61 branch = choose_potential_branch(N_('Checkout Branch'), N_('Checkout'))
62 if not branch:
63 return
64 cmds.do(cmds.CheckoutBranch, branch)
67 def cherry_pick():
68 """Launch the 'Cherry-Pick' dialog."""
69 revs, summaries = gitcmds.log_helper(all=True)
70 commits = select_commits(N_('Cherry-Pick Commit'),
71 revs, summaries, multiselect=False)
72 if not commits:
73 return
74 cmds.do(cmds.CherryPick, commits)
77 def new_repo():
78 """Prompt for a new directory and create a new Git repository
80 :returns str: repository path or None if no repository was created.
82 """
83 path = qtutils.opendir_dialog(N_('New Repository...'), core.getcwd())
84 if not path:
85 return None
86 # Avoid needlessly calling `git init`.
87 if git.is_git_worktree(path) or git.is_git_dir(path):
88 # We could prompt here and confirm that they really didn't
89 # mean to open an existing repository, but I think
90 # treating it like an "Open" is a sensible DWIM answer.
91 return path
93 status, out, err = core.run_command(['git', 'init', path])
94 if status == 0:
95 return path
96 else:
97 title = N_('Error Creating Repository')
98 msg = (N_('"%(command)s" returned exit status %(status)d') %
99 dict(command='git init %s' % path, status=status))
100 details = N_('Output:\n%s') % out
101 if err:
102 details += '\n\n'
103 details += N_('Errors: %s') % err
104 qtutils.critical(title, msg, details)
105 return None
108 def open_new_repo():
109 dirname = new_repo()
110 if not dirname:
111 return
112 cmds.do(cmds.OpenRepo, dirname)
115 def prompt_for_clone():
117 Present a GUI for cloning a repository.
119 Returns the target directory and URL
122 url, ok = qtutils.prompt(N_('Path or URL to clone (Env. $VARS okay)'))
123 url = utils.expandpath(url)
124 if not ok or not url:
125 return None
126 try:
127 # Pick a suitable basename by parsing the URL
128 newurl = url.replace('\\', '/').rstrip('/')
129 default = newurl.rsplit('/', 1)[-1]
130 if default == '.git':
131 # The end of the URL is /.git, so assume it's a file path
132 default = os.path.basename(os.path.dirname(newurl))
133 if default.endswith('.git'):
134 # The URL points to a bare repo
135 default = default[:-4]
136 if url == '.':
137 # The URL is the current repo
138 default = os.path.basename(core.getcwd())
139 if not default:
140 raise
141 except:
142 Interaction.information(
143 N_('Error Cloning'),
144 N_('Could not parse Git URL: "%s"') % url)
145 Interaction.log(N_('Could not parse Git URL: "%s"') % url)
146 return None
148 # Prompt the user for a directory to use as the parent directory
149 msg = N_('Select a parent directory for the new clone')
150 dirname = qtutils.opendir_dialog(msg, main.model().getcwd())
151 if not dirname:
152 return None
153 count = 1
154 destdir = os.path.join(dirname, default)
155 olddestdir = destdir
156 if core.exists(destdir):
157 # An existing path can be specified
158 msg = (N_('"%s" already exists, cola will create a new directory') %
159 destdir)
160 Interaction.information(N_('Directory Exists'), msg)
162 # Make sure the new destdir doesn't exist
163 while core.exists(destdir):
164 destdir = olddestdir + str(count)
165 count += 1
167 return url, destdir
170 def export_patches():
171 """Run 'git format-patch' on a list of commits."""
172 revs, summaries = gitcmds.log_helper()
173 to_export = select_commits(N_('Export Patches'), revs, summaries)
174 if not to_export:
175 return
176 cmds.do(cmds.FormatPatch, reversed(to_export), reversed(revs))
179 def diff_expression():
180 """Diff using an arbitrary expression."""
181 tracked = gitcmds.tracked_branch()
182 current = gitcmds.current_branch()
183 if tracked and current:
184 ref = tracked + '..' + current
185 else:
186 ref = 'origin/master..'
187 difftool.diff_expression(qtutils.active_window(), ref)
190 def open_repo():
191 dirname = qtutils.opendir_dialog(N_('Open Git Repository...'),
192 main.model().getcwd())
193 if not dirname:
194 return
195 cmds.do(cmds.OpenRepo, dirname)
198 def open_repo_in_new_window():
199 """Spawn a new cola session."""
200 dirname = qtutils.opendir_dialog(N_('Open Git Repository...'),
201 main.model().getcwd())
202 if not dirname:
203 return
204 cmds.do(cmds.OpenNewRepo, dirname)
207 def load_commitmsg():
208 """Load a commit message from a file."""
209 filename = qtutils.open_file(N_('Load Commit Message'),
210 directory=main.model().getcwd())
211 if filename:
212 cmds.do(cmds.LoadCommitMessageFromFile, filename)
215 def choose_from_dialog(get, title, button_text, default, icon=None):
216 parent = qtutils.active_window()
217 return get(title, button_text, parent, default=default, icon=icon)
220 def choose_ref(title, button_text, default=None, icon=None):
221 return choose_from_dialog(completion.GitRefDialog.get,
222 title, button_text, default, icon=icon)
225 def choose_branch(title, button_text, default=None, icon=None):
226 return choose_from_dialog(completion.GitBranchDialog.get,
227 title, button_text, default, icon=icon)
230 def choose_potential_branch(title, button_text, default=None, icon=None):
231 return choose_from_dialog(completion.GitPotentialBranchDialog.get,
232 title, button_text, default, icon=icon)
235 def choose_remote_branch(title, button_text, default=None, icon=None):
236 return choose_from_dialog(completion.GitRemoteBranchDialog.get,
237 title, button_text, default, icon=icon)
240 def review_branch():
241 """Diff against an arbitrary revision, branch, tag, etc."""
242 branch = choose_ref(N_('Select Branch to Review'), N_('Review'))
243 if not branch:
244 return
245 merge_base = gitcmds.merge_base_parent(branch)
246 difftool.diff_commits(qtutils.active_window(), merge_base, branch)
249 class CloneTask(qtutils.Task):
250 """Clones a Git repository"""
252 def __init__(self, url, destdir, spawn, parent):
253 qtutils.Task.__init__(self, parent)
254 self.url = url
255 self.destdir = destdir
256 self.spawn = spawn
257 self.cmd = None
259 def task(self):
260 """Runs the model action and captures the result"""
261 self.cmd = cmds.do(cmds.Clone, self.url, self.destdir, spawn=self.spawn)
262 return self.cmd
265 def clone_repo(parent, runtask, progress, finish, spawn):
266 """Clone a repository asynchronously with progress animation"""
267 result = prompt_for_clone()
268 if result is None:
269 return
270 # Use a thread to update in the background
271 url, destdir = result
272 progress.set_details(N_('Clone Repository'),
273 N_('Cloning repository at %s') % url)
274 task = CloneTask(url, destdir, spawn, parent)
275 runtask.start(task, finish=finish, progress=progress)
278 def report_clone_repo_errors(task):
279 """Report errors from the clone task if they exist"""
280 if task.cmd is None or task.cmd.ok:
281 return
282 Interaction.critical(task.cmd.error_message,
283 message=task.cmd.error_message,
284 details=task.cmd.error_details)
287 def rename_branch():
288 """Launch the 'Rename Branch' dialogs."""
289 branch = choose_branch(N_('Rename Existing Branch'), N_('Select'))
290 if not branch:
291 return
292 new_branch = choose_branch(N_('Enter New Branch Name'), N_('Rename'))
293 if not new_branch:
294 return
295 cmds.do(cmds.RenameBranch, branch, new_branch)
298 def reset_branch_head():
299 ref = choose_ref(N_('Reset Branch Head'), N_('Reset'), default='HEAD^')
300 if ref:
301 cmds.do(cmds.ResetBranchHead, ref)
304 def reset_worktree():
305 ref = choose_ref(N_('Reset Worktree'), N_('Reset'))
306 if ref:
307 cmds.do(cmds.ResetWorktree, ref)