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 def _get_tracking(self
, current
):
287 remote
= get_output("git config branch.%s.remote" % current
)
289 raise YapError("No tracking branch configured for '%s'" % current
)
291 merge
= get_output("git config branch.%s.merge" % current
)
293 raise YapError("No tracking branch configured for '%s'" % current
)
294 return remote
[0], merge
296 @short_help("make a local copy of an existing repository")
298 The first argument is a URL to the existing repository. This can be an
299 absolute path if the repository is local, or a URL with the git://,
300 ssh://, or http:// schemes. By default, the directory used is the last
301 component of the URL, sans '.git'. This can be overridden by providing
304 def cmd_clone(self
, url
, directory
=None):
307 if '://' not in url
and url
[0] != '/':
308 url
= os
.path
.join(os
.getcwd(), url
)
310 url
= url
.rstrip('/')
311 if directory
is None:
312 directory
= url
.rsplit('/')[-1]
313 directory
= directory
.replace('.git', '')
318 raise YapError("Directory exists: %s" % directory
)
321 self
.cmd_repo("origin", url
)
322 self
.cmd_fetch("origin")
325 if not run_command("git rev-parse --verify refs/remotes/origin/HEAD"):
326 hash = get_output("git rev-parse refs/remotes/origin/HEAD")[0]
327 for b
in get_output("git for-each-ref --format='%(refname)' 'refs/remotes/origin/*'"):
328 if get_output("git rev-parse %s" % b
)[0] == hash:
332 if not run_command("git rev-parse --verify refs/remotes/origin/master"):
333 branch
= "refs/remotes/origin/master"
335 branch
= get_output("git for-each-ref --format='%(refname)' 'refs/remotes/origin/*'")
338 hash = get_output("git rev-parse %s" % branch
)
340 branch
= branch
.replace('refs/remotes/origin/', '')
341 run_safely("git update-ref refs/heads/%s %s" % (branch
, hash[0]))
342 run_safely("git symbolic-ref HEAD refs/heads/%s" % branch
)
343 self
.cmd_revert(**{'-a': 1})
345 @short_help("turn a directory into a repository")
347 Converts the current working directory into a repository. The primary
348 side-effect of this command is the creation of a '.git' subdirectory.
349 No files are added nor commits made.
352 os
.system("git init")
354 @short_help("add a new file to the repository")
356 The arguments are the files to be added to the repository. Once added,
357 the files will show as "unstaged changes" in the output of 'status'. To
358 reverse the effects of this command, see 'rm'.
360 def cmd_add(self
, *files
):
369 @short_help("delete a file from the repository")
371 The arguments are the files to be removed from the current revision of
372 the repository. The files will still exist in any past commits that the
373 files may have been a part of. The file is not actually deleted, it is
374 just no longer tracked as part of the repository.
376 def cmd_rm(self
, *files
):
385 @short_help("stage changes in a file for commit")
387 The arguments are the files to be staged. Staging changes is a way to
388 build up a commit when you do not want to commit all changes at once.
389 To commit only staged changes, use the '-d' flag to 'commit.' To
390 reverse the effects of this command, see 'unstage'. Once staged, the
391 files will show as "staged changes" in the output of 'status'.
393 def cmd_stage(self
, *files
):
402 @short_help("unstage changes in a file")
404 The arguments are the files to be unstaged. Once unstaged, the files
405 will show as "unstaged changes" in the output of 'status'. The '-a'
406 flag can be used to unstage all staged changes at once.
409 def cmd_unstage(self
, *files
, **flags
):
423 @short_help("show files with staged and unstaged changes")
425 Show the files in the repository with changes since the last commit,
426 categorized based on whether the changes are staged or not. A file may
427 appear under each heading if the same file has both staged and unstaged
430 def cmd_status(self
):
432 branch
= get_output("git symbolic-ref HEAD")[0]
433 branch
= branch
.replace('refs/heads/', '')
434 print "Current branch: %s" % branch
436 print "Files with staged changes:"
437 files
= self
._get
_staged
_files
()
443 print "Files with unstaged changes:"
444 prefix
= get_output("git rev-parse --show-prefix")
445 files
= self
._get
_unstaged
_files
()
448 f
= os
.path
.join(prefix
[0], f
)
453 @short_help("remove uncommitted changes from a file (*)")
455 The arguments are the files whose changes will be reverted. If the '-a'
456 flag is given, then all files will have uncommitted changes removed.
457 Note that there is no way to reverse this command short of manually
458 editing each file again.
461 def cmd_revert(self
, *files
, **flags
):
465 run_safely("git checkout-index -u -f -a")
476 @short_help("record changes to files as a new commit")
478 Create a new commit recording changes since the last commit. If there
479 are only unstaged changes, those will be recorded. If there are only
480 staged changes, those will be recorded. Otherwise, you will have to
481 specify either the '-a' flag or the '-d' flag to commit all changes or
482 only staged changes, respectively. To reverse the effects of this
483 command, see 'uncommit'.
485 @takes_options("adm:")
486 def cmd_commit(self
, **flags
):
488 self
._check
_rebasing
()
489 self
._check
_commit
(**flags
)
490 if not self
._get
_staged
_files
():
491 raise YapError("No changes to commit")
492 msg
= flags
.get('-m', None)
496 @short_help("reverse the actions of the last commit")
498 Reverse the effects of the last 'commit' operation. The changes that
499 were part of the previous commit will show as "staged changes" in the
500 output of 'status'. This means that if no files were changed since the
501 last commit was created, 'uncommit' followed by 'commit' is a lossless
504 def cmd_uncommit(self
):
509 @short_help("report the current version of yap")
510 def cmd_version(self
):
511 print "Yap version 0.1"
513 @short_help("show the changelog for particular versions or files")
515 The arguments are the files with which to filter history. If none are
516 given, all changes are listed. Otherwise only commits that affected one
517 or more of the given files are listed. The -r option changes the
518 starting revision for traversing history. By default, history is listed
522 def cmd_log(self
, *paths
, **flags
):
523 "[-r <rev>] <path>..."
524 rev
= flags
.get('-r', 'HEAD')
525 paths
= ' '.join(paths
)
526 os
.system("git log --name-status '%s' -- %s" % (rev
, paths
))
528 @short_help("show staged, unstaged, or all uncommitted changes")
530 Show staged, unstaged, or all uncommitted changes. By default, all
531 changes are shown. The '-u' flag causes only unstaged changes to be
532 shown. The '-d' flag causes only staged changes to be shown.
535 def cmd_diff(self
, **flags
):
537 if '-u' in flags
and '-d' in flags
:
538 raise YapError("Conflicting flags: -u and -d")
540 pager
= self
._get
_pager
_cmd
()
543 os
.system("git diff-files -p | %s" % pager
)
545 os
.system("git diff-index --cached -p HEAD | %s" % pager
)
547 os
.system("git diff-index -p HEAD | %s" % pager
)
549 @short_help("list, create, or delete branches")
551 If no arguments are specified, a list of local branches is given. The
552 current branch is indicated by a "*" next to the name. If an argument
553 is given, it is taken as the name of a new branch to create. The branch
554 will start pointing at the current HEAD. See 'point' for details on
555 changing the revision of the new branch. Note that this command does
556 not switch the current working branch. See 'switch' for details on
557 changing the current working branch.
559 The '-d' flag can be used to delete local branches. If the delete
560 operation would remove the last branch reference to a given line of
561 history (colloquially referred to as "dangling commits"), yap will
562 report an error and abort. The '-f' flag can be used to force the delete
565 @takes_options("fd:")
566 def cmd_branch(self
, branch
=None, **flags
):
567 "[ [-f] -d <branch> | <branch> ]"
568 force
= '-f' in flags
570 self
._delete
_branch
(flags
['-d'], force
)
574 if branch
is not None:
575 ref
= get_output("git rev-parse --verify HEAD")
577 raise YapError("No branch point yet. Make a commit")
578 run_safely("git update-ref 'refs/heads/%s' '%s'" % (branch
, ref
[0]))
580 current
= get_output("git symbolic-ref HEAD")[0]
581 branches
= get_output("git for-each-ref --format='%(refname)' 'refs/heads/*'")
587 b
= b
.replace('refs/heads/', '')
590 @short_help("change the current working branch")
592 The argument is the name of the branch to make the current working
593 branch. This command will fail if there are uncommitted changes to any
594 files. Otherwise, the contents of the files in the working directory
595 are updated to reflect their state in the new branch. Additionally, any
596 future commits are added to the new branch instead of the previous line
599 def cmd_switch(self
, branch
):
601 ref
= get_output("git rev-parse --verify 'refs/heads/%s'" % branch
)
603 raise YapError("No such branch: %s" % branch
)
605 # XXX: support merging like git-checkout
606 if self
._get
_unstaged
_files
() or self
._get
_staged
_files
():
607 raise YapError("You have uncommitted changes. Commit them first")
609 run_safely("git symbolic-ref HEAD refs/heads/'%s'" % branch
)
610 run_safely("git read-tree -u -m HEAD")
611 run_safely("git checkout-index -u -f -a")
614 @short_help("move the current branch to a different revision")
616 The argument is the hash of the commit to which the current branch
617 should point, or alternately a branch or tag (a.k.a, "committish"). If
618 moving the branch would create "dangling commits" (see 'branch'), yap
619 will report an error and abort. The '-f' flag can be used to force the
620 operation in spite of this.
623 def cmd_point(self
, where
, **flags
):
625 head
= get_output("git rev-parse --verify HEAD")
627 raise YapError("No commit yet; nowhere to point")
629 ref
= get_output("git rev-parse --verify '%s'" % where
)
631 raise YapError("Not a valid ref: %s" % where
)
633 if self
._get
_unstaged
_files
() or self
._get
_staged
_files
():
634 raise YapError("You have uncommitted changes. Commit them first")
636 type = get_output("git cat-file -t '%s'" % ref
[0])
637 if type and type[0] == "tag":
638 tag
= get_output("git cat-file tag '%s'" % ref
[0])
639 ref
[0] = tag
[0].split(' ')[1]
641 run_safely("git update-ref HEAD '%s'" % ref
[0])
643 if '-f' not in flags
:
644 name
= get_output("git name-rev --name-only '%s'" % head
[0])[0]
645 if name
== "undefined":
646 os
.system("git update-ref HEAD '%s'" % head
[0])
647 raise YapError("Pointing there will lose commits. Use -f to force")
649 run_safely("git read-tree -u -m HEAD")
650 run_safely("git checkout-index -u -f -a")
652 @short_help("alter history by dropping or amending commits")
654 This command operates in two distinct modes, "amend" and "drop" mode.
655 In drop mode, the given commit is removed from the history of the
656 current branch, as though that commit never happened. By default the
659 In amend mode, the uncommitted changes present are merged into a
660 previous commit. This is useful for correcting typos or adding missed
661 files into past commits. By default the commit used is HEAD.
663 While rewriting history it is possible that conflicts will arise. If
664 this happens, the rewrite will pause and you will be prompted to resolve
665 the conflicts and stage them. Once that is done, you will run "yap
666 history continue." If instead you want the conflicting commit removed
667 from history (perhaps your changes supercede that commit) you can run
668 "yap history skip". Once the rewrite completes, your branch will be on
669 the same commit as when the rewrite started.
671 def cmd_history(self
, subcmd
, *args
):
672 "amend | drop <commit>"
674 if subcmd
not in ("amend", "drop", "continue", "skip"):
678 When you have resolved the conflicts run \"yap history continue\".
679 To skip the problematic patch, run \"yap history skip\"."""
681 if subcmd
== "continue":
682 os
.system("git am -3 -r --resolvemsg='%s'" % resolvemsg
)
685 os
.system("git reset --hard")
686 os
.system("git am -3 --skip --resolvemsg='%s'" % resolvemsg
)
689 if subcmd
== "amend":
690 flags
, args
= getopt
.getopt(args
, "ad")
700 if run_command("git rev-parse --verify '%s'" % commit
):
701 raise YapError("Not a valid commit: %s" % commit
)
703 self
._check
_rebasing
()
705 if subcmd
== "amend":
706 self
._check
_commit
(**flags
)
707 if self
._get
_unstaged
_files
():
708 # XXX: handle unstaged changes better
709 raise YapError("Commit away changes that you aren't amending")
711 stash
= get_output("git stash create")
713 run_command("git reset --hard")
714 fd
, tmpfile
= tempfile
.mkstemp("yap")
718 os
.system("git format-patch -k --stdout '%s' > %s" % (commit
, tmpfile
))
719 if subcmd
== "amend":
720 self
.cmd_point(commit
, **{'-f': True})
722 if subcmd
== "amend":
723 rc
= os
.system("git stash apply --index %s" % stash
[0])
725 raise YapError("Failed to apply stash")
728 if subcmd
== "amend":
732 self
.cmd_point("%s^" % commit
, **{'-f': True})
734 stat
= os
.stat(tmpfile
)
737 rc
= os
.system("git am -3 --resolvemsg=\'%s\' %s" % (resolvemsg
, tmpfile
))
739 raise YapError("Failed to apply changes")
744 run_command("git stash apply --index %s" % stash
[0])
747 @short_help("show the changes introduced by a given commit")
749 By default, the changes in the last commit are shown. To override this,
750 specify a hash, branch, or tag (committish). The hash of the commit,
751 the commit's author, log message, and a diff of the changes are shown.
753 def cmd_show(self
, commit
="HEAD"):
755 os
.system("git show '%s'" % commit
)
757 @short_help("apply the changes in a given commit to the current branch")
759 The argument is the hash, branch, or tag (committish) of the commit to
760 be applied. In general, it only makes sense to apply commits that
761 happened on another branch. The '-r' flag can be used to have the
762 changes in the given commit reversed from the current branch. In
763 general, this only makes sense for commits that happened on the current
767 def cmd_cherry_pick(self
, commit
, **flags
):
770 os
.system("git revert '%s'" % commit
)
772 os
.system("git cherry-pick '%s'" % commit
)
774 @short_help("list, add, or delete configured remote repositories")
776 When invoked with no arguments, this command will show the list of
777 currently configured remote repositories, giving both the name and URL
778 of each. To add a new repository, give the desired name as the first
779 argument and the URL as the second. The '-d' flag can be used to remove
780 a previously added repository.
783 def cmd_repo(self
, name
=None, url
=None, **flags
):
784 "[<name> <url> | -d <name>]"
785 if name
is not None and url
is None:
789 if flags
['-d'] not in [ x
[0] for x
in self
._list
_remotes
() ]:
790 raise YapError("No such repository: %s" % flags
['-d'])
791 os
.system("git config --unset remote.%s.url" % flags
['-d'])
792 os
.system("git config --unset remote.%s.fetch" % flags
['-d'])
795 if name
in [ x
[0] for x
in self
._list
_remotes
() ]:
796 raise YapError("Repository '%s' already exists" % flags
['-d'])
797 os
.system("git config remote.%s.url %s" % (name
, url
))
798 os
.system("git config remote.%s.fetch +refs/heads/*:refs/remotes/%s/*" % (name
, name
))
800 for remote
, url
in self
._list
_remotes
():
801 print "%-20s %s" % (remote
, url
)
804 def cmd_push(self
, repo
, **flags
):
807 if repo
not in [ x
[0] for x
in self
._list
_remotes
() ]:
808 raise YapError("No such repository: %s" % repo
)
810 current
= get_output("git symbolic-ref HEAD")
812 raise YapError("Not on a branch!")
814 current
= current
[0].replace('refs/heads/', '')
815 remote
= get_output("git config branch.%s.remote" % current
)
816 if remote
and remote
[0] == repo
:
817 merge
= get_output("git config branch.%s.merge" % current
)
821 if '-c' not in flags
and '-d' not in flags
:
822 if run_command("git rev-parse --verify refs/remotes/%s/%s"
823 % (repo
, ref
.replace('refs/heads/', ''))):
824 raise YapError("No matching branch on that repo. Use -c to create a new branch there.")
829 lhs
= "refs/heads/%s" % current
830 rc
= os
.system("git push %s %s:%s" % (repo
, lhs
, ref
))
832 raise YapError("Push failed.")
834 def cmd_fetch(self
, repo
):
836 # XXX allow defaulting of repo? yap.default
837 if repo
not in [ x
[0] for x
in self
._list
_remotes
() ]:
838 raise YapError("No such repository: %s" % repo
)
839 os
.system("git fetch %s" % repo
)
841 def cmd_update(self
, subcmd
=None):
843 if subcmd
and subcmd
not in ["continue", "skip"]:
847 When you have resolved the conflicts run \"yap history continue\".
848 To skip the problematic patch, run \"yap history skip\"."""
850 if subcmd
== "continue":
851 os
.system("git am -3 -r --resolvemsg='%s'" % resolvemsg
)
854 os
.system("git reset --hard")
855 os
.system("git am -3 --skip --resolvemsg='%s'" % resolvemsg
)
858 self
._check
_rebasing
()
859 if self
._get
_unstaged
_files
() or self
._get
_staged
_files
():
860 raise YapError("You have uncommitted changes. Commit them first")
862 current
= get_output("git symbolic-ref HEAD")
864 raise YapError("Not on a branch!")
866 current
= current
[0].replace('refs/heads/', '')
867 remote
, merge
= self
._get
_tracking
(current
)
868 merge
= merge
[0].replace('refs/heads/', '')
870 self
.cmd_fetch(remote
)
871 base
= get_output("git merge-base HEAD refs/remotes/%s/%s" % (remote
, merge
))
874 fd
, tmpfile
= tempfile
.mkstemp("yap")
876 os
.system("git format-patch -k --stdout '%s' > %s" % (base
[0], tmpfile
))
877 self
.cmd_point("refs/remotes/%s/%s" % (remote
, merge
), **{'-f': True})
879 stat
= os
.stat(tmpfile
)
882 rc
= os
.system("git am -3 --resolvemsg=\'%s\' %s" % (resolvemsg
, tmpfile
))
884 raise YapError("Failed to apply changes")
888 def cmd_track(self
, repo
=None, branch
=None):
891 current
= get_output("git symbolic-ref HEAD")
893 raise YapError("Not on a branch!")
894 current
= current
[0].replace('refs/heads/', '')
896 if repo
is None and branch
is None:
897 repo
, merge
= self
._get
_tracking
(current
)
898 merge
= merge
[0].replace('refs/heads/', '')
899 print "Branch '%s' tracking refs/remotes/%s/%s" % (current
, repo
, merge
)
902 if repo
is None or branch
is None:
905 if repo
not in [ x
[0] for x
in self
._list
_remotes
() ]:
906 raise YapError("No such repository: %s" % repo
)
908 if run_command("git rev-parse --verify refs/remotes/%s/%s" % (repo
, branch
)):
909 raise YapError("No such branch '%s' on repository '%s'" % (repo
, branch
))
911 os
.system("git config branch.%s.remote '%s'" % (current
, repo
))
912 os
.system("git config branch.%s.merge 'refs/heads/%s'" % (current
, branch
))
913 print "Branch '%s' now tracking refs/remotes/%s/%s" % (current
, repo
, branch
)
915 def cmd_help(self
, cmd
=None):
918 attr
= self
.__getattribute
__("cmd_"+cmd
.replace('-', '_'))
919 except AttributeError:
920 raise YapError("No such command: %s" % cmd
)
922 help = attr
.long_help
923 except AttributeError:
924 raise YapError("Sorry, no help for '%s'. Ask Steven." % cmd
)
926 print >>sys
.stderr
, "The '%s' command" % cmd
927 print >>sys
.stderr
, "\tyap %s %s" % (cmd
, attr
.__doc
__)
928 print >>sys
.stderr
, "%s" % help
931 print >> sys
.stderr
, "Yet Another (Git) Porcelein"
934 for name
in dir(self
):
935 if not name
.startswith('cmd_'):
937 attr
= self
.__getattribute
__(name
)
938 if not callable(attr
):
941 short_msg
= attr
.short_help
942 except AttributeError:
945 name
= name
.replace('cmd_', '')
946 name
= name
.replace('_', '-')
947 print >> sys
.stderr
, "%-16s%s" % (name
, short_msg
)
949 print >> sys
.stderr
, "(*) Indicates that the command is not readily reversible"
952 print >> sys
.stderr
, "usage: %s <command>" % os
.path
.basename(sys
.argv
[0])
953 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 track push fetch update history version"
955 def main(self
, args
):
963 debug
= os
.getenv('YAP_DEBUG')
966 command
= command
.replace('-', '_')
969 for p
in self
.plugins
:
971 meth
= p
.__getattribute
__("cmd_"+command
)
972 except AttributeError:
976 default_meth
= self
.__getattribute
__("cmd_"+command
)
977 except AttributeError:
986 if "options" in meth
.__dict
__:
987 options
= meth
.options
988 if default_meth
and "options" in default_meth
.__dict
__:
989 options
+= default_meth
.options
990 flags
, args
= getopt
.getopt(args
, options
)
996 for p
in self
.plugins
:
998 meth
= p
.__getattribute
__("pre_"+command
)
999 except AttributeError:
1001 meth(*args
, **flags
)
1003 meth(*args
, **flags
)
1006 for p
in self
.plugins
:
1008 meth
= p
.__getattribute
__("post_"+command
)
1009 except AttributeError:
1013 except (TypeError, getopt
.GetoptError
):
1016 print "%s %s %s" % (sys
.argv
[0], command
, meth
.__doc
__)
1018 print >> sys
.stderr
, e
1020 except AttributeError: