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 def _unstage_all(self
):
281 run_safely("git read-tree -m HEAD")
283 run_safely("git read-tree HEAD")
284 run_safely("git update-index -q --refresh")
286 @short_help("make a local copy of an existing repository")
288 The first argument is a URL to the existing repository. This can be an
289 absolute path if the repository is local, or a URL with the git://,
290 ssh://, or http:// schemes. By default, the directory used is the last
291 component of the URL, sans '.git'. This can be overridden by providing
294 def cmd_clone(self
, url
, directory
=None):
297 if '://' not in url
and url
[0] != '/':
298 url
= os
.path
.join(os
.getcwd(), url
)
300 if directory
is None:
301 directory
= url
.rsplit('/')[-1]
302 directory
= directory
.replace('.git', '')
307 self
.cmd_repo("origin", url
)
308 self
.cmd_fetch("origin")
311 if not run_command("git rev-parse --verify refs/remotes/origin/HEAD"):
312 hash = get_output("git rev-parse refs/remotes/origin/HEAD")[0]
313 for b
in get_output("git for-each-ref --format='%(refname)' 'refs/remotes/origin/*'"):
314 if get_output("git rev-parse %s" % b
)[0] == hash:
318 if not run_command("git rev-parse --verify refs/remotes/origin/master"):
319 branch
= "refs/remotes/origin/master"
321 branch
= get_output("git for-each-ref --format='%(refname)' 'refs/remotes/origin/*'")
324 hash = get_output("git rev-parse %s" % branch
)
326 branch
= branch
.replace('refs/remotes/origin/', '')
327 run_safely("git update-ref refs/heads/%s %s" % (branch
, hash[0]))
328 run_safely("git symbolic-ref HEAD refs/heads/%s" % branch
)
329 self
.cmd_revert(**{'-a': 1})
331 @short_help("turn a directory into a repository")
333 Converts the current working directory into a repository. The primary
334 side-effect of this command is the creation of a '.git' subdirectory.
335 No files are added nor commits made.
338 os
.system("git init")
340 @short_help("add a new file to the repository")
342 The arguments are the files to be added to the repository. Once added,
343 the files will show as "unstaged changes" in the output of 'status'. To
344 reverse the effects of this command, see 'rm'.
346 def cmd_add(self
, *files
):
355 @short_help("delete a file from the repository")
357 The arguments are the files to be removed from the current revision of
358 the repository. The files will still exist in any past commits that the
359 files may have been a part of. The file is not actually deleted, it is
360 just no longer tracked as part of the repository.
362 def cmd_rm(self
, *files
):
371 @short_help("stage changes in a file for commit")
373 The arguments are the files to be staged. Staging changes is a way to
374 build up a commit when you do not want to commit all changes at once.
375 To commit only staged changes, use the '-d' flag to 'commit.' To
376 reverse the effects of this command, see 'unstage'. Once staged, the
377 files will show as "staged changes" in the output of 'status'.
379 def cmd_stage(self
, *files
):
388 @short_help("unstage changes in a file")
390 The arguments are the files to be unstaged. Once unstaged, the files
391 will show as "unstaged changes" in the output of 'status'. The '-a'
392 flag can be used to unstage all staged changes at once.
395 def cmd_unstage(self
, *files
, **flags
):
409 @short_help("show files with staged and unstaged changes")
411 Show the files in the repository with changes since the last commit,
412 categorized based on whether the changes are staged or not. A file may
413 appear under each heading if the same file has both staged and unstaged
416 def cmd_status(self
):
418 branch
= get_output("git symbolic-ref HEAD")[0]
419 branch
= branch
.replace('refs/heads/', '')
420 print "Current branch: %s" % branch
422 print "Files with staged changes:"
423 files
= self
._get
_staged
_files
()
429 print "Files with unstaged changes:"
430 prefix
= get_output("git rev-parse --show-prefix")
431 files
= self
._get
_unstaged
_files
()
434 f
= os
.path
.join(prefix
[0], f
)
439 @short_help("remove uncommitted changes from a file (*)")
441 The arguments are the files whose changes will be reverted. If the '-a'
442 flag is given, then all files will have uncommitted changes removed.
443 Note that there is no way to reverse this command short of manually
444 editing each file again.
447 def cmd_revert(self
, *files
, **flags
):
451 run_safely("git checkout-index -u -f -a")
462 @short_help("record changes to files as a new commit")
464 Create a new commit recording changes since the last commit. If there
465 are only unstaged changes, those will be recorded. If there are only
466 staged changes, those will be recorded. Otherwise, you will have to
467 specify either the '-a' flag or the '-d' flag to commit all changes or
468 only staged changes, respectively. To reverse the effects of this
469 command, see 'uncommit'.
471 @takes_options("adm:")
472 def cmd_commit(self
, **flags
):
474 self
._check
_rebasing
()
475 self
._check
_commit
(**flags
)
476 if not self
._get
_staged
_files
():
477 raise YapError("No changes to commit")
478 msg
= flags
.get('-m', None)
482 @short_help("reverse the actions of the last commit")
484 Reverse the effects of the last 'commit' operation. The changes that
485 were part of the previous commit will show as "staged changes" in the
486 output of 'status'. This means that if no files were changed since the
487 last commit was created, 'uncommit' followed by 'commit' is a lossless
490 def cmd_uncommit(self
):
495 @short_help("report the current version of yap")
496 def cmd_version(self
):
497 print "Yap version 0.1"
499 @short_help("show the changelog for particular versions or files")
501 The arguments are the files with which to filter history. If none are
502 given, all changes are listed. Otherwise only commits that affected one
503 or more of the given files are listed. The -r option changes the
504 starting revision for traversing history. By default, history is listed
508 def cmd_log(self
, *paths
, **flags
):
509 "[-r <rev>] <path>..."
510 rev
= flags
.get('-r', 'HEAD')
511 paths
= ' '.join(paths
)
512 os
.system("git log --name-status '%s' -- %s" % (rev
, paths
))
514 @short_help("show staged, unstaged, or all uncommitted changes")
516 Show staged, unstaged, or all uncommitted changes. By default, all
517 changes are shown. The '-u' flag causes only unstaged changes to be
518 shown. The '-d' flag causes only staged changes to be shown.
521 def cmd_diff(self
, **flags
):
523 if '-u' in flags
and '-d' in flags
:
524 raise YapError("Conflicting flags: -u and -d")
526 pager
= self
._get
_pager
_cmd
()
529 os
.system("git diff-files -p | %s" % pager
)
531 os
.system("git diff-index --cached -p HEAD | %s" % pager
)
533 os
.system("git diff-index -p HEAD | %s" % pager
)
535 @short_help("list, create, or delete branches")
537 If no arguments are specified, a list of local branches is given. The
538 current branch is indicated by a "*" next to the name. If an argument
539 is given, it is taken as the name of a new branch to create. The branch
540 will start pointing at the current HEAD. See 'point' for details on
541 changing the revision of the new branch. Note that this command does
542 not switch the current working branch. See 'switch' for details on
543 changing the current working branch.
545 The '-d' flag can be used to delete local branches. If the delete
546 operation would remove the last branch reference to a given line of
547 history (colloquially referred to as "dangling commits"), yap will
548 report an error and abort. The '-f' flag can be used to force the delete
551 @takes_options("fd:")
552 def cmd_branch(self
, branch
=None, **flags
):
553 "[ [-f] -d <branch> | <branch> ]"
554 force
= '-f' in flags
556 self
._delete
_branch
(flags
['-d'], force
)
560 if branch
is not None:
561 ref
= get_output("git rev-parse --verify HEAD")
563 raise YapError("No branch point yet. Make a commit")
564 run_safely("git update-ref 'refs/heads/%s' '%s'" % (branch
, ref
[0]))
566 current
= get_output("git symbolic-ref HEAD")[0]
567 branches
= get_output("git for-each-ref --format='%(refname)' 'refs/heads/*'")
573 b
= b
.replace('refs/heads/', '')
576 @short_help("change the current working branch")
578 The argument is the name of the branch to make the current working
579 branch. This command will fail if there are uncommitted changes to any
580 files. Otherwise, the contents of the files in the working directory
581 are updated to reflect their state in the new branch. Additionally, any
582 future commits are added to the new branch instead of the previous line
585 def cmd_switch(self
, branch
):
587 ref
= get_output("git rev-parse --verify 'refs/heads/%s'" % branch
)
589 raise YapError("No such branch: %s" % branch
)
591 # XXX: support merging like git-checkout
592 if self
._get
_unstaged
_files
() or self
._get
_staged
_files
():
593 raise YapError("You have uncommitted changes. Commit them first")
595 run_safely("git symbolic-ref HEAD refs/heads/'%s'" % branch
)
596 run_safely("git read-tree -u -m HEAD")
597 run_safely("git checkout-index -u -f -a")
600 @short_help("move the current branch to a different revision")
602 The argument is the hash of the commit to which the current branch
603 should point, or alternately a branch or tag (a.k.a, "committish"). If
604 moving the branch would create "dangling commits" (see 'branch'), yap
605 will report an error and abort. The '-f' flag can be used to force the
606 operation in spite of this.
609 def cmd_point(self
, where
, **flags
):
611 head
= get_output("git rev-parse --verify HEAD")
613 raise YapError("No commit yet; nowhere to point")
615 ref
= get_output("git rev-parse --verify '%s'" % where
)
617 raise YapError("Not a valid ref: %s" % where
)
619 if self
._get
_unstaged
_files
() or self
._get
_staged
_files
():
620 raise YapError("You have uncommitted changes. Commit them first")
622 type = get_output("git cat-file -t '%s'" % ref
[0])
623 if type and type[0] == "tag":
624 tag
= get_output("git cat-file tag '%s'" % ref
[0])
625 ref
[0] = tag
[0].split(' ')[1]
627 run_safely("git update-ref HEAD '%s'" % ref
[0])
629 if '-f' not in flags
:
630 name
= get_output("git name-rev --name-only '%s'" % head
[0])[0]
631 if name
== "undefined":
632 os
.system("git update-ref HEAD '%s'" % head
[0])
633 raise YapError("Pointing there will lose commits. Use -f to force")
635 run_safely("git read-tree -u -m HEAD")
636 run_safely("git checkout-index -u -f -a")
638 @short_help("alter history by dropping or amending commits")
640 This command operates in two distinct modes, "amend" and "drop" mode.
641 In drop mode, the given commit is removed from the history of the
642 current branch, as though that commit never happened. By default the
645 In amend mode, the uncommitted changes present are merged into a
646 previous commit. This is useful for correcting typos or adding missed
647 files into past commits. By default the commit used is HEAD.
649 While rewriting history it is possible that conflicts will arise. If
650 this happens, the rewrite will pause and you will be prompted to resolve
651 the conflicts and stage them. Once that is done, you will run "yap
652 history continue." If instead you want the conflicting commit removed
653 from history (perhaps your changes supercede that commit) you can run
654 "yap history skip". Once the rewrite completes, your branch will be on
655 the same commit as when the rewrite started.
657 def cmd_history(self
, subcmd
, *args
):
658 "amend | drop <commit>"
660 if subcmd
not in ("amend", "drop", "continue", "skip"):
664 When you have resolved the conflicts run \"yap history continue\".
665 To skip the problematic patch, run \"yap history skip\"."""
667 if subcmd
== "continue":
668 os
.system("git am -3 -r --resolvemsg='%s'" % resolvemsg
)
671 os
.system("git reset --hard")
672 os
.system("git am -3 --skip --resolvemsg='%s'" % resolvemsg
)
675 if subcmd
== "amend":
676 flags
, args
= getopt
.getopt(args
, "ad")
686 if run_command("git rev-parse --verify '%s'" % commit
):
687 raise YapError("Not a valid commit: %s" % commit
)
689 self
._check
_rebasing
()
691 if subcmd
== "amend":
692 self
._check
_commit
(**flags
)
693 if self
._get
_unstaged
_files
():
694 # XXX: handle unstaged changes better
695 raise YapError("Commit away changes that you aren't amending")
698 stash
= get_output("git stash create")
699 run_command("git reset --hard")
700 if subcmd
== "amend" and not stash
:
701 raise YapError("Failed to stash; no changes?")
704 fd
, tmpfile
= tempfile
.mkstemp("yap")
706 os
.system("git format-patch -k --stdout '%s' > %s" % (commit
, tmpfile
))
707 if subcmd
== "amend":
708 self
.cmd_point(commit
, **{'-f': True})
710 if subcmd
== "amend":
711 rc
= os
.system("git stash apply --index %s" % stash
[0])
713 raise YapError("Failed to apply stash")
716 if subcmd
== "amend":
720 self
.cmd_point("%s^" % commit
, **{'-f': True})
722 stat
= os
.stat(tmpfile
)
725 rc
= os
.system("git am -3 --resolvemsg=\'%s\' %s" % (resolvemsg
, tmpfile
))
727 raise YapError("Failed to apply changes")
730 run_command("git stash apply --index %s" % stash
[0])
735 @short_help("show the changes introduced by a given commit")
737 By default, the changes in the last commit are shown. To override this,
738 specify a hash, branch, or tag (committish). The hash of the commit,
739 the commit's author, log message, and a diff of the changes are shown.
741 def cmd_show(self
, commit
="HEAD"):
743 os
.system("git show '%s'" % commit
)
745 @short_help("apply the changes in a given commit to the current branch")
747 The argument is the hash, branch, or tag (committish) of the commit to
748 be applied. In general, it only makes sense to apply commits that
749 happened on another branch. The '-r' flag can be used to have the
750 changes in the given commit reversed from the current branch. In
751 general, this only makes sense for commits that happened on the current
755 def cmd_cherry_pick(self
, commit
, **flags
):
758 os
.system("git revert '%s'" % commit
)
760 os
.system("git cherry-pick '%s'" % commit
)
762 @short_help("list, add, or delete configured remote repositories")
764 When invoked with no arguments, this command will show the list of
765 currently configured remote repositories, giving both the name and URL
766 of each. To add a new repository, give the desired name as the first
767 argument and the URL as the second. The '-d' flag can be used to remove
768 a previously added repository.
771 def cmd_repo(self
, name
=None, url
=None, **flags
):
772 "[<name> <url> | -d <name>]"
773 if name
is not None and url
is None:
777 if flags
['-d'] not in [ x
[0] for x
in self
._list
_remotes
() ]:
778 raise YapError("No such repository: %s" % flags
['-d'])
779 os
.system("git config --unset remote.%s.url" % flags
['-d'])
780 os
.system("git config --unset remote.%s.fetch" % flags
['-d'])
783 if name
in [ x
[0] for x
in self
._list
_remotes
() ]:
784 raise YapError("Repository '%s' already exists" % flags
['-d'])
785 os
.system("git config remote.%s.url %s" % (name
, url
))
786 os
.system("git config remote.%s.fetch +refs/heads/*:refs/remotes/%s/*" % (name
, name
))
788 for remote
, url
in self
._list
_remotes
():
789 print "%-20s %s" % (remote
, url
)
792 def cmd_push(self
, repo
, **flags
):
795 if repo
not in [ x
[0] for x
in self
._list
_remotes
() ]:
796 raise YapError("No such repository: %s" % repo
)
798 current
= get_output("git symbolic-ref HEAD")
800 raise YapError("Not on a branch!")
802 current
= current
[0].replace('refs/heads/', '')
803 remote
= get_output("git config branch.%s.remote" % current
)
804 if remote
and remote
[0] == repo
:
805 merge
= get_output("git config branch.%s.merge" % current
)
809 if '-c' not in flags
and '-d' not in flags
:
810 if run_command("git rev-parse --verify refs/remotes/%s/%s"
811 % (repo
, ref
.replace('refs/heads/', ''))):
812 raise YapError("No matching branch on that repo. Use -c to create a new branch there.")
817 lhs
= "refs/heads/%s" % current
818 rc
= os
.system("git push %s %s:%s" % (repo
, lhs
, ref
))
820 raise YapError("Push failed.")
822 def cmd_fetch(self
, repo
):
824 # XXX allow defaulting of repo? yap.default
825 if repo
not in [ x
[0] for x
in self
._list
_remotes
() ]:
826 raise YapError("No such repository: %s" % repo
)
827 os
.system("git fetch %s" % repo
)
829 def cmd_update(self
, subcmd
=None):
831 if subcmd
and subcmd
not in ["continue", "skip"]:
835 When you have resolved the conflicts run \"yap history continue\".
836 To skip the problematic patch, run \"yap history skip\"."""
838 if subcmd
== "continue":
839 os
.system("git am -3 -r --resolvemsg='%s'" % resolvemsg
)
842 os
.system("git reset --hard")
843 os
.system("git am -3 --skip --resolvemsg='%s'" % resolvemsg
)
846 self
._check
_rebasing
()
847 if self
._get
_unstaged
_files
() or self
._get
_staged
_files
():
848 raise YapError("You have uncommitted changes. Commit them first")
850 current
= get_output("git symbolic-ref HEAD")
852 raise YapError("Not on a branch!")
854 current
= current
[0].replace('refs/heads/', '')
855 remote
= get_output("git config branch.%s.remote" % current
)
857 raise YapError("No tracking branch configured for '%s'" % current
)
859 merge
= get_output("git config branch.%s.merge" % current
)
861 raise YapError("No tracking branch configured for '%s'" % current
)
862 merge
= merge
[0].replace('refs/heads/', '')
864 self
.cmd_fetch(remote
[0])
865 base
= get_output("git merge-base HEAD refs/remotes/%s/%s" % (remote
[0], merge
))
868 fd
, tmpfile
= tempfile
.mkstemp("yap")
870 os
.system("git format-patch -k --stdout '%s' > %s" % (base
[0], tmpfile
))
871 self
.cmd_point("refs/remotes/%s/%s" % (remote
[0], merge
), **{'-f': True})
873 stat
= os
.stat(tmpfile
)
876 rc
= os
.system("git am -3 --resolvemsg=\'%s\' %s" % (resolvemsg
, tmpfile
))
878 raise YapError("Failed to apply changes")
882 def cmd_help(self
, cmd
=None):
885 attr
= self
.__getattribute
__("cmd_"+cmd
.replace('-', '_'))
886 except AttributeError:
887 raise YapError("No such command: %s" % cmd
)
889 help = attr
.long_help
890 except AttributeError:
891 raise YapError("Sorry, no help for '%s'. Ask Steven." % cmd
)
893 print >>sys
.stderr
, "The '%s' command" % cmd
894 print >>sys
.stderr
, "\tyap %s %s" % (cmd
, attr
.__doc
__)
895 print >>sys
.stderr
, "%s" % help
898 print >> sys
.stderr
, "Yet Another (Git) Porcelein"
901 for name
in dir(self
):
902 if not name
.startswith('cmd_'):
904 attr
= self
.__getattribute
__(name
)
905 if not callable(attr
):
908 short_msg
= attr
.short_help
909 except AttributeError:
912 name
= name
.replace('cmd_', '')
913 name
= name
.replace('_', '-')
914 print >> sys
.stderr
, "%-16s%s" % (name
, short_msg
)
916 print >> sys
.stderr
, "(*) Indicates that the command is not readily reversible"
919 print >> sys
.stderr
, "usage: %s <command>" % os
.path
.basename(sys
.argv
[0])
920 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"
922 def main(self
, args
):
930 debug
= os
.getenv('YAP_DEBUG')
933 command
= command
.replace('-', '_')
936 for p
in self
.plugins
:
938 meth
= p
.__getattribute
__("cmd_"+command
)
939 except AttributeError:
943 default_meth
= self
.__getattribute
__("cmd_"+command
)
944 except AttributeError:
953 if "options" in meth
.__dict
__:
954 options
= meth
.options
955 if default_meth
and "options" in default_meth
.__dict
__:
956 options
+= default_meth
.options
957 flags
, args
= getopt
.getopt(args
, options
)
963 for p
in self
.plugins
:
965 meth
= p
.__getattribute
__("pre_"+command
)
966 except AttributeError:
973 for p
in self
.plugins
:
975 meth
= p
.__getattribute
__("post_"+command
)
976 except AttributeError:
980 except (TypeError, getopt
.GetoptError
):
983 print "%s %s %s" % (sys
.argv
[0], command
, meth
.__doc
__)
985 print >> sys
.stderr
, e
987 except AttributeError: