Created
March 17, 2014 12:46
-
-
Save tommcdo/9598595 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Description: | |
# Issue various Git commands on remote servers | |
# | |
# Commands: | |
# Hubot create <type> repository <nickname> = <user>/<repository> - Define a repository for linking | |
# Hubot create server <nickname> - Define a new server | |
# Hubot set <property> on server <nickname> to <value> - Set a server's user, address, directory, or repo | |
# Hubot alias server <nickname> to <alias> - Create an alias to a server | |
# Hubot describe server <nickname> - Show details of a server | |
# Hubot delete server <nickname> - Delete server details | |
# Hubot list servers - Show a list of servers by nickname | |
# Hubot branch <nickname> - Display the branch a server is checked out on | |
# Hubot show last <N> commits on <nickname> - Show recent commits on server | |
# Hubot <repository> wiki <topic> - Link to the wiki article on a topic | |
# Hubot set default repository to <nickname> - Set the default repository | |
exec = require('child_process').exec | |
module.exports = (robot) => | |
servers = () -> robot.brain.data.servers ?= {} | |
serverAliases = () -> robot.brain.data.serverAliases ?= {} | |
gitRepos = () -> robot.brain.data.gitRepos ?= {} | |
# Generate repository URLs | |
class GitRepo | |
constructor: (@user, @repo) -> | |
url: () -> return "#{@user}/#{@repo}" | |
commit: (c) -> return c | |
branch: (b) -> return b | |
wiki: (w) -> return false | |
# Generate Bitbucket repository URLs | |
class BitbucketRepo extends GitRepo | |
url: () -> return "https://bitbucket.org/#{@user}/#{@repo}" | |
commit: (c) -> return "#{@url()}/commits/#{c}" | |
branch: (b) -> return "#{@url()}/branch/#{b}" | |
wiki: (w) -> return "#{@url()}/wiki/#{encodeURIComponent(w)}" | |
# Run a command on a server | |
class Command | |
constructor: (name) -> | |
@server = servers()[getServerName(name)] | |
commands: () -> | |
return [] | |
build: () -> | |
return "ssh #{@server.user}@#{@server.address} 'cd #{@server.directory}; #{@commands().join('; ')}'" | |
filter: (output) -> | |
return output | |
run: (callback) -> | |
exec @build(), (error, stdout, stderr) => | |
unless error | |
callback @filter stdout | |
# Command to show the checked out branch | |
class BranchCommand extends Command | |
commands: () -> | |
return [ | |
"git log --oneline @{u}..HEAD | wc -l", | |
"git rev-parse --abbrev-ref HEAD", | |
"git rev-parse HEAD" | |
] | |
filter: (output) -> | |
[ahead, branch, commit] = output.split "\n" | |
repo = repoFactory(@server.repo) | |
address = link @server.address, "http://#{@server.address}" | |
branch = link branch, repo.branch(branch) | |
commit = ((c) -> if ahead == "0" then link c, repo.commit(commit) else c)(commit.substring 0, 8) | |
return "#{address} is on #{branch} at #{commit}" | |
# Command to show recent commits | |
class CommitsCommand extends Command | |
constructor: (name, @n) -> | |
super name | |
commands: () -> | |
return [ | |
"git log --oneline @{u}..HEAD | head -n#{@n}", | |
"echo '-----'", | |
"git log --oneline | head -n#{@n}" | |
] | |
filter: (output) -> | |
[unpushed, pushed] = output.split /^-----\n/m | |
pushed = pushed.substring unpushed.length | |
pushed = pushed.replace /^([0-9a-f]+)/gm, link('$1', repoFactory(@server.repo).commit('$1')) | |
address = link @server.address, "http://#{@server.address}" | |
title = "\#\#\#\# #{address}\n" | |
return title + unpushed + pushed | |
# Grab an instance of a repository URL builder | |
repoFactory = (key) -> | |
types = | |
bitbucket: BitbucketRepo | |
repo = gitRepos()[key] | |
return new types[repo.type](repo.user, repo.repo) | |
# Create a markdown link | |
link = (label, href) -> return "[#{label}](#{href})" | |
# Look up a server name by its nickname or alias | |
getServerName = (name) -> | |
unless name of servers() | |
unless name of serverAliases() | |
return false | |
return serverAliases()[name] | |
return name | |
# Assert that a server name exists | |
found = (name, msg) -> | |
unless resolvedName = getServerName name | |
msg.send "I don't know of a server called #{name}." | |
return false | |
return resolvedName | |
# Define a repository. | |
robot.respond /create (.*) repo(?:sitory)? (.*) = (.*)\/(.*)/i, (msg) -> | |
[type, key, user, repo] = msg.match.slice 1, 5 | |
gitRepos()[key] = | |
type: type | |
user: user | |
repo: repo | |
url = (repoFactory key).url() | |
msg.send "Okay, created repository #{key} for #{url}" | |
# Define a server. | |
robot.respond /(?:define|create) server (.*)/i, (msg) -> | |
if name = getServerName(msg.match[1]) | |
return msg.send "I already have a server called #{name}." | |
name = msg.match[1] | |
servers()[name] = | |
address: null | |
user: 'git' | |
directory: null | |
repo: robot.brain.data.defaultGitRepo | |
aliases: [] | |
msg.send "Okay, created a new server called #{name}." | |
# Set a server property. | |
robot.respond /set (.*) o[fn] server (.*) (?:to|=) (.*)/i, (msg) -> | |
return unless name = found msg.match[1], msg | |
property = msg.match[1] | |
value = msg.match[3] | |
unless property of servers()[name] | |
return msg.send "I'm not interested in a server's #{property}." | |
servers()[name][property] = value | |
msg.send "Okay, #{name} updated with #{property} = #{value}." | |
# Set a server alias. | |
robot.respond /alias server (.*) (?:to|as|=) (.*)/i, (msg) -> | |
return unless name = found msg.match[1], msg | |
alias = msg.match[2] | |
if alias of serverAliases() | |
msg.send "#{alias} is already an alias for #{name}." | |
else | |
servers()[name].aliases.push alias | |
serverAliases()[alias] = name | |
msg.send "Okay, set up alias #{alias} for #{name}." | |
# List a server's configuration properties. | |
robot.respond /describe server (.*)/i, (msg) -> | |
return unless name = found msg.match[1], msg | |
description = "\#\#\# #{name}\n\n" | |
server = servers()[name] | |
if server.user? | |
description += "- User: #{server.user}\n" | |
if server.address? | |
addressLink = link server.address, "http://#{server.address}" | |
description += "- Address: #{addressLink}\n" | |
if server.repo? | |
description += "- Repository: #{repoFactory(server.repo).url()}\n" | |
if server.directory? | |
description += "- Directory: `#{server.directory}`\n" | |
if server.aliases.length > 0 | |
description += "- Aliases: #{server.aliases.join(', ')}\n" | |
msg.send description | |
# Delete a server. | |
robot.respond /delete server (.*)/i, (msg) -> | |
return unless name = found msg.match[1], msg | |
delete serverAliases()[alias] for alias in servers()[name].aliases | |
delete servers()[name] | |
msg.send "Okay, server #{name} has been deleted forever." | |
# List available servers. | |
robot.respond /list servers/i, (msg) -> | |
list = "" | |
for name, server of servers() | |
list += "- #{name}\n" | |
msg.send list || "I don't know any servers, #{msg.message.user.name}." | |
# Show the current branch for a server. | |
robot.respond /branch (.*)/i, (msg) -> | |
return unless name = found msg.match[1], msg | |
new BranchCommand(name).run msg.send.bind msg | |
# Show recent commits on a server. | |
robot.respond /show last (\d+) commits on (.*)/i, (msg) -> | |
return unless name = found msg.match[2], msg | |
n = msg.match[1] | |
new CommitsCommand(name, n).run msg.send.bind msg | |
# Link to a wiki article. | |
robot.respond /(?:(.*) )?wiki (.*)/i, (msg) -> | |
[repo, topic] = msg.match.slice 1, 3 | |
repo ?= robot.brain.data.defaultGitRepo | |
return msg.send "I don't know that wiki." unless repo of gitRepos() | |
wiki = link topic, repoFactory(repo).wiki topic | |
msg.send "Check it out: #{wiki}." | |
# Set the default repo. | |
robot.respond /set default repo(?:sitory)? (?:to|=) (.*)/i, (msg) -> | |
robot.brain.data.defaultGitRepo = msg.match[1] | |
msg.send "You got it, #{msg.message.user.name}." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment