main.model: Factor out tempfile handling
[git-cola.git] / cola / cmds.py
blobd0b560cb43d95dc7154fb9e59ba9e2db7709be30
1 import os
2 import sys
3 import platform
5 from cStringIO import StringIO
7 import cola
8 from cola import i18n
9 from cola import core
10 from cola import errors
11 from cola import gitcfg
12 from cola import gitcmds
13 from cola import utils
14 from cola import signals
15 from cola import cmdfactory
16 from cola import difftool
17 from cola.diffparse import DiffParser
18 from cola.models import selection
20 _notifier = cola.notifier()
21 _factory = cmdfactory.factory()
22 _config = gitcfg.instance()
25 class BaseCommand(object):
26 """Base class for all commands; provides the command pattern"""
27 def __init__(self):
28 self.undoable = False
30 def is_undoable(self):
31 """Can this be undone?"""
32 return self.undoable
34 def name(self):
35 """Return this command's name."""
36 return self.__class__.__name__
38 def do(self):
39 raise NotImplementedError('%s.do() is unimplemented' % self.name())
41 def undo(self):
42 raise NotImplementedError('%s.undo() is unimplemented' % self.name())
45 class Command(BaseCommand):
46 """Base class for commands that modify the main model"""
47 def __init__(self):
48 """Initialize the command and stash away values for use in do()"""
49 # These are commonly used so let's make it easier to write new commands.
50 BaseCommand.__init__(self)
51 self.model = cola.model()
53 self.old_diff_text = self.model.diff_text
54 self.old_filename = self.model.filename
55 self.old_mode = self.model.mode
56 self.old_head = self.model.head
58 self.new_diff_text = self.old_diff_text
59 self.new_filename = self.old_filename
60 self.new_head = self.old_head
61 self.new_mode = self.old_mode
63 def do(self):
64 """Perform the operation."""
65 self.model.set_diff_text(self.new_diff_text)
66 self.model.set_filename(self.new_filename)
67 self.model.set_head(self.new_head)
68 self.model.set_mode(self.new_mode)
70 def undo(self):
71 """Undo the operation."""
72 self.model.set_diff_text(self.old_diff_text)
73 self.model.set_filename(self.old_filename)
74 self.model.set_head(self.old_head)
75 self.model.set_mode(self.old_mode)
78 class AmendMode(Command):
79 """Try to amend a commit."""
80 def __init__(self, amend):
81 Command.__init__(self)
82 self.undoable = True
83 self.skip = False
84 self.amending = amend
85 self.old_commitmsg = self.model.commitmsg
87 if self.amending:
88 self.new_mode = self.model.mode_amend
89 self.new_head = 'HEAD^'
90 self.new_commitmsg = self.model.prev_commitmsg()
91 return
92 # else, amend unchecked, regular commit
93 self.new_mode = self.model.mode_none
94 self.new_head = 'HEAD'
95 self.new_diff_text = ''
96 self.new_commitmsg = self.model.commitmsg
97 # If we're going back into new-commit-mode then search the
98 # undo stack for a previous amend-commit-mode and grab the
99 # commit message at that point in time.
100 if not _factory.undostack:
101 return
102 undo_count = len(_factory.undostack)
103 for i in xrange(undo_count):
104 # Find the latest AmendMode command
105 idx = undo_count - i - 1
106 cmdobj = _factory.undostack[idx]
107 if type(cmdobj) is not AmendMode:
108 continue
109 if cmdobj.amending:
110 self.new_commitmsg = cmdobj.old_commitmsg
111 break
113 def do(self):
114 """Leave/enter amend mode."""
115 """Attempt to enter amend mode. Do not allow this when merging."""
116 if self.amending:
117 if os.path.exists(self.model.git.git_path('MERGE_HEAD')):
118 self.skip = True
119 _notifier.broadcast(signals.amend, False)
120 _factory.prompt_user(signals.information,
121 'Oops! Unmerged',
122 'You are in the middle of a merge.\n'
123 'You cannot amend while merging.')
124 return
125 self.skip = False
126 _notifier.broadcast(signals.amend, self.amending)
127 self.model.set_commitmsg(self.new_commitmsg)
128 Command.do(self)
129 self.model.update_file_status()
131 def undo(self):
132 if self.skip:
133 return
134 self.model.set_commitmsg(self.old_commitmsg)
135 Command.undo(self)
136 self.model.update_file_status()
139 class ApplyDiffSelection(Command):
140 def __init__(self, staged, selected, offset, selection, apply_to_worktree):
141 Command.__init__(self)
142 self.staged = staged
143 self.selected = selected
144 self.offset = offset
145 self.selection = selection
146 self.apply_to_worktree = apply_to_worktree
148 def do(self):
149 # The normal worktree vs index scenario
150 parser = DiffParser(self.model,
151 filename=self.model.filename,
152 cached=self.staged,
153 reverse=self.apply_to_worktree)
154 status, output = \
155 parser.process_diff_selection(self.selected,
156 self.offset,
157 self.selection,
158 apply_to_worktree=self.apply_to_worktree)
159 _notifier.broadcast(signals.log_cmd, status, output)
160 # Redo the diff to show changes
161 if self.staged:
162 diffcmd = DiffStaged([self.model.filename])
163 else:
164 diffcmd = Diff([self.model.filename])
165 diffcmd.do()
166 self.model.update_file_status()
169 class ApplyPatches(Command):
170 def __init__(self, patches):
171 Command.__init__(self)
172 patches.sort()
173 self.patches = patches
175 def do(self):
176 diff_text = ''
177 num_patches = len(self.patches)
178 orig_head = self.model.git.rev_parse('HEAD')
180 for idx, patch in enumerate(self.patches):
181 status, output = self.model.git.am(patch,
182 with_status=True,
183 with_stderr=True)
184 # Log the git-am command
185 _notifier.broadcast(signals.log_cmd, status, output)
187 if num_patches > 1:
188 diff = self.model.git.diff('HEAD^!', stat=True)
189 diff_text += 'Patch %d/%d - ' % (idx+1, num_patches)
190 diff_text += '%s:\n%s\n\n' % (os.path.basename(patch), diff)
192 diff_text += 'Summary:\n'
193 diff_text += self.model.git.diff(orig_head, stat=True)
195 # Display a diffstat
196 self.model.set_diff_text(diff_text)
198 self.model.update_file_status()
200 _factory.prompt_user(signals.information,
201 'Patch(es) Applied',
202 '%d patch(es) applied:\n\n%s' %
203 (len(self.patches),
204 '\n'.join(map(os.path.basename, self.patches))))
207 class HeadChangeCommand(Command):
208 """Changes the model's current head."""
209 def __init__(self, treeish):
210 Command.__init__(self)
211 self.new_head = treeish
212 self.new_diff_text = ''
214 def do(self):
215 Command.do(self)
216 self.model.update_file_status()
219 class Checkout(Command):
221 A command object for git-checkout.
223 'argv' is handed off directly to git.
226 def __init__(self, argv, checkout_branch=False):
227 Command.__init__(self)
228 self.argv = argv
229 self.checkout_branch = checkout_branch
230 self.new_diff_text = ''
232 def do(self):
233 status, output = self.model.git.checkout(with_stderr=True,
234 with_status=True, *self.argv)
235 _notifier.broadcast(signals.log_cmd, status, output)
236 if self.checkout_branch:
237 self.model.update_status()
238 else:
239 self.model.update_file_status()
242 class CheckoutBranch(Checkout):
243 """Checkout a branch."""
244 def __init__(self, branch, checkout_branch=True):
245 Checkout.__init__(self, [branch])
248 class CherryPick(Command):
249 """Cherry pick commits into the current branch."""
250 def __init__(self, commits):
251 Command.__init__(self)
252 self.commits = commits
254 def do(self):
255 self.model.cherry_pick_list(self.commits)
256 self.model.update_file_status()
259 class ResetMode(Command):
260 """Reset the mode and clear the model's diff text."""
261 def __init__(self):
262 Command.__init__(self)
263 self.new_mode = self.model.mode_none
264 self.new_head = 'HEAD'
265 self.new_diff_text = ''
267 def do(self):
268 Command.do(self)
269 self.model.update_file_status()
272 class Commit(ResetMode):
273 """Attempt to create a new commit."""
274 def __init__(self, amend, msg):
275 ResetMode.__init__(self)
276 self.amend = amend
277 self.msg = core.encode(msg)
278 self.old_commitmsg = self.model.commitmsg
279 self.new_commitmsg = ''
281 def do(self):
282 tmpfile = utils.tmp_filename('commit-message')
283 status, output = self.model.commit_with_msg(self.msg, tmpfile, amend=self.amend)
284 if status == 0:
285 ResetMode.do(self)
286 self.model.set_commitmsg(self.new_commitmsg)
287 title = 'Commit: '
288 else:
289 title = 'Commit failed: '
290 _notifier.broadcast(signals.log_cmd, status, title+output)
293 class Ignore(Command):
294 """Add files to .gitignore"""
295 def __init__(self, filenames):
296 Command.__init__(self)
297 self.filenames = filenames
299 def do(self):
300 new_additions = ''
301 for fname in self.filenames:
302 new_additions = new_additions + fname + '\n'
303 for_status = new_additions
304 if new_additions:
305 if os.path.exists('.gitignore'):
306 current_list = utils.slurp('.gitignore')
307 new_additions = new_additions + current_list
308 utils.write('.gitignore', new_additions)
309 _notifier.broadcast(signals.log_cmd,
311 'Added to .gitignore:\n%s' % for_status)
312 self.model.update_file_status()
315 class Delete(Command):
316 """Simply delete files."""
317 def __init__(self, filenames):
318 Command.__init__(self)
319 self.filenames = filenames
320 # We could git-hash-object stuff and provide undo-ability
321 # as an option. Heh.
322 def do(self):
323 rescan = False
324 for filename in self.filenames:
325 if filename:
326 try:
327 os.remove(filename)
328 rescan=True
329 except:
330 _factory.prompt_user(signals.information,
331 'Error'
332 'Deleting "%s" failed.' % filename)
333 if rescan:
334 self.model.update_file_status()
336 class DeleteBranch(Command):
337 """Delete a git branch."""
338 def __init__(self, branch):
339 Command.__init__(self)
340 self.branch = branch
342 def do(self):
343 status, output = self.model.delete_branch(self.branch)
344 title = ''
345 if output.startswith('error:'):
346 output = 'E' + output[1:]
347 else:
348 title = 'Info: '
349 _notifier.broadcast(signals.log_cmd, status, title + output)
352 class Diff(Command):
353 """Perform a diff and set the model's current text."""
354 def __init__(self, filenames, cached=False):
355 Command.__init__(self)
356 # Guard against the list of files being empty
357 if not filenames:
358 return
359 opts = {}
360 if cached:
361 cached = not self.model.read_only()
362 opts = dict(ref=self.model.head)
364 self.new_filename = filenames[0]
365 self.old_filename = self.model.filename
366 if not self.model.read_only():
367 if self.model.mode != self.model.mode_amend:
368 self.new_mode = self.model.mode_worktree
369 self.new_diff_text = gitcmds.diff_helper(filename=self.new_filename,
370 cached=cached, **opts)
373 class DiffMode(HeadChangeCommand):
374 """Enter diff mode and clear the model's diff text."""
375 def __init__(self, treeish):
376 HeadChangeCommand.__init__(self, treeish)
377 self.new_mode = self.model.mode_diff
380 class DiffExprMode(HeadChangeCommand):
381 """Enter diff-expr mode and clear the model's diff text."""
382 def __init__(self, treeish):
383 HeadChangeCommand.__init__(self, treeish)
384 self.new_mode = self.model.mode_diff_expr
387 class Diffstat(Command):
388 """Perform a diffstat and set the model's diff text."""
389 def __init__(self):
390 Command.__init__(self)
391 diff = self.model.git.diff(self.model.head,
392 unified=_config.get('diff.context', 3),
393 no_color=True,
394 M=True,
395 stat=True)
396 self.new_diff_text = core.decode(diff)
397 if not self.model.read_only():
398 if self.model.mode != self.model.mode_amend:
399 self.new_mode = self.model.mode_worktree
402 class DiffStaged(Diff):
403 """Perform a staged diff on a file."""
404 def __init__(self, filenames):
405 Diff.__init__(self, filenames, cached=True)
406 if not self.model.read_only():
407 if self.model.mode != self.model.mode_amend:
408 self.new_mode = self.model.mode_index
411 class DiffStagedSummary(Command):
412 def __init__(self):
413 Command.__init__(self)
414 cached = not self.model.read_only()
415 diff = self.model.git.diff(self.model.head,
416 cached=cached,
417 no_color=True,
418 patch_with_stat=True,
419 M=True)
420 self.new_diff_text = core.decode(diff)
421 if not self.model.read_only():
422 if self.model.mode != self.model.mode_amend:
423 self.new_mode = self.model.mode_index
426 class Difftool(Command):
427 """Run git-difftool limited by path."""
428 def __init__(self, staged, filenames):
429 Command.__init__(self)
430 self.staged = staged
431 self.filenames = filenames
433 def do(self):
434 if not self.filenames:
435 return
436 args = []
437 if self.staged and not self.model.read_only():
438 args.append('--cached')
439 if self.model.head != 'HEAD':
440 args.append(self.model.head)
441 args.append('--')
442 args.extend(self.filenames)
443 difftool.launch(args)
446 class Edit(Command):
447 """Edit a file using the configured gui.editor."""
448 def __init__(self, filenames, line_number=None):
449 Command.__init__(self)
450 self.filenames = filenames
451 self.line_number = line_number
453 def do(self):
454 filename = self.filenames[0]
455 if not os.path.exists(filename):
456 return
457 editor = self.model.editor()
458 if editor == 'gvim' and self.line_number:
459 utils.fork([editor, filename, '+'+self.line_number])
460 else:
461 utils.fork([editor, filename])
464 class FormatPatch(Command):
465 """Output a patch series given all revisions and a selected subset."""
466 def __init__(self, to_export, revs):
467 Command.__init__(self)
468 self.to_export = to_export
469 self.revs = revs
471 def do(self):
472 status, output = gitcmds.format_patchsets(self.to_export, self.revs)
473 _notifier.broadcast(signals.log_cmd, status, output)
476 class GrepMode(Command):
477 def __init__(self, txt):
478 """Perform a git-grep."""
479 Command.__init__(self)
480 self.new_mode = self.model.mode_grep
481 self.new_diff_text = core.decode(self.model.git.grep(txt, n=True))
484 class LoadCommitMessage(Command):
485 """Loads a commit message from a path."""
486 def __init__(self, path):
487 Command.__init__(self)
488 self.undoable = True
489 self.path = path
490 self.old_commitmsg = self.model.commitmsg
491 self.old_directory = self.model.directory
493 def do(self):
494 path = self.path
495 if not path or not os.path.isfile(path):
496 raise errors.UsageError('Error: cannot find commit template',
497 '%s: No such file or directory.' % path)
498 self.model.set_directory(os.path.dirname(path))
499 self.model.set_commitmsg(utils.slurp(path))
501 def undo(self):
502 self.model.set_commitmsg(self.old_commitmsg)
503 self.model.set_directory(self.old_directory)
506 class LoadCommitTemplate(LoadCommitMessage):
507 """Loads the commit message template specified by commit.template."""
508 def __init__(self):
509 LoadCommitMessage.__init__(self, _config.get('commit.template'))
511 def do(self):
512 if self.path is None:
513 raise errors.UsageError('Error: unconfigured commit template',
514 'A commit template has not been configured.\n'
515 'Use "git config" to define "commit.template"\n'
516 'so that it points to a commit template.')
517 return LoadCommitMessage.do(self)
520 class LoadPreviousMessage(Command):
521 """Try to amend a commit."""
522 def __init__(self, sha1):
523 Command.__init__(self)
524 self.sha1 = sha1
525 self.old_commitmsg = self.model.commitmsg
526 self.new_commitmsg = self.model.prev_commitmsg(sha1)
527 self.undoable = True
529 def do(self):
530 self.model.set_commitmsg(self.new_commitmsg)
532 def undo(self):
533 self.model.set_commitmsg(self.old_commitmsg)
536 class Mergetool(Command):
537 """Launch git-mergetool on a list of paths."""
538 def __init__(self, paths):
539 Command.__init__(self)
540 self.paths = paths
542 def do(self):
543 if not self.paths:
544 return
545 utils.fork(['git', 'mergetool', '--no-prompt', '--'] + self.paths)
548 class OpenRepo(Command):
549 """Launches git-cola on a repo."""
550 def __init__(self, dirname):
551 Command.__init__(self)
552 self.new_directory = dirname
554 def do(self):
555 self.model.set_directory(self.new_directory)
556 utils.fork([sys.executable, sys.argv[0], '--repo', self.new_directory])
559 class Clone(Command):
560 """Clones a repository and optionally spawns a new cola session."""
561 def __init__(self, url, destdir, spawn=True):
562 Command.__init__(self)
563 self.url = url
564 self.new_directory = destdir
565 self.spawn = spawn
567 def do(self):
568 self.model.git.clone(self.url, self.new_directory,
569 with_stderr=True, with_status=True)
570 if self.spawn:
571 utils.fork(['python', sys.argv[0], '--repo', self.new_directory])
574 rescan = 'rescan'
576 class Rescan(Command):
577 """Rescans for changes."""
578 def do(self):
579 self.model.update_status()
582 rescan_and_refresh = 'rescan_and_refresh'
584 class RescanAndRefresh(Command):
585 """Rescans for changes."""
586 def do(self):
587 self.model.update_status(update_index=True)
590 class ReviewBranchMode(Command):
591 """Enter into review-branch mode."""
592 def __init__(self, branch):
593 Command.__init__(self)
594 self.new_mode = self.model.mode_review
595 self.new_head = gitcmds.merge_base_parent(branch)
596 self.new_diff_text = ''
598 def do(self):
599 Command.do(self)
600 self.model.update_status()
603 class RunConfigAction(Command):
604 """Run a user-configured action, typically from the "Tools" menu"""
605 def __init__(self, name):
606 Command.__init__(self)
607 self.name = name
608 self.model = cola.model()
610 def do(self):
611 for env in ('FILENAME', 'REVISION', 'ARGS'):
612 try:
613 del os.environ[env]
614 except KeyError:
615 pass
616 rev = None
617 args = None
618 opts = _config.get_guitool_opts(self.name)
619 cmd = opts.get('cmd')
620 if 'title' not in opts:
621 opts['title'] = cmd
623 if 'prompt' not in opts or opts.get('prompt') is True:
624 prompt = i18n.gettext('Are you sure you want to run %s?') % cmd
625 opts['prompt'] = prompt
627 if opts.get('needsfile'):
628 filename = selection.filename()
629 if not filename:
630 _factory.prompt_user(signals.information,
631 'Please select a file',
632 '"%s" requires a selected file' % cmd)
633 return
634 os.environ['FILENAME'] = filename
636 if opts.get('revprompt') or opts.get('argprompt'):
637 while True:
638 ok = _factory.prompt_user(signals.run_config_action, cmd, opts)
639 if not ok:
640 return
641 rev = opts.get('revision')
642 args = opts.get('args')
643 if opts.get('revprompt') and not rev:
644 title = 'Invalid Revision'
645 msg = 'The revision expression cannot be empty.'
646 _factory.prompt_user(signals.critical, title, msg)
647 continue
648 break
650 elif opts.get('confirm'):
651 title = os.path.expandvars(opts.get('title'))
652 prompt = os.path.expandvars(opts.get('prompt'))
653 if not _factory.prompt_user(signals.question, title, prompt):
654 return
655 if rev:
656 os.environ['REVISION'] = rev
657 if args:
658 os.environ['ARGS'] = args
659 title = os.path.expandvars(cmd)
660 _notifier.broadcast(signals.log_cmd, 0, 'running: ' + title)
661 cmd = ['sh', '-c', cmd]
663 if opts.get('noconsole'):
664 status, out, err = utils.run_command(cmd, flag_error=False)
665 else:
666 status, out, err = _factory.prompt_user(signals.run_command,
667 title, cmd)
669 _notifier.broadcast(signals.log_cmd, status,
670 'stdout: %s\nstatus: %s\nstderr: %s' %
671 (out.rstrip(), status, err.rstrip()))
673 if not opts.get('norescan'):
674 self.model.update_status()
675 return status
678 class SetDiffText(Command):
679 def __init__(self, text):
680 Command.__init__(self)
681 self.undoable = True
682 self.new_diff_text = text
685 class ShowUntracked(Command):
686 """Show an untracked file."""
687 # We don't actually do anything other than set the mode right now.
688 # TODO check the mimetype for the file and handle things
689 # generically.
690 def __init__(self, filenames):
691 Command.__init__(self)
692 if not self.model.read_only():
693 if self.model.mode != self.model.mode_amend:
694 self.new_mode = self.model.mode_untracked
695 # TODO new_diff_text = utils.file_preview(filenames[0])
698 class SignOff(Command):
699 def __init__(self):
700 Command.__init__(self)
701 self.undoable = True
702 self.old_commitmsg = self.model.commitmsg
704 def do(self):
705 signoff = self.signoff()
706 if signoff in self.model.commitmsg:
707 return
708 self.model.set_commitmsg(self.model.commitmsg + '\n' + signoff)
710 def undo(self):
711 self.model.set_commitmsg(self.old_commitmsg)
713 def signoff(self):
714 try:
715 import pwd
716 user = pwd.getpwuid(os.getuid()).pw_name
717 except ImportError:
718 user = os.getenv('USER', 'unknown')
720 name = _config.get('user.name', user)
721 email = _config.get('user.email', '%s@%s' % (user, platform.node()))
722 return '\nSigned-off-by: %s <%s>' % (name, email)
725 class Stage(Command):
726 """Stage a set of paths."""
727 def __init__(self, paths):
728 Command.__init__(self)
729 self.paths = paths
731 def do(self):
732 msg = 'Staging: %s' % (', '.join(self.paths))
733 _notifier.broadcast(signals.log_cmd, 0, msg)
734 self.model.stage_paths(self.paths)
737 class StageModified(Stage):
738 """Stage all modified files."""
739 def __init__(self):
740 Stage.__init__(self, None)
741 self.paths = self.model.modified
744 class StageUnmerged(Stage):
745 """Stage all modified files."""
746 def __init__(self):
747 Stage.__init__(self, None)
748 self.paths = self.model.unmerged
751 class StageUntracked(Stage):
752 """Stage all untracked files."""
753 def __init__(self):
754 Stage.__init__(self, None)
755 self.paths = self.model.untracked
758 class Tag(Command):
759 """Create a tag object."""
760 def __init__(self, name, revision, sign=False, message=''):
761 Command.__init__(self)
762 self._name = name
763 self._message = core.encode(message)
764 self._revision = revision
765 self._sign = sign
767 def do(self):
768 log_msg = 'Tagging: "%s" as "%s"' % (self._revision, self._name)
769 opts = {}
770 if self._message:
771 opts['F'] = utils.tmp_filename('tag-message')
772 utils.write(opts['F'], self._message)
774 if self._sign:
775 log_msg += ', GPG-signed'
776 opts['s'] = True
777 status, output = self.model.git.tag(self._name,
778 self._revision,
779 with_status=True,
780 with_stderr=True,
781 **opts)
782 else:
783 opts['a'] = bool(self._message)
784 status, output = self.model.git.tag(self._name,
785 self._revision,
786 with_status=True,
787 with_stderr=True,
788 **opts)
789 if 'F' in opts:
790 os.unlink(opts['F'])
792 if output:
793 log_msg += '\nOutput:\n%s' % output
795 _notifier.broadcast(signals.log_cmd, status, log_msg)
796 if status == 0:
797 self.model.update_status()
800 class Unstage(Command):
801 """Unstage a set of paths."""
802 def __init__(self, paths):
803 Command.__init__(self)
804 self.paths = paths
806 def do(self):
807 msg = 'Unstaging: %s' % (', '.join(self.paths))
808 _notifier.broadcast(signals.log_cmd, 0, msg)
809 self.model.unstage_paths(self.paths)
812 class UnstageAll(Command):
813 """Unstage all files; resets the index."""
814 def do(self):
815 self.model.unstage_all()
818 class UnstageSelected(Unstage):
819 """Unstage selected files."""
820 def __init__(self):
821 Unstage.__init__(self, cola.selection_model().staged)
824 class UntrackedSummary(Command):
825 """List possible .gitignore rules as the diff text."""
826 def __init__(self):
827 Command.__init__(self)
828 untracked = self.model.untracked
829 suffix = len(untracked) > 1 and 's' or ''
830 io = StringIO()
831 io.write('# %s untracked file%s\n' % (len(untracked), suffix))
832 if untracked:
833 io.write('# possible .gitignore rule%s:\n' % suffix)
834 for u in untracked:
835 io.write('/'+core.encode(u))
836 self.new_diff_text = core.decode(io.getvalue())
838 if not self.model.read_only():
839 if self.model.mode != self.model.mode_amend:
840 self.new_mode = self.model.mode_untracked
843 class UpdateFileStatus(Command):
844 """Rescans for changes."""
845 def do(self):
846 self.model.update_file_status()
849 class VisualizeAll(Command):
850 """Visualize all branches."""
851 def do(self):
852 browser = self.model.history_browser()
853 utils.fork([browser, '--all'])
856 class VisualizeCurrent(Command):
857 """Visualize all branches."""
858 def do(self):
859 browser = self.model.history_browser()
860 utils.fork([browser, self.model.currentbranch])
863 class VisualizePaths(Command):
864 """Path-limited visualization."""
865 def __init__(self, paths):
866 Command.__init__(self)
867 browser = self.model.history_browser()
868 if paths:
869 self.argv = [browser] + paths
870 else:
871 self.argv = [browser]
873 def do(self):
874 utils.fork(self.argv)
877 visualize_revision = 'visualize_revision'
879 class VisualizeRevision(Command):
880 """Visualize a specific revision."""
881 def __init__(self, revision, paths=None):
882 Command.__init__(self)
883 self.revision = revision
884 self.paths = paths
886 def do(self):
887 argv = [self.model.history_browser()]
888 if self.revision:
889 argv.append(self.revision)
890 if self.paths:
891 argv.append('--')
892 argv.extend(self.paths)
893 utils.fork(argv)
896 def register():
898 Register signal mappings with the factory.
900 These commands are automatically created and run when
901 their corresponding signal is broadcast by the notifier.
904 signal_to_command_map = {
905 signals.amend_mode: AmendMode,
906 signals.apply_diff_selection: ApplyDiffSelection,
907 signals.apply_patches: ApplyPatches,
908 signals.clone: Clone,
909 signals.checkout: Checkout,
910 signals.checkout_branch: CheckoutBranch,
911 signals.cherry_pick: CherryPick,
912 signals.commit: Commit,
913 signals.delete: Delete,
914 signals.delete_branch: DeleteBranch,
915 signals.diff: Diff,
916 signals.diff_mode: DiffMode,
917 signals.diff_expr_mode: DiffExprMode,
918 signals.diff_staged: DiffStaged,
919 signals.diffstat: Diffstat,
920 signals.difftool: Difftool,
921 signals.edit: Edit,
922 signals.format_patch: FormatPatch,
923 signals.grep: GrepMode,
924 signals.ignore: Ignore,
925 signals.load_commit_message: LoadCommitMessage,
926 signals.load_commit_template: LoadCommitTemplate,
927 signals.load_previous_message: LoadPreviousMessage,
928 signals.modified_summary: Diffstat,
929 signals.mergetool: Mergetool,
930 signals.open_repo: OpenRepo,
931 signals.rescan: Rescan,
932 signals.rescan_and_refresh: RescanAndRefresh,
933 signals.reset_mode: ResetMode,
934 signals.review_branch_mode: ReviewBranchMode,
935 signals.run_config_action: RunConfigAction,
936 signals.set_diff_text: SetDiffText,
937 signals.show_untracked: ShowUntracked,
938 signals.signoff: SignOff,
939 signals.stage: Stage,
940 signals.stage_modified: StageModified,
941 signals.stage_unmerged: StageUnmerged,
942 signals.stage_untracked: StageUntracked,
943 signals.staged_summary: DiffStagedSummary,
944 signals.tag: Tag,
945 signals.unstage: Unstage,
946 signals.unstage_all: UnstageAll,
947 signals.unstage_selected: UnstageSelected,
948 signals.untracked_summary: UntrackedSummary,
949 signals.update_file_status: UpdateFileStatus,
950 signals.visualize_all: VisualizeAll,
951 signals.visualize_current: VisualizeCurrent,
952 signals.visualize_paths: VisualizePaths,
953 signals.visualize_revision: VisualizeRevision,
956 for signal, cmd in signal_to_command_map.iteritems():
957 _factory.add_global_command(signal, cmd)