From da46922c9f4371e6e5baf3a1d1197f2cbb2320b6 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sun, 8 Apr 2012 22:01:42 -0400 Subject: [PATCH] Update !git to use GitPython --- earwigbot/commands/git.py | 181 +++++++++++++++++++++++++--------------------- 1 file changed, 98 insertions(+), 83 deletions(-) diff --git a/earwigbot/commands/git.py b/earwigbot/commands/git.py index c22d719..2f580b6 100644 --- a/earwigbot/commands/git.py +++ b/earwigbot/commands/git.py @@ -20,9 +20,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import shlex -import subprocess -import re +import time + +import git from earwigbot.commands import BaseCommand @@ -30,6 +30,10 @@ class Command(BaseCommand): """Commands to interface with the bot's git repository; use '!git' for a sub-command list.""" name = "git" + repos = { + "core": "/home/earwig/git/earwigbot", + "plugins": "/home/earwig/git/earwigbot-plugins", + } def process(self, data): self.data = data @@ -37,44 +41,41 @@ class Command(BaseCommand): msg = "you must be a bot owner to use this command." self.reply(data, msg) return - - if not data.args: + if not data.args or data.args[0] == "help": self.do_help() return - if data.args[0] == "help": - self.do_help() + command = data.args[0] + try: + repo_name = data.args[1] + except IndexError: + repos = "\x0302" + "\x0301, \x0301".join(self.repos) + "\x0301" + msg = "which repo do you want to work with (options are {0})?" + self.reply(data, msg.format(repos)) + return + if repo_name not in self.repos: + repos = "\x0302" + "\x0301, \x0301".join(self.repos) + "\x0301" + msg = "repository must be one of the following: {0}" + self.reply(data, msg.format(repos)) + return + self.repo = git.Repo(self.repos[repo_name]) - elif data.args[0] == "branch": + if command == "branch": self.do_branch() - - elif data.args[0] == "branches": + elif command == "branches": self.do_branches() - - elif data.args[0] == "checkout": + elif command == "checkout": self.do_checkout() - - elif data.args[0] == "delete": + elif command == "delete": self.do_delete() - - elif data.args[0] == "pull": + elif command == "pull": self.do_pull() - - elif data.args[0] == "status": + elif command == "status": self.do_status() - else: # They asked us to do something we don't know msg = "unknown argument: \x0303{0}\x0301.".format(data.args[0]) self.reply(data, msg) - def exec_shell(self, command): - """Execute a shell command and get the output.""" - command = shlex.split(command) - result = subprocess.check_output(command, stderr=subprocess.STDOUT) - if result: - result = result[:-1] # Strip newline - return result - def do_help(self): """Display all commands.""" help = { @@ -93,102 +94,116 @@ class Command(BaseCommand): def do_branch(self): """Get our current branch.""" - branch = self.exec_shell("git name-rev --name-only HEAD") + branch = self.repo.active_branch.name msg = "currently on branch \x0302{0}\x0301.".format(branch) self.reply(self.data, msg) def do_branches(self): """Get a list of branches.""" - branches = self.exec_shell("git branch") - # Remove extraneous characters: - branches = branches.replace('\n* ', ', ') - branches = branches.replace('* ', ' ') - branches = branches.replace('\n ', ', ') - branches = branches.strip() - msg = "branches: \x0302{0}\x0301.".format(branches) + branches = [branch.name for branch in self.repo.branches] + msg = "branches: \x0302{0}\x0301.".format(", ".join(branches)) self.reply(self.data, msg) def do_checkout(self): """Switch branches.""" try: - branch = self.data.args[1] - except IndexError: # no branch name provided + target = self.data.args[2] + except IndexError: # No branch name provided self.reply(self.data, "switch to which branch?") return - current_branch = self.exec_shell("git name-rev --name-only HEAD") + current_branch = self.repo.active_branch.name + if target == current_branch: + msg = "already on \x0302{0}\x0301!".format(target) + self.reply(self.data, msg) + return try: - result = self.exec_shell("git checkout %s" % branch) - if "Already on" in result: - msg = "already on \x0302{0}\x0301!".format(branch) - self.reply(self.data, msg) - else: - ms = "switched from branch \x0302{1}\x0301 to \x0302{1}\x0301." - msg = ms.format(current_branch, branch) - self.reply(self.data, msg) - - except subprocess.CalledProcessError: - # Git couldn't switch branches; assume the branch doesn't exist: - msg = "branch \x0302{0}\x0301 doesn't exist!".format(branch) + ref = getattr(self.repo.branches, target) + except AttributeError: + msg = "branch \x0302{0}\x0301 doesn't exist!".format(target) + self.reply(self.data, msg) + else: + ref.checkout() + ms = "switched from branch \x0302{1}\x0301 to \x0302{1}\x0301." + msg = ms.format(current_branch, target) self.reply(self.data, msg) def do_delete(self): """Delete a branch, while making sure that we are not already on it.""" try: - delete_branch = self.data.args[1] - except IndexError: # no branch name provided + target = self.data.args[2] + except IndexError: # No branch name provided self.reply(self.data, "delete which branch?") return - current_branch = self.exec_shell("git name-rev --name-only HEAD") - - if current_branch == delete_branch: + current_branch = self.repo.active_branch.name + if current_branch == target: msg = "you're currently on this branch; please checkout to a different branch before deleting." self.reply(self.data, msg) return try: - self.exec_shell("git branch -d %s" % delete_branch) + ref = getattr(self.repo.branches, target) + except AttributeError: + msg = "branch \x0302{0}\x0301 doesn't exist!".format(target) + self.reply(self.data, msg) + else: + self.repo.git.branch("-d", ref) msg = "branch \x0302{0}\x0301 has been deleted locally." - self.reply(self.data, msg.format(delete_branch)) - except subprocess.CalledProcessError: - # Git couldn't switch branches; assume the branch doesn't exist: - msg = "branch \x0302{0}\x0301 doesn't exist!".format(delete_branch) + self.reply(self.data, msg.format(target)) + + def get_remote(self): + try: + remote_name = self.data.args[2] + except IndexError: + remote_name = "origin" + try: + return getattr(self.repo.remotes, remote_name) + except AttributeError: + msg = "unknown remote: \x0302{0}\x0301".format(remote_name) self.reply(self.data, msg) def do_pull(self): """Pull from our remote repository.""" - branch = self.exec_shell("git name-rev --name-only HEAD") + branch = current_branch = self.repo.active_branch.name msg = "pulling from remote (currently on \x0302{0}\x0301)..." self.reply(self.data, msg.format(branch)) - result = self.exec_shell("git pull") + remote = self.get_remote() + if not remote: + return + result = remote.pull() + updated = [info for info in result if info.flags != info.HEAD_UPTODATE] - if "Already up-to-date." in result: - self.reply(self.data, "done; no new changes.") + if updated: + update = ", ".join([info.ref.remote_head for info in updated]) + msg = "done; updates to {1} (from {2}).".format(update, remote.url) + self.reply(self.data, msg) else: - regex = "\s*((.*?)\sfile(.*?)tions?\(-\))" - changes = re.findall(regex, result)[0][0] - try: - cmnd_remt = "git config --get branch.{0}.remote".format(branch) - remote = self.exec_shell(cmnd_remt) - cmnd_url = "git config --get remote.{0}.url".format(remote) - url = self.exec_shell(cmnd_url) - msg = "done; {0} [from {1}].".format(changes, url) - self.reply(self.data, msg) - except subprocess.CalledProcessError: - # Something in .git/config is not specified correctly, so we - # cannot get the remote's URL. However, pull was a success: - self.reply(self.data, "done; %s." % changes) + self.reply(self.data, "done; no new changes.") def do_status(self): """Check whether we have anything to pull.""" - last = self.exec_shell('git log -n 1 --pretty="%ar"') - result = self.exec_shell("git fetch --dry-run") - if not result: # Nothing was fetched, so remote and local are equal - msg = "last commit was {0}. Local copy is \x02up-to-date\x0F with remote." - self.reply(self.data, msg.format(last)) + last = self.repo.head.object.committed_date + diff = time.mktime(time.gmtime()) - last + if diff < 60: + since = "{0} seconds".format(diff) + elif diff < 60 * 60: + since = "{0} minutes".format(int(diff / 60)) + elif diff < 60 * 60 * 24: + since = "{0} hours".format(int(diff / 60 / 60)) else: + since = "{0} days".format(int(diff / 60 / 60 / 24)) + + remote = self.get_remote() + if not remote: + return + result = remote.fetch(dry_run=True) + + if [info for info in result if info.flags != info.HEAD_UPTODATE]: msg = "last local commit was {0}. Remote is \x02ahead\x0F of local copy." - self.reply(self.data, msg.format(last)) + self.reply(self.data, msg.format(since)) + else: + msg = "last commit was {0} ago. Local copy is \x02up-to-date\x0F with remote." + self.reply(self.data, msg.format(since))