A Python robot that edits Wikipedia and interacts with people over IRC https://en.wikipedia.org/wiki/User:EarwigBot
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

176 lines
6.6 KiB

  1. # -*- coding: utf-8 -*-
  2. import shlex
  3. import subprocess
  4. import re
  5. from classes import BaseCommand
  6. import config
  7. class Command(BaseCommand):
  8. """Commands to interface with the bot's git repository; use '!git' for a
  9. sub-command list."""
  10. name = "git"
  11. def process(self, data):
  12. self.data = data
  13. if data.host not in config.irc["permissions"]["owners"]:
  14. msg = "you must be a bot owner to use this command."
  15. self.connection.reply(data, msg)
  16. return
  17. if not data.args:
  18. self.do_help()
  19. return
  20. if data.args[0] == "help":
  21. self.do_help()
  22. elif data.args[0] == "branch":
  23. self.do_branch()
  24. elif data.args[0] == "branches":
  25. self.do_branches()
  26. elif data.args[0] == "checkout":
  27. self.do_checkout()
  28. elif data.args[0] == "delete":
  29. self.do_delete()
  30. elif data.args[0] == "pull":
  31. self.do_pull()
  32. elif data.args[0] == "status":
  33. self.do_status()
  34. else: # They asked us to do something we don't know
  35. msg = "unknown argument: \x0303{0}\x0301.".format(data.args[0])
  36. self.connection.reply(data, msg)
  37. def exec_shell(self, command):
  38. """Execute a shell command and get the output."""
  39. command = shlex.split(command)
  40. result = subprocess.check_output(command, stderr=subprocess.STDOUT)
  41. if result:
  42. result = result[:-1] # Strip newline
  43. return result
  44. def do_help(self):
  45. """Display all commands."""
  46. help = {
  47. "branch": "get current branch",
  48. "branches": "get all branches",
  49. "checkout": "switch branches",
  50. "delete": "delete an old branch",
  51. "pull": "update everything from the remote server",
  52. "status": "check if we are up-to-date",
  53. }
  54. msg = ""
  55. for key in sorted(help.keys()):
  56. msg += "\x0303{0}\x0301 ({1}), ".format(key, help[key])
  57. msg = msg[:-2] # Trim last comma and space
  58. self.connection.reply(self.data, "sub-commands are: {0}.".format(msg))
  59. def do_branch(self):
  60. """Get our current branch."""
  61. branch = self.exec_shell("git name-rev --name-only HEAD")
  62. msg = "currently on branch \x0302{0}\x0301.".format(branch)
  63. self.connection.reply(self.data, msg)
  64. def do_branches(self):
  65. """Get a list of branches."""
  66. branches = self.exec_shell("git branch")
  67. # Remove extraneous characters:
  68. branches = branches.replace('\n* ', ', ')
  69. branches = branches.replace('* ', ' ')
  70. branches = branches.replace('\n ', ', ')
  71. branches = branches.strip()
  72. msg = "branches: \x0302{0}\x0301.".format(branches)
  73. self.connection.reply(self.data, msg)
  74. def do_checkout(self):
  75. """Switch branches."""
  76. try:
  77. branch = self.data.args[1]
  78. except IndexError: # no branch name provided
  79. self.connection.reply(self.data, "switch to which branch?")
  80. return
  81. current_branch = self.exec_shell("git name-rev --name-only HEAD")
  82. try:
  83. result = self.exec_shell("git checkout %s" % branch)
  84. if "Already on" in result:
  85. msg = "already on \x0302{0}\x0301!".format(branch)
  86. self.connection.reply(self.data, msg)
  87. else:
  88. ms = "switched from branch \x0302{1}\x0301 to \x0302{1}\x0301."
  89. msg = ms.format(current_branch, branch)
  90. self.connection.reply(self.data, msg)
  91. except subprocess.CalledProcessError:
  92. # Git couldn't switch branches; assume the branch doesn't exist:
  93. msg = "branch \x0302{0}\x0301 doesn't exist!".format(branch)
  94. self.connection.reply(self.data, msg)
  95. def do_delete(self):
  96. """Delete a branch, while making sure that we are not already on it."""
  97. try:
  98. delete_branch = self.data.args[1]
  99. except IndexError: # no branch name provided
  100. self.connection.reply(self.data, "delete which branch?")
  101. return
  102. current_branch = self.exec_shell("git name-rev --name-only HEAD")
  103. if current_branch == delete_branch:
  104. msg = "you're currently on this branch; please checkout to a different branch before deleting."
  105. self.connection.reply(self.data, msg)
  106. return
  107. try:
  108. self.exec_shell("git branch -d %s" % delete_branch)
  109. msg = "branch \x0302{0}\x0301 has been deleted locally."
  110. self.connection.reply(self.data, msg.format(delete_branch))
  111. except subprocess.CalledProcessError:
  112. # Git couldn't switch branches; assume the branch doesn't exist:
  113. msg = "branch \x0302{0}\x0301 doesn't exist!".format(delete_branch)
  114. self.connection.reply(self.data, msg)
  115. def do_pull(self):
  116. """Pull from our remote repository."""
  117. branch = self.exec_shell("git name-rev --name-only HEAD")
  118. msg = "pulling from remote (currently on \x0302{0}\x0301)..."
  119. self.connection.reply(self.data, msg.format(branch))
  120. result = self.exec_shell("git pull")
  121. if "Already up-to-date." in result:
  122. self.connection.reply(self.data, "done; no new changes.")
  123. else:
  124. regex = "\s*((.*?)\sfile(.*?)tions?\(-\))"
  125. changes = re.findall(regex, result)[0][0]
  126. try:
  127. cmnd_remt = "git config --get branch.{0}.remote".format(branch)
  128. remote = self.exec_shell(cmnd_remt)
  129. cmnd_url = "git config --get remote.{0}.url".format(remote)
  130. url = self.exec_shell(cmnd_url)
  131. msg = "done; {0} [from {1}].".format(changes, url)
  132. self.connection.reply(self.data, msg)
  133. except subprocess.CalledProcessError:
  134. # Something in .git/config is not specified correctly, so we
  135. # cannot get the remote's URL. However, pull was a success:
  136. self.connection.reply(self.data, "done; %s." % changes)
  137. def do_status(self):
  138. """Check whether we have anything to pull."""
  139. last = self.exec_shell('git log -n 1 --pretty="%ar"')
  140. result = self.exec_shell("git fetch --dry-run")
  141. if not result: # Nothing was fetched, so remote and local are equal
  142. msg = "last commit was {0}. Local copy is \x02up-to-date\x0F with remote."
  143. self.connection.reply(self.data, msg.format(last))
  144. else:
  145. msg = "last local commit was {0}. Remote is \x02ahead\x0F of local copy."
  146. self.connection.reply(self.data, msg.format(last))