File: guicmds.py

package info (click to toggle)
git-cola 2.10-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 4,444 kB
  • ctags: 3,201
  • sloc: python: 20,539; sh: 311; makefile: 310; tcl: 48; xml: 27
file content (307 lines) | stat: -rw-r--r-- 9,724 bytes parent folder | download
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)