8 from plugin
import YapPlugin
11 class ShellError(Exception):
12 def __init__(self
, cmd
, rc
):
17 return "%s returned %d" % (self
.cmd
, self
.rc
)
19 class YapError(Exception):
20 def __init__(self
, msg
):
30 plugindir
= os
.path
.expanduser("~/.yap/plugins")
31 for p
in glob
.glob(os
.path
.join(plugindir
, "*.py")):
34 for cls
in glbls
.values():
35 if not type(cls
) == type:
37 if not issubclass(cls
, YapPlugin
):
45 if not func
.startswith('cmd_'):
47 if func
in self
.overrides
:
48 print >>sys
.stderr
, "Plugin %s overrides already overridden function %s. Disabling" % (p
, func
)
49 self
.plugins
.remove(x
)
52 def _add_new_file(self
, file):
53 repo
= get_output('git rev-parse --git-dir')[0]
54 dir = os
.path
.join(repo
, 'yap')
59 files
= self
._get
_new
_files
()
61 path
= os
.path
.join(dir, 'new-files')
62 pickle
.dump(files
, open(path
, 'w'))
64 def _get_new_files(self
):
65 repo
= get_output('git rev-parse --git-dir')[0]
66 path
= os
.path
.join(repo
, 'yap', 'new-files')
68 files
= pickle
.load(file(path
))
75 if get_output("git ls-files --cached '%s'" % f
) != []:
80 def _remove_new_file(self
, file):
81 files
= self
._get
_new
_files
()
82 files
= filter(lambda x
: x
!= file, files
)
84 repo
= get_output('git rev-parse --git-dir')[0]
85 path
= os
.path
.join(repo
, 'yap', 'new-files')
86 pickle
.dump(files
, open(path
, 'w'))
88 def _clear_new_files(self
):
89 repo
= get_output('git rev-parse --git-dir')[0]
90 path
= os
.path
.join(repo
, 'yap', 'new-files')
93 def _assert_file_exists(self
, file):
94 if not os
.access(file, os
.R_OK
):
95 raise YapError("No such file: %s" % file)
97 def _get_staged_files(self
):
98 if run_command("git rev-parse HEAD"):
99 files
= get_output("git ls-files --cached")
101 files
= get_output("git diff-index --cached --name-only HEAD")
104 def _get_unstaged_files(self
):
105 files
= self
._get
_new
_files
()
106 files
+= get_output("git ls-files -m")
109 def _delete_branch(self
, branch
, force
):
110 current
= get_output("git symbolic-ref HEAD")[0]
111 current
= current
.replace('refs/heads/', '')
112 if branch
== current
:
113 raise YapError("Can't delete current branch")
115 ref
= get_output("git rev-parse --verify 'refs/heads/%s'" % branch
)
117 raise YapError("No such branch: %s" % branch
)
118 run_safely("git update-ref -d 'refs/heads/%s' '%s'" % (branch
, ref
[0]))
121 name
= get_output("git name-rev --name-only '%s'" % ref
[0])[0]
122 if name
== 'undefined':
123 run_command("git update-ref 'refs/heads/%s' '%s'" % (branch
, ref
[0]))
124 raise YapError("Refusing to delete leaf branch (use -f to force)")
125 def _get_pager_cmd(self
):
126 if 'YAP_PAGER' in os
.environ
:
127 return os
.environ
['YAP_PAGER']
128 elif 'GIT_PAGER' in os
.environ
:
129 return os
.environ
['GIT_PAGER']
130 elif 'PAGER' in os
.environ
:
131 return os
.environ
['PAGER']
135 def _add_one(self
, file):
136 self
._assert
_file
_exists
(file)
137 x
= get_output("git ls-files '%s'" % file)
139 raise YapError("File '%s' already in repository" % file)
140 self
._add
_new
_file
(file)
142 def _rm_one(self
, file):
143 self
._assert
_file
_exists
(file)
144 if get_output("git ls-files '%s'" % file) != []:
145 run_safely("git rm --cached '%s'" % file)
146 self
._remove
_new
_file
(file)
148 def _stage_one(self
, file):
149 self
._assert
_file
_exists
(file)
150 run_safely("git update-index --add '%s'" % file)
152 def _unstage_one(self
, file):
153 self
._assert
_file
_exists
(file)
154 if run_command("git rev-parse HEAD"):
155 run_safely("git update-index --force-remove '%s'" % file)
157 run_safely("git diff-index -p HEAD '%s' | git apply -R --cached" % file)
159 def _revert_one(self
, file):
160 self
._assert
_file
_exists
(file)
161 self
._unstage
_one
(file)
162 run_safely("git checkout-index -u -f '%s'" % file)
164 def _parse_commit(self
, commit
):
165 lines
= get_output("git cat-file commit '%s'" % commit
)
170 if mode
!= 'commit' and l
.strip() == "":
175 commit
['log'].append(l
)
182 commit
['log'] = '\n'.join(commit
['log'])
185 def _check_commit(self
, **flags
):
186 if '-a' in flags
and '-d' in flags
:
187 raise YapError("Conflicting flags: -a and -d")
189 if '-d' not in flags
and self
._get
_unstaged
_files
():
190 if '-a' not in flags
and self
._get
_staged
_files
():
191 raise YapError("Staged and unstaged changes present. Specify what to commit")
192 os
.system("git diff-files -p | git apply --cached")
193 for f
in self
._get
_new
_files
():
196 def _do_uncommit(self
):
197 commit
= self
._parse
_commit
("HEAD")
198 repo
= get_output('git rev-parse --git-dir')[0]
199 dir = os
.path
.join(repo
, 'yap')
204 msg_file
= os
.path
.join(dir, 'msg')
205 fd
= file(msg_file
, 'w')
206 print >>fd
, commit
['log']
209 tree
= get_output("git rev-parse --verify HEAD^")
210 run_safely("git update-ref -m uncommit HEAD '%s'" % tree
[0])
212 def _do_commit(self
, msg
=None):
213 tree
= get_output("git write-tree")[0]
214 parent
= get_output("git rev-parse --verify HEAD 2> /dev/null")[0]
216 if os
.environ
.has_key('YAP_EDITOR'):
217 editor
= os
.environ
['YAP_EDITOR']
218 elif os
.environ
.has_key('GIT_EDITOR'):
219 editor
= os
.environ
['GIT_EDITOR']
220 elif os
.environ
.has_key('EDITOR'):
221 editor
= os
.environ
['EDITOR']
225 fd
, tmpfile
= tempfile
.mkstemp("yap")
230 repo
= get_output('git rev-parse --git-dir')[0]
231 msg_file
= os
.path
.join(repo
, 'yap', 'msg')
232 if os
.access(msg_file
, os
.R_OK
):
234 fd2
= file(tmpfile
, 'w')
235 for l
in fd1
.xreadlines():
236 print >>fd2
, l
.strip()
239 if os
.system("%s '%s'" % (editor
, tmpfile
)) != 0:
240 raise YapError("Editing commit message failed")
247 raise YapError("Refusing to use empty commit message")
249 (fd_w
, fd_r
) = os
.popen2("git stripspace > %s" % tmpfile
)
255 commit
= get_output("git commit-tree '%s' -p '%s' < '%s'" % (tree
, parent
, tmpfile
))
257 commit
= get_output("git commit-tree '%s' < '%s'" % (tree
, tmpfile
))
260 run_safely("git update-ref HEAD '%s'" % commit
[0])
262 def _check_rebasing(self
):
263 repo
= get_output('git rev-parse --git-dir')[0]
264 dotest
= os
.path
.join(repo
, '.dotest')
265 if os
.access(dotest
, os
.R_OK
):
266 raise YapError("A git operation is in progress. Complete it first")
267 dotest
= os
.path
.join(repo
, '..', '.dotest')
268 if os
.access(dotest
, os
.R_OK
):
269 raise YapError("A git operation is in progress. Complete it first")
271 def _list_remotes(self
):
272 remotes
= get_output("git config --get-regexp '^remote.*.url'")
274 remote
, url
= x
.split(' ')
275 remote
= remote
.replace('remote.', '')
276 remote
= remote
.replace('.url', '')
279 @short_help("make a local copy of an existing repository")
281 The first argument is a URL to the existing repository. This can be an
282 absolute path if the repository is local, or a URL with the git://,
283 ssh://, or http:// schemes. By default, the directory used is the last
284 component of the URL, sans '.git'. This can be overridden by providing
287 def cmd_clone(self
, url
, directory
=None):
290 if '://' not in url
and url
[0] != '/':
291 url
= os
.path
.join(os
.getcwd(), url
)
293 if directory
is None:
294 directory
= url
.rsplit('/')[-1]
295 directory
= directory
.replace('.git', '')
300 self
.cmd_repo("origin", url
)
301 self
.cmd_fetch("origin")
304 if not run_command("git rev-parse --verify refs/remotes/origin/HEAD"):
305 hash = get_output("git rev-parse refs/remotes/origin/HEAD")[0]
306 for b
in get_output("git for-each-ref --format='%(refname)' 'refs/remotes/origin/*'"):
307 if get_output("git rev-parse %s" % b
)[0] == hash:
311 if not run_command("git rev-parse --verify refs/remotes/origin/master"):
312 branch
= "refs/remotes/origin/master"
314 branch
= get_output("git for-each-ref --format='%(refname)' 'refs/remotes/origin/*'")
317 hash = get_output("git rev-parse %s" % branch
)
319 branch
= branch
.replace('refs/remotes/origin/', '')
320 run_safely("git update-ref refs/heads/%s %s" % (branch
, hash[0]))
321 run_safely("git symbolic-ref HEAD refs/heads/%s" % branch
)
322 self
.cmd_revert(**{'-a': 1})
324 @short_help("turn a directory into a repository")
326 Converts the current working directory into a repository. The primary
327 side-effect of this command is the creation of a '.git' subdirectory.
328 No files are added nor commits made.
331 os
.system("git init")
333 @short_help("add a new file to the repository")
335 The arguments are the files to be added to the repository. Once added,
336 the files will show as "unstaged changes" in the output of 'status'. To
337 reverse the effects of this command, see 'rm'.
339 def cmd_add(self
, *files
):
348 @short_help("delete a file from the repository")
350 The arguments are the files to be removed from the current revision of
351 the repository. The files will still exist in any past commits that the
352 files may have been a part of. The file is not actually deleted, it is
353 just no longer tracked as part of the repository.
355 def cmd_rm(self
, *files
):
364 @short_help("stage changes in a file for commit")
366 The arguments are the files to be staged. Staging changes is a way to
367 build up a commit when you do not want to commit all changes at once.
368 To commit only staged changes, use the '-d' flag to 'commit.' To
369 reverse the effects of this command, see 'unstage'. Once staged, the
370 files will show as "staged changes" in the output of 'status'.
372 def cmd_stage(self
, *files
):
381 @short_help("unstage changes in a file")
383 The arguments are the files to be unstaged. Once unstaged, the files
384 will show as "unstaged changes" in the output of 'status'. The '-a'
385 flag can be used to unstage all staged changes at once.
388 def cmd_unstage(self
, *files
, **flags
):
392 run_safely("git read-tree -m HEAD")
394 run_safely("git read-tree HEAD")
395 run_safely("git update-index -q --refresh")
406 @short_help("show files with staged and unstaged changes")
408 Show the files in the repository with changes since the last commit,
409 categorized based on whether the changes are staged or not. A file may
410 appear under each heading if the same file has both staged and unstaged
413 def cmd_status(self
):
415 branch
= get_output("git symbolic-ref HEAD")[0]
416 branch
= branch
.replace('refs/heads/', '')
417 print "Current branch: %s" % branch
419 print "Files with staged changes:"
420 files
= self
._get
_staged
_files
()
426 print "Files with unstaged changes:"
427 prefix
= get_output("git rev-parse --show-prefix")
428 files
= self
._get
_unstaged
_files
()
431 f
= os
.path
.join(prefix
[0], f
)
436 @short_help("remove uncommitted changes from a file (*)")
438 The arguments are the files whose changes will be reverted. If the '-a'
439 flag is given, then all files will have uncommitted changes removed.
440 Note that there is no way to reverse this command short of manually
441 editing each file again.
444 def cmd_revert(self
, *files
, **flags
):
447 run_safely("git read-tree -u -m HEAD")
448 run_safely("git checkout-index -u -f -a")
459 @short_help("record changes to files as a new commit")
461 Create a new commit recording changes since the last commit. If there
462 are only unstaged changes, those will be recorded. If there are only
463 staged changes, those will be recorded. Otherwise, you will have to
464 specify either the '-a' flag or the '-d' flag to commit all changes or
465 only staged changes, respectively. To reverse the effects of this
466 command, see 'uncommit'.
468 @takes_options("adm:")
469 def cmd_commit(self
, **flags
):
471 self
._check
_rebasing
()
472 self
._check
_commit
(**flags
)
473 if not self
._get
_staged
_files
():
474 raise YapError("No changes to commit")
475 msg
= flags
.get('-m', None)
479 @short_help("reverse the actions of the last commit")
481 Reverse the effects of the last 'commit' operation. The changes that
482 were part of the previous commit will show as "staged changes" in the
483 output of 'status'. This means that if no files were changed since the
484 last commit was created, 'uncommit' followed by 'commit' is a lossless
487 def cmd_uncommit(self
):
492 @short_help("report the current version of yap")
493 def cmd_version(self
):
494 print "Yap version 0.1"
496 @short_help("show the changelog for particular versions or files")
498 The arguments are the files with which to filter history. If none are
499 given, all changes are listed. Otherwise only commits that affected one
500 or more of the given files are listed. The -r option changes the
501 starting revision for traversing history. By default, history is listed
505 def cmd_log(self
, *paths
, **flags
):
506 "[-r <rev>] <path>..."
507 rev
= flags
.get('-r', 'HEAD')
508 paths
= ' '.join(paths
)
509 os
.system("git log --name-status '%s' -- %s" % (rev
, paths
))
511 @short_help("show staged, unstaged, or all uncommitted changes")
513 Show staged, unstaged, or all uncommitted changes. By default, all
514 changes are shown. The '-u' flag causes only unstaged changes to be
515 shown. The '-d' flag causes only staged changes to be shown.
518 def cmd_diff(self
, **flags
):
520 if '-u' in flags
and '-d' in flags
:
521 raise YapError("Conflicting flags: -u and -d")
523 pager
= self
._get
_pager
_cmd
()
526 os
.system("git diff-files -p | %s" % pager
)
528 os
.system("git diff-index --cached -p HEAD | %s" % pager
)
530 os
.system("git diff-index -p HEAD | %s" % pager
)
532 @short_help("list, create, or delete branches")
534 If no arguments are specified, a list of local branches is given. The
535 current branch is indicated by a "*" next to the name. If an argument
536 is given, it is taken as the name of a new branch to create. The branch
537 will start pointing at the current HEAD. See 'point' for details on
538 changing the revision of the new branch. Note that this command does
539 not switch the current working branch. See 'switch' for details on
540 changing the current working branch.
542 The '-d' flag can be used to delete local branches. If the delete
543 operation would remove the last branch reference to a given line of
544 history (colloquially referred to as "dangling commits"), yap will
545 report an error and abort. The '-f' flag can be used to force the delete
548 @takes_options("fd:")
549 def cmd_branch(self
, branch
=None, **flags
):
550 "[ [-f] -d <branch> | <branch> ]"
551 force
= '-f' in flags
553 self
._delete
_branch
(flags
['-d'], force
)
557 if branch
is not None:
558 ref
= get_output("git rev-parse --verify HEAD")
560 raise YapError("No branch point yet. Make a commit")
561 run_safely("git update-ref 'refs/heads/%s' '%s'" % (branch
, ref
[0]))
563 current
= get_output("git symbolic-ref HEAD")[0]
564 branches
= get_output("git for-each-ref --format='%(refname)' 'refs/heads/*'")
570 b
= b
.replace('refs/heads/', '')
573 @short_help("change the current working branch")
575 The argument is the name of the branch to make the current working
576 branch. This command will fail if there are uncommitted changes to any
577 files. Otherwise, the contents of the files in the working directory
578 are updated to reflect their state in the new branch. Additionally, any
579 future commits are added to the new branch instead of the previous line
582 def cmd_switch(self
, branch
):
584 ref
= get_output("git rev-parse --verify 'refs/heads/%s'" % branch
)
586 raise YapError("No such branch: %s" % branch
)
588 # XXX: support merging like git-checkout
589 if self
._get
_unstaged
_files
() or self
._get
_staged
_files
():
590 raise YapError("You have uncommitted changes. Commit them first")
592 run_safely("git symbolic-ref HEAD refs/heads/'%s'" % branch
)
593 run_safely("git read-tree -u -m HEAD")
594 run_safely("git checkout-index -u -f -a")
597 @short_help("move the current branch to a different revision")
599 The argument is the hash of the commit to which the current branch
600 should point, or alternately a branch or tag (a.k.a, "committish"). If
601 moving the branch would create "dangling commits" (see 'branch'), yap
602 will report an error and abort. The '-f' flag can be used to force the
603 operation in spite of this.
606 def cmd_point(self
, where
, **flags
):
608 head
= get_output("git rev-parse --verify HEAD")
610 raise YapError("No commit yet; nowhere to point")
612 ref
= get_output("git rev-parse --verify '%s'" % where
)
614 raise YapError("Not a valid ref: %s" % where
)
616 if self
._get
_unstaged
_files
() or self
._get
_staged
_files
():
617 raise YapError("You have uncommitted changes. Commit them first")
619 type = get_output("git cat-file -t '%s'" % ref
[0])
620 if type and type[0] == "tag":
621 tag
= get_output("git cat-file tag '%s'" % ref
[0])
622 ref
[0] = tag
[0].split(' ')[1]
624 run_safely("git update-ref HEAD '%s'" % ref
[0])
626 if '-f' not in flags
:
627 name
= get_output("git name-rev --name-only '%s'" % head
[0])[0]
628 if name
== "undefined":
629 os
.system("git update-ref HEAD '%s'" % head
[0])
630 raise YapError("Pointing there will lose commits. Use -f to force")
632 run_safely("git read-tree -u -m HEAD")
633 run_safely("git checkout-index -u -f -a")
635 @short_help("alter history by dropping or amending commits")
637 This command operates in two distinct modes, "amend" and "drop" mode.
638 In drop mode, the given commit is removed from the history of the
639 current branch, as though that commit never happened. By default the
642 In amend mode, the uncommitted changes present are merged into a
643 previous commit. This is useful for correcting typos or adding missed
644 files into past commits. By default the commit used is HEAD.
646 While rewriting history it is possible that conflicts will arise. If
647 this happens, the rewrite will pause and you will be prompted to resolve
648 the conflicts and stage them. Once that is done, you will run "yap
649 history continue." If instead you want the conflicting commit removed
650 from history (perhaps your changes supercede that commit) you can run
651 "yap history skip". Once the rewrite completes, your branch will be on
652 the same commit as when the rewrite started.
654 def cmd_history(self
, subcmd
, *args
):
655 "amend | drop <commit>"
657 if subcmd
not in ("amend", "drop", "continue", "skip"):
661 When you have resolved the conflicts run \"yap history continue\".
662 To skip the problematic patch, run \"yap history skip\"."""
664 if subcmd
== "continue":
665 os
.system("git am -3 -r --resolvemsg='%s'" % resolvemsg
)
668 os
.system("git reset --hard")
669 os
.system("git am -3 --skip --resolvemsg='%s'" % resolvemsg
)
672 if subcmd
== "amend":
673 flags
, args
= getopt
.getopt(args
, "ad")
683 if run_command("git rev-parse --verify '%s'" % commit
):
684 raise YapError("Not a valid commit: %s" % commit
)
686 self
._check
_rebasing
()
688 if subcmd
== "amend":
689 self
._check
_commit
(**flags
)
690 if self
._get
_unstaged
_files
():
691 # XXX: handle unstaged changes better
692 raise YapError("Commit away changes that you aren't amending")
695 stash
= get_output("git stash create")
696 run_command("git reset --hard")
697 if subcmd
== "amend" and not stash
:
698 raise YapError("Failed to stash; no changes?")
701 fd
, tmpfile
= tempfile
.mkstemp("yap")
703 os
.system("git format-patch -k --stdout '%s' > %s" % (commit
, tmpfile
))
704 if subcmd
== "amend":
705 self
.cmd_point(commit
, **{'-f': True})
707 if subcmd
== "amend":
708 rc
= os
.system("git stash apply --index %s" % stash
[0])
710 raise YapError("Failed to apply stash")
713 if subcmd
== "amend":
717 self
.cmd_point("%s^" % commit
, **{'-f': True})
719 stat
= os
.stat(tmpfile
)
722 rc
= os
.system("git am -3 --resolvemsg=\'%s\' %s" % (resolvemsg
, tmpfile
))
724 raise YapError("Failed to apply changes")
727 run_command("git stash apply --index %s" % stash
[0])
732 @short_help("show the changes introduced by a given commit")
734 By default, the changes in the last commit are shown. To override this,
735 specify a hash, branch, or tag (committish). The hash of the commit,
736 the commit's author, log message, and a diff of the changes are shown.
738 def cmd_show(self
, commit
="HEAD"):
740 os
.system("git show '%s'" % commit
)
742 @short_help("apply the changes in a given commit to the current branch")
744 The argument is the hash, branch, or tag (committish) of the commit to
745 be applied. In general, it only makes sense to apply commits that
746 happened on another branch. The '-r' flag can be used to have the
747 changes in the given commit reversed from the current branch. In
748 general, this only makes sense for commits that happened on the current
752 def cmd_cherry_pick(self
, commit
, **flags
):
755 os
.system("git revert '%s'" % commit
)
757 os
.system("git cherry-pick '%s'" % commit
)
759 @short_help("list, add, or delete configured remote repositories")
761 When invoked with no arguments, this command will show the list of
762 currently configured remote repositories, giving both the name and URL
763 of each. To add a new repository, give the desired name as the first
764 argument and the URL as the second. The '-d' flag can be used to remove
765 a previously added repository.
768 def cmd_repo(self
, name
=None, url
=None, **flags
):
769 "[<name> <url> | -d <name>]"
770 if name
is not None and url
is None:
774 if flags
['-d'] not in [ x
[0] for x
in self
._list
_remotes
() ]:
775 raise YapError("No such repository: %s" % flags
['-d'])
776 os
.system("git config --unset remote.%s.url" % flags
['-d'])
777 os
.system("git config --unset remote.%s.fetch" % flags
['-d'])
780 if name
in [ x
[0] for x
in self
._list
_remotes
() ]:
781 raise YapError("Repository '%s' already exists" % flags
['-d'])
782 os
.system("git config remote.%s.url %s" % (name
, url
))
783 os
.system("git config remote.%s.fetch +refs/heads/*:refs/remotes/%s/*" % (name
, name
))
785 for remote
, url
in self
._list
_remotes
():
786 print "%-20s %s" % (remote
, url
)
789 def cmd_push(self
, repo
, **flags
):
792 if repo
not in [ x
[0] for x
in self
._list
_remotes
() ]:
793 raise YapError("No such repository: %s" % repo
)
795 current
= get_output("git symbolic-ref HEAD")
797 raise YapError("Not on a branch!")
799 current
= current
[0].replace('refs/heads/', '')
800 remote
= get_output("git config branch.%s.remote" % current
)
801 if remote
and remote
[0] == repo
:
802 merge
= get_output("git config branch.%s.merge" % current
)
806 if '-c' not in flags
and '-d' not in flags
:
807 if run_command("git rev-parse --verify refs/remotes/%s/%s"
808 % (repo
, ref
.replace('refs/heads/', ''))):
809 raise YapError("No matching branch on that repo. Use -c to create a new branch there.")
814 lhs
= "refs/heads/%s" % current
815 rc
= os
.system("git push %s %s:%s" % (repo
, lhs
, ref
))
817 raise YapError("Push failed.")
819 def cmd_fetch(self
, repo
):
820 # XXX allow defaulting of repo? yap.default
821 if repo
not in [ x
[0] for x
in self
._list
_remotes
() ]:
822 raise YapError("No such repository: %s" % repo
)
823 os
.system("git fetch %s" % repo
)
825 def cmd_help(self
, cmd
=None):
828 attr
= self
.__getattribute
__("cmd_"+cmd
.replace('-', '_'))
829 except AttributeError:
830 raise YapError("No such command: %s" % cmd
)
832 help = attr
.long_help
833 except AttributeError:
834 raise YapError("Sorry, no help for '%s'. Ask Steven." % cmd
)
836 print >>sys
.stderr
, "The '%s' command" % cmd
837 print >>sys
.stderr
, "\tyap %s %s" % (cmd
, attr
.__doc
__)
838 print >>sys
.stderr
, "%s" % help
841 print >> sys
.stderr
, "Yet Another (Git) Porcelein"
844 for name
in dir(self
):
845 if not name
.startswith('cmd_'):
847 attr
= self
.__getattribute
__(name
)
848 if not callable(attr
):
851 short_msg
= attr
.short_help
852 except AttributeError:
855 name
= name
.replace('cmd_', '')
856 name
= name
.replace('_', '-')
857 print >> sys
.stderr
, "%-16s%s" % (name
, short_msg
)
859 print >> sys
.stderr
, "(*) Indicates that the command is not readily reversible"
862 print >> sys
.stderr
, "usage: %s <command>" % os
.path
.basename(sys
.argv
[0])
863 print >> sys
.stderr
, " valid commands: help init clone add rm stage unstage status revert commit uncommit log show diff branch switch point cherry-pick repo history version"
865 def main(self
, args
):
873 debug
= os
.getenv('YAP_DEBUG')
876 command
= command
.replace('-', '_')
879 for p
in self
.plugins
:
881 meth
= p
.__getattribute
__("cmd_"+command
)
882 except AttributeError:
886 default_meth
= self
.__getattribute
__("cmd_"+command
)
887 except AttributeError:
896 if "options" in meth
.__dict
__:
897 options
= meth
.options
898 if default_meth
and "options" in default_meth
.__dict
__:
899 options
+= default_meth
.options
900 flags
, args
= getopt
.getopt(args
, options
)
906 for p
in self
.plugins
:
908 meth
= p
.__getattribute
__("pre_"+command
)
909 except AttributeError:
916 for p
in self
.plugins
:
918 meth
= p
.__getattribute
__("post_"+command
)
919 except AttributeError:
923 except (TypeError, getopt
.GetoptError
):
926 print "%s %s %s" % (sys
.argv
[0], command
, meth
.__doc
__)
928 print >> sys
.stderr
, e
930 except AttributeError: