Additional IRC commands and bot tasks for EarwigBot 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.

преди 11 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
преди 12 години
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. # Copyright (C) 2009-2014 Ben Kurtovic <ben.kurtovic@gmail.com>
  2. #
  3. # Permission is hereby granted, free of charge, to any person obtaining a copy
  4. # of this software and associated documentation files (the "Software"), to deal
  5. # in the Software without restriction, including without limitation the rights
  6. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. # copies of the Software, and to permit persons to whom the Software is
  8. # furnished to do so, subject to the following conditions:
  9. #
  10. # The above copyright notice and this permission notice shall be included in
  11. # all copies or substantial portions of the Software.
  12. #
  13. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19. # SOFTWARE.
  20. import time
  21. import git
  22. from earwigbot.commands import Command
  23. class Git(Command):
  24. """Commands to interface with configurable git repositories; use '!git' for
  25. a sub-command list."""
  26. name = "git"
  27. def setup(self):
  28. try:
  29. self.repos = self.config.commands[self.name]["repos"]
  30. except KeyError:
  31. self.repos = None
  32. def process(self, data):
  33. self.data = data
  34. if not self.config.irc["permissions"].is_owner(data):
  35. msg = "You must be a bot owner to use this command."
  36. self.reply(data, msg)
  37. return
  38. if not data.args or data.args[0] == "help":
  39. self.do_help()
  40. return
  41. if not self.repos:
  42. self.reply(data, "No repos are specified in the config file.")
  43. return
  44. command = data.args[0]
  45. try:
  46. repo_name = data.args[1]
  47. except IndexError:
  48. repos = self.get_repos()
  49. msg = "Which repo do you want to work with (options are {0})?"
  50. self.reply(data, msg.format(repos))
  51. return
  52. if repo_name not in self.repos:
  53. repos = self.get_repos()
  54. msg = "Repository must be one of the following: {0}."
  55. self.reply(data, msg.format(repos))
  56. return
  57. self.repo = git.Repo(self.repos[repo_name])
  58. if command == "branch":
  59. self.do_branch()
  60. elif command == "branches":
  61. self.do_branches()
  62. elif command == "checkout":
  63. self.do_checkout()
  64. elif command == "delete":
  65. self.do_delete()
  66. elif command == "pull":
  67. self.do_pull()
  68. elif command == "status":
  69. self.do_status()
  70. else: # They asked us to do something we don't know
  71. msg = f"Unknown argument: \x0303{data.args[0]}\x0f."
  72. self.reply(data, msg)
  73. def get_repos(self):
  74. data = self.repos.iteritems()
  75. repos = [f"\x0302{k}\x0f ({v})" for k, v in data]
  76. return ", ".join(repos)
  77. def get_remote(self):
  78. try:
  79. remote_name = self.data.args[2]
  80. except IndexError:
  81. remote_name = "origin"
  82. try:
  83. return getattr(self.repo.remotes, remote_name)
  84. except AttributeError:
  85. msg = f"Unknown remote: \x0302{remote_name}\x0f."
  86. self.reply(self.data, msg)
  87. def get_time_since(self, date):
  88. diff = time.mktime(time.gmtime()) - date
  89. if diff < 60:
  90. return f"{int(diff)} seconds"
  91. if diff < 60 * 60:
  92. return f"{int(diff / 60)} minutes"
  93. if diff < 60 * 60 * 24:
  94. return f"{int(diff / 60 / 60)} hours"
  95. return f"{int(diff / 60 / 60 / 24)} days"
  96. def do_help(self):
  97. """Display all commands."""
  98. help = {
  99. "branch": "get current branch",
  100. "branches": "get all branches",
  101. "checkout": "switch branches",
  102. "delete": "delete an old branch",
  103. "pull": "update everything from the remote server",
  104. "status": "check if we are up-to-date",
  105. }
  106. subcommands = ""
  107. for key in sorted(help.keys()):
  108. subcommands += f"\x0303{key}\x0f ({help[key]}), "
  109. subcommands = subcommands[:-2] # Trim last comma and space
  110. msg = "Sub-commands are: {0}; repos are: {1}. Syntax: !git \x0303subcommand\x0f \x0302repo\x0f."
  111. self.reply(self.data, msg.format(subcommands, self.get_repos()))
  112. def do_branch(self):
  113. """Get our current branch."""
  114. branch = self.repo.active_branch.name
  115. msg = f"Currently on branch \x0302{branch}\x0f."
  116. self.reply(self.data, msg)
  117. def do_branches(self):
  118. """Get a list of branches."""
  119. branches = [branch.name for branch in self.repo.branches]
  120. msg = "Branches: \x0302{}\x0f.".format(", ".join(branches))
  121. self.reply(self.data, msg)
  122. def do_checkout(self):
  123. """Switch branches."""
  124. try:
  125. target = self.data.args[2]
  126. except IndexError: # No branch name provided
  127. self.reply(self.data, "Wwitch to which branch?")
  128. return
  129. current_branch = self.repo.active_branch.name
  130. if target == current_branch:
  131. msg = f"Already on \x0302{target}\x0f!"
  132. self.reply(self.data, msg)
  133. return
  134. try:
  135. ref = getattr(self.repo.branches, target)
  136. except AttributeError:
  137. msg = f"Branch \x0302{target}\x0f doesn't exist!"
  138. self.reply(self.data, msg)
  139. else:
  140. ref.checkout()
  141. ms = "Switched from branch \x0302{0}\x0f to \x0302{1}\x0f."
  142. msg = ms.format(current_branch, target)
  143. self.reply(self.data, msg)
  144. log = "{0} checked out branch {1} of {2}"
  145. logmsg = log.format(self.data.nick, target, self.repo.working_dir)
  146. self.logger.info(logmsg)
  147. def do_delete(self):
  148. """Delete a branch, while making sure that we are not already on it."""
  149. try:
  150. target = self.data.args[2]
  151. except IndexError: # No branch name provided
  152. self.reply(self.data, "Delete which branch?")
  153. return
  154. current_branch = self.repo.active_branch.name
  155. if current_branch == target:
  156. msg = "You're currently on this branch; please checkout to a different branch before deleting."
  157. self.reply(self.data, msg)
  158. return
  159. try:
  160. ref = getattr(self.repo.branches, target)
  161. except AttributeError:
  162. msg = f"Branch \x0302{target}\x0f doesn't exist!"
  163. self.reply(self.data, msg)
  164. else:
  165. self.repo.git.branch("-d", ref)
  166. msg = "Branch \x0302{0}\x0f has been deleted locally."
  167. self.reply(self.data, msg.format(target))
  168. log = "{0} deleted branch {1} of {2}"
  169. logmsg = log.format(self.data.nick, target, self.repo.working_dir)
  170. self.logger.info(logmsg)
  171. def do_pull(self):
  172. """Pull from our remote repository."""
  173. branch = self.repo.active_branch.name
  174. msg = "Pulling from remote (currently on \x0302{0}\x0f)..."
  175. self.reply(self.data, msg.format(branch))
  176. remote = self.get_remote()
  177. if not remote:
  178. return
  179. result = remote.pull()
  180. updated = [info for info in result if info.flags != info.HEAD_UPTODATE]
  181. if updated:
  182. branches = ", ".join([info.ref.remote_head for info in updated])
  183. msg = "Done; updates to \x0302{0}\x0f (from {1})."
  184. self.reply(self.data, msg.format(branches, remote.url))
  185. log = "{0} pulled {1} of {2} (updates to {3})"
  186. self.logger.info(
  187. log.format(self.data.nick, remote.name, self.repo.working_dir, branches)
  188. )
  189. else:
  190. self.reply(self.data, "Done; no new changes.")
  191. log = "{0} pulled {1} of {2} (no updates)"
  192. self.logger.info(
  193. log.format(self.data.nick, remote.name, self.repo.working_dir)
  194. )
  195. def do_status(self):
  196. """Check if we have anything to pull."""
  197. remote = self.get_remote()
  198. if not remote:
  199. return
  200. since = self.get_time_since(self.repo.head.object.committed_date)
  201. result = remote.fetch(dry_run=True)
  202. updated = [info for info in result if info.flags != info.HEAD_UPTODATE]
  203. if updated:
  204. branches = ", ".join([info.ref.remote_head for info in updated])
  205. msg = "Last local commit was \x02{0}\x0f ago; updates to \x0302{1}\x0f."
  206. self.reply(self.data, msg.format(since, branches))
  207. log = "{0} got status of {1} of {2} (updates to {3})"
  208. self.logger.info(
  209. log.format(self.data.nick, remote.name, self.repo.working_dir, branches)
  210. )
  211. else:
  212. msg = (
  213. "Last commit was \x02{0}\x0f ago. Local copy is up-to-date with remote."
  214. )
  215. self.reply(self.data, msg.format(since))
  216. log = "{0} pulled {1} of {2} (no updates)"
  217. self.logger.info(
  218. log.format(self.data.nick, remote.name, self.repo.working_dir)
  219. )