browse: display errors when saving blobs
[git-cola.git] / cola / guicmds.py
blobcad1d6c694d3408c6e08ece8bade6f9ef3478b19
1 from __future__ import absolute_import, division, print_function, unicode_literals
2 import os
4 from qtpy import QtGui
6 from . import cmds
7 from . import core
8 from . import difftool
9 from . import display
10 from . import gitcmds
11 from . import icons
12 from . import qtutils
13 from .i18n import N_
14 from .interaction import Interaction
15 from .widgets import completion
16 from .widgets import editremotes
17 from .widgets import switcher
18 from .widgets.browse import BrowseBranch
19 from .widgets.selectcommits import select_commits
20 from .widgets.selectcommits import select_commits_and_output
23 def delete_branch(context):
24 """Launch the 'Delete Branch' dialog."""
25 icon = icons.discard()
26 branch = choose_branch(context, N_('Delete Branch'), N_('Delete'), icon=icon)
27 if not branch:
28 return
29 cmds.do(cmds.DeleteBranch, context, branch)
32 def delete_remote_branch(context):
33 """Launch the 'Delete Remote Branch' dialog."""
34 remote_branch = choose_remote_branch(
35 context, N_('Delete Remote Branch'), N_('Delete'), icon=icons.discard()
37 if not remote_branch:
38 return
39 remote, branch = gitcmds.parse_remote_branch(remote_branch)
40 if remote and branch:
41 cmds.do(cmds.DeleteRemoteBranch, context, remote, branch)
44 def browse_current(context):
45 """Launch the 'Browse Current Branch' dialog."""
46 branch = gitcmds.current_branch(context)
47 BrowseBranch.browse(context, branch)
50 def browse_other(context):
51 """Prompt for a branch and inspect content at that point in time."""
52 # Prompt for a branch to browse
53 branch = choose_ref(context, N_('Browse Commits...'), N_('Browse'))
54 if not branch:
55 return
56 BrowseBranch.browse(context, branch)
59 def checkout_branch(context):
60 """Launch the 'Checkout Branch' dialog."""
61 branch = choose_potential_branch(context, N_('Checkout Branch'), N_('Checkout'))
62 if not branch:
63 return
64 cmds.do(cmds.CheckoutBranch, context, branch)
67 def cherry_pick(context):
68 """Launch the 'Cherry-Pick' dialog."""
69 revs, summaries = gitcmds.log_helper(context, all=True)
70 commits = select_commits(
71 context, N_('Cherry-Pick Commit'), revs, summaries, multiselect=False
73 if not commits:
74 return
75 cmds.do(cmds.CherryPick, context, commits)
78 def new_repo(context):
79 """Prompt for a new directory and create a new Git repository
81 :returns str: repository path or None if no repository was created.
83 """
84 git = context.git
85 path = qtutils.opendir_dialog(N_('New Repository...'), core.getcwd())
86 if not path:
87 return None
88 # Avoid needlessly calling `git init`.
89 if git.is_git_repository(path):
90 # We could prompt here and confirm that they really didn't
91 # mean to open an existing repository, but I think
92 # treating it like an "Open" is a sensible DWIM answer.
93 return path
95 status, out, err = git.init(path)
96 if status == 0:
97 return path
99 title = N_('Error Creating Repository')
100 Interaction.command_error(title, 'git init', status, out, err)
101 return None
104 def open_new_repo(context):
105 dirname = new_repo(context)
106 if not dirname:
107 return
108 cmds.do(cmds.OpenRepo, context, dirname)
111 def new_bare_repo(context):
112 result = None
113 repo = prompt_for_new_bare_repo()
114 if not repo:
115 return result
116 # Create bare repo
117 ok = cmds.do(cmds.NewBareRepo, context, repo)
118 if not ok:
119 return result
120 # Add a new remote pointing to the bare repo
121 parent = qtutils.active_window()
122 add_remote = editremotes.add_remote(
123 context, parent, name=os.path.basename(repo), url=repo, readonly_url=True
125 if add_remote:
126 result = repo
128 return result
131 def prompt_for_new_bare_repo():
132 path = qtutils.opendir_dialog(N_('Select Directory...'), core.getcwd())
133 if not path:
134 return None
136 bare_repo = None
137 default = os.path.basename(core.getcwd())
138 if not default.endswith('.git'):
139 default += '.git'
140 while not bare_repo:
141 name, ok = qtutils.prompt(
142 N_('Enter a name for the new bare repo'),
143 title=N_('New Bare Repository...'),
144 text=default,
146 if not name or not ok:
147 return None
148 if not name.endswith('.git'):
149 name += '.git'
150 repo = os.path.join(path, name)
151 if core.isdir(repo):
152 Interaction.critical(N_('Error'), N_('"%s" already exists') % repo)
153 else:
154 bare_repo = repo
156 return bare_repo
159 def export_patches(context):
160 """Run 'git format-patch' on a list of commits."""
161 revs, summaries = gitcmds.log_helper(context)
162 to_export_and_output = select_commits_and_output(
163 context, N_('Export Patches'), revs, summaries
165 if not to_export_and_output['to_export']:
166 return
168 cmds.do(
169 cmds.FormatPatch,
170 context,
171 reversed(to_export_and_output['to_export']),
172 reversed(revs),
173 to_export_and_output['output'],
177 def diff_against_commit(context):
178 """Diff against any commit and checkout changes using the Diff Editor"""
179 icon = icons.compare()
180 ref = choose_ref(context, N_('Diff Against Commit'), N_('Diff'), icon=icon)
181 if not ref:
182 return
183 cmds.do(cmds.DiffAgainstCommitMode, context, ref)
186 def diff_expression(context):
187 """Diff using an arbitrary expression."""
188 tracked = gitcmds.tracked_branch(context)
189 current = gitcmds.current_branch(context)
190 if tracked and current:
191 ref = tracked + '..' + current
192 else:
193 ref = '@{upstream}..'
194 difftool.diff_expression(context, qtutils.active_window(), ref)
197 def open_repo(context):
198 model = context.model
199 dirname = qtutils.opendir_dialog(N_('Open Git Repository'), model.getcwd())
200 if not dirname:
201 return
202 cmds.do(cmds.OpenRepo, context, dirname)
205 def open_repo_in_new_window(context):
206 """Spawn a new cola session."""
207 model = context.model
208 dirname = qtutils.opendir_dialog(N_('Open Git Repository'), model.getcwd())
209 if not dirname:
210 return
211 cmds.do(cmds.OpenNewRepo, context, dirname)
214 def open_quick_repo_search(context, parent=None):
215 """Open a Quick Repository Search dialog"""
216 if parent is None:
217 parent = qtutils.active_window()
218 settings = context.settings
219 items = settings.bookmarks + settings.recent
221 if items:
222 cfg = context.cfg
223 default_repo = cfg.get('cola.defaultrepo')
225 entries = QtGui.QStandardItemModel()
226 added = set()
227 normalize = display.normalize_path
228 star_icon = icons.star()
229 folder_icon = icons.folder()
231 for item in items:
232 key = normalize(item['path'])
233 if key in added:
234 continue
236 name = item['name']
237 if default_repo == item['path']:
238 icon = star_icon
239 else:
240 icon = folder_icon
242 entry = switcher.switcher_item(key, icon, name)
243 entries.appendRow(entry)
244 added.add(key)
246 title = N_('Quick Open Repository')
247 place_holder = N_('Search repositories by name...')
248 switcher.switcher_inner_view(
249 context,
250 entries,
251 title,
252 place_holder=place_holder,
253 enter_action=lambda entry: cmds.do(cmds.OpenRepo, context, entry.key),
254 parent=parent,
258 def load_commitmsg(context):
259 """Load a commit message from a file."""
260 model = context.model
261 filename = qtutils.open_file(N_('Load Commit Message'), directory=model.getcwd())
262 if filename:
263 cmds.do(cmds.LoadCommitMessageFromFile, context, filename)
266 def choose_from_dialog(get, context, title, button_text, default, icon=None):
267 parent = qtutils.active_window()
268 return get(context, title, button_text, parent, default=default, icon=icon)
271 def choose_ref(context, title, button_text, default=None, icon=None):
272 return choose_from_dialog(
273 completion.GitRefDialog.get, context, title, button_text, default, icon=icon
277 def choose_branch(context, title, button_text, default=None, icon=None):
278 return choose_from_dialog(
279 completion.GitBranchDialog.get, context, title, button_text, default, icon=icon
283 def choose_potential_branch(context, title, button_text, default=None, icon=None):
284 return choose_from_dialog(
285 completion.GitCheckoutBranchDialog.get,
286 context,
287 title,
288 button_text,
289 default,
290 icon=icon,
294 def choose_remote_branch(context, title, button_text, default=None, icon=None):
295 return choose_from_dialog(
296 completion.GitRemoteBranchDialog.get,
297 context,
298 title,
299 button_text,
300 default,
301 icon=icon,
305 def review_branch(context):
306 """Diff against an arbitrary revision, branch, tag, etc."""
307 branch = choose_ref(context, N_('Select Branch to Review'), N_('Review'))
308 if not branch:
309 return
310 merge_base = gitcmds.merge_base_parent(context, branch)
311 difftool.diff_commits(context, qtutils.active_window(), merge_base, branch)
314 def rename_branch(context):
315 """Launch the 'Rename Branch' dialogs."""
316 branch = choose_branch(context, N_('Rename Existing Branch'), N_('Select'))
317 if not branch:
318 return
319 new_branch = choose_branch(context, N_('Enter New Branch Name'), N_('Rename'))
320 if not new_branch:
321 return
322 cmds.do(cmds.RenameBranch, context, branch, new_branch)
325 def reset_soft(context):
326 title = N_('Reset Branch (Soft)')
327 ok_text = N_('Reset Branch')
328 ref = choose_ref(context, title, ok_text, default='HEAD^')
329 if ref:
330 cmds.do(cmds.ResetSoft, context, ref)
333 def reset_mixed(context):
334 title = N_('Reset Branch and Stage (Mixed)')
335 ok_text = N_('Reset')
336 ref = choose_ref(context, title, ok_text, default='HEAD^')
337 if ref:
338 cmds.do(cmds.ResetMixed, context, ref)
341 def reset_keep(context):
342 title = N_('Reset All (Keep Unstaged Changes)')
343 ref = choose_ref(context, title, N_('Reset and Restore'))
344 if ref:
345 cmds.do(cmds.ResetKeep, context, ref)
348 def reset_merge(context):
349 title = N_('Restore Worktree and Reset All (Merge)')
350 ok_text = N_('Reset and Restore')
351 ref = choose_ref(context, title, ok_text, default='HEAD^')
352 if ref:
353 cmds.do(cmds.ResetMerge, context, ref)
356 def reset_hard(context):
357 title = N_('Restore Worktree and Reset All (Hard)')
358 ok_text = N_('Reset and Restore')
359 ref = choose_ref(context, title, ok_text, default='HEAD^')
360 if ref:
361 cmds.do(cmds.ResetHard, context, ref)
364 def restore_worktree(context):
365 title = N_('Restore Worktree')
366 ok_text = N_('Restore Worktree')
367 ref = choose_ref(context, title, ok_text, default='HEAD^')
368 if ref:
369 cmds.do(cmds.RestoreWorktree, context, ref)
372 def install():
373 """Install the GUI-model interaction hooks"""
374 Interaction.choose_ref = staticmethod(choose_ref)