1from __future__ import division, absolute_import, unicode_literals
2import os
3
4from . import cmds
5from . import core
6from . import difftool
7from . import gitcmds
8from . import icons
9from . import qtutils
10from .i18n import N_
11from .interaction import Interaction
12from .widgets import completion
13from .widgets import editremotes
14from .widgets.browse import BrowseBranch
15from .widgets.selectcommits import select_commits
16from .widgets.selectcommits import select_commits_and_output
17
18
19def delete_branch(context):
20    """Launch the 'Delete Branch' dialog."""
21    icon = icons.discard()
22    branch = choose_branch(context, N_('Delete Branch'), N_('Delete'), icon=icon)
23    if not branch:
24        return
25    cmds.do(cmds.DeleteBranch, context, branch)
26
27
28def delete_remote_branch(context):
29    """Launch the 'Delete Remote Branch' dialog."""
30    remote_branch = choose_remote_branch(
31        context, N_('Delete Remote Branch'), N_('Delete'), icon=icons.discard()
32    )
33    if not remote_branch:
34        return
35    remote, branch = gitcmds.parse_remote_branch(remote_branch)
36    if remote and branch:
37        cmds.do(cmds.DeleteRemoteBranch, context, remote, branch)
38
39
40def browse_current(context):
41    """Launch the 'Browse Current Branch' dialog."""
42    branch = gitcmds.current_branch(context)
43    BrowseBranch.browse(context, branch)
44
45
46def browse_other(context):
47    """Prompt for a branch and inspect content at that point in time."""
48    # Prompt for a branch to browse
49    branch = choose_ref(context, N_('Browse Commits...'), N_('Browse'))
50    if not branch:
51        return
52    BrowseBranch.browse(context, branch)
53
54
55def checkout_branch(context):
56    """Launch the 'Checkout Branch' dialog."""
57    branch = choose_potential_branch(context, N_('Checkout Branch'), N_('Checkout'))
58    if not branch:
59        return
60    cmds.do(cmds.CheckoutBranch, context, branch)
61
62
63def cherry_pick(context):
64    """Launch the 'Cherry-Pick' dialog."""
65    revs, summaries = gitcmds.log_helper(context, all=True)
66    commits = select_commits(
67        context, N_('Cherry-Pick Commit'), revs, summaries, multiselect=False
68    )
69    if not commits:
70        return
71    cmds.do(cmds.CherryPick, context, commits)
72
73
74def new_repo(context):
75    """Prompt for a new directory and create a new Git repository
76
77    :returns str: repository path or None if no repository was created.
78
79    """
80    git = context.git
81    path = qtutils.opendir_dialog(N_('New Repository...'), core.getcwd())
82    if not path:
83        return None
84    # Avoid needlessly calling `git init`.
85    if git.is_git_repository(path):
86        # We could prompt here and confirm that they really didn't
87        # mean to open an existing repository, but I think
88        # treating it like an "Open" is a sensible DWIM answer.
89        return path
90
91    status, out, err = git.init(path)
92    if status == 0:
93        return path
94
95    title = N_('Error Creating Repository')
96    Interaction.command_error(title, 'git init', status, out, err)
97    return None
98
99
100def open_new_repo(context):
101    dirname = new_repo(context)
102    if not dirname:
103        return
104    cmds.do(cmds.OpenRepo, context, dirname)
105
106
107def new_bare_repo(context):
108    result = None
109    repo = prompt_for_new_bare_repo()
110    if not repo:
111        return result
112    # Create bare repo
113    ok = cmds.do(cmds.NewBareRepo, context, repo)
114    if not ok:
115        return result
116    # Add a new remote pointing to the bare repo
117    parent = qtutils.active_window()
118    add_remote = editremotes.add_remote(
119        context, parent, name=os.path.basename(repo), url=repo, readonly_url=True
120    )
121    if add_remote:
122        result = repo
123
124    return result
125
126
127def prompt_for_new_bare_repo():
128    path = qtutils.opendir_dialog(N_('Select Directory...'), core.getcwd())
129    if not path:
130        return None
131
132    bare_repo = None
133    default = os.path.basename(core.getcwd())
134    if not default.endswith('.git'):
135        default += '.git'
136    while not bare_repo:
137        name, ok = qtutils.prompt(
138            N_('Enter a name for the new bare repo'),
139            title=N_('New Bare Repository...'),
140            text=default,
141        )
142        if not name or not ok:
143            return None
144        if not name.endswith('.git'):
145            name += '.git'
146        repo = os.path.join(path, name)
147        if core.isdir(repo):
148            Interaction.critical(N_('Error'), N_('"%s" already exists') % repo)
149        else:
150            bare_repo = repo
151
152    return bare_repo
153
154
155def export_patches(context):
156    """Run 'git format-patch' on a list of commits."""
157    revs, summaries = gitcmds.log_helper(context)
158    to_export_and_output = select_commits_and_output(
159        context, N_('Export Patches'), revs, summaries
160    )
161    if not to_export_and_output['to_export']:
162        return
163
164    cmds.do(
165        cmds.FormatPatch,
166        context,
167        reversed(to_export_and_output['to_export']),
168        reversed(revs),
169        to_export_and_output['output'],
170    )
171
172
173def diff_expression(context):
174    """Diff using an arbitrary expression."""
175    tracked = gitcmds.tracked_branch(context)
176    current = gitcmds.current_branch(context)
177    if tracked and current:
178        ref = tracked + '..' + current
179    else:
180        ref = '@{upstream}..'
181    difftool.diff_expression(context, qtutils.active_window(), ref)
182
183
184def open_repo(context):
185    model = context.model
186    dirname = qtutils.opendir_dialog(N_('Open Git Repository...'), model.getcwd())
187    if not dirname:
188        return
189    cmds.do(cmds.OpenRepo, context, dirname)
190
191
192def open_repo_in_new_window(context):
193    """Spawn a new cola session."""
194    model = context.model
195    dirname = qtutils.opendir_dialog(N_('Open Git Repository...'), model.getcwd())
196    if not dirname:
197        return
198    cmds.do(cmds.OpenNewRepo, context, dirname)
199
200
201def load_commitmsg(context):
202    """Load a commit message from a file."""
203    model = context.model
204    filename = qtutils.open_file(N_('Load Commit Message'), directory=model.getcwd())
205    if filename:
206        cmds.do(cmds.LoadCommitMessageFromFile, context, filename)
207
208
209def choose_from_dialog(get, context, title, button_text, default, icon=None):
210    parent = qtutils.active_window()
211    return get(context, title, button_text, parent, default=default, icon=icon)
212
213
214def choose_ref(context, title, button_text, default=None, icon=None):
215    return choose_from_dialog(
216        completion.GitRefDialog.get, context, title, button_text, default, icon=icon
217    )
218
219
220def choose_branch(context, title, button_text, default=None, icon=None):
221    return choose_from_dialog(
222        completion.GitBranchDialog.get, context, title, button_text, default, icon=icon
223    )
224
225
226def choose_potential_branch(context, title, button_text, default=None, icon=None):
227    return choose_from_dialog(
228        completion.GitCheckoutBranchDialog.get,
229        context,
230        title,
231        button_text,
232        default,
233        icon=icon,
234    )
235
236
237def choose_remote_branch(context, title, button_text, default=None, icon=None):
238    return choose_from_dialog(
239        completion.GitRemoteBranchDialog.get,
240        context,
241        title,
242        button_text,
243        default,
244        icon=icon,
245    )
246
247
248def review_branch(context):
249    """Diff against an arbitrary revision, branch, tag, etc."""
250    branch = choose_ref(context, N_('Select Branch to Review'), N_('Review'))
251    if not branch:
252        return
253    merge_base = gitcmds.merge_base_parent(context, branch)
254    difftool.diff_commits(context, qtutils.active_window(), merge_base, branch)
255
256
257def rename_branch(context):
258    """Launch the 'Rename Branch' dialogs."""
259    branch = choose_branch(context, N_('Rename Existing Branch'), N_('Select'))
260    if not branch:
261        return
262    new_branch = choose_branch(context, N_('Enter New Branch Name'), N_('Rename'))
263    if not new_branch:
264        return
265    cmds.do(cmds.RenameBranch, context, branch, new_branch)
266
267
268def reset_soft(context):
269    title = N_('Reset Branch (Soft)')
270    ok_text = N_('Reset Branch')
271    ref = choose_ref(context, title, ok_text, default='HEAD^')
272    if ref:
273        cmds.do(cmds.ResetSoft, context, ref)
274
275
276def reset_mixed(context):
277    title = N_('Reset Branch and Stage (Mixed)')
278    ok_text = N_('Reset')
279    ref = choose_ref(context, title, ok_text, default='HEAD^')
280    if ref:
281        cmds.do(cmds.ResetMixed, context, ref)
282
283
284def reset_keep(context):
285    title = N_('Reset All (Keep Unstaged Changes)')
286    ref = choose_ref(context, title, N_('Reset and Restore'))
287    if ref:
288        cmds.do(cmds.ResetKeep, context, ref)
289
290
291def reset_merge(context):
292    title = N_('Restore Worktree and Reset All (Merge)')
293    ok_text = N_('Reset and Restore')
294    ref = choose_ref(context, title, ok_text, default='HEAD^')
295    if ref:
296        cmds.do(cmds.ResetMerge, context, ref)
297
298
299def reset_hard(context):
300    title = N_('Restore Worktree and Reset All (Hard)')
301    ok_text = N_('Reset and Restore')
302    ref = choose_ref(context, title, ok_text, default='HEAD^')
303    if ref:
304        cmds.do(cmds.ResetHard, context, ref)
305
306
307def restore_worktree(context):
308    title = N_('Restore Worktree')
309    ok_text = N_('Restore Worktree')
310    ref = choose_ref(context, title, ok_text, default='HEAD^')
311    if ref:
312        cmds.do(cmds.RestoreWorktree, context, ref)
313
314
315def install():
316    """Install the GUI-model interaction hooks"""
317    Interaction.choose_ref = staticmethod(choose_ref)
318