Browse Source

Merge branch 'feature/git-command' into develop

tags/v0.1^2
Ben Kurtovic 12 years ago
parent
commit
a1b3c28aa4
1 changed files with 131 additions and 90 deletions
  1. +131
    -90
      earwigbot/commands/git.py

+ 131
- 90
earwigbot/commands/git.py View File

@@ -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,43 +41,66 @@ 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 = self.get_repos()
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 = self.get_repos()
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 get_repos(self):
data = self.repos.iteritems()
repos = ["\x0302{0}\x0301 ({1})".format(k, v) for k, v in data]
return ", ".join(repos)

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 get_time_since(self, date):
diff = time.mktime(time.gmtime()) - date
if diff < 60:
return "{0} seconds".format(int(diff))
if diff < 60 * 60:
return "{0} minutes".format(int(diff / 60))
if diff < 60 * 60 * 24:
return "{0} hours".format(int(diff / 60 / 60))
return "{0} days".format(int(diff / 60 / 60 / 24))

def do_help(self):
"""Display all commands."""
@@ -85,110 +112,124 @@ class Command(BaseCommand):
"pull": "update everything from the remote server",
"status": "check if we are up-to-date",
}
msg = ""
subcommands = ""
for key in sorted(help.keys()):
msg += "\x0303{0}\x0301 ({1}), ".format(key, help[key])
msg = msg[:-2] # Trim last comma and space
self.reply(self.data, "sub-commands are: {0}.".format(msg))
subcommands += "\x0303{0}\x0301 ({1}), ".format(key, help[key])
subcommands = subcommands[:-2] # Trim last comma and space
msg = "sub-commands are: {0}; repos are: {1}. Syntax: !git \x0303subcommand\x0301 \x0302repo\x0301."
self.reply(self.data, msg.format(subcommands, self.get_repos()))

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)
log = "{0} checked out branch {1} of {2}"
logmsg = log.format(self.data.nick, target, self.repo.working_dir)
self.logger.info(logmsg)

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)
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)
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(target))
log = "{0} deleted branch {1} of {2}"
logmsg = log.format(self.data.nick, target, self.repo.working_dir)
self.logger.info(logmsg)

def do_pull(self):
"""Pull from our remote repository."""
branch = self.exec_shell("git name-rev --name-only HEAD")
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")

if "Already up-to-date." in result:
self.reply(self.data, "done; no new changes.")
remote = self.get_remote()
if not remote:
return
result = remote.pull()
updated = [info for info in result if info.flags != info.HEAD_UPTODATE]

if updated:
branches = ", ".join([info.ref.remote_head for info in updated])
msg = "done; updates to \x0302{0}\x0301 (from {1})."
self.reply(self.data, msg.format(branches, remote.url))
log = "{0} pulled {1} of {2} (updates to {3})"
self.logger.info(log.format(self.data.nick, remote.name,
self.repo.working_dir, branches))
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.")
log = "{0} pulled {1} of {2} (no updates)"
self.logger.info(log.format(self.data.nick, remote.name,
self.repo.working_dir))

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))
"""Check if we have anything to pull."""
remote = self.get_remote()
if not remote:
return
since = self.get_time_since(self.repo.head.object.committed_date)
result = remote.fetch(dry_run=True)
updated = [info for info in result if info.flags != info.HEAD_UPTODATE]

if updated:
branches = ", ".join([info.ref.remote_head for info in updated])
msg = "last local commit was \x02{0}\x0F ago; updates to \x0302{1}\x0301."
self.reply(self.data, msg.format(since, branches))
log = "{0} got status of {1} of {2} (updates to {3})"
self.logger.info(log.format(self.data.nick, remote.name,
self.repo.working_dir, branches))
else:
msg = "last local commit was {0}. Remote is \x02ahead\x0F of local copy."
self.reply(self.data, msg.format(last))
msg = "last commit was \x02{0}\x0F ago. Local copy is up-to-date with remote."
self.reply(self.data, msg.format(since))
log = "{0} pulled {1} of {2} (no updates)"
self.logger.info(log.format(self.data.nick, remote.name,
self.repo.working_dir))

Loading…
Cancel
Save