Skip to content

Instantly share code, notes, and snippets.

@tommcdo
Created March 17, 2014 12:46
Show Gist options
  • Save tommcdo/9598595 to your computer and use it in GitHub Desktop.
Save tommcdo/9598595 to your computer and use it in GitHub Desktop.
# 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