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.

git.py 6.3 KiB

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