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