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.

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