<|start_filename|>build/runtime/Dockerfile<|end_filename|>
FROM lambci/lambda-base:build

RUN yum install -y yum-utils rpm-build ncurses-devel; \
  yumdownloader --source bash && \
  yum-builddep -y bash && \
  rpm -ivh *.rpm && \
  rm -rf /dev/core /dev/fd /dev/tty && \
  rpmbuild -bi --nocheck /usr/src/rpm/SPECS/bash.spec
RUN mv /usr/src/rpm/BUILDROOT/bash-*/bin /tmp/


FROM lambci/yumda:1

ARG GIT_VERSION

RUN yum install -y git-${GIT_VERSION} zip perl-Digest-SHA


FROM lambci/lambda-base:build

WORKDIR /opt

COPY --from=0 /tmp/bin ./bin
COPY --from=1 /lambda/opt .

ARG PIP_VERSION

RUN python3 -m venv /opt && /opt/bin/pip install -U "pip==${PIP_VERSION}" setuptools wheel && \
  find /opt/lib/python3.6 \( -name examples -or -name "*.exe" -or -name __pycache__ \) | xargs rm -rf

ARG NODE_VERSION

RUN curl -sSL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz | \
  tar -xJ --strip-components 1

RUN curl -O https://raw.githubusercontent.com/lambci/node-custom-lambda/${NODE_VERSION}/v12.x/layer.zip && \
  unzip -o layer.zip && \
  rm layer.zip

ARG NPM_VERSION

RUN npm install -g npm@${NPM_VERSION} && \
  rm -rf LICENSE README.md CHANGELOG.md share/{man,doc} \
    lib/node_modules/npm/{docs,man,changelogs,node_modules/ajv/dist} && \
  find lib/node_modules -name '*.ts' -delete

ARG AWS_SDK_VERSION

RUN npm install -g aws-sdk@${AWS_SDK_VERSION} && \
  rm -rf lib/node_modules/aws-sdk/dist && \
  find lib/node_modules -name '*.ts' -delete

RUN mkdir nodejs && mv lib/node_modules nodejs/ && \
  cd bin && \
  ln -sf ../nodejs/node_modules/npm/bin/npm-cli.js npm && \
  ln -sf ../nodejs/node_modules/npm/bin/npx-cli.js npx

RUN zip -yr /tmp/layer.zip .


<|start_filename|>test/fixtures/push.tag.json<|end_filename|>
{
  "body": "{\"ref\":\"refs/tags/whatever\",\"before\":\"0000000000000000000000000000000000000000\",\"after\":\"ab64f6ecd8775edb64af24d9adfbbe9ce54f030a\",\"created\":true,\"deleted\":false,\"forced\":true,\"base_ref\":\"refs/heads/master\",\"compare\":\"https://github.com/mhart/test-ci-project/compare/whatever\",\"commits\":[],\"head_commit\":{\"id\":\"ab64f6ecd8775edb64af24d9adfbbe9ce54f030a\",\"tree_id\":\"6516e23572fd5137cce4e877ceafc9576f084c7d\",\"distinct\":true,\"message\":\"fix cmd\",\"timestamp\":\"2016-06-27T22:57:51-04:00\",\"author\":{\"name\":\"<NAME>\",\"email\":\"<EMAIL>\",\"username\":\"mhart\"},\"committer\":{\"name\":\"<NAME>\",\"email\":\"<EMAIL>\",\"username\":\"mhart\"},\"added\":[],\"removed\":[],\"modified\":[\"package.json\"]},\"repository\":{\"id\":34931472,\"name\":\"test-ci-project\",\"full_name\":\"mhart/test-ci-project\",\"owner\":{\"name\":\"mhart\",\"email\":\"<EMAIL>\"},\"private\":false,\"description\":null,\"fork\":false,\"created_at\":1430529913,\"updated_at\":\"2016-06-02T22:36:43Z\",\"pushed_at\":1467083098,\"clone_url\":\"https://github.com/mhart/test-ci-project.git\",\"homepage\":null,\"size\":325,\"stargazers_count\":0,\"watchers_count\":0,\"language\":\"JavaScript\",\"has_issues\":true,\"has_downloads\":true,\"has_wiki\":true,\"has_pages\":false,\"forks_count\":1,\"open_issues_count\":1,\"forks\":1,\"open_issues\":1,\"watchers\":0,\"default_branch\":\"master\",\"stargazers\":0,\"master_branch\":\"master\"},\"pusher\":{\"name\":\"mhart\",\"email\":\"<EMAIL>\"},\"sender\":{\"login\":\"mhart\",\"id\":367936,\"avatar_url\":\"https://avatars.githubusercontent.com/u/367936?v=3\",\"gravatar_id\":\"\",\"type\":\"User\",\"site_admin\":false}}",
  "headers": {
    "X-Github-Event": "push"
  }
}


<|start_filename|>index.js<|end_filename|>
var config = require('./utils/config')
var log = require('./utils/log')
var cfn = require('./cfn')
var actions = require('./actions')
var webhook = require('./sources/webhook')

exports.handler = function(event, context, cb) {

  log.init(`LambCI v${config.VERSION} triggered on stack "${config.STACK}"\n`) // STACK is usually 'lambci'

  // Check if it's the CloudFormation stack calling us
  if (event.ResourceType == 'Custom::ConfigUpdater') {

    return cfn.update(event, context, cb)

  // Or a custom (manual) event
  } else if (typeof actions[event.action] == 'function') {

    return actions[event.action](event, context, cb)

  // Otherwise it should be a GitHub webhook
  } else if (event.httpMethod == 'POST') {

    return webhook.build(event, context, cb)
  }

  log.error('Unknown event, ignoring:\n%j', event)
  return cb(new Error('Unknown event'))
}



<|start_filename|>utils/config.js<|end_filename|>
var path = require('path')
var fs = require('fs')
var execSync = require('child_process').execSync
var utils = require('.')
var log = require('./log')

// Function name is often something like `lambci-build` or `lambci-public-build`
// So this will usually resolve to `lambci` or `lambci-public` or similar
exports.STACK = (process.env.AWS_LAMBDA_FUNCTION_NAME || 'lambci').replace(/-build.*$/, '')

exports.VERSION = require('../package.json').version

// This is used to check latest pkg version – feel free to remove if you don't want this
exports.CHECK_VERSION_URL = `https://lambci.s3.amazonaws.com/fn/latest.txt?x-lambci-version=${exports.VERSION}`

exports.BASE_DIR = '/tmp/lambci'
exports.HOME_DIR = path.join(exports.BASE_DIR, 'home') // eg: /tmp/lambci/home
exports.BASE_BUILD_DIR = path.join(exports.BASE_DIR, 'build') // eg: /tmp/lambci/build

// Config Order (lowest to highest precedence):
// DEFAULT_CONFIG (below)
// 'global' config from DynamoDB
// 'gh/<owner>/<reponame>' config from DynamoDB
// package.json file with 'lambci' key
// .lambci.js or .lambci.json file

exports.DEFAULT_CONFIG = {
  cmd: 'npm ci && npm test',
  env: {
  },
  secretEnv: {
    GITHUB_TOKEN: '',
    GITHUB_SECRET: '',
    SLACK_TOKEN: '',
  },
  s3Bucket: '',
  notifications: {
    slack: {
      channel: '#general',
      username: 'LambCI',
      iconUrl: 'https://lambci.s3.amazonaws.com/assets/logo-48x48.png',
      asUser: false,
    },
    // Example SNS notifications config:
    // sns: {
      // topicArn: 'arn:aws:sns:us-east-1:1234:lambci-StatusTopic-1WF8BT36',
    // },
  },
  build: false, // Build nothing by default except master and PRs
  branches: {
    master: true,
  },
  pullRequests: {
    fromSelfPublicRepo: true,
    fromSelfPrivateRepo: true,
    fromForkPublicRepo: {
      build: true,
      inheritSecrets: false,
      allowConfigOverrides: ['cmd', 'env'],
    },
    fromForkPrivateRepo: false,
  },
  s3PublicSecretNames: true,
  inheritSecrets: true,
  allowConfigOverrides: true,
  clearTmp: true,
  git: {
    depth: 5,
  },
}

exports.initSync = function(config) {
  // If we're in potentially unsafe environments (eg, we run public builds),
  // then we don't want ppl messing with *anything* in /tmp, so we blow it away each time
  // Can set `clearTmp` to avoid recreating the HOME_DIR, etc each time
  if (config.clearTmp) {
    // Don't have permissions to delete /tmp but can delete everything in it
    // Could also do `find ... -delete` for all files, but this is quicker
    execSync('find /tmp -mindepth 1 -maxdepth 1 -exec rm -rf {} +')
  }
  // The git executables blow up in size when zipped for some reason,
  // so we leave them tarred and untar in /tmp/...
  // TODO: figure out if there's a way around this
  execSync(`
    if ! [ -d ${exports.HOME_DIR} ]; then
      mkdir -p ${exports.HOME_DIR}
      cp -r ${__dirname}/../home/. ${exports.HOME_DIR}
    fi
  `)
}

exports.initConfig = function(configs, build) {
  var allConfigs = [Object.create(null), exports.DEFAULT_CONFIG].concat(configs || [])
  build.config = utils.merge.apply(null, allConfigs)
  return resolveBranchConfig(build)
}

// Add build-specific env vars and resolve file configs
exports.prepareBuildConfig = function(build) {
  var buildConfig = {
    env: {
      CI: true,
      LAMBCI: true,
      LAMBCI_REPO: build.repo,
      LAMBCI_BRANCH: build.branch,
      LAMBCI_CLONE_REPO: build.cloneRepo,
      LAMBCI_CHECKOUT_BRANCH: build.checkoutBranch,
      LAMBCI_COMMIT: build.commit,
      LAMBCI_PULL_REQUEST: build.prNum || '',
      AWS_REQUEST_ID: build.requestId,
    },
  }
  return utils.merge(exports.resolveFileConfigs(build), buildConfig)
}

exports.resolveFileConfigs = function(build) {
  build.config = resolveFileConfig(build)
  return resolveBranchConfig(build)
}

exports.resolveEnv = function(config) {
  var secretEnv = config.inheritSecrets && config.secretEnv
  return utils.merge(Object.create(null), config.env, secretEnv || {})
}

exports.checkVersion = function(cb) {
  if (!exports.CHECK_VERSION_URL) return cb()
  utils.request(exports.CHECK_VERSION_URL, function(err, res, body) {
    if (err || res.statusCode != 200) {
      log.error('Could not fetch latest LambCI version: %s', err || body)
      return cb()
    }
    var latestVersion = body.trim()
    if (utils.semverCmp(exports.VERSION, latestVersion) < 0) {
      log.info(`Your LambCI version is out of date. Latest is: v${latestVersion}\n`)
    }
    cb(null, latestVersion)
  })
}

function resolveBranchConfig(build) {
  var configObj, key

  if (build.eventType == 'pull_request') {
    var pullRequests = build.config.pullRequests

    if (typeof pullRequests == 'boolean') {
      configObj = pullRequests
    } else {
      pullRequests = pullRequests || {}
      key = build.isFork ?
        (build.isPrivate ? 'fromForkPrivateRepo' : 'fromForkPublicRepo') :
        (build.isPrivate ? 'fromSelfPrivateRepo' : 'fromSelfPublicRepo')
      configObj = pullRequests[key]
    }
  } else { // eventType == 'push'
    var branches = build.config.branches

    if (typeof branches == 'boolean') {
      configObj = branches
    } else {
      branches = branches || {}
      configObj = branches[build.branch]
      if (configObj == null) {
        key = Object.keys(branches).find(branch => {
          var match = branch.match(/^!?\/(.+)\/$/)
          if (!match) return false
          var keyMatches = new RegExp(match[1]).test(build.branch)
          return branch[0] == '!' ? !keyMatches : keyMatches
        })
        configObj = branches[key]
      }
    }
  }
  if (typeof configObj == 'boolean') {
    configObj = {build: configObj}
  }
  if (typeof configObj == 'object' && configObj) {
    build.config = utils.merge(Object.create(null), build.config, configObj)
  }
  return build.config
}

function resolveFileConfig(build) {
  var packageConfig, dotConfig

  if (!build.config.allowConfigOverrides) return build.config

  try {
    var packageJson = JSON.parse(fs.readFileSync(path.join(build.cloneDir, 'package.json'), 'utf8'))
    packageConfig = packageJson.lambci
  } catch (e) {
    packageConfig = undefined
  }

  try {
    // Only use `require` if we're in a safe environment
    // It will look for .lambci.js and .lambci.json
    if (build.config.inheritSecrets) {
      var resolvedPath = require.resolve(path.join(build.cloneDir, '.lambci'))
      delete require.cache[resolvedPath] // Refresh each build
      dotConfig = require(resolvedPath)
    } else {
      dotConfig = JSON.parse(fs.readFileSync(path.join(build.cloneDir, '.lambci.json'), 'utf8'))
    }
  } catch (e) {
    dotConfig = undefined
  }

  var fileConfig = utils.merge(Object.create(null), packageConfig, dotConfig)

  if (Array.isArray(build.config.allowConfigOverrides)) {
    fileConfig = build.config.allowConfigOverrides.reduce((obj, key) => {
      if ({}.hasOwnProperty.call(fileConfig, key)) obj[key] = fileConfig[key]
      return obj
    }, {})
  }

  return utils.merge(Object.create(null), build.config, fileConfig)
}



<|start_filename|>Dockerfile<|end_filename|>
FROM lambci/yumda:1

RUN yum install -y gcc72-c++


FROM lambci/lambda:provided

WORKDIR /tmp/lambci/build

ENV HOME=/tmp/lambci/home
ENV PATH=$HOME/.local/bin:$HOME/usr/bin:/var/task/node_modules/.bin:/opt/bin:$PATH \
  LD_LIBRARY_PATH=$HOME/usr/lib64:$LD_LIBRARY_PATH \
  PYTHONPATH=$HOME/.local/lib/python3.6/site-packages:/opt/lib/python3.6/site-packages \
  PERL5LIB=/opt/share/perl5/vendor_perl:/opt/lib/perl5/vendor_perl \
  SHELL=/opt/bin/bash \
  TERM=xterm-256color \
  FORCE_COLOR=true \
  NPM_CONFIG_COLOR=always \
  MOCHA_COLORS=true

USER root

COPY --from=0 /lambda/opt /opt

ADD ./lambda.zip ./runtime/layer.zip /tmp/

RUN unzip -q /tmp/lambda.zip -d /var/task && \
  unzip -q /tmp/layer.zip -d /opt && \
  rm /tmp/*.zip

RUN chown -R slicer:497 /var/task && chown -R sbx_user1051:495 /tmp

USER sbx_user1051

RUN mkdir -p $HOME && \
  cp -r /var/task/home/. $HOME

ENTRYPOINT []
CMD /opt/bin/bash


<|start_filename|>build/gcc/Dockerfile<|end_filename|>
FROM lambci/lambda-base

RUN find /usr ! -type d | sort > fs.txt
RUN yum install -y \
  gcc48-4.8.5 \
  gcc48-c++-4.8.5 \
  libasan-4.8.5 \
  libatomic-4.8.5 \
  libitm-4.8.5 \
  libmudflap-4.8.5 \
  libquadmath-4.8.5 \
  || true
RUN bash -c 'comm -13 fs.txt <(find /usr ! -type d | sort)' | grep -v ^/usr/share | tar -c -T - | tar -x -C /tmp --strip-components=1
RUN rm -rf /tmp/lib/gcc/x86_64-amazon-linux/4.8.5/32
RUN cp /lib64/libgcc_s-4.8.5-20150702.so.1 /tmp/lib64/
RUN for lib in libc libpthread; do sed -i s_/usr/_/tmp/_ /tmp/lib64/${lib}.so; done
RUN for lib in libasan libatomic libitm libmudflap libmudflapth libquadmath libtsan; do \
    sed -i s_/usr/_/tmp/_ /tmp/lib/gcc/x86_64-amazon-linux/4.8.5/${lib}.so; \
  done
RUN cd /tmp/bin && \
  ln -sf c++48 c++ && \
  ln -sf gcc48-c89 c89 && \
  ln -sf gcc48-c99 c99 && \
  ln -sf gcc48 cc && \
  ln -sf cpp48 cpp && \
  ln -sf g++48 g++ && \
  ln -sf gcc48 gcc && \
  ln -sf gcov48 gcov && \
  cd /tmp/lib64 && \
  ln -sf libstdc++.so.6.0.19 libstdc++.so && \
  ln -sf /usr/lib64/pkcs11/p11-kit-trust.so p11-kit-trust.so && \
  cd /tmp/lib/gcc/x86_64-amazon-linux/4.8.5 && \
  ln -sf /tmp/lib64/libgcc_s-4.8.5-20150702.so.1 libgcc_s.so && \
  ln -sf /usr/lib64/libgomp.so.1.0.0 libgomp.so && \
  ln -sf /usr/libexec/getconf/POSIX_V6_LP64_OFF64 /tmp/libexec/getconf/default


<|start_filename|>test/parsers.js<|end_filename|>
var assert = require('chai').assert
var log = require('../utils/log')
var webhook = require('../sources/webhook')

log.raw = () => {}

describe('parsers', function() {

  describe('webhook', function() {

    it('should parse pull request opened events', function() {
      var webhookEvent = require('./fixtures/pullRequest.opened.json')
      var build = webhook.parseEvent(webhookEvent, 'pull_request')
      assert(build.event)
      delete build.event
      assert.deepEqual(build, {
        project: 'gh/mhart/test-ci-project',
        eventType: 'pull_request',
        repo: 'mhart/test-ci-project',
        isPrivate: false,
        isFork: true,
        branch: 'master',
        cloneRepo: 'lambci/test-ci-project',
        checkoutBranch: 'mhart-patch-3',
        commit: '<PASSWORD>',
        prNum: 4,
        user: 'mhart',
        comment: 'Create README.md',
        baseCommit: '<PASSWORD>',
      })
    })

    it('should parse pull request synchronize events', function() {
      var webhookEvent = require('./fixtures/pullRequest.synchronize.json')
      var build = webhook.parseEvent(webhookEvent, 'pull_request')
      assert(build.event)
      delete build.event
      assert.deepEqual(build, {
        project: 'gh/mhart/test-ci-project',
        eventType: 'pull_request',
        repo: 'mhart/test-ci-project',
        isPrivate: false,
        isFork: false,
        branch: 'master',
        cloneRepo: 'mhart/test-ci-project',
        checkoutBranch: 'mhart-patch-1',
        commit: '<PASSWORD>',
        prNum: 1,
        user: 'mhart',
        comment: 'Create README.md',
        baseCommit: '<PASSWORD>',
      })
    })

    it('should parse push events', function() {
      var webhookEvent = require('./fixtures/push.force.json')
      var build = webhook.parseEvent(webhookEvent, 'push')
      assert(build.event)
      delete build.event
      assert.instanceOf(build.committers, Set)
      assert.deepEqual(Array.from(build.committers), ['mhart'])
      delete build.committers
      assert.deepEqual(build, {
        project: 'gh/mhart/test-ci-project',
        eventType: 'push',
        repo: 'mhart/test-ci-project',
        isPrivate: false,
        branch: 'mhart-patch-1',
        cloneRepo: 'mhart/test-ci-project',
        checkoutBranch: 'mhart-patch-1',
        commit: '<PASSWORD>',
        user: 'mhart',
        comment: 'Add failing test',
        baseCommit: '<PASSWORD>',
      })
    })

    it('should parse delete events', function() {
      var webhookEvent = require('./fixtures/push.deleted.json')
      var build = webhook.parseEvent(webhookEvent, 'push')
      assert.equal(build.ignore, 'Branch some-new-branch was deleted')
    })

    it('should parse tag events', function() {
      var webhookEvent = require('./fixtures/push.tag.json')
      var build = webhook.parseEvent(webhookEvent, 'push')
      assert.equal(build.ignore, 'Ref does not match any branches: refs/tags/whatever')
    })

    it('should parse events if committers have no username', function() {
      var webhookEvent = require('./fixtures/push.force.json')
      var gitEvent = JSON.parse(webhookEvent.body)
      gitEvent.commits.concat(gitEvent.head_commit).forEach(commit => {
        delete commit.author.username
        delete commit.committer.username
      })
      webhookEvent.body = JSON.stringify(gitEvent)
      var build = webhook.parseEvent(webhookEvent, 'push')
      assert(build.event)
      delete build.event
      assert.instanceOf(build.committers, Set)
      assert.deepEqual(Array.from(build.committers), [])
      delete build.committers
      assert.deepEqual(build, {
        project: 'gh/mhart/test-ci-project',
        eventType: 'push',
        repo: 'mhart/test-ci-project',
        isPrivate: false,
        branch: 'mhart-patch-1',
        cloneRepo: 'mhart/test-ci-project',
        checkoutBranch: 'mhart-patch-1',
        commit: '<PASSWORD>',
        user: 'mhart',
        comment: 'Add failing test',
        baseCommit: '<PASSWORD>',
      })
    })

  })

})


<|start_filename|>sources/webhook.js<|end_filename|>
var crypto = require('crypto')
var AWS = require('aws-sdk')
var log = require('../utils/log')
var db = require('../db')
var github = require('./github')

var WEBHOOK_CONFIG_TTL = 60 * 1000 // Cache for 1 min to mitigate high request volume
var WEBHOOK_CONFIG = {
  secret: '',
  fetchedAt: 0,
}

var lambda = new AWS.Lambda()

exports.build = function(event, context, cb) {

  var done = function(err, data, statusCode) {
    log.logIfErr(err)
    cb(err, !err && {
      statusCode: statusCode || 200,
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify(data || {}),
    })
  }

  var clientError = function(msg) {
    log.error(msg)
    done(null, {error: msg}, 400)
  }

  // Currently only GitHub is supported as a source
  var eventType = event.headers && event.headers['X-GitHub-Event']
  if (!eventType) return clientError('Missing X-GitHub-Event header')

  var signature = ((event.headers['X-Hub-Signature'] || '').match(/^sha1=([0-9a-f]{40})$/) || [])[1]
  if (!signature) return clientError('Missing X-Hub-Signature header')

  getWebhookSecret(function(err, secret) {
    if (err) return done(err)
    if (!secret) return clientError('Missing secretEnv.GITHUB_SECRET in global config')

    var signedBody = crypto.createHmac('sha1', secret).update(event.body || '').digest('hex')
    if (signature !== signedBody) return clientError('X-Hub-Signature does not match signed body')

    if (eventType == 'ping') {
      log.info('Ping event, not running build')
      return done(null, {msg: 'pong'})
    }

    var buildData

    try {
      buildData = exports.parseEvent(event, eventType)
    } catch (e) {
      return clientError(e.message)
    }

    if (buildData.ignore) {
      log.info(buildData.ignore)
      log.info('Not running build')
      return done(null, {msg: buildData.ignore})
    }

    lambda.invoke({
      InvocationType: 'Event',
      FunctionName: process.env.AWS_LAMBDA_FUNCTION_NAME,
      Payload: JSON.stringify(Object.assign({action: 'build'}, buildData)),
    }, done)
  })
}

exports.parseEvent = function(event, eventType) {
  var innerEvent

  try {
    innerEvent = JSON.parse(event.body)
  } catch (e) {
    throw new Error(`Could not parse webhook body as JSON: ${e.message}`)
  }

  return github.parseEvent(innerEvent, eventType)
}

function getWebhookSecret(cb) {
  if (Date.now() < WEBHOOK_CONFIG.fetchedAt + WEBHOOK_CONFIG_TTL) {
    log.info('Using cached webhook secret')
    return cb(null, WEBHOOK_CONFIG.secret)
  }
  db.getGlobalConfigValue('secretEnv.GITHUB_SECRET', function(err, config) {
    if (err) return cb(err)
    WEBHOOK_CONFIG.secret = config && config.secretEnv && config.secretEnv.GITHUB_SECRET
    if (WEBHOOK_CONFIG.secret) {
      WEBHOOK_CONFIG.fetchedAt = Date.now()
    }
    cb(null, WEBHOOK_CONFIG.secret)
  })
}


<|start_filename|>test/fixtures/pullRequest.failure.json<|end_filename|>
{
  "body": "{\"action\":\"synchronize\",\"number\":1,\"pull_request\":{\"id\":34579121,\"number\":1,\"state\":\"open\",\"locked\":false,\"title\":\"Create README.md\",\"user\":{\"login\":\"mhart\",\"id\":367936,\"avatar_url\":\"https://avatars.githubusercontent.com/u/367936?v=3\",\"gravatar_id\":\"\",\"type\":\"User\",\"site_admin\":false},\"body\":\"\",\"created_at\":\"2015-05-02T01:52:23Z\",\"updated_at\":\"2016-06-27T20:10:36Z\",\"closed_at\":null,\"merged_at\":null,\"merge_commit_sha\":\"0613661031d0b4686d1a700210e30b132e8205b5\",\"assignee\":null,\"assignees\":[],\"milestone\":null,\"head\":{\"label\":\"mhart:mhart-patch-1\",\"ref\":\"mhart-patch-1\",\"sha\":\"5f05e4f5340d42bf805630f5f92634fd79d3700a\",\"user\":{\"login\":\"mhart\",\"id\":367936,\"avatar_url\":\"https://avatars.githubusercontent.com/u/367936?v=3\",\"gravatar_id\":\"\",\"type\":\"User\",\"site_admin\":false},\"repo\":{\"id\":34931472,\"name\":\"test-ci-project\",\"full_name\":\"mhart/test-ci-project\",\"owner\":{\"login\":\"mhart\",\"id\":367936,\"avatar_url\":\"https://avatars.githubusercontent.com/u/367936?v=3\",\"gravatar_id\":\"\",\"type\":\"User\",\"site_admin\":false},\"private\":false,\"description\":null,\"fork\":false,\"created_at\":\"2015-05-02T01:25:13Z\",\"updated_at\":\"2016-06-02T22:36:43Z\",\"pushed_at\":\"2016-06-27T20:10:36Z\",\"clone_url\":\"https://github.com/mhart/test-ci-project.git\",\"homepage\":null,\"size\":325,\"stargazers_count\":0,\"watchers_count\":0,\"language\":\"JavaScript\",\"has_issues\":true,\"has_downloads\":true,\"has_wiki\":true,\"has_pages\":false,\"forks_count\":1,\"open_issues_count\":1,\"forks\":1,\"open_issues\":1,\"watchers\":0,\"default_branch\":\"master\"}},\"base\":{\"label\":\"mhart:master\",\"ref\":\"master\",\"sha\":\"20630f7fb959db56bc96a81f628db3331b80d6cf\",\"user\":{\"login\":\"mhart\",\"id\":367936,\"avatar_url\":\"https://avatars.githubusercontent.com/u/367936?v=3\",\"gravatar_id\":\"\",\"type\":\"User\",\"site_admin\":false},\"repo\":{\"id\":34931472,\"name\":\"test-ci-project\",\"full_name\":\"mhart/test-ci-project\",\"owner\":{\"login\":\"mhart\",\"id\":367936,\"avatar_url\":\"https://avatars.githubusercontent.com/u/367936?v=3\",\"gravatar_id\":\"\",\"type\":\"User\",\"site_admin\":false},\"private\":false,\"description\":null,\"fork\":false,\"created_at\":\"2015-05-02T01:25:13Z\",\"updated_at\":\"2016-06-02T22:36:43Z\",\"pushed_at\":\"2016-06-27T20:10:36Z\",\"clone_url\":\"https://github.com/mhart/test-ci-project.git\",\"homepage\":null,\"size\":325,\"stargazers_count\":0,\"watchers_count\":0,\"language\":\"JavaScript\",\"has_issues\":true,\"has_downloads\":true,\"has_wiki\":true,\"has_pages\":false,\"forks_count\":1,\"open_issues_count\":1,\"forks\":1,\"open_issues\":1,\"watchers\":0,\"default_branch\":\"master\"}},\"merged\":false,\"mergeable\":null,\"mergeable_state\":\"unknown\",\"merged_by\":null,\"comments\":0,\"review_comments\":0,\"commits\":11,\"additions\":10,\"deletions\":0,\"changed_files\":3},\"before\":\"cb9a8bfea83f9c7d15a800e9aeafac2529cda3bd\",\"after\":\"5f05e4f5340d42bf805630f5f92634fd79d3700a\",\"repository\":{\"id\":34931472,\"name\":\"test-ci-project\",\"full_name\":\"mhart/test-ci-project\",\"owner\":{\"login\":\"mhart\",\"id\":367936,\"avatar_url\":\"https://avatars.githubusercontent.com/u/367936?v=3\",\"gravatar_id\":\"\",\"type\":\"User\",\"site_admin\":false},\"private\":false,\"description\":null,\"fork\":false,\"created_at\":\"2015-05-02T01:25:13Z\",\"updated_at\":\"2016-06-02T22:36:43Z\",\"pushed_at\":\"2016-06-27T20:10:36Z\",\"clone_url\":\"https://github.com/mhart/test-ci-project.git\",\"homepage\":null,\"size\":325,\"stargazers_count\":0,\"watchers_count\":0,\"language\":\"JavaScript\",\"has_issues\":true,\"has_downloads\":true,\"has_wiki\":true,\"has_pages\":false,\"forks_count\":1,\"open_issues_count\":1,\"forks\":1,\"open_issues\":1,\"watchers\":0,\"default_branch\":\"master\"},\"sender\":{\"login\":\"mhart\",\"id\":367936,\"avatar_url\":\"https://avatars.githubusercontent.com/u/367936?v=3\",\"gravatar_id\":\"\",\"type\":\"User\",\"site_admin\":false}}",
  "headers": {
    "X-Github-Event": "pull_request"
  }
}


<|start_filename|>test/fixtures/push.normal.json<|end_filename|>
{
  "body": "{\"ref\":\"refs/heads/master\",\"before\":\"5e0f22b382bcb594420cd93387c9ca4a10040b1c\",\"after\":\"a0e1c022661238e05fddb937350da7c6c1172786\",\"created\":false,\"deleted\":false,\"forced\":false,\"base_ref\":null,\"compare\":\"https://github.com/mhart/test-ci-project/compare/5e0f22b382bc...a0e1c0226612\",\"commits\":[{\"id\":\"02ff5b42679b0844dccf030720fc3792890fb60f\",\"tree_id\":\"cc4a519110422b1a8af3252cb8a6c697c4765fa5\",\"distinct\":true,\"message\":\"Try Go with make\",\"timestamp\":\"2016-06-28T21:58:46-04:00\",\"author\":{\"name\":\"<NAME>\",\"email\":\"<EMAIL>\",\"username\":\"mhart\"},\"committer\":{\"name\":\"<NAME>\",\"email\":\"<EMAIL>\",\"username\":\"mhart\"},\"added\":[\"build-go-make.sh\"],\"removed\":[],\"modified\":[\"package.json\"]},{\"id\":\"f7fa87465ddf38bb557ff7ebdd1b46564296cf12\",\"tree_id\":\"f90aee3374f4e7e617d01465278c2c9abab0381f\",\"distinct\":true,\"message\":\"Add test Dockerfile\",\"timestamp\":\"2016-06-30T17:44:22-04:00\",\"author\":{\"name\":\"<NAME>\",\"email\":\"<EMAIL>\",\"username\":\"mhart\"},\"committer\":{\"name\":\"<NAME>\",\"email\":\"<EMAIL>\",\"username\":\"mhart\"},\"added\":[\".dockerignore\",\"Dockerfile.test\"],\"removed\":[],\"modified\":[]},{\"id\":\"a0e1c022661238e05fddb937350da7c6c1172786\",\"tree_id\":\"e07ab28dcfb7b39495f01af5393c8d11438593b9\",\"distinct\":true,\"message\":\"Revert to single npm test command\",\"timestamp\":\"2016-07-02T17:49:49-04:00\",\"author\":{\"name\":\"<NAME>\",\"email\":\"<EMAIL>\",\"username\":\"mhart\"},\"committer\":{\"name\":\"<NAME>\",\"email\":\"<EMAIL>\",\"username\":\"mhart\"},\"added\":[],\"removed\":[],\"modified\":[\".dockerignore\",\".gitignore\",\"package.json\"]}],\"head_commit\":{\"id\":\"a0e1c022661238e05fddb937350da7c6c1172786\",\"tree_id\":\"e07ab28dcfb7b39495f01af5393c8d11438593b9\",\"distinct\":true,\"message\":\"Revert to single npm test command\",\"timestamp\":\"2016-07-02T17:49:49-04:00\",\"author\":{\"name\":\"<NAME>\",\"email\":\"<EMAIL>\",\"username\":\"mhart\"},\"committer\":{\"name\":\"<NAME>\",\"email\":\"<EMAIL>\",\"username\":\"mhart\"},\"added\":[],\"removed\":[],\"modified\":[\".dockerignore\",\".gitignore\",\"package.json\"]},\"repository\":{\"id\":34931472,\"name\":\"test-ci-project\",\"full_name\":\"mhart/test-ci-project\",\"owner\":{\"name\":\"mhart\",\"email\":\"<EMAIL>\"},\"private\":false,\"description\":null,\"fork\":false,\"created_at\":1430529913,\"updated_at\":\"2016-06-02T22:36:43Z\",\"pushed_at\":1467496195,\"clone_url\":\"https://github.com/mhart/test-ci-project.git\",\"homepage\":null,\"size\":892,\"stargazers_count\":0,\"watchers_count\":0,\"language\":\"JavaScript\",\"has_issues\":true,\"has_downloads\":true,\"has_wiki\":true,\"has_pages\":false,\"forks_count\":1,\"open_issues_count\":1,\"forks\":1,\"open_issues\":1,\"watchers\":0,\"default_branch\":\"master\",\"stargazers\":0,\"master_branch\":\"master\"},\"pusher\":{\"name\":\"mhart\",\"email\":\"<EMAIL>\"},\"sender\":{\"login\":\"mhart\",\"id\":367936,\"avatar_url\":\"https://avatars.githubusercontent.com/u/367936?v=3\",\"gravatar_id\":\"\",\"type\":\"User\",\"site_admin\":false}}",
  "headers": {
    "X-Github-Event": "push"
  }
}
