1 from __future__
import division
, absolute_import
, unicode_literals
6 from PyQt4
import QtGui
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
26 """Launch the 'Delete Branch' dialog."""
27 icon
= icons
.discard()
28 branch
= choose_branch(N_('Delete Branch'), N_('Delete'), icon
=icon
)
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'),
40 rgx
= re
.compile(r
'^(?P<remote>[^/]+)/(?P<branch>.+)$')
41 match
= rgx
.match(branch
)
43 remote
= match
.group('remote')
44 branch
= match
.group('branch')
45 cmds
.do(cmds
.DeleteRemoteBranch
, remote
, branch
)
49 """Launch the 'Browse Current Branch' dialog."""
50 branch
= gitcmds
.current_branch()
51 BrowseDialog
.browse(branch
)
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'))
60 BrowseDialog
.browse(branch
)
63 def checkout_branch():
64 """Launch the 'Checkout Branch' dialog."""
65 branch
= choose_potential_branch(N_('Checkout Branch'), N_('Checkout'))
68 cmds
.do(cmds
.CheckoutBranch
, branch
)
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)
78 cmds
.do(cmds
.CherryPick
, commits
)
82 """Prompt for a new directory and create a new Git repository
84 :returns str: repository path or None if no repository was created.
87 dlg
= QtGui
.QFileDialog()
88 dlg
.setFileMode(QtGui
.QFileDialog
.Directory
)
89 dlg
.setOption(QtGui
.QFileDialog
.ShowDirsOnly
)
92 if dlg
.exec_() != QtGui
.QFileDialog
.Accepted
:
94 paths
= dlg
.selectedFiles()
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.
107 status
, out
, err
= core
.run_command(['git', 'init', path
])
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
117 details
+= N_('Errors: %s') % err
118 qtutils
.critical(title
, msg
, details
)
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
:
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]
151 # The URL is the current repo
152 default
= os
.path
.basename(core
.getcwd())
156 Interaction
.information(
158 N_('Could not parse Git URL: "%s"') % url
)
159 Interaction
.log(N_('Could not parse Git URL: "%s"') % url
)
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())
168 destdir
= os
.path
.join(dirname
, default
)
170 if core
.exists(destdir
):
171 # An existing path can be specified
172 msg
= (N_('"%s" already exists, cola will create a new directory') %
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
)
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
)
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
200 ref
= 'origin/master..'
201 difftool
.diff_expression(qtutils
.active_window(), ref
)
205 dirname
= qtutils
.opendir_dialog(N_('Open Git Repository...'),
206 main
.model().getcwd())
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())
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())
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
)
255 """Diff against an arbitrary revision, branch, tag, etc."""
256 branch
= choose_ref(N_('Select Branch to Review'), N_('Review'))
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
)
269 self
.destdir
= destdir
274 """Runs the model action and captures the result"""
275 self
.cmd
= cmds
.do(cmds
.Clone
, self
.url
, self
.destdir
, spawn
=self
.spawn
)
279 def clone_repo(parent
, runtask
, progress
, finish
, spawn
):
280 """Clone a repostiory asynchronously with progress animation"""
281 result
= prompt_for_clone()
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
:
296 Interaction
.critical(task
.cmd
.error_message
,
297 message
=task
.cmd
.error_message
,
298 details
=task
.cmd
.error_details
)
301 """Launch the 'Rename Branch' dialogs."""
302 branch
= choose_branch(N_('Rename Existing Branch'), N_('Select'))
305 new_branch
= choose_branch(N_('Enter Branch New Name'), N_('Rename'))
308 cmds
.do(cmds
.RenameBranch
, branch
, new_branch
)