cola/sequenceeditor.py: TODO -> FIXME
[git-cola.git] / cola / models / stash.py
blobc3447f62e50541cf0c42290ca61f1a42eebdff2a
1 import re
3 from .. import cmds
4 from .. import core
5 from .. import gitcmds
6 from .. import utils
7 from ..i18n import N_
8 from ..git import STDOUT
9 from ..interaction import Interaction
12 class StashModel:
13 def __init__(self, context):
14 self.context = context
15 self.git = context.git
16 self.model = model = context.model
17 if not model.initialized:
18 model.update_status()
20 def stash_list(self, *args):
21 return self.git.stash('list', *args)[STDOUT].splitlines()
23 def is_staged(self):
24 return bool(self.model.staged)
26 def is_changed(self):
27 model = self.model
28 return bool(model.modified or model.staged)
30 def stash_info(self, revids=False, names=False):
31 """Parses "git stash list" and returns a list of stashes."""
32 stashes = self.stash_list(r'--format=%gd/%aD/%gs')
33 split_stashes = [s.split('/', 2) for s in stashes if s]
34 stashes = [f'{s[0]}: {s[2]}' for s in split_stashes]
35 revids = [s[0] for s in split_stashes]
36 author_dates = [s[1] for s in split_stashes]
37 names = [s[2] for s in split_stashes]
39 return stashes, revids, author_dates, names
41 def stash_diff(self, rev):
42 git = self.git
43 diffstat = git.stash('show', rev)[STDOUT]
44 diff = git.stash('show', '-p', '--no-ext-diff', rev)[STDOUT]
45 return diffstat + '\n\n' + diff
48 class ApplyStash(cmds.ContextCommand):
49 def __init__(self, context, stash_index, index, pop):
50 super().__init__(context)
51 self.stash_ref = 'refs/' + stash_index
52 self.index = index
53 self.pop = pop
55 def do(self):
56 ref = self.stash_ref
57 pop = self.pop
58 if pop:
59 action = 'pop'
60 else:
61 action = 'apply'
62 if self.index:
63 args = [action, '--index', ref]
64 else:
65 args = [action, ref]
66 status, out, err = self.git.stash(*args)
67 if status == 0:
68 Interaction.log_status(status, out, err)
69 else:
70 title = N_('Error')
71 cmdargs = core.list2cmdline(args)
72 Interaction.command_error(title, 'git stash ' + cmdargs, status, out, err)
73 self.model.update_status()
76 class DropStash(cmds.ContextCommand):
77 def __init__(self, context, stash_index):
78 super().__init__(context)
79 self.stash_ref = 'refs/' + stash_index
81 def do(self):
82 git = self.git
83 status, out, err = git.stash('drop', self.stash_ref)
84 if status == 0:
85 Interaction.log_status(status, out, err)
86 match = re.search(r'\((.*)\)', out)
87 if match:
88 return match.group(1)
89 return ''
90 title = N_('Error')
91 Interaction.command_error(
92 title, 'git stash drop ' + self.stash_ref, status, out, err
94 return ''
97 class SaveStash(cmds.ContextCommand):
98 def __init__(self, context, stash_name, keep_index):
99 super().__init__(context)
100 self.stash_name = stash_name
101 self.keep_index = keep_index
103 def do(self):
104 if self.keep_index:
105 args = ['push', '--keep-index', '-m', self.stash_name]
106 else:
107 args = ['push', '-m', self.stash_name]
108 status, out, err = self.git.stash(*args)
109 if status == 0:
110 Interaction.log_status(status, out, err)
111 else:
112 title = N_('Error')
113 cmdargs = core.list2cmdline(args)
114 Interaction.command_error(title, 'git stash ' + cmdargs, status, out, err)
116 self.model.update_status()
119 class RenameStash(cmds.ContextCommand):
120 """Rename the stash"""
122 def __init__(self, context, stash_index, stash_name):
123 super().__init__(context)
124 self.context = context
125 self.stash_index = stash_index
126 self.stash_name = stash_name
128 def do(self):
129 # Drop the stash first and get the returned ref
130 ref = DropStash(self.context, self.stash_index).do()
131 # Store the stash with a new name
132 if ref:
133 args = ['store', '-m', self.stash_name, ref]
134 status, out, err = self.git.stash(*args)
135 if status == 0:
136 Interaction.log_status(status, out, err)
137 else:
138 title = N_('Error')
139 cmdargs = core.list2cmdline(args)
140 Interaction.command_error(
141 title, 'git stash ' + cmdargs, status, out, err
143 else:
144 title = N_('Error Renaming Stash')
145 msg = N_('"git stash drop" did not return a ref to rename.')
146 Interaction.critical(title, message=msg)
148 self.model.update_status()
151 class StashIndex(cmds.ContextCommand):
152 """Stash the index away"""
154 def __init__(self, context, stash_name):
155 super().__init__(context)
156 self.stash_name = stash_name
158 def do(self):
159 # Manually create a stash representing the index state
160 context = self.context
161 git = self.git
162 name = self.stash_name
163 branch = gitcmds.current_branch(context)
164 head = gitcmds.rev_parse(context, 'HEAD')
165 message = f'On {branch}: {name}'
167 # Get the message used for the "index" commit
168 status, out, err = git.rev_list('HEAD', '--', oneline=True, n=1)
169 if status != 0:
170 stash_error('rev-list', status, out, err)
171 return
172 head_msg = out.strip()
174 # Create a commit representing the index
175 status, out, err = git.write_tree()
176 if status != 0:
177 stash_error('write-tree', status, out, err)
178 return
179 index_tree = out.strip()
181 status, out, err = git.commit_tree(
182 '-m', f'index on {branch}: {head_msg}', '-p', head, index_tree
184 if status != 0:
185 stash_error('commit-tree', status, out, err)
186 return
187 index_commit = out.strip()
189 # Create a commit representing the worktree
190 status, out, err = git.commit_tree(
191 '-p', head, '-p', index_commit, '-m', message, index_tree
193 if status != 0:
194 stash_error('commit-tree', status, out, err)
195 return
196 worktree_commit = out.strip()
198 # Record the stash entry
199 status, out, err = git.update_ref(
200 '-m', message, 'refs/stash', worktree_commit, create_reflog=True
202 if status != 0:
203 stash_error('update-ref', status, out, err)
204 return
206 # Sync the worktree with the post-stash state. We've created the
207 # stash ref, so now we have to remove the staged changes from the
208 # worktree. We do this by applying a reverse diff of the staged
209 # changes. The diff from stash->HEAD is a reverse diff of the stash.
210 patch = utils.tmp_filename('stash')
211 with core.xopen(patch, mode='wb') as patch_fd:
212 status, out, err = git.diff_tree(
213 'refs/stash', 'HEAD', '--', binary=True, _stdout=patch_fd
215 if status != 0:
216 stash_error('diff-tree', status, out, err)
217 return
219 # Apply the patch
220 status, out, err = git.apply(patch)
221 core.unlink(patch)
222 ok = status == 0
223 if ok:
224 # Finally, clear the index we just stashed
225 git.reset()
226 else:
227 stash_error('apply', status, out, err)
229 self.model.update_status()
232 def stash_error(cmd, status, out, err):
233 title = N_('Error creating stash')
234 Interaction.command_error(title, cmd, status, out, err)