Unvendor Archery (#459)

diff --git a/.github/workflows/comment_bot.yml b/.github/workflows/comment_bot.yml
index 9e10300..6ca0953 100644
--- a/.github/workflows/comment_bot.yml
+++ b/.github/workflows/comment_bot.yml
@@ -33,13 +33,13 @@
       - name: Checkout Arrow
         uses: actions/checkout@v2
         with:
-          path: arrow
+          repository: apache/arrow
       - name: Set up Python
         uses: actions/setup-python@v2
         with:
           python-version: 3.8
       - name: Install Archery and Crossbow dependencies
-        run: pip install -e arrow/dev/archery[bot]
+        run: pip install -e dev/archery[bot]
       - name: Handle Github comment event
         env:
           ARROW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -49,78 +49,6 @@
             --event-name ${{ github.event_name }} \
             --event-payload ${{ github.event_path }}
 
-  autotune:
-    name: "Fix all the things"
-    if: startsWith(github.event.comment.body, '@github-actions autotune')
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      - uses: r-lib/actions/pr-fetch@master
-        with:
-          repo-token: ${{ secrets.GITHUB_TOKEN }}
-      - name: See what is different
-        run: |
-          set -ex
-          git remote add upstream https://github.com/apache/arrow
-          git fetch upstream
-          changed() {
-            git diff --name-only HEAD..upstream/master | grep -e "$1" >/dev/null 2>&1
-          }
-          if changed '^r/.*\.R$'; then
-            echo "R_DOCS=true" >> $GITHUB_ENV
-          fi
-          if changed 'cmake' || changed 'CMake'; then
-            echo "CMAKE_FORMAT=true" >> $GITHUB_ENV
-          fi
-          if changed '^cpp/src'; then
-            echo "CLANG_FORMAT_CPP=true" >> $GITHUB_ENV
-          fi
-          if changed '^r/src'; then
-            echo "CLANG_FORMAT_R=true" >> $GITHUB_ENV
-          fi
-      - name: Run cmake_format
-        if: env.CMAKE_FORMAT == 'true' || endsWith(github.event.comment.body, 'everything')
-        run: |
-          set -ex
-          export PATH=/home/runner/.local/bin:$PATH
-          python3 -m pip install --upgrade pip setuptools wheel
-          python3 -m pip install -r dev/archery/requirements-lint.txt
-          python3 run-cmake-format.py
-      - name: Run clang-format on cpp
-        if: env.CLANG_FORMAT_CPP == 'true' || endsWith(github.event.comment.body, 'everything')
-        run: |
-          . .env # To get the clang version we use
-          cpp/build-support/run_clang_format.py \
-              --clang_format_binary=clang-format-${CLANG_TOOLS} \
-              --exclude_glob=cpp/build-support/lint_exclusions.txt \
-              --source_dir=cpp/src --quiet --fix
-      - name: Run clang-format on r
-        if: env.CLANG_FORMAT_R == 'true' || endsWith(github.event.comment.body, 'everything')
-        run: |
-          . .env # To get the clang version we use
-          cpp/build-support/run_clang_format.py \
-              --clang_format_binary=clang-format-${CLANG_TOOLS} \
-              --exclude_glob=cpp/build-support/lint_exclusions.txt \
-              --source_dir=r/src --quiet --fix
-      - uses: r-lib/actions/setup-r@v1
-        if: env.R_DOCS == 'true' || endsWith(github.event.comment.body, 'everything')
-      - name: Update R docs
-        if: env.R_DOCS == 'true' || endsWith(github.event.comment.body, 'everything')
-        shell: Rscript {0}
-        run: |
-          source("ci/etc/rprofile")
-          install.packages(c("remotes", "roxygen2"))
-          remotes::install_deps("r")
-          roxygen2::roxygenize("r")
-      - name: Commit results
-        run: |
-          git config user.name "$(git log -1 --pretty=format:%an)"
-          git config user.email "$(git log -1 --pretty=format:%ae)"
-          git commit -a -m 'Autoformat/render all the things [automated commit]' || echo "No changes to commit"
-      - uses: r-lib/actions/pr-push@master
-        with:
-          repo-token: ${{ secrets.GITHUB_TOKEN }}
-
   rebase:
     name: "Rebase"
     if: startsWith(github.event.comment.body, '@github-actions rebase')
diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml
index 545cb97..548f0dd 100644
--- a/.github/workflows/dev.yml
+++ b/.github/workflows/dev.yml
@@ -27,19 +27,31 @@
   ARCHERY_DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
 
 jobs:
-  lint:
-    name: Lint C++, Python, R, Rust, Docker, RAT
+
+  rat:
+    name: Release Audit Tool (RAT)
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - name: Checkout Arrow
+        uses: actions/checkout@v2
+        with:
+          repository: apache/arrow
+          submodules: true
+          fetch-depth: 0
+      - name: Checkout Arrow Rust
+        uses: actions/checkout@v2
+        with:
+          path: rust
+          fetch-depth: 0
       - name: Setup Python
         uses: actions/setup-python@v1
         with:
           python-version: 3.8
       - name: Setup Archery
-        run: pip install -e dev/archery[docker]
+        run: pip install -e dev/archery[lint]
       - name: Lint
         run: archery lint --rat
+
   prettier:
     name: Use prettier to check formatting of documents
     runs-on: ubuntu-latest
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 115bfad..cab6dd3 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -33,16 +33,13 @@
           repository: apache/arrow
           submodules: true
           fetch-depth: 0
-      # this is temporary: once rust is removed from `apache/arrow`, we are good to go.
-      - name: Remove Rust from arrow
-        run: rm -rf rust/
       - name: Checkout Arrow Rust
         uses: actions/checkout@v2
         with:
           path: rust
           fetch-depth: 0
       - name: Setup Python
-        uses: actions/setup-python@v1
+        uses: actions/setup-python@v2
         with:
           python-version: 3.8
       - name: Setup Archery
diff --git a/dev/archery/MANIFEST.in b/dev/archery/MANIFEST.in
deleted file mode 100644
index 90fe034..0000000
--- a/dev/archery/MANIFEST.in
+++ /dev/null
@@ -1,4 +0,0 @@
-include ../../LICENSE.txt
-include ../../NOTICE.txt
-
-include archery/reports/*
diff --git a/dev/archery/archery/__init__.py b/dev/archery/archery/__init__.py
deleted file mode 100644
index 13a8339..0000000
--- a/dev/archery/archery/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
diff --git a/dev/archery/archery/benchmark/__init__.py b/dev/archery/archery/benchmark/__init__.py
deleted file mode 100644
index 13a8339..0000000
--- a/dev/archery/archery/benchmark/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
diff --git a/dev/archery/archery/benchmark/codec.py b/dev/archery/archery/benchmark/codec.py
deleted file mode 100644
index 4157890..0000000
--- a/dev/archery/archery/benchmark/codec.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-
-import json
-
-from ..benchmark.core import Benchmark, BenchmarkSuite
-from ..benchmark.runner import BenchmarkRunner, StaticBenchmarkRunner
-from ..benchmark.compare import BenchmarkComparator
-
-
-class JsonEncoder(json.JSONEncoder):
-    def default(self, o):
-        if isinstance(o, Benchmark):
-            return BenchmarkCodec.encode(o)
-
-        if isinstance(o, BenchmarkSuite):
-            return BenchmarkSuiteCodec.encode(o)
-
-        if isinstance(o, BenchmarkRunner):
-            return BenchmarkRunnerCodec.encode(o)
-
-        if isinstance(o, BenchmarkComparator):
-            return BenchmarkComparatorCodec.encode(o)
-
-        return json.JSONEncoder.default(self, o)
-
-
-class BenchmarkCodec:
-    @staticmethod
-    def encode(b):
-        return {
-            "name": b.name,
-            "unit": b.unit,
-            "less_is_better": b.less_is_better,
-            "values": b.values,
-            "time_unit": b.time_unit,
-            "times": b.times,
-            "counters": b.counters,
-        }
-
-    @staticmethod
-    def decode(dct, **kwargs):
-        return Benchmark(**dct, **kwargs)
-
-
-class BenchmarkSuiteCodec:
-    @staticmethod
-    def encode(bs):
-        return {
-            "name": bs.name,
-            "benchmarks": [BenchmarkCodec.encode(b) for b in bs.benchmarks]
-        }
-
-    @staticmethod
-    def decode(dct, **kwargs):
-        benchmarks = [BenchmarkCodec.decode(b)
-                      for b in dct.pop("benchmarks", [])]
-        return BenchmarkSuite(benchmarks=benchmarks, **dct, **kwargs)
-
-
-class BenchmarkRunnerCodec:
-    @staticmethod
-    def encode(br):
-        return {"suites": [BenchmarkSuiteCodec.encode(s) for s in br.suites]}
-
-    @staticmethod
-    def decode(dct, **kwargs):
-        suites = [BenchmarkSuiteCodec.decode(s)
-                  for s in dct.pop("suites", [])]
-        return StaticBenchmarkRunner(suites=suites, **dct, **kwargs)
-
-
-class BenchmarkComparatorCodec:
-    @staticmethod
-    def encode(bc):
-        comparator = bc.formatted
-
-        suite_name = bc.suite_name
-        if suite_name:
-            comparator["suite"] = suite_name
-
-        return comparator
diff --git a/dev/archery/archery/benchmark/compare.py b/dev/archery/archery/benchmark/compare.py
deleted file mode 100644
index 622b801..0000000
--- a/dev/archery/archery/benchmark/compare.py
+++ /dev/null
@@ -1,173 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-
-# Define a global regression threshold as 5%. This is purely subjective and
-# flawed. This does not track cumulative regression.
-DEFAULT_THRESHOLD = 0.05
-
-
-def items_per_seconds_fmt(value):
-    if value < 1000:
-        return "{} items/sec".format(value)
-    if value < 1000**2:
-        return "{:.3f}K items/sec".format(value / 1000)
-    if value < 1000**3:
-        return "{:.3f}M items/sec".format(value / 1000**2)
-    else:
-        return "{:.3f}G items/sec".format(value / 1000**3)
-
-
-def bytes_per_seconds_fmt(value):
-    if value < 1024:
-        return "{} bytes/sec".format(value)
-    if value < 1024**2:
-        return "{:.3f} KiB/sec".format(value / 1024)
-    if value < 1024**3:
-        return "{:.3f} MiB/sec".format(value / 1024**2)
-    if value < 1024**4:
-        return "{:.3f} GiB/sec".format(value / 1024**3)
-    else:
-        return "{:.3f} TiB/sec".format(value / 1024**4)
-
-
-def change_fmt(value):
-    return "{:.3%}".format(value)
-
-
-def formatter_for_unit(unit):
-    if unit == "bytes_per_second":
-        return bytes_per_seconds_fmt
-    elif unit == "items_per_second":
-        return items_per_seconds_fmt
-    else:
-        return lambda x: x
-
-
-class BenchmarkComparator:
-    """ Compares two benchmarks.
-
-    Encodes the logic of comparing two benchmarks and taking a decision on
-    if it induce a regression.
-    """
-
-    def __init__(self, contender, baseline, threshold=DEFAULT_THRESHOLD,
-                 suite_name=None):
-        self.contender = contender
-        self.baseline = baseline
-        self.threshold = threshold
-        self.suite_name = suite_name
-
-    @property
-    def name(self):
-        return self.baseline.name
-
-    @property
-    def less_is_better(self):
-        return self.baseline.less_is_better
-
-    @property
-    def unit(self):
-        return self.baseline.unit
-
-    @property
-    def change(self):
-        new = self.contender.value
-        old = self.baseline.value
-
-        if old == 0 and new == 0:
-            return 0.0
-        if old == 0:
-            return 0.0
-
-        return float(new - old) / abs(old)
-
-    @property
-    def confidence(self):
-        """ Indicate if a comparison of benchmarks should be trusted. """
-        return True
-
-    @property
-    def regression(self):
-        change = self.change
-        adjusted_change = change if self.less_is_better else -change
-        return (self.confidence and adjusted_change > self.threshold)
-
-    @property
-    def formatted(self):
-        fmt = formatter_for_unit(self.unit)
-        return {
-            "benchmark": self.name,
-            "change": change_fmt(self.change),
-            "regression": self.regression,
-            "baseline": fmt(self.baseline.value),
-            "contender": fmt(self.contender.value),
-            "unit": self.unit,
-            "less_is_better": self.less_is_better,
-            "counters": str(self.baseline.counters)
-        }
-
-    def compare(self, comparator=None):
-        return {
-            "benchmark": self.name,
-            "change": self.change,
-            "regression": self.regression,
-            "baseline": self.baseline.value,
-            "contender": self.contender.value,
-            "unit": self.unit,
-            "less_is_better": self.less_is_better,
-            "counters": self.baseline.counters
-        }
-
-    def __call__(self, **kwargs):
-        return self.compare(**kwargs)
-
-
-def pairwise_compare(contender, baseline):
-    dict_contender = {e.name: e for e in contender}
-    dict_baseline = {e.name: e for e in baseline}
-
-    for name in (dict_contender.keys() & dict_baseline.keys()):
-        yield name, (dict_contender[name], dict_baseline[name])
-
-
-class RunnerComparator:
-    """ Compares suites/benchmarks from runners.
-
-    It is up to the caller that ensure that runners are compatible (both from
-    the same language implementation).
-    """
-
-    def __init__(self, contender, baseline, threshold=DEFAULT_THRESHOLD):
-        self.contender = contender
-        self.baseline = baseline
-        self.threshold = threshold
-
-    @property
-    def comparisons(self):
-        contender = self.contender.suites
-        baseline = self.baseline.suites
-        suites = pairwise_compare(contender, baseline)
-
-        for suite_name, (suite_cont, suite_base) in suites:
-            benchmarks = pairwise_compare(
-                suite_cont.benchmarks, suite_base.benchmarks)
-
-            for _, (bench_cont, bench_base) in benchmarks:
-                yield BenchmarkComparator(bench_cont, bench_base,
-                                          threshold=self.threshold,
-                                          suite_name=suite_name)
diff --git a/dev/archery/archery/benchmark/core.py b/dev/archery/archery/benchmark/core.py
deleted file mode 100644
index 5a92271..0000000
--- a/dev/archery/archery/benchmark/core.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-
-def median(values):
-    n = len(values)
-    if n == 0:
-        raise ValueError("median requires at least one value")
-    elif n % 2 == 0:
-        return (values[(n // 2) - 1] + values[n // 2]) / 2
-    else:
-        return values[n // 2]
-
-
-class Benchmark:
-    def __init__(self, name, unit, less_is_better, values, time_unit,
-                 times, counters=None):
-        self.name = name
-        self.unit = unit
-        self.less_is_better = less_is_better
-        self.values = sorted(values)
-        self.time_unit = time_unit
-        self.times = sorted(times)
-        self.median = median(self.values)
-        self.counters = counters or {}
-
-    @property
-    def value(self):
-        return self.median
-
-    def __repr__(self):
-        return "Benchmark[name={},value={}]".format(self.name, self.value)
-
-
-class BenchmarkSuite:
-    def __init__(self, name, benchmarks):
-        self.name = name
-        self.benchmarks = benchmarks
-
-    def __repr__(self):
-        return "BenchmarkSuite[name={}, benchmarks={}]".format(
-            self.name, self.benchmarks
-        )
diff --git a/dev/archery/archery/benchmark/google.py b/dev/archery/archery/benchmark/google.py
deleted file mode 100644
index ebcc526..0000000
--- a/dev/archery/archery/benchmark/google.py
+++ /dev/null
@@ -1,174 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from itertools import filterfalse, groupby, tee
-import json
-import subprocess
-from tempfile import NamedTemporaryFile
-
-from .core import Benchmark
-from ..utils.command import Command
-
-
-def partition(pred, iterable):
-    # adapted from python's examples
-    t1, t2 = tee(iterable)
-    return list(filter(pred, t1)), list(filterfalse(pred, t2))
-
-
-class GoogleBenchmarkCommand(Command):
-    """ Run a google benchmark binary.
-
-    This assumes the binary supports the standard command line options,
-    notably `--benchmark_filter`, `--benchmark_format`, etc...
-    """
-
-    def __init__(self, benchmark_bin, benchmark_filter=None):
-        self.bin = benchmark_bin
-        self.benchmark_filter = benchmark_filter
-
-    def list_benchmarks(self):
-        argv = ["--benchmark_list_tests"]
-        if self.benchmark_filter:
-            argv.append("--benchmark_filter={}".format(self.benchmark_filter))
-        result = self.run(*argv, stdout=subprocess.PIPE,
-                          stderr=subprocess.PIPE)
-        return str.splitlines(result.stdout.decode("utf-8"))
-
-    def results(self, repetitions=1):
-        with NamedTemporaryFile() as out:
-            argv = ["--benchmark_repetitions={}".format(repetitions),
-                    "--benchmark_out={}".format(out.name),
-                    "--benchmark_out_format=json"]
-
-            if self.benchmark_filter:
-                argv.append(
-                    "--benchmark_filter={}".format(self.benchmark_filter)
-                )
-
-            self.run(*argv, check=True)
-            return json.load(out)
-
-
-class GoogleBenchmarkObservation:
-    """ Represents one run of a single (google c++) benchmark.
-
-    Aggregates are reported by Google Benchmark executables alongside
-    other observations whenever repetitions are specified (with
-    `--benchmark_repetitions` on the bare benchmark, or with the
-    archery option `--repetitions`). Aggregate observations are not
-    included in `GoogleBenchmark.runs`.
-
-    RegressionSumKernel/32768/0                 1 us          1 us  25.8077GB/s
-    RegressionSumKernel/32768/0                 1 us          1 us  25.7066GB/s
-    RegressionSumKernel/32768/0                 1 us          1 us  25.1481GB/s
-    RegressionSumKernel/32768/0                 1 us          1 us  25.846GB/s
-    RegressionSumKernel/32768/0                 1 us          1 us  25.6453GB/s
-    RegressionSumKernel/32768/0_mean            1 us          1 us  25.6307GB/s
-    RegressionSumKernel/32768/0_median          1 us          1 us  25.7066GB/s
-    RegressionSumKernel/32768/0_stddev          0 us          0 us  288.046MB/s
-    """
-
-    def __init__(self, name, real_time, cpu_time, time_unit, run_type,
-                 size=None, bytes_per_second=None, items_per_second=None,
-                 **counters):
-        self._name = name
-        self.real_time = real_time
-        self.cpu_time = cpu_time
-        self.time_unit = time_unit
-        self.run_type = run_type
-        self.size = size
-        self.bytes_per_second = bytes_per_second
-        self.items_per_second = items_per_second
-        self.counters = counters
-
-    @property
-    def is_aggregate(self):
-        """ Indicate if the observation is a run or an aggregate. """
-        return self.run_type == "aggregate"
-
-    @property
-    def is_realtime(self):
-        """ Indicate if the preferred value is realtime instead of cputime. """
-        return self.name.find("/real_time") != -1
-
-    @property
-    def name(self):
-        name = self._name
-        return name.rsplit("_", maxsplit=1)[0] if self.is_aggregate else name
-
-    @property
-    def time(self):
-        return self.real_time if self.is_realtime else self.cpu_time
-
-    @property
-    def value(self):
-        """ Return the benchmark value."""
-        return self.bytes_per_second or self.items_per_second or self.time
-
-    @property
-    def unit(self):
-        if self.bytes_per_second:
-            return "bytes_per_second"
-        elif self.items_per_second:
-            return "items_per_second"
-        else:
-            return self.time_unit
-
-    def __repr__(self):
-        return str(self.value)
-
-
-class GoogleBenchmark(Benchmark):
-    """ A set of GoogleBenchmarkObservations. """
-
-    def __init__(self, name, runs):
-        """ Initialize a GoogleBenchmark.
-
-        Parameters
-        ----------
-        name: str
-              Name of the benchmark
-        runs: list(GoogleBenchmarkObservation)
-              Repetitions of GoogleBenchmarkObservation run.
-
-        """
-        self.name = name
-        # exclude google benchmark aggregate artifacts
-        _, runs = partition(lambda b: b.is_aggregate, runs)
-        self.runs = sorted(runs, key=lambda b: b.value)
-        unit = self.runs[0].unit
-        time_unit = self.runs[0].time_unit
-        less_is_better = not unit.endswith("per_second")
-        values = [b.value for b in self.runs]
-        times = [b.real_time for b in self.runs]
-        # Slight kludge to extract the UserCounters for each benchmark
-        counters = self.runs[0].counters
-        super().__init__(name, unit, less_is_better, values, time_unit, times,
-                         counters)
-
-    def __repr__(self):
-        return "GoogleBenchmark[name={},runs={}]".format(self.names, self.runs)
-
-    @classmethod
-    def from_json(cls, payload):
-        def group_key(x):
-            return x.name
-
-        benchmarks = map(lambda x: GoogleBenchmarkObservation(**x), payload)
-        groups = groupby(sorted(benchmarks, key=group_key), group_key)
-        return [cls(k, list(bs)) for k, bs in groups]
diff --git a/dev/archery/archery/benchmark/runner.py b/dev/archery/archery/benchmark/runner.py
deleted file mode 100644
index 5718bca..0000000
--- a/dev/archery/archery/benchmark/runner.py
+++ /dev/null
@@ -1,212 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import glob
-import json
-import os
-import re
-
-from .core import BenchmarkSuite
-from .google import GoogleBenchmarkCommand, GoogleBenchmark
-from ..lang.cpp import CppCMakeDefinition, CppConfiguration
-from ..utils.cmake import CMakeBuild
-from ..utils.logger import logger
-
-
-def regex_filter(re_expr):
-    if re_expr is None:
-        return lambda s: True
-    re_comp = re.compile(re_expr)
-    return lambda s: re_comp.search(s)
-
-
-DEFAULT_REPETITIONS = 1
-
-
-class BenchmarkRunner:
-    def __init__(self, suite_filter=None, benchmark_filter=None,
-                 repetitions=DEFAULT_REPETITIONS):
-        self.suite_filter = suite_filter
-        self.benchmark_filter = benchmark_filter
-        self.repetitions = repetitions
-
-    @property
-    def suites(self):
-        raise NotImplementedError("BenchmarkRunner must implement suites")
-
-    @staticmethod
-    def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
-        """ Returns a BenchmarkRunner from a path or a git revision.
-
-        First, it checks if `rev_or_path` is a valid path (or string) of a json
-        object that can deserialize to a BenchmarkRunner. If so, it initialize
-        a StaticBenchmarkRunner from it. This allows memoizing the result of a
-        run in a file or a string.
-
-        Second, it checks if `rev_or_path` points to a valid CMake build
-        directory.  If so, it creates a CppBenchmarkRunner with this existing
-        CMakeBuild.
-
-        Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
-        the given revision and create a fresh CMakeBuild.
-        """
-        build = None
-        if StaticBenchmarkRunner.is_json_result(rev_or_path):
-            return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
-        elif CMakeBuild.is_build_dir(rev_or_path):
-            build = CMakeBuild.from_path(rev_or_path)
-            return CppBenchmarkRunner(build, **kwargs)
-        else:
-            # Revisions can references remote via the `/` character, ensure
-            # that the revision is path friendly
-            path_rev = rev_or_path.replace("/", "_")
-            root_rev = os.path.join(root, path_rev)
-            os.mkdir(root_rev)
-
-            clone_dir = os.path.join(root_rev, "arrow")
-            # Possibly checkout the sources at given revision, no need to
-            # perform cleanup on cloned repository as root_rev is reclaimed.
-            src_rev, _ = src.at_revision(rev_or_path, clone_dir)
-            cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
-            build_dir = os.path.join(root_rev, "build")
-            return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)
-
-
-class StaticBenchmarkRunner(BenchmarkRunner):
-    """ Run suites from a (static) set of suites. """
-
-    def __init__(self, suites, **kwargs):
-        self._suites = suites
-        super().__init__(**kwargs)
-
-    @property
-    def list_benchmarks(self):
-        for suite in self._suites:
-            for benchmark in suite.benchmarks:
-                yield "{}.{}".format(suite.name, benchmark.name)
-
-    @property
-    def suites(self):
-        suite_fn = regex_filter(self.suite_filter)
-        benchmark_fn = regex_filter(self.benchmark_filter)
-
-        for suite in (s for s in self._suites if suite_fn(s.name)):
-            benchmarks = [b for b in suite.benchmarks if benchmark_fn(b.name)]
-            yield BenchmarkSuite(suite.name, benchmarks)
-
-    @classmethod
-    def is_json_result(cls, path_or_str):
-        builder = None
-        try:
-            builder = cls.from_json(path_or_str)
-        except BaseException:
-            pass
-
-        return builder is not None
-
-    @staticmethod
-    def from_json(path_or_str, **kwargs):
-        # .codec imported here to break recursive imports
-        from .codec import BenchmarkRunnerCodec
-        if os.path.isfile(path_or_str):
-            with open(path_or_str) as f:
-                loaded = json.load(f)
-        else:
-            loaded = json.loads(path_or_str)
-        return BenchmarkRunnerCodec.decode(loaded, **kwargs)
-
-    def __repr__(self):
-        return "BenchmarkRunner[suites={}]".format(list(self.suites))
-
-
-class CppBenchmarkRunner(BenchmarkRunner):
-    """ Run suites from a CMakeBuild. """
-
-    def __init__(self, build, **kwargs):
-        """ Initialize a CppBenchmarkRunner. """
-        self.build = build
-        super().__init__(**kwargs)
-
-    @staticmethod
-    def default_configuration(**kwargs):
-        """ Returns the default benchmark configuration. """
-        return CppConfiguration(
-            build_type="release", with_tests=False, with_benchmarks=True,
-            with_compute=True,
-            with_csv=True,
-            with_dataset=True,
-            with_json=True,
-            with_parquet=True,
-            with_python=False,
-            with_brotli=True,
-            with_bz2=True,
-            with_lz4=True,
-            with_snappy=True,
-            with_zlib=True,
-            with_zstd=True,
-            **kwargs)
-
-    @property
-    def suites_binaries(self):
-        """ Returns a list of benchmark binaries for this build. """
-        # Ensure build is up-to-date to run benchmarks
-        self.build()
-        # Not the best method, but works for now
-        glob_expr = os.path.join(self.build.binaries_dir, "*-benchmark")
-        return {os.path.basename(b): b for b in glob.glob(glob_expr)}
-
-    def suite(self, name, suite_bin):
-        """ Returns the resulting benchmarks for a given suite. """
-        suite_cmd = GoogleBenchmarkCommand(suite_bin, self.benchmark_filter)
-
-        # Ensure there will be data
-        benchmark_names = suite_cmd.list_benchmarks()
-        if not benchmark_names:
-            return None
-
-        results = suite_cmd.results(repetitions=self.repetitions)
-        benchmarks = GoogleBenchmark.from_json(results.get("benchmarks"))
-        return BenchmarkSuite(name, benchmarks)
-
-    @property
-    def list_benchmarks(self):
-        for suite_name, suite_bin in self.suites_binaries.items():
-            suite_cmd = GoogleBenchmarkCommand(suite_bin)
-            for benchmark_name in suite_cmd.list_benchmarks():
-                yield "{}.{}".format(suite_name, benchmark_name)
-
-    @property
-    def suites(self):
-        """ Returns all suite for a runner. """
-        suite_matcher = regex_filter(self.suite_filter)
-
-        suite_and_binaries = self.suites_binaries
-        for suite_name in suite_and_binaries:
-            if not suite_matcher(suite_name):
-                logger.debug("Ignoring suite {}".format(suite_name))
-                continue
-
-            suite_bin = suite_and_binaries[suite_name]
-            suite = self.suite(suite_name, suite_bin)
-
-            # Filter may exclude all benchmarks
-            if not suite:
-                logger.debug("Suite {} executed but no results"
-                             .format(suite_name))
-                continue
-
-            yield suite
diff --git a/dev/archery/archery/bot.py b/dev/archery/archery/bot.py
deleted file mode 100644
index c69cf91..0000000
--- a/dev/archery/archery/bot.py
+++ /dev/null
@@ -1,261 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import os
-import shlex
-from pathlib import Path
-from functools import partial
-import tempfile
-
-import click
-import github
-
-from .utils.git import git
-from .utils.logger import logger
-from .crossbow import Repo, Queue, Config, Target, Job, CommentReport
-
-
-class EventError(Exception):
-    pass
-
-
-class CommandError(Exception):
-
-    def __init__(self, message):
-        self.message = message
-
-
-class _CommandMixin:
-
-    def get_help_option(self, ctx):
-        def show_help(ctx, param, value):
-            if value and not ctx.resilient_parsing:
-                raise click.UsageError(ctx.get_help())
-        option = super().get_help_option(ctx)
-        option.callback = show_help
-        return option
-
-    def __call__(self, message, **kwargs):
-        args = shlex.split(message)
-        try:
-            with self.make_context(self.name, args=args, obj=kwargs) as ctx:
-                return self.invoke(ctx)
-        except click.ClickException as e:
-            raise CommandError(e.format_message())
-
-
-class Command(_CommandMixin, click.Command):
-    pass
-
-
-class Group(_CommandMixin, click.Group):
-
-    def command(self, *args, **kwargs):
-        kwargs.setdefault('cls', Command)
-        return super().command(*args, **kwargs)
-
-    def group(self, *args, **kwargs):
-        kwargs.setdefault('cls', Group)
-        return super().group(*args, **kwargs)
-
-    def parse_args(self, ctx, args):
-        if not args and self.no_args_is_help and not ctx.resilient_parsing:
-            raise click.UsageError(ctx.get_help())
-        return super().parse_args(ctx, args)
-
-
-command = partial(click.command, cls=Command)
-group = partial(click.group, cls=Group)
-
-
-class CommentBot:
-
-    def __init__(self, name, handler, token=None):
-        # TODO(kszucs): validate
-        assert isinstance(name, str)
-        assert callable(handler)
-        self.name = name
-        self.handler = handler
-        self.github = github.Github(token)
-
-    def parse_command(self, payload):
-        # only allow users of apache org to submit commands, for more see
-        # https://developer.github.com/v4/enum/commentauthorassociation/
-        allowed_roles = {'OWNER', 'MEMBER', 'CONTRIBUTOR'}
-        mention = '@{}'.format(self.name)
-        comment = payload['comment']
-
-        if payload['sender']['login'] == self.name:
-            raise EventError("Don't respond to itself")
-        elif payload['action'] not in {'created', 'edited'}:
-            raise EventError("Don't respond to comment deletion")
-        elif comment['author_association'] not in allowed_roles:
-            raise EventError(
-                "Don't respond to comments from non-authorized users"
-            )
-        elif not comment['body'].lstrip().startswith(mention):
-            raise EventError("The bot is not mentioned")
-
-        return payload['comment']['body'].split(mention)[-1].strip()
-
-    def handle(self, event, payload):
-        try:
-            command = self.parse_command(payload)
-        except EventError as e:
-            logger.error(e)
-            # see the possible reasons in the validate method
-            return
-
-        if event == 'issue_comment':
-            return self.handle_issue_comment(command, payload)
-        elif event == 'pull_request_review_comment':
-            return self.handle_review_comment(command, payload)
-        else:
-            raise ValueError("Unexpected event type {}".format(event))
-
-    def handle_issue_comment(self, command, payload):
-        repo = self.github.get_repo(payload['repository']['id'], lazy=True)
-        issue = repo.get_issue(payload['issue']['number'])
-
-        try:
-            pull = issue.as_pull_request()
-        except github.GithubException:
-            return issue.create_comment(
-                "The comment bot only listens to pull request comments!"
-            )
-
-        comment = pull.get_issue_comment(payload['comment']['id'])
-        try:
-            self.handler(command, issue=issue, pull_request=pull,
-                         comment=comment)
-        except CommandError as e:
-            logger.error(e)
-            pull.create_issue_comment("```\n{}\n```".format(e.message))
-        except Exception as e:
-            logger.exception(e)
-            comment.create_reaction('-1')
-        else:
-            comment.create_reaction('+1')
-
-    def handle_review_comment(self, payload):
-        raise NotImplementedError()
-
-
-@group(name='@github-actions')
-@click.pass_context
-def actions(ctx):
-    """Ursabot"""
-    ctx.ensure_object(dict)
-
-
-@actions.group()
-@click.option('--crossbow', '-c', default='ursacomputing/crossbow',
-              help='Crossbow repository on github to use')
-@click.pass_obj
-def crossbow(obj, crossbow):
-    """
-    Trigger crossbow builds for this pull request
-    """
-    obj['crossbow_repo'] = crossbow
-
-
-def _clone_arrow_and_crossbow(dest, crossbow_repo, pull_request):
-    """
-    Clone the repositories and initialize crossbow objects.
-
-    Parameters
-    ----------
-    dest : Path
-        Filesystem path to clone the repositories to.
-    crossbow_repo : str
-        Github repository name, like kszucs/crossbow.
-    pull_request : pygithub.PullRequest
-        Object containing information about the pull request the comment bot
-        was triggered from.
-    """
-    arrow_path = dest / 'arrow'
-    queue_path = dest / 'crossbow'
-
-    # clone arrow and checkout the pull request's branch
-    pull_request_ref = 'pull/{}/head:{}'.format(
-        pull_request.number, pull_request.head.ref
-    )
-    git.clone(pull_request.base.repo.clone_url, str(arrow_path))
-    git.fetch('origin', pull_request_ref, git_dir=arrow_path)
-    git.checkout(pull_request.head.ref, git_dir=arrow_path)
-
-    # clone crossbow repository
-    crossbow_url = 'https://github.com/{}'.format(crossbow_repo)
-    git.clone(crossbow_url, str(queue_path))
-
-    # initialize crossbow objects
-    github_token = os.environ['CROSSBOW_GITHUB_TOKEN']
-    arrow = Repo(arrow_path)
-    queue = Queue(queue_path, github_token=github_token, require_https=True)
-
-    return (arrow, queue)
-
-
-@crossbow.command()
-@click.argument('tasks', nargs=-1, required=False)
-@click.option('--group', '-g', 'groups', multiple=True,
-              help='Submit task groups as defined in tests.yml')
-@click.option('--param', '-p', 'params', multiple=True,
-              help='Additional task parameters for rendering the CI templates')
-@click.option('--arrow-version', '-v', default=None,
-              help='Set target version explicitly.')
-@click.pass_obj
-def submit(obj, tasks, groups, params, arrow_version):
-    """
-    Submit crossbow testing tasks.
-
-    See groups defined in arrow/dev/tasks/tests.yml
-    """
-    crossbow_repo = obj['crossbow_repo']
-    pull_request = obj['pull_request']
-    with tempfile.TemporaryDirectory() as tmpdir:
-        tmpdir = Path(tmpdir)
-        arrow, queue = _clone_arrow_and_crossbow(
-            dest=Path(tmpdir),
-            crossbow_repo=crossbow_repo,
-            pull_request=pull_request,
-        )
-        # load available tasks configuration and groups from yaml
-        config = Config.load_yaml(arrow.path / "dev" / "tasks" / "tasks.yml")
-        config.validate()
-
-        # initialize the crossbow build's target repository
-        target = Target.from_repo(arrow, version=arrow_version,
-                                  remote=pull_request.head.repo.clone_url,
-                                  branch=pull_request.head.ref)
-
-        # parse additional job parameters
-        params = dict([p.split("=") for p in params])
-
-        # instantiate the job object
-        job = Job.from_config(config=config, target=target, tasks=tasks,
-                              groups=groups, params=params)
-
-        # add the job to the crossbow queue and push to the remote repository
-        queue.put(job, prefix="actions")
-        queue.push()
-
-        # render the response comment's content
-        report = CommentReport(job, crossbow_repo=crossbow_repo)
-
-        # send the response
-        pull_request.create_issue_comment(report.show())
diff --git a/dev/archery/archery/cli.py b/dev/archery/archery/cli.py
deleted file mode 100644
index 4bbde75..0000000
--- a/dev/archery/archery/cli.py
+++ /dev/null
@@ -1,1092 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from collections import namedtuple
-from io import StringIO
-import click
-import errno
-import json
-import logging
-import os
-import pathlib
-import sys
-
-from .benchmark.codec import JsonEncoder
-from .benchmark.compare import RunnerComparator, DEFAULT_THRESHOLD
-from .benchmark.runner import BenchmarkRunner, CppBenchmarkRunner
-from .lang.cpp import CppCMakeDefinition, CppConfiguration
-from .utils.lint import linter, python_numpydoc, LintValidationException
-from .utils.logger import logger, ctx as log_ctx
-from .utils.source import ArrowSources, InvalidArrowSource
-from .utils.tmpdir import tmpdir
-
-# Set default logging to INFO in command line.
-logging.basicConfig(level=logging.INFO)
-
-
-class ArrowBool(click.types.BoolParamType):
-    """
-    ArrowBool supports the 'ON' and 'OFF' values on top of the values
-    supported by BoolParamType. This is convenient to port script which exports
-    CMake options variables.
-    """
-    name = "boolean"
-
-    def convert(self, value, param, ctx):
-        if isinstance(value, str):
-            lowered = value.lower()
-            if lowered == "on":
-                return True
-            elif lowered == "off":
-                return False
-
-        return super().convert(value, param, ctx)
-
-
-BOOL = ArrowBool()
-
-
-@click.group()
-@click.option("--debug", type=BOOL, is_flag=True, default=False,
-              help="Increase logging with debugging output.")
-@click.option("--pdb", type=BOOL, is_flag=True, default=False,
-              help="Invoke pdb on uncaught exception.")
-@click.option("-q", "--quiet", type=BOOL, is_flag=True, default=False,
-              help="Silence executed commands.")
-@click.pass_context
-def archery(ctx, debug, pdb, quiet):
-    """ Apache Arrow developer utilities.
-
-    See sub-commands help with `archery <cmd> --help`.
-
-    """
-    # Ensure ctx.obj exists
-    ctx.ensure_object(dict)
-
-    log_ctx.quiet = quiet
-    if debug:
-        logger.setLevel(logging.DEBUG)
-
-    ctx.debug = debug
-
-    if pdb:
-        import pdb
-        sys.excepthook = lambda t, v, e: pdb.pm()
-
-
-def validate_arrow_sources(ctx, param, src):
-    """ Ensure a directory contains Arrow cpp sources. """
-    try:
-        return ArrowSources.find(src)
-    except InvalidArrowSource as e:
-        raise click.BadParameter(str(e))
-
-
-build_dir_type = click.Path(dir_okay=True, file_okay=False, resolve_path=True)
-# Supported build types
-build_type = click.Choice(["debug", "relwithdebinfo", "release"],
-                          case_sensitive=False)
-# Supported warn levels
-warn_level_type = click.Choice(["everything", "checkin", "production"],
-                               case_sensitive=False)
-
-simd_level = click.Choice(["NONE", "SSE4_2", "AVX2", "AVX512"],
-                          case_sensitive=True)
-
-
-def cpp_toolchain_options(cmd):
-    options = [
-        click.option("--cc", metavar="<compiler>", help="C compiler."),
-        click.option("--cxx", metavar="<compiler>", help="C++ compiler."),
-        click.option("--cxx-flags", help="C++ compiler flags."),
-        click.option("--cpp-package-prefix",
-                     help=("Value to pass for ARROW_PACKAGE_PREFIX and "
-                           "use ARROW_DEPENDENCY_SOURCE=SYSTEM"))
-    ]
-    return _apply_options(cmd, options)
-
-
-def _apply_options(cmd, options):
-    for option in options:
-        cmd = option(cmd)
-    return cmd
-
-
-@archery.command(short_help="Initialize an Arrow C++ build")
-@click.option("--src", metavar="<arrow_src>", default=None,
-              callback=validate_arrow_sources,
-              help="Specify Arrow source directory")
-# toolchain
-@cpp_toolchain_options
-@click.option("--build-type", default=None, type=build_type,
-              help="CMake's CMAKE_BUILD_TYPE")
-@click.option("--warn-level", default="production", type=warn_level_type,
-              help="Controls compiler warnings -W(no-)error.")
-@click.option("--use-gold-linker", default=True, type=BOOL,
-              help="Toggles ARROW_USE_LD_GOLD option.")
-@click.option("--simd-level", default="SSE4_2", type=simd_level,
-              help="Toggles ARROW_SIMD_LEVEL option.")
-# Tests and benchmarks
-@click.option("--with-tests", default=True, type=BOOL,
-              help="Build with tests.")
-@click.option("--with-benchmarks", default=None, type=BOOL,
-              help="Build with benchmarks.")
-@click.option("--with-examples", default=None, type=BOOL,
-              help="Build with examples.")
-@click.option("--with-integration", default=None, type=BOOL,
-              help="Build with integration test executables.")
-# Static checks
-@click.option("--use-asan", default=None, type=BOOL,
-              help="Toggle ARROW_USE_ASAN sanitizer.")
-@click.option("--use-tsan", default=None, type=BOOL,
-              help="Toggle ARROW_USE_TSAN sanitizer.")
-@click.option("--use-ubsan", default=None, type=BOOL,
-              help="Toggle ARROW_USE_UBSAN sanitizer.")
-@click.option("--with-fuzzing", default=None, type=BOOL,
-              help="Toggle ARROW_FUZZING.")
-# Components
-@click.option("--with-compute", default=None, type=BOOL,
-              help="Build the Arrow compute module.")
-@click.option("--with-csv", default=None, type=BOOL,
-              help="Build the Arrow CSV parser module.")
-@click.option("--with-cuda", default=None, type=BOOL,
-              help="Build the Arrow CUDA extensions.")
-@click.option("--with-dataset", default=None, type=BOOL,
-              help="Build the Arrow dataset module.")
-@click.option("--with-filesystem", default=None, type=BOOL,
-              help="Build the Arrow filesystem layer.")
-@click.option("--with-flight", default=None, type=BOOL,
-              help="Build with Flight rpc support.")
-@click.option("--with-gandiva", default=None, type=BOOL,
-              help="Build with Gandiva expression compiler support.")
-@click.option("--with-hdfs", default=None, type=BOOL,
-              help="Build the Arrow HDFS bridge.")
-@click.option("--with-hiveserver2", default=None, type=BOOL,
-              help="Build the HiveServer2 client and arrow adapater.")
-@click.option("--with-ipc", default=None, type=BOOL,
-              help="Build the Arrow IPC extensions.")
-@click.option("--with-json", default=None, type=BOOL,
-              help="Build the Arrow JSON parser module.")
-@click.option("--with-jni", default=None, type=BOOL,
-              help="Build the Arrow JNI lib.")
-@click.option("--with-mimalloc", default=None, type=BOOL,
-              help="Build the Arrow mimalloc based allocator.")
-@click.option("--with-parquet", default=None, type=BOOL,
-              help="Build with Parquet file support.")
-@click.option("--with-plasma", default=None, type=BOOL,
-              help="Build with Plasma object store support.")
-@click.option("--with-python", default=None, type=BOOL,
-              help="Build the Arrow CPython extesions.")
-@click.option("--with-r", default=None, type=BOOL,
-              help="Build the Arrow R extensions. This is not a CMake option, "
-              "it will toggle required options")
-@click.option("--with-s3", default=None, type=BOOL,
-              help="Build Arrow with S3 support.")
-# Compressions
-@click.option("--with-brotli", default=None, type=BOOL,
-              help="Build Arrow with brotli compression.")
-@click.option("--with-bz2", default=None, type=BOOL,
-              help="Build Arrow with bz2 compression.")
-@click.option("--with-lz4", default=None, type=BOOL,
-              help="Build Arrow with lz4 compression.")
-@click.option("--with-snappy", default=None, type=BOOL,
-              help="Build Arrow with snappy compression.")
-@click.option("--with-zlib", default=None, type=BOOL,
-              help="Build Arrow with zlib compression.")
-@click.option("--with-zstd", default=None, type=BOOL,
-              help="Build Arrow with zstd compression.")
-# CMake extra feature
-@click.option("--cmake-extras", type=str, multiple=True,
-              help="Extra flags/options to pass to cmake invocation. "
-              "Can be stacked")
-@click.option("--install-prefix", type=str,
-              help="Destination directory where files are installed. Expand to"
-              "CMAKE_INSTALL_PREFIX. Defaults to to $CONDA_PREFIX if the"
-              "variable exists.")
-# misc
-@click.option("-f", "--force", type=BOOL, is_flag=True, default=False,
-              help="Delete existing build directory if found.")
-@click.option("--targets", type=str, multiple=True,
-              help="Generator targets to run. Can be stacked.")
-@click.argument("build_dir", type=build_dir_type)
-@click.pass_context
-def build(ctx, src, build_dir, force, targets, **kwargs):
-    """ Initialize a C++ build directory.
-
-    The build command creates a directory initialized with Arrow's cpp source
-    cmake and configuration. It can also optionally invoke the generator to
-    test the build (and used in scripts).
-
-    Note that archery will carry the caller environment. It will also not touch
-    an existing directory, one must use the `--force` option to remove the
-    existing directory.
-
-    Examples:
-
-    \b
-    # Initialize build with clang8 and avx2 support in directory `clang8-build`
-    \b
-    archery build --cc=clang-8 --cxx=clang++-8 --cxx-flags=-mavx2 clang8-build
-
-    \b
-    # Builds and run test
-    archery build --targets=all --targets=test build
-    """
-    # Arrow's cpp cmake configuration
-    conf = CppConfiguration(**kwargs)
-    # This is a closure around cmake invocation, e.g. calling `def.build()`
-    # yields a directory ready to be run with the generator
-    cmake_def = CppCMakeDefinition(src.cpp, conf)
-    # Create build directory
-    build = cmake_def.build(build_dir, force=force)
-
-    for target in targets:
-        build.run(target)
-
-
-LintCheck = namedtuple('LintCheck', ('option_name', 'help'))
-
-lint_checks = [
-    LintCheck('clang-format', "Format C++ files with clang-format."),
-    LintCheck('clang-tidy', "Lint C++ files with clang-tidy."),
-    LintCheck('cpplint', "Lint C++ files with cpplint."),
-    LintCheck('iwyu', "Lint changed C++ files with Include-What-You-Use."),
-    LintCheck('python',
-              "Format and lint Python files with autopep8 and flake8."),
-    LintCheck('numpydoc', "Lint Python files with numpydoc."),
-    LintCheck('cmake-format', "Format CMake files with cmake-format.py."),
-    LintCheck('rat',
-              "Check all sources files for license texts via Apache RAT."),
-    LintCheck('r', "Lint R files."),
-    LintCheck('rust', "Lint Rust files."),
-    LintCheck('docker', "Lint Dockerfiles with hadolint."),
-]
-
-
-def decorate_lint_command(cmd):
-    """
-    Decorate the lint() command function to add individual per-check options.
-    """
-    for check in lint_checks:
-        option = click.option("--{0}/--no-{0}".format(check.option_name),
-                              default=None, help=check.help)
-        cmd = option(cmd)
-    return cmd
-
-
-@archery.command(short_help="Check Arrow source tree for errors")
-@click.option("--src", metavar="<arrow_src>", default=".",
-              help="Specify Arrow source directory")
-@click.option("--fix", is_flag=True, type=BOOL, default=False,
-              help="Toggle fixing the lint errors if the linter supports it.")
-@click.option("--iwyu_all", is_flag=True, type=BOOL, default=False,
-              help="Run IWYU on all C++ files if enabled")
-@click.option("-a", "--all", is_flag=True, default=False,
-              help="Enable all checks.")
-@decorate_lint_command
-@click.pass_context
-def lint(ctx, src, fix, iwyu_all, **checks):
-    src = ArrowSources(src)
-
-    if checks.pop('all'):
-        # "--all" is given => enable all non-selected checks
-        for k, v in checks.items():
-            if v is None:
-                checks[k] = True
-    if not any(checks.values()):
-        raise click.UsageError(
-            "Need to enable at least one lint check (try --help)")
-    try:
-        linter(src, fix, iwyu_all=iwyu_all, **checks)
-    except LintValidationException:
-        sys.exit(1)
-
-
-@archery.command(short_help="Lint python docstring with NumpyDoc")
-@click.argument('symbols', nargs=-1)
-@click.option("--src", metavar="<arrow_src>", default=None,
-              callback=validate_arrow_sources,
-              help="Specify Arrow source directory")
-@click.option("--allow-rule", "-a", multiple=True,
-              help="Allow only these rules")
-@click.option("--disallow-rule", "-d", multiple=True,
-              help="Disallow these rules")
-def numpydoc(src, symbols, allow_rule, disallow_rule):
-    """
-    Pass list of modules or symbols as arguments to restrict the validation.
-
-    By default all modules of pyarrow are tried to be validated.
-
-    Examples
-    --------
-    archery numpydoc pyarrow.dataset
-    archery numpydoc pyarrow.csv pyarrow.json pyarrow.parquet
-    archery numpydoc pyarrow.array
-    """
-    disallow_rule = disallow_rule or {'GL01', 'SA01', 'EX01', 'ES01'}
-    try:
-        results = python_numpydoc(symbols, allow_rules=allow_rule,
-                                  disallow_rule=disallow_rule)
-        for result in results:
-            result.ok()
-    except LintValidationException:
-        sys.exit(1)
-
-
-@archery.group()
-@click.pass_context
-def benchmark(ctx):
-    """ Arrow benchmarking.
-
-    Use the diff sub-command to benchmark revisions, and/or build directories.
-    """
-    pass
-
-
-def benchmark_common_options(cmd):
-    options = [
-        click.option("--src", metavar="<arrow_src>", show_default=True,
-                     default=None, callback=validate_arrow_sources,
-                     help="Specify Arrow source directory"),
-        click.option("--preserve", type=BOOL, default=False, show_default=True,
-                     is_flag=True,
-                     help="Preserve workspace for investigation."),
-        click.option("--output", metavar="<output>",
-                     type=click.File("w", encoding="utf8"), default="-",
-                     help="Capture output result into file."),
-        click.option("--cmake-extras", type=str, multiple=True,
-                     help="Extra flags/options to pass to cmake invocation. "
-                     "Can be stacked"),
-    ]
-
-    cmd = cpp_toolchain_options(cmd)
-    return _apply_options(cmd, options)
-
-
-def benchmark_filter_options(cmd):
-    options = [
-        click.option("--suite-filter", metavar="<regex>", show_default=True,
-                     type=str, default=None,
-                     help="Regex filtering benchmark suites."),
-        click.option("--benchmark-filter", metavar="<regex>",
-                     show_default=True, type=str, default=None,
-                     help="Regex filtering benchmarks.")
-    ]
-    return _apply_options(cmd, options)
-
-
-@benchmark.command(name="list", short_help="List benchmark suite")
-@click.argument("rev_or_path", metavar="[<rev_or_path>]",
-                default="WORKSPACE", required=False)
-@benchmark_common_options
-@click.pass_context
-def benchmark_list(ctx, rev_or_path, src, preserve, output, cmake_extras,
-                   **kwargs):
-    """ List benchmark suite.
-    """
-    with tmpdir(preserve=preserve) as root:
-        logger.debug("Running benchmark {}".format(rev_or_path))
-
-        conf = CppBenchmarkRunner.default_configuration(
-            cmake_extras=cmake_extras, **kwargs)
-
-        runner_base = BenchmarkRunner.from_rev_or_path(
-            src, root, rev_or_path, conf)
-
-        for b in runner_base.list_benchmarks:
-            click.echo(b, file=output)
-
-
-@benchmark.command(name="run", short_help="Run benchmark suite")
-@click.argument("rev_or_path", metavar="[<rev_or_path>]",
-                default="WORKSPACE", required=False)
-@benchmark_common_options
-@benchmark_filter_options
-@click.option("--repetitions", type=int, default=1, show_default=True,
-              help=("Number of repetitions of each benchmark. Increasing "
-                    "may improve result precision."))
-@click.pass_context
-def benchmark_run(ctx, rev_or_path, src, preserve, output, cmake_extras,
-                  suite_filter, benchmark_filter, repetitions, **kwargs):
-    """ Run benchmark suite.
-
-    This command will run the benchmark suite for a single build. This is
-    used to capture (and/or publish) the results.
-
-    The caller can optionally specify a target which is either a git revision
-    (commit, tag, special values like HEAD) or a cmake build directory.
-
-    When a commit is referenced, a local clone of the arrow sources (specified
-    via --src) is performed and the proper branch is created. This is done in
-    a temporary directory which can be left intact with the `--preserve` flag.
-
-    The special token "WORKSPACE" is reserved to specify the current git
-    workspace. This imply that no clone will be performed.
-
-    Examples:
-
-    \b
-    # Run the benchmarks on current git workspace
-    \b
-    archery benchmark run
-
-    \b
-    # Run the benchmarks on current previous commit
-    \b
-    archery benchmark run HEAD~1
-
-    \b
-    # Run the benchmarks on current previous commit
-    \b
-    archery benchmark run --output=run.json
-    """
-    with tmpdir(preserve=preserve) as root:
-        logger.debug("Running benchmark {}".format(rev_or_path))
-
-        conf = CppBenchmarkRunner.default_configuration(
-            cmake_extras=cmake_extras, **kwargs)
-
-        runner_base = BenchmarkRunner.from_rev_or_path(
-            src, root, rev_or_path, conf,
-            repetitions=repetitions,
-            suite_filter=suite_filter, benchmark_filter=benchmark_filter)
-
-        json.dump(runner_base, output, cls=JsonEncoder)
-
-
-@benchmark.command(name="diff", short_help="Compare benchmark suites")
-@benchmark_common_options
-@benchmark_filter_options
-@click.option("--threshold", type=float, default=DEFAULT_THRESHOLD,
-              show_default=True,
-              help="Regression failure threshold in percentage.")
-@click.option("--repetitions", type=int, default=1, show_default=True,
-              help=("Number of repetitions of each benchmark. Increasing "
-                    "may improve result precision."))
-@click.option("--no-counters", type=BOOL, default=False, is_flag=True,
-              help="Hide counters field in diff report.")
-@click.argument("contender", metavar="[<contender>",
-                default=ArrowSources.WORKSPACE, required=False)
-@click.argument("baseline", metavar="[<baseline>]]", default="origin/master",
-                required=False)
-@click.pass_context
-def benchmark_diff(ctx, src, preserve, output, cmake_extras,
-                   suite_filter, benchmark_filter, repetitions, no_counters,
-                   threshold, contender, baseline, **kwargs):
-    """Compare (diff) benchmark runs.
-
-    This command acts like git-diff but for benchmark results.
-
-    The caller can optionally specify both the contender and the baseline. If
-    unspecified, the contender will default to the current workspace (like git)
-    and the baseline will default to master.
-
-    Each target (contender or baseline) can either be a git revision
-    (commit, tag, special values like HEAD) or a cmake build directory. This
-    allow comparing git commits, and/or different compilers and/or compiler
-    flags.
-
-    When a commit is referenced, a local clone of the arrow sources (specified
-    via --src) is performed and the proper branch is created. This is done in
-    a temporary directory which can be left intact with the `--preserve` flag.
-
-    The special token "WORKSPACE" is reserved to specify the current git
-    workspace. This imply that no clone will be performed.
-
-    Examples:
-
-    \b
-    # Compare workspace (contender) with master (baseline)
-    \b
-    archery benchmark diff
-
-    \b
-    # Compare master (contender) with latest version (baseline)
-    \b
-    export LAST=$(git tag -l "apache-arrow-[0-9]*" | sort -rV | head -1)
-    \b
-    archery benchmark diff master "$LAST"
-
-    \b
-    # Compare g++7 (contender) with clang++-8 (baseline) builds
-    \b
-    archery build --with-benchmarks=true \\
-            --cxx-flags=-ftree-vectorize \\
-            --cc=gcc-7 --cxx=g++-7 gcc7-build
-    \b
-    archery build --with-benchmarks=true \\
-            --cxx-flags=-flax-vector-conversions \\
-            --cc=clang-8 --cxx=clang++-8 clang8-build
-    \b
-    archery benchmark diff gcc7-build clang8-build
-
-    \b
-    # Compare default targets but scoped to the suites matching
-    # `^arrow-compute-aggregate` and benchmarks matching `(Sum|Mean)Kernel`.
-    \b
-    archery benchmark diff --suite-filter="^arrow-compute-aggregate" \\
-            --benchmark-filter="(Sum|Mean)Kernel"
-
-    \b
-    # Capture result in file `result.json`
-    \b
-    archery benchmark diff --output=result.json
-    \b
-    # Equivalently with no stdout clutter.
-    archery --quiet benchmark diff > result.json
-
-    \b
-    # Comparing with a cached results from `archery benchmark run`
-    \b
-    archery benchmark run --output=run.json HEAD~1
-    \b
-    # This should not recompute the benchmark from run.json
-    archery --quiet benchmark diff WORKSPACE run.json > result.json
-    """
-    with tmpdir(preserve=preserve) as root:
-        logger.debug("Comparing {} (contender) with {} (baseline)"
-                     .format(contender, baseline))
-
-        conf = CppBenchmarkRunner.default_configuration(
-            cmake_extras=cmake_extras, **kwargs)
-
-        runner_cont = BenchmarkRunner.from_rev_or_path(
-            src, root, contender, conf,
-            repetitions=repetitions,
-            suite_filter=suite_filter,
-            benchmark_filter=benchmark_filter)
-        runner_base = BenchmarkRunner.from_rev_or_path(
-            src, root, baseline, conf,
-            repetitions=repetitions,
-            suite_filter=suite_filter,
-            benchmark_filter=benchmark_filter)
-
-        runner_comp = RunnerComparator(runner_cont, runner_base, threshold)
-
-        # TODO(kszucs): test that the output is properly formatted jsonlines
-        comparisons_json = _get_comparisons_as_json(runner_comp.comparisons)
-        formatted = _format_comparisons_with_pandas(comparisons_json,
-                                                    no_counters)
-        output.write(formatted)
-        output.write('\n')
-
-
-def _get_comparisons_as_json(comparisons):
-    buf = StringIO()
-    for comparator in comparisons:
-        json.dump(comparator, buf, cls=JsonEncoder)
-        buf.write("\n")
-
-    return buf.getvalue()
-
-
-def _format_comparisons_with_pandas(comparisons_json, no_counters):
-    import pandas as pd
-    df = pd.read_json(StringIO(comparisons_json), lines=True)
-    # parse change % so we can sort by it
-    df['change %'] = df.pop('change').str[:-1].map(float)
-    first_regression = len(df) - df['regression'].sum()
-
-    fields = ['benchmark', 'baseline', 'contender', 'change %']
-    if not no_counters:
-        fields += ['counters']
-
-    df = df[fields].sort_values(by='change %', ascending=False)
-
-    def labelled(title, df):
-        if len(df) == 0:
-            return ''
-        title += ': ({})'.format(len(df))
-        df_str = df.to_string(index=False)
-        bar = '-' * df_str.index('\n')
-        return '\n'.join([bar, title, bar, df_str])
-
-    return '\n\n'.join([labelled('Non-regressions', df[:first_regression]),
-                        labelled('Regressions', df[first_regression:])])
-
-
-# ----------------------------------------------------------------------
-# Integration testing
-
-def _set_default(opt, default):
-    if opt is None:
-        return default
-    return opt
-
-
-@archery.command(short_help="Execute protocol and Flight integration tests")
-@click.option('--with-all', is_flag=True, default=False,
-              help=('Include all known languages by default '
-                    'in integration tests'))
-@click.option('--random-seed', type=int, default=12345,
-              help="Seed for PRNG when generating test data")
-@click.option('--with-cpp', type=bool, default=False,
-              help='Include C++ in integration tests')
-@click.option('--with-java', type=bool, default=False,
-              help='Include Java in integration tests')
-@click.option('--with-js', type=bool, default=False,
-              help='Include JavaScript in integration tests')
-@click.option('--with-go', type=bool, default=False,
-              help='Include Go in integration tests')
-@click.option('--with-rust', type=bool, default=False,
-              help='Include Rust in integration tests')
-@click.option('--write_generated_json', default=False,
-              help='Generate test JSON to indicated path')
-@click.option('--run-flight', is_flag=True, default=False,
-              help='Run Flight integration tests')
-@click.option('--debug', is_flag=True, default=False,
-              help='Run executables in debug mode as relevant')
-@click.option('--serial', is_flag=True, default=False,
-              help='Run tests serially, rather than in parallel')
-@click.option('--tempdir', default=None,
-              help=('Directory to use for writing '
-                    'integration test temporary files'))
-@click.option('stop_on_error', '-x', '--stop-on-error',
-              is_flag=True, default=False,
-              help='Stop on first error')
-@click.option('--gold-dirs', multiple=True,
-              help="gold integration test file paths")
-@click.option('-k', '--match',
-              help=("Substring for test names to include in run, "
-                    "e.g. -k primitive"))
-def integration(with_all=False, random_seed=12345, **args):
-    from .integration.runner import write_js_test_json, run_all_tests
-    import numpy as np
-
-    # FIXME(bkietz) Include help strings for individual testers.
-    # For example, CPPTester's ARROW_CPP_EXE_PATH environment variable.
-
-    # Make runs involving data generation deterministic
-    np.random.seed(random_seed)
-
-    gen_path = args['write_generated_json']
-
-    languages = ['cpp', 'java', 'js', 'go', 'rust']
-
-    enabled_languages = 0
-    for lang in languages:
-        param = 'with_{}'.format(lang)
-        if with_all:
-            args[param] = with_all
-
-        if args[param]:
-            enabled_languages += 1
-
-    if gen_path:
-        try:
-            os.makedirs(gen_path)
-        except OSError as e:
-            if e.errno != errno.EEXIST:
-                raise
-        write_js_test_json(gen_path)
-    else:
-        if enabled_languages == 0:
-            raise Exception("Must enable at least 1 language to test")
-        run_all_tests(**args)
-
-
-@archery.command()
-@click.option('--event-name', '-n', required=True)
-@click.option('--event-payload', '-p', type=click.File('r', encoding='utf8'),
-              default='-', required=True)
-@click.option('--arrow-token', envvar='ARROW_GITHUB_TOKEN',
-              help='OAuth token for responding comment in the arrow repo')
-@click.option('--crossbow-token', '-ct', envvar='CROSSBOW_GITHUB_TOKEN',
-              help='OAuth token for pushing to the crossow repository')
-def trigger_bot(event_name, event_payload, arrow_token, crossbow_token):
-    from .bot import CommentBot, actions
-
-    event_payload = json.loads(event_payload.read())
-
-    bot = CommentBot(name='github-actions', handler=actions, token=arrow_token)
-    bot.handle(event_name, event_payload)
-
-
-def _mock_compose_calls(compose):
-    from types import MethodType
-    from subprocess import CompletedProcess
-
-    def _mock(compose, executable):
-        def _execute(self, *args, **kwargs):
-            params = ['{}={}'.format(k, v)
-                      for k, v in self.config.params.items()]
-            command = ' '.join(params + [executable] + list(args))
-            click.echo(command)
-            return CompletedProcess([], 0)
-        return MethodType(_execute, compose)
-
-    compose._execute_docker = _mock(compose, executable='docker')
-    compose._execute_compose = _mock(compose, executable='docker-compose')
-
-
-@archery.group('docker')
-@click.option("--src", metavar="<arrow_src>", default=None,
-              callback=validate_arrow_sources,
-              help="Specify Arrow source directory.")
-@click.option('--dry-run/--execute', default=False,
-              help="Display the docker-compose commands instead of executing "
-                   "them.")
-@click.pass_obj
-def docker_compose(obj, src, dry_run):
-    """Interact with docker-compose based builds."""
-    from .docker import DockerCompose
-
-    config_path = src.path / 'docker-compose.yml'
-    if not config_path.exists():
-        raise click.ClickException(
-            "Docker compose configuration cannot be found in directory {}, "
-            "try to pass the arrow source directory explicitly.".format(src)
-        )
-
-    # take the docker-compose parameters like PYTHON, PANDAS, UBUNTU from the
-    # environment variables to keep the usage similar to docker-compose
-    compose = DockerCompose(config_path, params=os.environ)
-    if dry_run:
-        _mock_compose_calls(compose)
-    obj['compose'] = compose
-
-
-@docker_compose.command('build')
-@click.argument('image')
-@click.option('--force-pull/--no-pull', default=True,
-              help="Whether to force pull the image and its ancestor images")
-@click.option('--using-docker-cli', default=False, is_flag=True,
-              envvar='ARCHERY_USE_DOCKER_CLI',
-              help="Use docker CLI directly for building instead of calling "
-                   "docker-compose. This may help to reuse cached layers.")
-@click.option('--using-docker-buildx', default=False, is_flag=True,
-              envvar='ARCHERY_USE_DOCKER_BUILDX',
-              help="Use buildx with docker CLI directly for building instead "
-                   "of calling docker-compose or the plain docker build "
-                   "command. This option makes the build cache reusable "
-                   "across hosts.")
-@click.option('--use-cache/--no-cache', default=True,
-              help="Whether to use cache when building the image and its "
-                   "ancestor images")
-@click.option('--use-leaf-cache/--no-leaf-cache', default=True,
-              help="Whether to use cache when building only the (leaf) image "
-                   "passed as the argument. To disable caching for both the "
-                   "image and its ancestors use --no-cache option.")
-@click.pass_obj
-def docker_compose_build(obj, image, *, force_pull, using_docker_cli,
-                         using_docker_buildx, use_cache, use_leaf_cache):
-    """
-    Execute docker-compose builds.
-    """
-    from .docker import UndefinedImage
-
-    compose = obj['compose']
-
-    using_docker_cli |= using_docker_buildx
-    try:
-        if force_pull:
-            compose.pull(image, pull_leaf=use_leaf_cache,
-                         using_docker=using_docker_cli)
-        compose.build(image, use_cache=use_cache,
-                      use_leaf_cache=use_leaf_cache,
-                      using_docker=using_docker_cli,
-                      using_buildx=using_docker_buildx)
-    except UndefinedImage as e:
-        raise click.ClickException(
-            "There is no service/image defined in docker-compose.yml with "
-            "name: {}".format(str(e))
-        )
-    except RuntimeError as e:
-        raise click.ClickException(str(e))
-
-
-@docker_compose.command('run')
-@click.argument('image')
-@click.argument('command', required=False, default=None)
-@click.option('--env', '-e', multiple=True,
-              help="Set environment variable within the container")
-@click.option('--user', '-u', default=None,
-              help="Username or UID to run the container with")
-@click.option('--force-pull/--no-pull', default=True,
-              help="Whether to force pull the image and its ancestor images")
-@click.option('--force-build/--no-build', default=True,
-              help="Whether to force build the image and its ancestor images")
-@click.option('--build-only', default=False, is_flag=True,
-              help="Pull and/or build the image, but do not run it")
-@click.option('--using-docker-cli', default=False, is_flag=True,
-              envvar='ARCHERY_USE_DOCKER_CLI',
-              help="Use docker CLI directly for building instead of calling "
-                   "docker-compose. This may help to reuse cached layers.")
-@click.option('--using-docker-buildx', default=False, is_flag=True,
-              envvar='ARCHERY_USE_DOCKER_BUILDX',
-              help="Use buildx with docker CLI directly for building instead "
-                   "of calling docker-compose or the plain docker build "
-                   "command. This option makes the build cache reusable "
-                   "across hosts.")
-@click.option('--use-cache/--no-cache', default=True,
-              help="Whether to use cache when building the image and its "
-                   "ancestor images")
-@click.option('--use-leaf-cache/--no-leaf-cache', default=True,
-              help="Whether to use cache when building only the (leaf) image "
-                   "passed as the argument. To disable caching for both the "
-                   "image and its ancestors use --no-cache option.")
-@click.option('--volume', '-v', multiple=True,
-              help="Set volume within the container")
-@click.pass_obj
-def docker_compose_run(obj, image, command, *, env, user, force_pull,
-                       force_build, build_only, using_docker_cli,
-                       using_docker_buildx, use_cache,
-                       use_leaf_cache, volume):
-    """Execute docker-compose builds.
-
-    To see the available builds run `archery docker images`.
-
-    Examples:
-
-    # execute a single build
-    archery docker run conda-python
-
-    # execute the builds but disable the image pulling
-    archery docker run --no-cache conda-python
-
-    # pass a docker-compose parameter, like the python version
-    PYTHON=3.8 archery docker run conda-python
-
-    # disable the cache only for the leaf image
-    PANDAS=master archery docker run --no-leaf-cache conda-python-pandas
-
-    # entirely skip building the image
-    archery docker run --no-pull --no-build conda-python
-
-    # pass runtime parameters via docker environment variables
-    archery docker run -e CMAKE_BUILD_TYPE=release ubuntu-cpp
-
-    # set a volume
-    archery docker run -v $PWD/build:/build ubuntu-cpp
-
-    # starting an interactive bash session for debugging
-    archery docker run ubuntu-cpp bash
-    """
-    from .docker import UndefinedImage
-
-    compose = obj['compose']
-    using_docker_cli |= using_docker_buildx
-
-    env = dict(kv.split('=', 1) for kv in env)
-    try:
-        if force_pull:
-            compose.pull(image, pull_leaf=use_leaf_cache,
-                         using_docker=using_docker_cli)
-        if force_build:
-            compose.build(image, use_cache=use_cache,
-                          use_leaf_cache=use_leaf_cache,
-                          using_docker=using_docker_cli,
-                          using_buildx=using_docker_buildx)
-        if build_only:
-            return
-        compose.run(
-            image,
-            command=command,
-            env=env,
-            user=user,
-            using_docker=using_docker_cli,
-            volumes=volume
-        )
-    except UndefinedImage as e:
-        raise click.ClickException(
-            "There is no service/image defined in docker-compose.yml with "
-            "name: {}".format(str(e))
-        )
-    except RuntimeError as e:
-        raise click.ClickException(str(e))
-
-
-@docker_compose.command('push')
-@click.argument('image')
-@click.option('--user', '-u', required=False, envvar='ARCHERY_DOCKER_USER',
-              help='Docker repository username')
-@click.option('--password', '-p', required=False,
-              envvar='ARCHERY_DOCKER_PASSWORD',
-              help='Docker repository password')
-@click.option('--using-docker-cli', default=False, is_flag=True,
-              help="Use docker CLI directly for building instead of calling "
-                   "docker-compose. This may help to reuse cached layers.")
-@click.pass_obj
-def docker_compose_push(obj, image, user, password, using_docker_cli):
-    """Push the generated docker-compose image."""
-    compose = obj['compose']
-    compose.push(image, user=user, password=password,
-                 using_docker=using_docker_cli)
-
-
-@docker_compose.command('images')
-@click.pass_obj
-def docker_compose_images(obj):
-    """List the available docker-compose images."""
-    compose = obj['compose']
-    click.echo('Available images:')
-    for image in compose.images():
-        click.echo(' - {}'.format(image))
-
-
-@archery.group('release')
-@click.option("--src", metavar="<arrow_src>", default=None,
-              callback=validate_arrow_sources,
-              help="Specify Arrow source directory.")
-@click.option("--jira-cache", type=click.Path(), default=None,
-              help="File path to cache queried JIRA issues per version.")
-@click.pass_obj
-def release(obj, src, jira_cache):
-    """Release releated commands."""
-    from .release import Jira, CachedJira
-
-    jira = Jira()
-    if jira_cache is not None:
-        jira = CachedJira(jira_cache, jira=jira)
-
-    obj['jira'] = jira
-    obj['repo'] = src.path
-
-
-@release.command('curate')
-@click.argument('version')
-@click.pass_obj
-def release_curate(obj, version):
-    """Release curation."""
-    from .release import Release
-
-    release = Release.from_jira(version, jira=obj['jira'], repo=obj['repo'])
-    curation = release.curate()
-
-    click.echo(curation.render('console'))
-
-
-@release.group('changelog')
-def release_changelog():
-    """Release changelog."""
-    pass
-
-
-@release_changelog.command('add')
-@click.argument('version')
-@click.pass_obj
-def release_changelog_add(obj, version):
-    """Prepend the changelog with the current release"""
-    from .release import Release
-
-    jira, repo = obj['jira'], obj['repo']
-
-    # just handle the current version
-    release = Release.from_jira(version, jira=jira, repo=repo)
-    if release.is_released:
-        raise ValueError('This version has been already released!')
-
-    changelog = release.changelog()
-    changelog_path = pathlib.Path(repo) / 'CHANGELOG.md'
-
-    current_content = changelog_path.read_text()
-    new_content = changelog.render('markdown') + current_content
-
-    changelog_path.write_text(new_content)
-    click.echo("CHANGELOG.md is updated!")
-
-
-@release_changelog.command('generate')
-@click.argument('version')
-@click.argument('output', type=click.File('w', encoding='utf8'), default='-')
-@click.pass_obj
-def release_changelog_generate(obj, version, output):
-    """Generate the changelog of a specific release."""
-    from .release import Release
-
-    jira, repo = obj['jira'], obj['repo']
-
-    # just handle the current version
-    release = Release.from_jira(version, jira=jira, repo=repo)
-
-    changelog = release.changelog()
-    output.write(changelog.render('markdown'))
-
-
-@release_changelog.command('regenerate')
-@click.pass_obj
-def release_changelog_regenerate(obj):
-    """Regeneretate the whole CHANGELOG.md file"""
-    from .release import Release
-
-    jira, repo = obj['jira'], obj['repo']
-    changelogs = []
-
-    for version in jira.arrow_versions():
-        if not version.released:
-            continue
-        release = Release.from_jira(version, jira=jira, repo=repo)
-        click.echo('Querying changelog for version: {}'.format(version))
-        changelogs.append(release.changelog())
-
-    click.echo('Rendering new CHANGELOG.md file...')
-    changelog_path = pathlib.Path(repo) / 'CHANGELOG.md'
-    with changelog_path.open('w') as fp:
-        for cl in changelogs:
-            fp.write(cl.render('markdown'))
-
-
-@release.command('cherry-pick')
-@click.argument('version')
-@click.option('--dry-run/--execute', default=True,
-              help="Display the git commands instead of executing them.")
-@click.option('--recreate/--continue', default=True,
-              help="Recreate the maintenance branch or only apply unapplied "
-                   "patches.")
-@click.pass_obj
-def release_cherry_pick(obj, version, dry_run, recreate):
-    """
-    Cherry pick commits.
-    """
-    from .release import Release, MinorRelease, PatchRelease
-
-    release = Release.from_jira(version, jira=obj['jira'], repo=obj['repo'])
-    if not isinstance(release, (MinorRelease, PatchRelease)):
-        raise click.UsageError('Cherry-pick command only supported for minor '
-                               'and patch releases')
-
-    if not dry_run:
-        release.cherry_pick_commits(recreate_branch=recreate)
-        click.echo('Executed the following commands:\n')
-
-    click.echo(
-        'git checkout {} -b {}'.format(release.previous.tag, release.branch)
-    )
-    for commit in release.commits_to_pick():
-        click.echo('git cherry-pick {}'.format(commit.hexsha))
-
-
-try:
-    from .crossbow.cli import crossbow  # noqa
-except ImportError as exc:
-    missing_package = exc.name
-
-    @archery.command(
-        'crossbow',
-        context_settings={"ignore_unknown_options": True}
-    )
-    def crossbow():
-        raise click.ClickException(
-            "Couldn't import crossbow because of missing dependency: {}"
-            .format(missing_package)
-        )
-else:
-    archery.add_command(crossbow)
-
-
-if __name__ == "__main__":
-    archery(obj={})
diff --git a/dev/archery/archery/compat.py b/dev/archery/archery/compat.py
deleted file mode 100644
index 22cb9fc..0000000
--- a/dev/archery/archery/compat.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import pathlib
-
-
-def _is_path_like(path):
-    # PEP519 filesystem path protocol is available from python 3.6, so pathlib
-    # doesn't implement __fspath__ for earlier versions
-    return (isinstance(path, str) or
-            hasattr(path, '__fspath__') or
-            isinstance(path, pathlib.Path))
-
-
-def _ensure_path(path):
-    if isinstance(path, pathlib.Path):
-        return path
-    else:
-        return pathlib.Path(_stringify_path(path))
-
-
-def _stringify_path(path):
-    """
-    Convert *path* to a string or unicode path if possible.
-    """
-    if isinstance(path, str):
-        return path
-
-    # checking whether path implements the filesystem protocol
-    try:
-        return path.__fspath__()  # new in python 3.6
-    except AttributeError:
-        # fallback pathlib ckeck for earlier python versions than 3.6
-        if isinstance(path, pathlib.Path):
-            return str(path)
-
-    raise TypeError("not a path-like object")
diff --git a/dev/archery/archery/crossbow/__init__.py b/dev/archery/archery/crossbow/__init__.py
deleted file mode 100644
index bc72e81..0000000
--- a/dev/archery/archery/crossbow/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from .core import Config, Repo, Queue, Target, Job  # noqa
-from .reports import CommentReport, ConsoleReport, EmailReport  # noqa
diff --git a/dev/archery/archery/crossbow/cli.py b/dev/archery/archery/crossbow/cli.py
deleted file mode 100644
index 71c25e0..0000000
--- a/dev/archery/archery/crossbow/cli.py
+++ /dev/null
@@ -1,352 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from pathlib import Path
-
-import click
-
-from .core import Config, Repo, Queue, Target, Job, CrossbowError
-from .reports import EmailReport, ConsoleReport
-from ..utils.source import ArrowSources
-
-
-_default_arrow_path = ArrowSources.find().path
-_default_queue_path = _default_arrow_path.parent / "crossbow"
-_default_config_path = _default_arrow_path / "dev" / "tasks" / "tasks.yml"
-
-
-@click.group()
-@click.option('--github-token', '-t', default=None,
-              envvar="CROSSBOW_GITHUB_TOKEN",
-              help='OAuth token for GitHub authentication')
-@click.option('--arrow-path', '-a',
-              type=click.Path(), default=_default_arrow_path,
-              help='Arrow\'s repository path. Defaults to the repository of '
-                   'this script')
-@click.option('--queue-path', '-q',
-              type=click.Path(), default=_default_queue_path,
-              help='The repository path used for scheduling the tasks. '
-                   'Defaults to crossbow directory placed next to arrow')
-@click.option('--queue-remote', '-qr', default=None,
-              help='Force to use this remote URL for the Queue repository')
-@click.option('--output-file', metavar='<output>',
-              type=click.File('w', encoding='utf8'), default='-',
-              help='Capture output result into file.')
-@click.pass_context
-def crossbow(ctx, github_token, arrow_path, queue_path, queue_remote,
-             output_file):
-    """
-    Schedule packaging tasks or nightly builds on CI services.
-    """
-    ctx.ensure_object(dict)
-    ctx.obj['output'] = output_file
-    ctx.obj['arrow'] = Repo(arrow_path)
-    ctx.obj['queue'] = Queue(queue_path, remote_url=queue_remote,
-                             github_token=github_token, require_https=True)
-
-
-@crossbow.command()
-@click.option('--config-path', '-c',
-              type=click.Path(exists=True), default=_default_config_path,
-              help='Task configuration yml. Defaults to tasks.yml')
-@click.pass_obj
-def check_config(obj, config_path):
-    # load available tasks configuration and groups from yaml
-    config = Config.load_yaml(config_path)
-    config.validate()
-
-    output = obj['output']
-    config.show(output)
-
-
-@crossbow.command()
-@click.argument('tasks', nargs=-1, required=False)
-@click.option('--group', '-g', 'groups', multiple=True,
-              help='Submit task groups as defined in task.yml')
-@click.option('--param', '-p', 'params', multiple=True,
-              help='Additional task parameters for rendering the CI templates')
-@click.option('--job-prefix', default='build',
-              help='Arbitrary prefix for branch names, e.g. nightly')
-@click.option('--config-path', '-c',
-              type=click.Path(exists=True), default=_default_config_path,
-              help='Task configuration yml. Defaults to tasks.yml')
-@click.option('--arrow-version', '-v', default=None,
-              help='Set target version explicitly.')
-@click.option('--arrow-remote', '-r', default=None,
-              help='Set GitHub remote explicitly, which is going to be cloned '
-                   'on the CI services. Note, that no validation happens '
-                   'locally. Examples: https://github.com/apache/arrow or '
-                   'https://github.com/kszucs/arrow.')
-@click.option('--arrow-branch', '-b', default=None,
-              help='Give the branch name explicitly, e.g. master, ARROW-1949.')
-@click.option('--arrow-sha', '-t', default=None,
-              help='Set commit SHA or Tag name explicitly, e.g. f67a515, '
-                   'apache-arrow-0.11.1.')
-@click.option('--fetch/--no-fetch', default=True,
-              help='Fetch references (branches and tags) from the remote')
-@click.option('--dry-run/--commit', default=False,
-              help='Just display the rendered CI configurations without '
-                   'committing them')
-@click.option('--no-push/--push', default=False,
-              help='Don\'t push the changes')
-@click.pass_obj
-def submit(obj, tasks, groups, params, job_prefix, config_path, arrow_version,
-           arrow_remote, arrow_branch, arrow_sha, fetch, dry_run, no_push):
-    output = obj['output']
-    queue, arrow = obj['queue'], obj['arrow']
-
-    # load available tasks configuration and groups from yaml
-    config = Config.load_yaml(config_path)
-    try:
-        config.validate()
-    except CrossbowError as e:
-        raise click.ClickException(str(e))
-
-    # Override the detected repo url / remote, branch and sha - this aims to
-    # make release procedure a bit simpler.
-    # Note, that the target resivion's crossbow templates must be
-    # compatible with the locally checked out version of crossbow (which is
-    # in case of the release procedure), because the templates still
-    # contain some business logic (dependency installation, deployments)
-    # which will be reduced to a single command in the future.
-    target = Target.from_repo(arrow, remote=arrow_remote, branch=arrow_branch,
-                              head=arrow_sha, version=arrow_version)
-
-    # parse additional job parameters
-    params = dict([p.split("=") for p in params])
-
-    # instantiate the job object
-    try:
-        job = Job.from_config(config=config, target=target, tasks=tasks,
-                              groups=groups, params=params)
-    except CrossbowError as e:
-        raise click.ClickException(str(e))
-
-    job.show(output)
-    if dry_run:
-        return
-
-    if fetch:
-        queue.fetch()
-    queue.put(job, prefix=job_prefix)
-
-    if no_push:
-        click.echo('Branches and commits created but not pushed: `{}`'
-                   .format(job.branch))
-    else:
-        queue.push()
-        click.echo('Pushed job identifier is: `{}`'.format(job.branch))
-
-
-@crossbow.command()
-@click.argument('task', required=True)
-@click.option('--config-path', '-c',
-              type=click.Path(exists=True), default=_default_config_path,
-              help='Task configuration yml. Defaults to tasks.yml')
-@click.option('--arrow-version', '-v', default=None,
-              help='Set target version explicitly.')
-@click.option('--arrow-remote', '-r', default=None,
-              help='Set GitHub remote explicitly, which is going to be cloned '
-                   'on the CI services. Note, that no validation happens '
-                   'locally. Examples: https://github.com/apache/arrow or '
-                   'https://github.com/kszucs/arrow.')
-@click.option('--arrow-branch', '-b', default=None,
-              help='Give the branch name explicitly, e.g. master, ARROW-1949.')
-@click.option('--arrow-sha', '-t', default=None,
-              help='Set commit SHA or Tag name explicitly, e.g. f67a515, '
-                   'apache-arrow-0.11.1.')
-@click.option('--param', '-p', 'params', multiple=True,
-              help='Additional task parameters for rendering the CI templates')
-@click.pass_obj
-def render(obj, task, config_path, arrow_version, arrow_remote, arrow_branch,
-           arrow_sha, params):
-    """
-    Utility command to check the rendered CI templates.
-    """
-    from .core import _flatten
-
-    def highlight(code):
-        try:
-            from pygments import highlight
-            from pygments.lexers import YamlLexer
-            from pygments.formatters import TerminalFormatter
-            return highlight(code, YamlLexer(), TerminalFormatter())
-        except ImportError:
-            return code
-
-    arrow = obj['arrow']
-
-    target = Target.from_repo(arrow, remote=arrow_remote, branch=arrow_branch,
-                              head=arrow_sha, version=arrow_version)
-    config = Config.load_yaml(config_path)
-    params = dict([p.split("=") for p in params])
-    job = Job.from_config(config=config, target=target, tasks=[task],
-                          params=params)
-
-    for task_name, rendered_files in job.render_tasks().items():
-        for path, content in _flatten(rendered_files).items():
-            click.echo('#' * 80)
-            click.echo('### {:^72} ###'.format("/".join(path)))
-            click.echo('#' * 80)
-            click.echo(highlight(content))
-
-
-@crossbow.command()
-@click.argument('job-name', required=True)
-@click.option('--fetch/--no-fetch', default=True,
-              help='Fetch references (branches and tags) from the remote')
-@click.pass_obj
-def status(obj, job_name, fetch):
-    output = obj['output']
-    queue = obj['queue']
-    if fetch:
-        queue.fetch()
-    job = queue.get(job_name)
-    ConsoleReport(job).show(output)
-
-
-@crossbow.command()
-@click.argument('prefix', required=True)
-@click.option('--fetch/--no-fetch', default=True,
-              help='Fetch references (branches and tags) from the remote')
-@click.pass_obj
-def latest_prefix(obj, prefix, fetch):
-    queue = obj['queue']
-    if fetch:
-        queue.fetch()
-    latest = queue.latest_for_prefix(prefix)
-    click.echo(latest.branch)
-
-
-@crossbow.command()
-@click.argument('job-name', required=True)
-@click.option('--sender-name', '-n',
-              help='Name to use for report e-mail.')
-@click.option('--sender-email', '-e',
-              help='E-mail to use for report e-mail.')
-@click.option('--recipient-email', '-r',
-              help='Where to send the e-mail report')
-@click.option('--smtp-user', '-u',
-              help='E-mail address to use for SMTP login')
-@click.option('--smtp-password', '-P',
-              help='SMTP password to use for report e-mail.')
-@click.option('--smtp-server', '-s', default='smtp.gmail.com',
-              help='SMTP server to use for report e-mail.')
-@click.option('--smtp-port', '-p', default=465,
-              help='SMTP port to use for report e-mail.')
-@click.option('--poll/--no-poll', default=False,
-              help='Wait for completion if there are tasks pending')
-@click.option('--poll-max-minutes', default=180,
-              help='Maximum amount of time waiting for job completion')
-@click.option('--poll-interval-minutes', default=10,
-              help='Number of minutes to wait to check job status again')
-@click.option('--send/--dry-run', default=False,
-              help='Just display the report, don\'t send it')
-@click.option('--fetch/--no-fetch', default=True,
-              help='Fetch references (branches and tags) from the remote')
-@click.pass_obj
-def report(obj, job_name, sender_name, sender_email, recipient_email,
-           smtp_user, smtp_password, smtp_server, smtp_port, poll,
-           poll_max_minutes, poll_interval_minutes, send, fetch):
-    """
-    Send an e-mail report showing success/failure of tasks in a Crossbow run
-    """
-    output = obj['output']
-    queue = obj['queue']
-    if fetch:
-        queue.fetch()
-
-    job = queue.get(job_name)
-    report = EmailReport(
-        job=job,
-        sender_name=sender_name,
-        sender_email=sender_email,
-        recipient_email=recipient_email
-    )
-
-    if poll:
-        job.wait_until_finished(
-            poll_max_minutes=poll_max_minutes,
-            poll_interval_minutes=poll_interval_minutes
-        )
-
-    if send:
-        report.send(
-            smtp_user=smtp_user,
-            smtp_password=smtp_password,
-            smtp_server=smtp_server,
-            smtp_port=smtp_port
-        )
-    else:
-        report.show(output)
-
-
-@crossbow.command()
-@click.argument('job-name', required=True)
-@click.option('-t', '--target-dir',
-              default=_default_arrow_path / 'packages',
-              type=click.Path(file_okay=False, dir_okay=True),
-              help='Directory to download the build artifacts')
-@click.option('--dry-run/--execute', default=False,
-              help='Just display process, don\'t download anything')
-@click.option('--fetch/--no-fetch', default=True,
-              help='Fetch references (branches and tags) from the remote')
-@click.pass_obj
-def download_artifacts(obj, job_name, target_dir, dry_run, fetch):
-    """Download build artifacts from GitHub releases"""
-    output = obj['output']
-
-    # fetch the queue repository
-    queue = obj['queue']
-    if fetch:
-        queue.fetch()
-
-    # query the job's artifacts
-    job = queue.get(job_name)
-
-    # create directory to download the assets to
-    target_dir = Path(target_dir).absolute() / job_name
-    target_dir.mkdir(parents=True, exist_ok=True)
-
-    # download the assets while showing the job status
-    def asset_callback(task_name, task, asset):
-        if asset is not None:
-            path = target_dir / task_name / asset.name
-            path.parent.mkdir(exist_ok=True)
-            if not dry_run:
-                asset.download(path)
-
-    click.echo('Downloading {}\'s artifacts.'.format(job_name))
-    click.echo('Destination directory is {}'.format(target_dir))
-    click.echo()
-
-    report = ConsoleReport(job)
-    report.show(output, asset_callback=asset_callback)
-
-
-@crossbow.command()
-@click.option('--sha', required=True, help='Target committish')
-@click.option('--tag', required=True, help='Target tag')
-@click.option('--method', default='curl', help='Use cURL to upload')
-@click.option('--pattern', '-p', 'patterns', required=True, multiple=True,
-              help='File pattern to upload as assets')
-@click.pass_obj
-def upload_artifacts(obj, tag, sha, patterns, method):
-    queue = obj['queue']
-    queue.github_overwrite_release_assets(
-        tag_name=tag, target_commitish=sha, method=method, patterns=patterns
-    )
diff --git a/dev/archery/archery/crossbow/core.py b/dev/archery/archery/crossbow/core.py
deleted file mode 100644
index 9d3074a..0000000
--- a/dev/archery/archery/crossbow/core.py
+++ /dev/null
@@ -1,1162 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import os
-import re
-import fnmatch
-import glob
-import time
-import logging
-import mimetypes
-import subprocess
-import textwrap
-from io import StringIO
-from pathlib import Path
-from datetime import date
-
-import jinja2
-from ruamel.yaml import YAML
-
-try:
-    import github3
-    _have_github3 = True
-except ImportError:
-    github3 = object
-    _have_github3 = False
-
-try:
-    import pygit2
-except ImportError:
-    PygitRemoteCallbacks = object
-else:
-    PygitRemoteCallbacks = pygit2.RemoteCallbacks
-
-from ..utils.source import ArrowSources
-
-
-for pkg in ["requests", "urllib3", "github3"]:
-    logging.getLogger(pkg).setLevel(logging.WARNING)
-
-logger = logging.getLogger("crossbow")
-
-
-class CrossbowError(Exception):
-    pass
-
-
-def _flatten(mapping):
-    """Converts a hierarchical mapping to a flat dictionary"""
-    result = {}
-    for k, v in mapping.items():
-        if isinstance(v, dict):
-            for ik, iv in _flatten(v).items():
-                ik = ik if isinstance(ik, tuple) else (ik,)
-                result[(k,) + ik] = iv
-        elif isinstance(v, list):
-            for ik, iv in enumerate(_flatten(v)):
-                ik = ik if isinstance(ik, tuple) else (ik,)
-                result[(k,) + ik] = iv
-        else:
-            result[(k,)] = v
-    return result
-
-
-def _unflatten(mapping):
-    """Converts a flat tuple => object mapping to hierarchical one"""
-    result = {}
-    for path, value in mapping.items():
-        parents, leaf = path[:-1], path[-1]
-        # create the hierarchy until we reach the leaf value
-        temp = result
-        for parent in parents:
-            temp.setdefault(parent, {})
-            temp = temp[parent]
-        # set the leaf value
-        temp[leaf] = value
-
-    return result
-
-
-def _unflatten_tree(files):
-    """Converts a flat path => object mapping to a hierarchical directories
-
-    Input:
-        {
-            'path/to/file.a': a_content,
-            'path/to/file.b': b_content,
-            'path/file.c': c_content
-        }
-    Output:
-        {
-            'path': {
-                'to': {
-                    'file.a': a_content,
-                    'file.b': b_content
-                },
-                'file.c': c_content
-            }
-        }
-    """
-    files = {tuple(k.split('/')): v for k, v in files.items()}
-    return _unflatten(files)
-
-
-def _render_jinja_template(searchpath, template, params):
-    def format_all(items, pattern):
-        return [pattern.format(item) for item in items]
-
-    loader = jinja2.FileSystemLoader(searchpath)
-    env = jinja2.Environment(loader=loader, trim_blocks=True,
-                             lstrip_blocks=True,
-                             undefined=jinja2.StrictUndefined)
-    env.filters['format_all'] = format_all
-    template = env.get_template(template)
-    return template.render(**params)
-
-
-# configurations for setting up branch skipping
-# - appveyor has a feature to skip builds without an appveyor.yml
-# - travis reads from the master branch and applies the rules
-# - circle requires the configuration to be present on all branch, even ones
-#   that are configured to be skipped
-# - azure skips branches without azure-pipelines.yml by default
-# - github skips branches without .github/workflows/ by default
-
-_default_travis_yml = """
-branches:
-  only:
-    - master
-    - /.*-travis-.*/
-
-os: linux
-dist: trusty
-language: generic
-"""
-
-_default_circle_yml = """
-version: 2
-
-jobs:
-  build:
-    machine: true
-
-workflows:
-  version: 2
-  build:
-    jobs:
-      - build:
-          filters:
-            branches:
-              only:
-                - /.*-circle-.*/
-"""
-
-_default_tree = {
-    '.travis.yml': _default_travis_yml,
-    '.circleci/config.yml': _default_circle_yml
-}
-
-
-class GitRemoteCallbacks(PygitRemoteCallbacks):
-
-    def __init__(self, token):
-        self.token = token
-        self.attempts = 0
-        super().__init__()
-
-    def push_update_reference(self, refname, message):
-        pass
-
-    def update_tips(self, refname, old, new):
-        pass
-
-    def credentials(self, url, username_from_url, allowed_types):
-        # its a libgit2 bug, that it infinitely retries the authentication
-        self.attempts += 1
-
-        if self.attempts >= 5:
-            # pygit2 doesn't propagate the exception properly
-            msg = 'Wrong oauth personal access token'
-            print(msg)
-            raise CrossbowError(msg)
-
-        if allowed_types & pygit2.credentials.GIT_CREDTYPE_USERPASS_PLAINTEXT:
-            return pygit2.UserPass(self.token, 'x-oauth-basic')
-        else:
-            return None
-
-
-def _git_ssh_to_https(url):
-    return url.replace('git@github.com:', 'https://github.com/')
-
-
-class Repo:
-    """
-    Base class for interaction with local git repositories
-
-    A high level wrapper used for both reading revision information from
-    arrow's repository and pushing continuous integration tasks to the queue
-    repository.
-
-    Parameters
-    ----------
-    require_https : boolean, default False
-        Raise exception for SSH origin URLs
-    """
-
-    def __init__(self, path, github_token=None, remote_url=None,
-                 require_https=False):
-        self.path = Path(path)
-        self.github_token = github_token
-        self.require_https = require_https
-        self._remote_url = remote_url
-        self._pygit_repo = None
-        self._github_repo = None  # set by as_github_repo()
-        self._updated_refs = []
-
-    def __str__(self):
-        tpl = textwrap.dedent('''
-            Repo: {remote}@{branch}
-            Commit: {head}
-        ''')
-        return tpl.format(
-            remote=self.remote_url,
-            branch=self.branch.branch_name,
-            head=self.head
-        )
-
-    @property
-    def repo(self):
-        if self._pygit_repo is None:
-            self._pygit_repo = pygit2.Repository(str(self.path))
-        return self._pygit_repo
-
-    @property
-    def origin(self):
-        remote = self.repo.remotes['origin']
-        if self.require_https and remote.url.startswith('git@github.com'):
-            raise CrossbowError("Change SSH origin URL to HTTPS to use "
-                                "Crossbow: {}".format(remote.url))
-        return remote
-
-    def fetch(self):
-        refspec = '+refs/heads/*:refs/remotes/origin/*'
-        self.origin.fetch([refspec])
-
-    def push(self, refs=None, github_token=None):
-        github_token = github_token or self.github_token
-        if github_token is None:
-            raise RuntimeError(
-                'Could not determine GitHub token. Please set the '
-                'CROSSBOW_GITHUB_TOKEN environment variable to a '
-                'valid GitHub access token or pass one to --github-token.'
-            )
-        callbacks = GitRemoteCallbacks(github_token)
-        refs = refs or []
-        try:
-            self.origin.push(refs + self._updated_refs, callbacks=callbacks)
-        except pygit2.GitError:
-            raise RuntimeError('Failed to push updated references, '
-                               'potentially because of credential issues: {}'
-                               .format(self._updated_refs))
-        else:
-            self.updated_refs = []
-
-    @property
-    def head(self):
-        """Currently checked out commit's sha"""
-        return self.repo.head
-
-    @property
-    def branch(self):
-        """Currently checked out branch"""
-        try:
-            return self.repo.branches[self.repo.head.shorthand]
-        except KeyError:
-            return None  # detached
-
-    @property
-    def remote(self):
-        """Currently checked out branch's remote counterpart"""
-        try:
-            return self.repo.remotes[self.branch.upstream.remote_name]
-        except (AttributeError, KeyError):
-            return None  # cannot detect
-
-    @property
-    def remote_url(self):
-        """Currently checked out branch's remote counterpart URL
-
-        If an SSH github url is set, it will be replaced by the https
-        equivalent usable with GitHub OAuth token.
-        """
-        try:
-            return self._remote_url or _git_ssh_to_https(self.remote.url)
-        except AttributeError:
-            return None
-
-    @property
-    def user_name(self):
-        try:
-            return next(self.repo.config.get_multivar('user.name'))
-        except StopIteration:
-            return os.environ.get('GIT_COMMITTER_NAME', 'unknown')
-
-    @property
-    def user_email(self):
-        try:
-            return next(self.repo.config.get_multivar('user.email'))
-        except StopIteration:
-            return os.environ.get('GIT_COMMITTER_EMAIL', 'unknown')
-
-    @property
-    def signature(self):
-        return pygit2.Signature(self.user_name, self.user_email,
-                                int(time.time()))
-
-    def create_tree(self, files):
-        builder = self.repo.TreeBuilder()
-
-        for filename, content in files.items():
-            if isinstance(content, dict):
-                # create a subtree
-                tree_id = self.create_tree(content)
-                builder.insert(filename, tree_id, pygit2.GIT_FILEMODE_TREE)
-            else:
-                # create a file
-                blob_id = self.repo.create_blob(content)
-                builder.insert(filename, blob_id, pygit2.GIT_FILEMODE_BLOB)
-
-        tree_id = builder.write()
-        return tree_id
-
-    def create_commit(self, files, parents=None, message='',
-                      reference_name=None):
-        if parents is None:
-            # by default use the main branch as the base of the new branch
-            # required to reuse github actions cache across crossbow tasks
-            commit, _ = self.repo.resolve_refish("master")
-            parents = [commit.id]
-        tree_id = self.create_tree(files)
-
-        author = committer = self.signature
-        commit_id = self.repo.create_commit(reference_name, author, committer,
-                                            message, tree_id, parents)
-        return self.repo[commit_id]
-
-    def create_branch(self, branch_name, files, parents=None, message='',
-                      signature=None):
-        # create commit with the passed tree
-        commit = self.create_commit(files, parents=parents, message=message)
-
-        # create branch pointing to the previously created commit
-        branch = self.repo.create_branch(branch_name, commit)
-
-        # append to the pushable references
-        self._updated_refs.append('refs/heads/{}'.format(branch_name))
-
-        return branch
-
-    def create_tag(self, tag_name, commit_id, message=''):
-        tag_id = self.repo.create_tag(tag_name, commit_id,
-                                      pygit2.GIT_OBJ_COMMIT, self.signature,
-                                      message)
-
-        # append to the pushable references
-        self._updated_refs.append('refs/tags/{}'.format(tag_name))
-
-        return self.repo[tag_id]
-
-    def file_contents(self, commit_id, file):
-        commit = self.repo[commit_id]
-        entry = commit.tree[file]
-        blob = self.repo[entry.id]
-        return blob.data
-
-    def _parse_github_user_repo(self):
-        m = re.match(r'.*\/([^\/]+)\/([^\/\.]+)(\.git)?$', self.remote_url)
-        if m is None:
-            raise CrossbowError(
-                "Unable to parse the github owner and repository from the "
-                "repository's remote url '{}'".format(self.remote_url)
-            )
-        user, repo = m.group(1), m.group(2)
-        return user, repo
-
-    def as_github_repo(self, github_token=None):
-        """Converts it to a repository object which wraps the GitHub API"""
-        if self._github_repo is None:
-            if not _have_github3:
-                raise ImportError('Must install github3.py')
-            github_token = github_token or self.github_token
-            username, reponame = self._parse_github_user_repo()
-            session = github3.session.GitHubSession(
-                default_connect_timeout=10,
-                default_read_timeout=30
-            )
-            github = github3.GitHub(session=session)
-            github.login(token=github_token)
-            self._github_repo = github.repository(username, reponame)
-        return self._github_repo
-
-    def github_commit(self, sha):
-        repo = self.as_github_repo()
-        return repo.commit(sha)
-
-    def github_release(self, tag):
-        repo = self.as_github_repo()
-        try:
-            return repo.release_from_tag(tag)
-        except github3.exceptions.NotFoundError:
-            return None
-
-    def github_upload_asset_requests(self, release, path, name, mime,
-                                     max_retries=None, retry_backoff=None):
-        if max_retries is None:
-            max_retries = int(os.environ.get('CROSSBOW_MAX_RETRIES', 8))
-        if retry_backoff is None:
-            retry_backoff = int(os.environ.get('CROSSBOW_RETRY_BACKOFF', 5))
-
-        for i in range(max_retries):
-            try:
-                with open(path, 'rb') as fp:
-                    result = release.upload_asset(name=name, asset=fp,
-                                                  content_type=mime)
-            except github3.exceptions.ResponseError as e:
-                logger.error('Attempt {} has failed with message: {}.'
-                             .format(i + 1, str(e)))
-                logger.error('Error message {}'.format(e.msg))
-                logger.error('List of errors provided by Github:')
-                for err in e.errors:
-                    logger.error(' - {}'.format(err))
-
-                if e.code == 422:
-                    # 422 Validation Failed, probably raised because
-                    # ReleaseAsset already exists, so try to remove it before
-                    # reattempting the asset upload
-                    for asset in release.assets():
-                        if asset.name == name:
-                            logger.info('Release asset {} already exists, '
-                                        'removing it...'.format(name))
-                            asset.delete()
-                            logger.info('Asset {} removed.'.format(name))
-                            break
-            except github3.exceptions.ConnectionError as e:
-                logger.error('Attempt {} has failed with message: {}.'
-                             .format(i + 1, str(e)))
-            else:
-                logger.info('Attempt {} has finished.'.format(i + 1))
-                return result
-
-            time.sleep(retry_backoff)
-
-        raise RuntimeError('Github asset uploading has failed!')
-
-    def github_upload_asset_curl(self, release, path, name, mime):
-        upload_url, _ = release.upload_url.split('{?')
-        upload_url += '?name={}'.format(name)
-
-        command = [
-            'curl',
-            '--fail',
-            '-H', "Authorization: token {}".format(self.github_token),
-            '-H', "Content-Type: {}".format(mime),
-            '--data-binary', '@{}'.format(path),
-            upload_url
-        ]
-        return subprocess.run(command, shell=False, check=True)
-
-    def github_overwrite_release_assets(self, tag_name, target_commitish,
-                                        patterns, method='requests'):
-        # Since github has changed something the asset uploading via requests
-        # got instable, so prefer the cURL alternative.
-        # Potential cause:
-        #    sigmavirus24/github3.py/issues/779#issuecomment-379470626
-        repo = self.as_github_repo()
-        if not tag_name:
-            raise CrossbowError('Empty tag name')
-        if not target_commitish:
-            raise CrossbowError('Empty target commit for the release tag')
-
-        # remove the whole release if it already exists
-        try:
-            release = repo.release_from_tag(tag_name)
-        except github3.exceptions.NotFoundError:
-            pass
-        else:
-            release.delete()
-
-        release = repo.create_release(tag_name, target_commitish)
-        for pattern in patterns:
-            for path in glob.glob(pattern, recursive=True):
-                name = os.path.basename(path)
-                size = os.path.getsize(path)
-                mime = mimetypes.guess_type(name)[0] or 'application/zip'
-
-                logger.info(
-                    'Uploading asset `{}` with mimetype {} and size {}...'
-                    .format(name, mime, size)
-                )
-
-                if method == 'requests':
-                    self.github_upload_asset_requests(release, path, name=name,
-                                                      mime=mime)
-                elif method == 'curl':
-                    self.github_upload_asset_curl(release, path, name=name,
-                                                  mime=mime)
-                else:
-                    raise CrossbowError(
-                        'Unsupported upload method {}'.format(method)
-                    )
-
-
-class Queue(Repo):
-
-    def _latest_prefix_id(self, prefix):
-        pattern = re.compile(r'[\w\/-]*{}-(\d+)'.format(prefix))
-        matches = list(filter(None, map(pattern.match, self.repo.branches)))
-        if matches:
-            latest = max(int(m.group(1)) for m in matches)
-        else:
-            latest = -1
-        return latest
-
-    def _next_job_id(self, prefix):
-        """Auto increments the branch's identifier based on the prefix"""
-        latest_id = self._latest_prefix_id(prefix)
-        return '{}-{}'.format(prefix, latest_id + 1)
-
-    def latest_for_prefix(self, prefix):
-        latest_id = self._latest_prefix_id(prefix)
-        if latest_id < 0:
-            raise RuntimeError(
-                'No job has been submitted with prefix {} yet'.format(prefix)
-            )
-        job_name = '{}-{}'.format(prefix, latest_id)
-        return self.get(job_name)
-
-    def date_of(self, job):
-        # it'd be better to bound to the queue repository on deserialization
-        # and reorganize these methods to Job
-        branch_name = 'origin/{}'.format(job.branch)
-        branch = self.repo.branches[branch_name]
-        commit = self.repo[branch.target]
-        return date.fromtimestamp(commit.commit_time)
-
-    def jobs(self, pattern):
-        """Return jobs sorted by its identifier in reverse order"""
-        job_names = []
-        for name in self.repo.branches.remote:
-            origin, name = name.split('/', 1)
-            result = re.match(pattern, name)
-            if result:
-                job_names.append(name)
-
-        for name in sorted(job_names, reverse=True):
-            yield self.get(name)
-
-    def get(self, job_name):
-        branch_name = 'origin/{}'.format(job_name)
-        branch = self.repo.branches[branch_name]
-        try:
-            content = self.file_contents(branch.target, 'job.yml')
-        except KeyError:
-            raise CrossbowError(
-                'No job is found with name: {}'.format(job_name)
-            )
-
-        buffer = StringIO(content.decode('utf-8'))
-        job = yaml.load(buffer)
-        job.queue = self
-        return job
-
-    def put(self, job, prefix='build'):
-        if not isinstance(job, Job):
-            raise CrossbowError('`job` must be an instance of Job')
-        if job.branch is not None:
-            raise CrossbowError('`job.branch` is automatically generated, '
-                                'thus it must be blank')
-
-        if job.target.remote is None:
-            raise CrossbowError(
-                'Cannot determine git remote for the Arrow repository to '
-                'clone or push to, try to push the `{}` branch first to have '
-                'a remote tracking counterpart.'.format(job.target.branch)
-            )
-        if job.target.branch is None:
-            raise CrossbowError(
-                'Cannot determine the current branch of the Arrow repository '
-                'to clone or push to, perhaps it is in detached HEAD state. '
-                'Please checkout a branch.'
-            )
-
-        # auto increment and set next job id, e.g. build-85
-        job._queue = self
-        job.branch = self._next_job_id(prefix)
-
-        # create tasks' branches
-        for task_name, task in job.tasks.items():
-            # adding CI's name to the end of the branch in order to use skip
-            # patterns on travis and circleci
-            task.branch = '{}-{}-{}'.format(job.branch, task.ci, task_name)
-            params = {
-                **job.params,
-                "arrow": job.target,
-                "queue_remote_url": self.remote_url
-            }
-            files = task.render_files(job.template_searchpath, params=params)
-            branch = self.create_branch(task.branch, files=files)
-            self.create_tag(task.tag, branch.target)
-            task.commit = str(branch.target)
-
-        # create job's branch with its description
-        return self.create_branch(job.branch, files=job.render_files())
-
-
-def get_version(root, **kwargs):
-    """
-    Parse function for setuptools_scm that ignores tags for non-C++
-    subprojects, e.g. apache-arrow-js-XXX tags.
-    """
-    from setuptools_scm.git import parse as parse_git_version
-
-    # query the calculated version based on the git tags
-    kwargs['describe_command'] = (
-        'git describe --dirty --tags --long --match "apache-arrow-[0-9].*"'
-    )
-    version = parse_git_version(root, **kwargs)
-
-    # increment the minor version, because there can be patch releases created
-    # from maintenance branches where the tags are unreachable from the
-    # master's HEAD, so the git command above generates 0.17.0.dev300 even if
-    # arrow has a never 0.17.1 patch release
-    pattern = r"^(\d+)\.(\d+)\.(\d+)$"
-    match = re.match(pattern, str(version.tag))
-    major, minor, patch = map(int, match.groups())
-
-    # the bumped version number after 0.17.x will be 0.18.0.dev300
-    return "{}.{}.{}.dev{}".format(major, minor + 1, patch, version.distance)
-
-
-class Serializable:
-
-    @classmethod
-    def to_yaml(cls, representer, data):
-        tag = '!{}'.format(cls.__name__)
-        dct = {k: v for k, v in data.__dict__.items() if not k.startswith('_')}
-        return representer.represent_mapping(tag, dct)
-
-
-class Target(Serializable):
-    """
-    Describes target repository and revision the builds run against
-
-    This serializable data container holding information about arrow's
-    git remote, branch, sha and version number as well as some metadata
-    (currently only an email address where the notification should be sent).
-    """
-
-    def __init__(self, head, branch, remote, version, email=None):
-        self.head = head
-        self.email = email
-        self.branch = branch
-        self.remote = remote
-        self.version = version
-        self.no_rc_version = re.sub(r'-rc\d+\Z', '', version)
-        # Semantic Versioning 1.0.0: https://semver.org/spec/v1.0.0.html
-        #
-        # > A pre-release version number MAY be denoted by appending an
-        # > arbitrary string immediately following the patch version and a
-        # > dash. The string MUST be comprised of only alphanumerics plus
-        # > dash [0-9A-Za-z-].
-        #
-        # Example:
-        #
-        #   '0.16.1.dev10' ->
-        #   '0.16.1-dev10'
-        self.no_rc_semver_version = \
-            re.sub(r'\.(dev\d+)\Z', r'-\1', self.no_rc_version)
-
-    @classmethod
-    def from_repo(cls, repo, head=None, branch=None, remote=None, version=None,
-                  email=None):
-        """Initialize from a repository
-
-        Optionally override detected remote, branch, head, and/or version.
-        """
-        assert isinstance(repo, Repo)
-
-        if head is None:
-            head = str(repo.head.target)
-        if branch is None:
-            branch = repo.branch.branch_name
-        if remote is None:
-            remote = repo.remote_url
-        if version is None:
-            version = get_version(repo.path)
-        if email is None:
-            email = repo.user_email
-
-        return cls(head=head, email=email, branch=branch, remote=remote,
-                   version=version)
-
-
-class Task(Serializable):
-    """
-    Describes a build task and metadata required to render CI templates
-
-    A task is represented as a single git commit and branch containing jinja2
-    rendered files (currently appveyor.yml or .travis.yml configurations).
-
-    A task can't be directly submitted to a queue, must belong to a job.
-    Each task's unique identifier is its branch name, which is generated after
-    submitting the job to a queue.
-    """
-
-    def __init__(self, ci, template, artifacts=None, params=None):
-        assert ci in {
-            'circle',
-            'travis',
-            'appveyor',
-            'azure',
-            'github',
-            'drone',
-        }
-        self.ci = ci
-        self.template = template
-        self.artifacts = artifacts or []
-        self.params = params or {}
-        self.branch = None  # filled after adding to a queue
-        self.commit = None  # filled after adding to a queue
-        self._queue = None  # set by the queue object after put or get
-        self._status = None  # status cache
-        self._assets = None  # assets cache
-
-    def render_files(self, searchpath, params=None):
-        params = {**self.params, **(params or {}), "task": self}
-        try:
-            rendered = _render_jinja_template(searchpath, self.template,
-                                              params=params)
-        except jinja2.TemplateError as e:
-            raise RuntimeError(
-                'Failed to render template `{}` with {}: {}'.format(
-                    self.template, e.__class__.__name__, str(e)
-                )
-            )
-
-        tree = {**_default_tree, self.filename: rendered}
-        return _unflatten_tree(tree)
-
-    @property
-    def tag(self):
-        return self.branch
-
-    @property
-    def filename(self):
-        config_files = {
-            'circle': '.circleci/config.yml',
-            'travis': '.travis.yml',
-            'appveyor': 'appveyor.yml',
-            'azure': 'azure-pipelines.yml',
-            'github': '.github/workflows/crossbow.yml',
-            'drone': '.drone.yml',
-        }
-        return config_files[self.ci]
-
-    def status(self, force_query=False):
-        _status = getattr(self, '_status', None)
-        if force_query or _status is None:
-            github_commit = self._queue.github_commit(self.commit)
-            self._status = TaskStatus(github_commit)
-        return self._status
-
-    def assets(self, force_query=False):
-        _assets = getattr(self, '_assets', None)
-        if force_query or _assets is None:
-            github_release = self._queue.github_release(self.tag)
-            self._assets = TaskAssets(github_release,
-                                      artifact_patterns=self.artifacts)
-        return self._assets
-
-
-class TaskStatus:
-    """
-    Combine the results from status and checks API to a single state.
-
-    Azure pipelines uses checks API which doesn't provide a combined
-    interface like status API does, so we need to manually combine
-    both the commit statuses and the commit checks coming from
-    different API endpoint
-
-    Status.state: error, failure, pending or success, default pending
-    CheckRun.status: queued, in_progress or completed, default: queued
-    CheckRun.conclusion: success, failure, neutral, cancelled, timed_out
-                            or action_required, only set if
-                            CheckRun.status == 'completed'
-
-    1. Convert CheckRun's status and conclusion to one of Status.state
-    2. Merge the states based on the following rules:
-        - failure if any of the contexts report as error or failure
-        - pending if there are no statuses or a context is pending
-        - success if the latest status for all contexts is success
-        error otherwise.
-
-    Parameters
-    ----------
-    commit : github3.Commit
-        Commit to query the combined status for.
-
-    Returns
-    -------
-    TaskStatus(
-        combined_state='error|failure|pending|success',
-        github_status='original github status object',
-        github_check_runs='github checks associated with the commit',
-        total_count='number of statuses and checks'
-    )
-    """
-
-    def __init__(self, commit):
-        status = commit.status()
-        check_runs = list(commit.check_runs())
-        states = [s.state for s in status.statuses]
-
-        for check in check_runs:
-            if check.status == 'completed':
-                if check.conclusion in {'success', 'failure'}:
-                    states.append(check.conclusion)
-                elif check.conclusion in {'cancelled', 'timed_out',
-                                          'action_required'}:
-                    states.append('error')
-                # omit `neutral` conclusion
-            else:
-                states.append('pending')
-
-        # it could be more effective, but the following is more descriptive
-        combined_state = 'error'
-        if len(states):
-            if any(state in {'error', 'failure'} for state in states):
-                combined_state = 'failure'
-            elif any(state == 'pending' for state in states):
-                combined_state = 'pending'
-            elif all(state == 'success' for state in states):
-                combined_state = 'success'
-
-        # show link to the actual build, some of the CI providers implement
-        # the statuses API others implement the checks API, so display both
-        build_links = [s.target_url for s in status.statuses]
-        build_links += [c.html_url for c in check_runs]
-
-        self.combined_state = combined_state
-        self.github_status = status
-        self.github_check_runs = check_runs
-        self.total_count = len(states)
-        self.build_links = build_links
-
-
-class TaskAssets(dict):
-
-    def __init__(self, github_release, artifact_patterns):
-        # HACK(kszucs): don't expect uploaded assets of no atifacts were
-        # defiened for the tasks in order to spare a bit of github rate limit
-        if not artifact_patterns:
-            return
-
-        if github_release is None:
-            github_assets = {}  # no assets have been uploaded for the task
-        else:
-            github_assets = {a.name: a for a in github_release.assets()}
-
-        for pattern in artifact_patterns:
-            # artifact can be a regex pattern
-            compiled = re.compile(pattern)
-            matches = list(
-                filter(None, map(compiled.match, github_assets.keys()))
-            )
-            num_matches = len(matches)
-
-            # validate artifact pattern matches single asset
-            if num_matches == 0:
-                self[pattern] = None
-            elif num_matches == 1:
-                self[pattern] = github_assets[matches[0].group(0)]
-            else:
-                raise CrossbowError(
-                    'Only a single asset should match pattern `{}`, there are '
-                    'multiple ones: {}'.format(pattern, ', '.join(matches))
-                )
-
-    def missing_patterns(self):
-        return [pattern for pattern, asset in self.items() if asset is None]
-
-    def uploaded_assets(self):
-        return [asset for asset in self.values() if asset is not None]
-
-
-class Job(Serializable):
-    """Describes multiple tasks against a single target repository"""
-
-    def __init__(self, target, tasks, params=None, template_searchpath=None):
-        if not tasks:
-            raise ValueError('no tasks were provided for the job')
-        if not all(isinstance(task, Task) for task in tasks.values()):
-            raise ValueError('each `tasks` mus be an instance of Task')
-        if not isinstance(target, Target):
-            raise ValueError('`target` must be an instance of Target')
-        if not isinstance(target, Target):
-            raise ValueError('`target` must be an instance of Target')
-        if not isinstance(params, dict):
-            raise ValueError('`params` must be an instance of dict')
-
-        self.target = target
-        self.tasks = tasks
-        self.params = params or {}  # additional parameters for the tasks
-        self.branch = None  # filled after adding to a queue
-        self._queue = None  # set by the queue object after put or get
-        if template_searchpath is None:
-            self._template_searchpath = ArrowSources.find().path
-        else:
-            self._template_searchpath = template_searchpath
-
-    def render_files(self):
-        with StringIO() as buf:
-            yaml.dump(self, buf)
-            content = buf.getvalue()
-        tree = {**_default_tree, "job.yml": content}
-        return _unflatten_tree(tree)
-
-    def render_tasks(self, params=None):
-        result = {}
-        params = {
-            **self.params,
-            "arrow": self.target,
-            **(params or {})
-        }
-        for task_name, task in self.tasks.items():
-            files = task.render_files(self._template_searchpath, params)
-            result[task_name] = files
-        return result
-
-    @property
-    def template_searchpath(self):
-        return self._template_searchpath
-
-    @property
-    def queue(self):
-        assert isinstance(self._queue, Queue)
-        return self._queue
-
-    @queue.setter
-    def queue(self, queue):
-        assert isinstance(queue, Queue)
-        self._queue = queue
-        for task in self.tasks.values():
-            task._queue = queue
-
-    @property
-    def email(self):
-        return os.environ.get('CROSSBOW_EMAIL', self.target.email)
-
-    @property
-    def date(self):
-        return self.queue.date_of(self)
-
-    def show(self, stream=None):
-        return yaml.dump(self, stream=stream)
-
-    @classmethod
-    def from_config(cls, config, target, tasks=None, groups=None, params=None):
-        """
-        Intantiate a job from based on a config.
-
-        Parameters
-        ----------
-        config : dict
-            Deserialized content of tasks.yml
-        target : Target
-            Describes target repository and revision the builds run against.
-        tasks : Optional[List[str]], default None
-            List of glob patterns for matching task names.
-        groups : Optional[List[str]], default None
-            List of exact group names matching predefined task sets in the
-            config.
-        params : Optional[Dict[str, str]], default None
-            Additional rendering parameters for the task templates.
-
-        Returns
-        -------
-        Job
-
-        Raises
-        ------
-        Exception:
-            If invalid groups or tasks has been passed.
-        """
-        task_definitions = config.select(tasks, groups=groups)
-
-        # instantiate the tasks
-        tasks = {}
-        versions = {'version': target.version,
-                    'no_rc_version': target.no_rc_version,
-                    'no_rc_semver_version': target.no_rc_semver_version}
-        for task_name, task in task_definitions.items():
-            artifacts = task.pop('artifacts', None) or []  # because of yaml
-            artifacts = [fn.format(**versions) for fn in artifacts]
-            tasks[task_name] = Task(artifacts=artifacts, **task)
-
-        return cls(target=target, tasks=tasks, params=params,
-                   template_searchpath=config.template_searchpath)
-
-    def is_finished(self):
-        for task in self.tasks.values():
-            status = task.status(force_query=True)
-            if status.combined_state == 'pending':
-                return False
-        return True
-
-    def wait_until_finished(self, poll_max_minutes=120,
-                            poll_interval_minutes=10):
-        started_at = time.time()
-        while True:
-            if self.is_finished():
-                break
-
-            waited_for_minutes = (time.time() - started_at) / 60
-            if waited_for_minutes > poll_max_minutes:
-                msg = ('Exceeded the maximum amount of time waiting for job '
-                       'to finish, waited for {} minutes.')
-                raise RuntimeError(msg.format(waited_for_minutes))
-
-            logger.info('Waiting {} minutes and then checking again'
-                        .format(poll_interval_minutes))
-            time.sleep(poll_interval_minutes * 60)
-
-
-class Config(dict):
-
-    def __init__(self, tasks, template_searchpath):
-        super().__init__(tasks)
-        self.template_searchpath = template_searchpath
-
-    @classmethod
-    def load_yaml(cls, path):
-        path = Path(path)
-        searchpath = path.parent
-        rendered = _render_jinja_template(searchpath, template=path.name,
-                                          params={})
-        config = yaml.load(rendered)
-        return cls(config, template_searchpath=searchpath)
-
-    def show(self, stream=None):
-        return yaml.dump(dict(self), stream=stream)
-
-    def select(self, tasks=None, groups=None):
-        config_groups = dict(self['groups'])
-        config_tasks = dict(self['tasks'])
-        valid_groups = set(config_groups.keys())
-        valid_tasks = set(config_tasks.keys())
-        group_whitelist = list(groups or [])
-        task_whitelist = list(tasks or [])
-
-        # validate that the passed groups are defined in the config
-        requested_groups = set(group_whitelist)
-        invalid_groups = requested_groups - valid_groups
-        if invalid_groups:
-            msg = 'Invalid group(s) {!r}. Must be one of {!r}'.format(
-                invalid_groups, valid_groups
-            )
-            raise CrossbowError(msg)
-
-        # merge the tasks defined in the selected groups
-        task_patterns = [list(config_groups[name]) for name in group_whitelist]
-        task_patterns = set(sum(task_patterns, task_whitelist))
-
-        # treat the task names as glob patterns to select tasks more easily
-        requested_tasks = set()
-        for pattern in task_patterns:
-            matches = fnmatch.filter(valid_tasks, pattern)
-            if len(matches):
-                requested_tasks.update(matches)
-            else:
-                raise CrossbowError(
-                    "Unable to match any tasks for `{}`".format(pattern)
-                )
-
-        # validate that the passed and matched tasks are defined in the config
-        invalid_tasks = requested_tasks - valid_tasks
-        if invalid_tasks:
-            msg = 'Invalid task(s) {!r}. Must be one of {!r}'.format(
-                invalid_tasks, valid_tasks
-            )
-            raise CrossbowError(msg)
-
-        return {
-            task_name: config_tasks[task_name] for task_name in requested_tasks
-        }
-
-    def validate(self):
-        # validate that the task groups are properly referening the tasks
-        for group_name, group in self['groups'].items():
-            for pattern in group:
-                tasks = self.select(tasks=[pattern])
-                if not tasks:
-                    raise CrossbowError(
-                        "The pattern `{}` defined for task group `{}` is not "
-                        "matching any of the tasks defined in the "
-                        "configuration file.".format(pattern, group_name)
-                    )
-
-        # validate that the tasks are constructible
-        for task_name, task in self['tasks'].items():
-            try:
-                Task(**task)
-            except Exception as e:
-                raise CrossbowError(
-                    'Unable to construct a task object from the '
-                    'definition  of task `{}`. The original error message '
-                    'is: `{}`'.format(task_name, str(e))
-                )
-
-        # validate that the defined tasks are renderable, in order to to that
-        # define the required object with dummy data
-        target = Target(
-            head='e279a7e06e61c14868ca7d71dea795420aea6539',
-            branch='master',
-            remote='https://github.com/apache/arrow',
-            version='1.0.0dev123',
-            email='dummy@example.ltd'
-        )
-
-        for task_name, task in self['tasks'].items():
-            task = Task(**task)
-            files = task.render_files(
-                self.template_searchpath,
-                params=dict(
-                    arrow=target,
-                    queue_remote_url='https://github.com/org/crossbow'
-                )
-            )
-            if not files:
-                raise CrossbowError('No files have been rendered for task `{}`'
-                                    .format(task_name))
-
-
-# configure yaml serializer
-yaml = YAML()
-yaml.register_class(Job)
-yaml.register_class(Task)
-yaml.register_class(Target)
diff --git a/dev/archery/archery/crossbow/reports.py b/dev/archery/archery/crossbow/reports.py
deleted file mode 100644
index bc82db7..0000000
--- a/dev/archery/archery/crossbow/reports.py
+++ /dev/null
@@ -1,302 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import click
-import collections
-import operator
-import functools
-from io import StringIO
-import textwrap
-
-
-# TODO(kszucs): use archery.report.JinjaReport instead
-class Report:
-
-    def __init__(self, job):
-        self.job = job
-
-    def show(self):
-        raise NotImplementedError()
-
-
-class ConsoleReport(Report):
-    """Report the status of a Job to the console using click"""
-
-    # output table's header template
-    HEADER = '[{state:>7}] {branch:<52} {content:>16}'
-    DETAILS = ' └ {url}'
-
-    # output table's row template for assets
-    ARTIFACT_NAME = '{artifact:>69} '
-    ARTIFACT_STATE = '[{state:>7}]'
-
-    # state color mapping to highlight console output
-    COLORS = {
-        # from CombinedStatus
-        'error': 'red',
-        'failure': 'red',
-        'pending': 'yellow',
-        'success': 'green',
-        # custom state messages
-        'ok': 'green',
-        'missing': 'red'
-    }
-
-    def lead(self, state, branch, n_uploaded, n_expected):
-        line = self.HEADER.format(
-            state=state.upper(),
-            branch=branch,
-            content='uploaded {} / {}'.format(n_uploaded, n_expected)
-        )
-        return click.style(line, fg=self.COLORS[state.lower()])
-
-    def header(self):
-        header = self.HEADER.format(
-            state='state',
-            branch='Task / Branch',
-            content='Artifacts'
-        )
-        delimiter = '-' * len(header)
-        return '{}\n{}'.format(header, delimiter)
-
-    def artifact(self, state, pattern, asset):
-        if asset is None:
-            artifact = pattern
-            state = 'pending' if state == 'pending' else 'missing'
-        else:
-            artifact = asset.name
-            state = 'ok'
-
-        name_ = self.ARTIFACT_NAME.format(artifact=artifact)
-        state_ = click.style(
-            self.ARTIFACT_STATE.format(state=state.upper()),
-            self.COLORS[state]
-        )
-        return name_ + state_
-
-    def show(self, outstream, asset_callback=None):
-        echo = functools.partial(click.echo, file=outstream)
-
-        # write table's header
-        echo(self.header())
-
-        # write table's body
-        for task_name, task in sorted(self.job.tasks.items()):
-            # if not task_name.startswith("test-debian-10-python-3"):
-            #     continue
-            # write summary of the uploaded vs total assets
-            status = task.status()
-            assets = task.assets()
-
-            # mapping of artifact pattern to asset or None of not uploaded
-            n_expected = len(task.artifacts)
-            n_uploaded = len(assets.uploaded_assets())
-            echo(self.lead(status.combined_state, task_name, n_uploaded,
-                           n_expected))
-
-            # show link to the actual build, some of the CI providers implement
-            # the statuses API others implement the checks API, so display both
-            for link in status.build_links:
-                echo(self.DETAILS.format(url=link))
-
-            # write per asset status
-            for artifact_pattern, asset in assets.items():
-                if asset_callback is not None:
-                    asset_callback(task_name, task, asset)
-                echo(self.artifact(status.combined_state, artifact_pattern,
-                                   asset))
-
-
-class EmailReport(Report):
-
-    HEADER = textwrap.dedent("""
-        Arrow Build Report for Job {job_name}
-
-        All tasks: {all_tasks_url}
-    """)
-
-    TASK = textwrap.dedent("""
-          - {name}:
-            URL: {url}
-    """).strip()
-
-    EMAIL = textwrap.dedent("""
-        From: {sender_name} <{sender_email}>
-        To: {recipient_email}
-        Subject: {subject}
-
-        {body}
-    """).strip()
-
-    STATUS_HEADERS = {
-        # from CombinedStatus
-        'error': 'Errored Tasks:',
-        'failure': 'Failed Tasks:',
-        'pending': 'Pending Tasks:',
-        'success': 'Succeeded Tasks:',
-    }
-
-    def __init__(self, job, sender_name, sender_email, recipient_email):
-        self.sender_name = sender_name
-        self.sender_email = sender_email
-        self.recipient_email = recipient_email
-        super().__init__(job)
-
-    def url(self, query):
-        repo_url = self.job.queue.remote_url.strip('.git')
-        return '{}/branches/all?query={}'.format(repo_url, query)
-
-    def listing(self, tasks):
-        return '\n'.join(
-            sorted(
-                self.TASK.format(name=task_name, url=self.url(task.branch))
-                for task_name, task in tasks.items()
-            )
-        )
-
-    def header(self):
-        url = self.url(self.job.branch)
-        return self.HEADER.format(job_name=self.job.branch, all_tasks_url=url)
-
-    def subject(self):
-        return (
-            "[NIGHTLY] Arrow Build Report for Job {}".format(self.job.branch)
-        )
-
-    def body(self):
-        buffer = StringIO()
-        buffer.write(self.header())
-
-        tasks_by_state = collections.defaultdict(dict)
-        for task_name, task in self.job.tasks.items():
-            state = task.status().combined_state
-            tasks_by_state[state][task_name] = task
-
-        for state in ('failure', 'error', 'pending', 'success'):
-            if state in tasks_by_state:
-                tasks = tasks_by_state[state]
-                buffer.write('\n')
-                buffer.write(self.STATUS_HEADERS[state])
-                buffer.write('\n')
-                buffer.write(self.listing(tasks))
-                buffer.write('\n')
-
-        return buffer.getvalue()
-
-    def email(self):
-        return self.EMAIL.format(
-            sender_name=self.sender_name,
-            sender_email=self.sender_email,
-            recipient_email=self.recipient_email,
-            subject=self.subject(),
-            body=self.body()
-        )
-
-    def show(self, outstream):
-        outstream.write(self.email())
-
-    def send(self, smtp_user, smtp_password, smtp_server, smtp_port):
-        import smtplib
-
-        email = self.email()
-
-        server = smtplib.SMTP_SSL(smtp_server, smtp_port)
-        server.ehlo()
-        server.login(smtp_user, smtp_password)
-        server.sendmail(smtp_user, self.recipient_email, email)
-        server.close()
-
-
-class CommentReport(Report):
-
-    _markdown_badge = '[![{title}]({badge})]({url})'
-
-    badges = {
-        'github': _markdown_badge.format(
-            title='Github Actions',
-            url='https://github.com/{repo}/actions?query=branch:{branch}',
-            badge=(
-                'https://github.com/{repo}/workflows/Crossbow/'
-                'badge.svg?branch={branch}'
-            ),
-        ),
-        'azure': _markdown_badge.format(
-            title='Azure',
-            url=(
-                'https://dev.azure.com/{repo}/_build/latest'
-                '?definitionId=1&branchName={branch}'
-            ),
-            badge=(
-                'https://dev.azure.com/{repo}/_apis/build/status/'
-                '{repo_dotted}?branchName={branch}'
-            )
-        ),
-        'travis': _markdown_badge.format(
-            title='TravisCI',
-            url='https://travis-ci.com/{repo}/branches',
-            badge='https://img.shields.io/travis/{repo}/{branch}.svg'
-        ),
-        'circle': _markdown_badge.format(
-            title='CircleCI',
-            url='https://circleci.com/gh/{repo}/tree/{branch}',
-            badge=(
-                'https://img.shields.io/circleci/build/github'
-                '/{repo}/{branch}.svg'
-            )
-        ),
-        'appveyor': _markdown_badge.format(
-            title='Appveyor',
-            url='https://ci.appveyor.com/project/{repo}/history',
-            badge='https://img.shields.io/appveyor/ci/{repo}/{branch}.svg'
-        ),
-        'drone': _markdown_badge.format(
-            title='Drone',
-            url='https://cloud.drone.io/{repo}',
-            badge='https://img.shields.io/drone/build/{repo}/{branch}.svg'
-        ),
-    }
-
-    def __init__(self, job, crossbow_repo):
-        self.crossbow_repo = crossbow_repo
-        super().__init__(job)
-
-    def show(self):
-        url = 'https://github.com/{repo}/branches/all?query={branch}'
-        sha = self.job.target.head
-
-        msg = 'Revision: {}\n\n'.format(sha)
-        msg += 'Submitted crossbow builds: [{repo} @ {branch}]'
-        msg += '({})\n'.format(url)
-        msg += '\n|Task|Status|\n|----|------|'
-
-        tasks = sorted(self.job.tasks.items(), key=operator.itemgetter(0))
-        for key, task in tasks:
-            branch = task.branch
-
-            try:
-                template = self.badges[task.ci]
-                badge = template.format(
-                    repo=self.crossbow_repo,
-                    repo_dotted=self.crossbow_repo.replace('/', '.'),
-                    branch=branch
-                )
-            except KeyError:
-                badge = 'unsupported CI service `{}`'.format(task.ci)
-
-            msg += '\n|{}|{}|'.format(key, badge)
-
-        return msg.format(repo=self.crossbow_repo, branch=self.job.branch)
diff --git a/dev/archery/archery/crossbow/tests/fixtures/crossbow-job.yaml b/dev/archery/archery/crossbow/tests/fixtures/crossbow-job.yaml
deleted file mode 100644
index c37c7b5..0000000
--- a/dev/archery/archery/crossbow/tests/fixtures/crossbow-job.yaml
+++ /dev/null
@@ -1,51 +0,0 @@
-!Job
-target: !Target
-  head: f766a1d615dd1b7ee706d05102e579195951a61c
-  email: unkown
-  branch: refs/pull/4435/merge
-  remote: https://github.com/apache/arrow
-  version: 0.13.0.dev306
-  no_rc_version: 0.13.0.dev306
-tasks:
-  docker-cpp-cmake32: !Task
-    ci: circle
-    platform: linux
-    template: docker-tests/circle.linux.yml
-    artifacts: []
-    params:
-      commands:
-      - docker-compose build cpp-cmake32
-      - docker-compose run cpp-cmake32
-    branch: ursabot-1-circle-docker-cpp-cmake32
-    commit: a56b077c8d1b891a7935048e5672bf6fc07599ec
-  wheel-osx-cp37m: !Task
-    ci: travis
-    platform: osx
-    template: python-wheels/travis.osx.yml
-    artifacts:
-    - pyarrow-0.13.0.dev306-cp37-cp37m-macosx_10_6_intel.whl
-    params:
-      python_version: 3.7
-    branch: ursabot-1-travis-wheel-osx-cp37m
-    commit: a56b077c8d1b891a7935048e5672bf6fc07599ec
-  wheel-osx-cp36m: !Task
-    ci: travis
-    platform: osx
-    template: python-wheels/travis.osx.yml
-    artifacts:
-    - pyarrow-0.13.0.dev306-cp36-cp36m-macosx_10_6_intel.whl
-    params:
-      python_version: 3.6
-    branch: ursabot-1-travis-wheel-osx-cp36m
-    commit: a56b077c8d1b891a7935048e5672bf6fc07599ec
-  wheel-win-cp36m: !Task
-    ci: appveyor
-    platform: win
-    template: python-wheels/appveyor.yml
-    artifacts:
-    - pyarrow-0.13.0.dev306-cp36-cp36m-win_amd64.whl
-    params:
-      python_version: 3.6
-    branch: ursabot-1-appveyor-wheel-win-cp36m
-    commit: a56b077c8d1b891a7935048e5672bf6fc07599ec
-branch: ursabot-1
diff --git a/dev/archery/archery/crossbow/tests/fixtures/crossbow-success-message.md b/dev/archery/archery/crossbow/tests/fixtures/crossbow-success-message.md
deleted file mode 100644
index f914287..0000000
--- a/dev/archery/archery/crossbow/tests/fixtures/crossbow-success-message.md
+++ /dev/null
@@ -1,10 +0,0 @@
-Revision: {revision}
-
-Submitted crossbow builds: [{repo} @ {branch}](https://github.com/{repo}/branches/all?query={branch})
-
-| Task               | Status                                                                                                                                                                            |
-| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| docker-cpp-cmake32 | [![CircleCI](https://img.shields.io/circleci/build/github/{repo}/{branch}-circle-docker-cpp-cmake32.svg)](https://circleci.com/gh/{repo}/tree/{branch}-circle-docker-cpp-cmake32) |
-| wheel-osx-cp36m    | [![TravisCI](https://img.shields.io/travis/{repo}/{branch}-travis-wheel-osx-cp36m.svg)](https://travis-ci.com/{repo}/branches)                                                    |
-| wheel-osx-cp37m    | [![TravisCI](https://img.shields.io/travis/{repo}/{branch}-travis-wheel-osx-cp37m.svg)](https://travis-ci.com/{repo}/branches)                                                    |
-| wheel-win-cp36m    | [![Appveyor](https://img.shields.io/appveyor/ci/{repo}/{branch}-appveyor-wheel-win-cp36m.svg)](https://ci.appveyor.com/project/{repo}/history)                                    |
diff --git a/dev/archery/archery/crossbow/tests/test_core.py b/dev/archery/archery/crossbow/tests/test_core.py
deleted file mode 100644
index 5184742..0000000
--- a/dev/archery/archery/crossbow/tests/test_core.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from archery.utils.source import ArrowSources
-from archery.crossbow import Config
-
-
-def test_config():
-    src = ArrowSources.find()
-    conf = Config.load_yaml(src.dev / "tasks" / "tasks.yml")
-    conf.validate()
diff --git a/dev/archery/archery/crossbow/tests/test_crossbow_cli.py b/dev/archery/archery/crossbow/tests/test_crossbow_cli.py
deleted file mode 100644
index ee9ba1e..0000000
--- a/dev/archery/archery/crossbow/tests/test_crossbow_cli.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from click.testing import CliRunner
-import pytest
-
-from archery.crossbow.cli import crossbow
-from archery.utils.git import git
-
-
-@pytest.mark.integration
-def test_crossbow_submit(tmp_path):
-    runner = CliRunner()
-
-    def invoke(*args):
-        return runner.invoke(crossbow, ['--queue-path', str(tmp_path), *args])
-
-    # initialize an empty crossbow repository
-    git.run_cmd("init", str(tmp_path))
-    git.run_cmd("-C", str(tmp_path), "remote", "add", "origin",
-                "https://github.com/dummy/repo")
-    git.run_cmd("-C", str(tmp_path), "commit", "-m", "initial",
-                "--allow-empty")
-
-    result = invoke('check-config')
-    assert result.exit_code == 0
-
-    result = invoke('submit', '--no-fetch', '--no-push', '-g', 'wheel')
-    assert result.exit_code == 0
diff --git a/dev/archery/archery/crossbow/tests/test_reports.py b/dev/archery/archery/crossbow/tests/test_reports.py
deleted file mode 100644
index 0df292b..0000000
--- a/dev/archery/archery/crossbow/tests/test_reports.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import textwrap
-
-from archery.crossbow.core import yaml
-from archery.crossbow.reports import CommentReport
-
-
-def test_crossbow_comment_formatter(load_fixture):
-    msg = load_fixture('crossbow-success-message.md')
-    job = load_fixture('crossbow-job.yaml', decoder=yaml.load)
-
-    report = CommentReport(job, crossbow_repo='ursa-labs/crossbow')
-    expected = msg.format(
-        repo='ursa-labs/crossbow',
-        branch='ursabot-1',
-        revision='f766a1d615dd1b7ee706d05102e579195951a61c',
-        status='has been succeeded.'
-    )
-    assert report.show() == textwrap.dedent(expected).strip()
diff --git a/dev/archery/archery/docker.py b/dev/archery/archery/docker.py
deleted file mode 100644
index 17d4c71..0000000
--- a/dev/archery/archery/docker.py
+++ /dev/null
@@ -1,402 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import os
-import re
-import subprocess
-from io import StringIO
-
-from dotenv import dotenv_values
-from ruamel.yaml import YAML
-
-from .utils.command import Command, default_bin
-from .compat import _ensure_path
-
-
-def flatten(node, parents=None):
-    parents = list(parents or [])
-    if isinstance(node, str):
-        yield (node, parents)
-    elif isinstance(node, list):
-        for value in node:
-            yield from flatten(value, parents=parents)
-    elif isinstance(node, dict):
-        for key, value in node.items():
-            yield (key, parents)
-            yield from flatten(value, parents=parents + [key])
-    else:
-        raise TypeError(node)
-
-
-def _sanitize_command(cmd):
-    if isinstance(cmd, list):
-        cmd = " ".join(cmd)
-    return re.sub(r"\s+", " ", cmd)
-
-
-class UndefinedImage(Exception):
-    pass
-
-
-class ComposeConfig:
-
-    def __init__(self, config_path, dotenv_path, compose_bin, params=None):
-        config_path = _ensure_path(config_path)
-        if dotenv_path:
-            dotenv_path = _ensure_path(dotenv_path)
-        else:
-            dotenv_path = config_path.parent / '.env'
-        self._read_env(dotenv_path, params)
-        self._read_config(config_path, compose_bin)
-
-    def _read_env(self, dotenv_path, params):
-        """
-        Read .env and merge it with explicitly passed parameters.
-        """
-        self.dotenv = dotenv_values(str(dotenv_path))
-        if params is None:
-            self.params = {}
-        else:
-            self.params = {k: v for k, v in params.items() if k in self.dotenv}
-
-        # forward the process' environment variables
-        self.env = os.environ.copy()
-        # set the defaults from the dotenv files
-        self.env.update(self.dotenv)
-        # override the defaults passed as parameters
-        self.env.update(self.params)
-
-        # translate docker's architecture notation to a more widely used one
-        arch = self.env.get('ARCH', 'amd64')
-        arch_aliases = {
-            'amd64': 'x86_64',
-            'arm64v8': 'aarch64',
-            's390x': 's390x'
-        }
-        arch_short_aliases = {
-            'amd64': 'x64',
-            'arm64v8': 'arm64',
-            's390x': 's390x'
-        }
-        self.env['ARCH_ALIAS'] = arch_aliases.get(arch, arch)
-        self.env['ARCH_SHORT_ALIAS'] = arch_short_aliases.get(arch, arch)
-
-    def _read_config(self, config_path, compose_bin):
-        """
-        Validate and read the docker-compose.yml
-        """
-        yaml = YAML()
-        with config_path.open() as fp:
-            config = yaml.load(fp)
-
-        services = config['services'].keys()
-        self.hierarchy = dict(flatten(config.get('x-hierarchy', {})))
-        self.with_gpus = config.get('x-with-gpus', [])
-        nodes = self.hierarchy.keys()
-        errors = []
-
-        for name in self.with_gpus:
-            if name not in services:
-                errors.append(
-                    'Service `{}` defined in `x-with-gpus` bot not in '
-                    '`services`'.format(name)
-                )
-        for name in nodes - services:
-            errors.append(
-                'Service `{}` is defined in `x-hierarchy` bot not in '
-                '`services`'.format(name)
-            )
-        for name in services - nodes:
-            errors.append(
-                'Service `{}` is defined in `services` but not in '
-                '`x-hierarchy`'.format(name)
-            )
-
-        # trigger docker-compose's own validation
-        compose = Command('docker-compose')
-        args = ['--file', str(config_path), 'config']
-        result = compose.run(*args, env=self.env, check=False,
-                             stderr=subprocess.PIPE, stdout=subprocess.PIPE)
-
-        if result.returncode != 0:
-            # strip the intro line of docker-compose errors
-            errors += result.stderr.decode().splitlines()
-
-        if errors:
-            msg = '\n'.join([' - {}'.format(msg) for msg in errors])
-            raise ValueError(
-                'Found errors with docker-compose:\n{}'.format(msg)
-            )
-
-        rendered_config = StringIO(result.stdout.decode())
-        self.path = config_path
-        self.config = yaml.load(rendered_config)
-
-    def get(self, service_name):
-        try:
-            service = self.config['services'][service_name]
-        except KeyError:
-            raise UndefinedImage(service_name)
-        service['name'] = service_name
-        service['need_gpu'] = service_name in self.with_gpus
-        service['ancestors'] = self.hierarchy[service_name]
-        return service
-
-    def __getitem__(self, service_name):
-        return self.get(service_name)
-
-
-class Docker(Command):
-
-    def __init__(self, docker_bin=None):
-        self.bin = default_bin(docker_bin, "docker")
-
-
-class DockerCompose(Command):
-
-    def __init__(self, config_path, dotenv_path=None, compose_bin=None,
-                 params=None):
-        compose_bin = default_bin(compose_bin, 'docker-compose')
-        self.config = ComposeConfig(config_path, dotenv_path, compose_bin,
-                                    params)
-        self.bin = compose_bin
-        self.pull_memory = set()
-
-    def clear_pull_memory(self):
-        self.pull_memory = set()
-
-    def _execute_compose(self, *args, **kwargs):
-        # execute as a docker compose command
-        try:
-            result = super().run('--file', str(self.config.path), *args,
-                                 env=self.config.env, **kwargs)
-            result.check_returncode()
-        except subprocess.CalledProcessError as e:
-            def formatdict(d, template):
-                return '\n'.join(
-                    template.format(k, v) for k, v in sorted(d.items())
-                )
-            msg = (
-                "`{cmd}` exited with a non-zero exit code {code}, see the "
-                "process log above.\n\nThe docker-compose command was "
-                "invoked with the following parameters:\n\nDefaults defined "
-                "in .env:\n{dotenv}\n\nArchery was called with:\n{params}"
-            )
-            raise RuntimeError(
-                msg.format(
-                    cmd=' '.join(e.cmd),
-                    code=e.returncode,
-                    dotenv=formatdict(self.config.dotenv, template='  {}: {}'),
-                    params=formatdict(
-                        self.config.params, template='  export {}={}'
-                    )
-                )
-            )
-
-    def _execute_docker(self, *args, **kwargs):
-        # execute as a plain docker cli command
-        try:
-            result = Docker().run(*args, **kwargs)
-            result.check_returncode()
-        except subprocess.CalledProcessError as e:
-            raise RuntimeError(
-                "{} exited with non-zero exit code {}".format(
-                    ' '.join(e.cmd), e.returncode
-                )
-            )
-
-    def pull(self, service_name, pull_leaf=True, using_docker=False):
-        def _pull(service):
-            args = ['pull']
-            if service['image'] in self.pull_memory:
-                return
-
-            if using_docker:
-                try:
-                    self._execute_docker(*args, service['image'])
-                except Exception as e:
-                    # better --ignore-pull-failures handling
-                    print(e)
-            else:
-                args.append('--ignore-pull-failures')
-                self._execute_compose(*args, service['name'])
-
-            self.pull_memory.add(service['image'])
-
-        service = self.config.get(service_name)
-        for ancestor in service['ancestors']:
-            _pull(self.config.get(ancestor))
-        if pull_leaf:
-            _pull(service)
-
-    def build(self, service_name, use_cache=True, use_leaf_cache=True,
-              using_docker=False, using_buildx=False):
-        def _build(service, use_cache):
-            if 'build' not in service:
-                # nothing to do
-                return
-
-            args = []
-            cache_from = list(service.get('build', {}).get('cache_from', []))
-            if use_cache:
-                for image in cache_from:
-                    if image not in self.pull_memory:
-                        try:
-                            self._execute_docker('pull', image)
-                        except Exception as e:
-                            print(e)
-                        finally:
-                            self.pull_memory.add(image)
-            else:
-                args.append('--no-cache')
-
-            # turn on inline build cache, this is a docker buildx feature
-            # used to bundle the image build cache to the pushed image manifest
-            # so the build cache can be reused across hosts, documented at
-            # https://github.com/docker/buildx#--cache-tonametypetypekeyvalue
-            if self.config.env.get('BUILDKIT_INLINE_CACHE') == '1':
-                args.extend(['--build-arg', 'BUILDKIT_INLINE_CACHE=1'])
-
-            if using_buildx:
-                for k, v in service['build'].get('args', {}).items():
-                    args.extend(['--build-arg', '{}={}'.format(k, v)])
-
-                if use_cache:
-                    cache_ref = '{}-cache'.format(service['image'])
-                    cache_from = 'type=registry,ref={}'.format(cache_ref)
-                    cache_to = (
-                        'type=registry,ref={},mode=max'.format(cache_ref)
-                    )
-                    args.extend([
-                        '--cache-from', cache_from,
-                        '--cache-to', cache_to,
-                    ])
-
-                args.extend([
-                    '--output', 'type=docker',
-                    '-f', service['build']['dockerfile'],
-                    '-t', service['image'],
-                    service['build'].get('context', '.')
-                ])
-                self._execute_docker("buildx", "build", *args)
-            elif using_docker:
-                # better for caching
-                for k, v in service['build'].get('args', {}).items():
-                    args.extend(['--build-arg', '{}={}'.format(k, v)])
-                for img in cache_from:
-                    args.append('--cache-from="{}"'.format(img))
-                args.extend([
-                    '-f', service['build']['dockerfile'],
-                    '-t', service['image'],
-                    service['build'].get('context', '.')
-                ])
-                self._execute_docker("build", *args)
-            else:
-                self._execute_compose("build", *args, service['name'])
-
-        service = self.config.get(service_name)
-        # build ancestor services
-        for ancestor in service['ancestors']:
-            _build(self.config.get(ancestor), use_cache=use_cache)
-        # build the leaf/target service
-        _build(service, use_cache=use_cache and use_leaf_cache)
-
-    def run(self, service_name, command=None, *, env=None, volumes=None,
-            user=None, using_docker=False):
-        service = self.config.get(service_name)
-
-        args = []
-        if user is not None:
-            args.extend(['-u', user])
-
-        if env is not None:
-            for k, v in env.items():
-                args.extend(['-e', '{}={}'.format(k, v)])
-
-        if volumes is not None:
-            for volume in volumes:
-                args.extend(['--volume', volume])
-
-        if using_docker or service['need_gpu']:
-            # use gpus, requires docker>=19.03
-            if service['need_gpu']:
-                args.extend(['--gpus', 'all'])
-
-            if service.get('shm_size'):
-                args.extend(['--shm-size', service['shm_size']])
-
-            # append env variables from the compose conf
-            for k, v in service.get('environment', {}).items():
-                args.extend(['-e', '{}={}'.format(k, v)])
-
-            # append volumes from the compose conf
-            for v in service.get('volumes', []):
-                if not isinstance(v, str):
-                    # if not the compact string volume definition
-                    v = "{}:{}".format(v['source'], v['target'])
-                args.extend(['-v', v])
-
-            # infer whether an interactive shell is desired or not
-            if command in ['cmd.exe', 'bash', 'sh', 'powershell']:
-                args.append('-it')
-
-            # get the actual docker image name instead of the compose service
-            # name which we refer as image in general
-            args.append(service['image'])
-
-            # add command from compose if it wasn't overridden
-            if command is not None:
-                args.append(command)
-            else:
-                # replace whitespaces from the preformatted compose command
-                cmd = _sanitize_command(service.get('command', ''))
-                if cmd:
-                    args.append(cmd)
-
-            # execute as a plain docker cli command
-            self._execute_docker('run', '--rm', *args)
-        else:
-            # execute as a docker-compose command
-            args.append(service_name)
-            if command is not None:
-                args.append(command)
-            self._execute_compose('run', '--rm', *args)
-
-    def push(self, service_name, user=None, password=None, using_docker=False):
-        def _push(service):
-            if using_docker:
-                return self._execute_docker('push', service['image'])
-            else:
-                return self._execute_compose('push', service['name'])
-
-        if user is not None:
-            try:
-                # TODO(kszucs): have an option for a prompt
-                self._execute_docker('login', '-u', user, '-p', password)
-            except subprocess.CalledProcessError:
-                # hide credentials
-                msg = ('Failed to push `{}`, check the passed credentials'
-                       .format(service_name))
-                raise RuntimeError(msg) from None
-
-        service = self.config.get(service_name)
-        for ancestor in service['ancestors']:
-            _push(self.config.get(ancestor))
-        _push(service)
-
-    def images(self):
-        return sorted(self.config.hierarchy.keys())
diff --git a/dev/archery/archery/integration/__init__.py b/dev/archery/archery/integration/__init__.py
deleted file mode 100644
index 13a8339..0000000
--- a/dev/archery/archery/integration/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
diff --git a/dev/archery/archery/integration/datagen.py b/dev/archery/archery/integration/datagen.py
deleted file mode 100644
index 35ab289..0000000
--- a/dev/archery/archery/integration/datagen.py
+++ /dev/null
@@ -1,1604 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from collections import namedtuple, OrderedDict
-import binascii
-import json
-import os
-import random
-import tempfile
-
-import numpy as np
-
-from .util import frombytes, tobytes, random_bytes, random_utf8
-
-
-def metadata_key_values(pairs):
-    return [{'key': k, 'value': v} for k, v in pairs]
-
-
-class Field(object):
-
-    def __init__(self, name, *, nullable=True, metadata=None):
-        self.name = name
-        self.nullable = nullable
-        self.metadata = metadata or []
-
-    def get_json(self):
-        entries = [
-            ('name', self.name),
-            ('type', self._get_type()),
-            ('nullable', self.nullable),
-            ('children', self._get_children()),
-        ]
-
-        dct = self._get_dictionary()
-        if dct:
-            entries.append(('dictionary', dct))
-
-        if self.metadata is not None and len(self.metadata) > 0:
-            entries.append(('metadata', metadata_key_values(self.metadata)))
-
-        return OrderedDict(entries)
-
-    def _get_dictionary(self):
-        return None
-
-    def _make_is_valid(self, size, null_probability=0.4):
-        if self.nullable:
-            return (np.random.random_sample(size) > null_probability
-                    ).astype(np.int8)
-        else:
-            return np.ones(size, dtype=np.int8)
-
-
-class Column(object):
-
-    def __init__(self, name, count):
-        self.name = name
-        self.count = count
-
-    def __len__(self):
-        return self.count
-
-    def _get_children(self):
-        return []
-
-    def _get_buffers(self):
-        return []
-
-    def get_json(self):
-        entries = [
-            ('name', self.name),
-            ('count', self.count)
-        ]
-
-        buffers = self._get_buffers()
-        entries.extend(buffers)
-
-        children = self._get_children()
-        if len(children) > 0:
-            entries.append(('children', children))
-
-        return OrderedDict(entries)
-
-
-class PrimitiveField(Field):
-
-    def _get_children(self):
-        return []
-
-
-class PrimitiveColumn(Column):
-
-    def __init__(self, name, count, is_valid, values):
-        super().__init__(name, count)
-        self.is_valid = is_valid
-        self.values = values
-
-    def _encode_value(self, x):
-        return x
-
-    def _get_buffers(self):
-        return [
-            ('VALIDITY', [int(v) for v in self.is_valid]),
-            ('DATA', list([self._encode_value(x) for x in self.values]))
-        ]
-
-
-class NullColumn(Column):
-    # This subclass is for readability only
-    pass
-
-
-class NullField(PrimitiveField):
-
-    def __init__(self, name, metadata=None):
-        super().__init__(name, nullable=True,
-                         metadata=metadata)
-
-    def _get_type(self):
-        return OrderedDict([('name', 'null')])
-
-    def generate_column(self, size, name=None):
-        return NullColumn(name or self.name, size)
-
-
-TEST_INT_MAX = 2 ** 31 - 1
-TEST_INT_MIN = ~TEST_INT_MAX
-
-
-class IntegerField(PrimitiveField):
-
-    def __init__(self, name, is_signed, bit_width, *, nullable=True,
-                 metadata=None,
-                 min_value=TEST_INT_MIN,
-                 max_value=TEST_INT_MAX):
-        super().__init__(name, nullable=nullable,
-                         metadata=metadata)
-        self.is_signed = is_signed
-        self.bit_width = bit_width
-        self.min_value = min_value
-        self.max_value = max_value
-
-    def _get_generated_data_bounds(self):
-        if self.is_signed:
-            signed_iinfo = np.iinfo('int' + str(self.bit_width))
-            min_value, max_value = signed_iinfo.min, signed_iinfo.max
-        else:
-            unsigned_iinfo = np.iinfo('uint' + str(self.bit_width))
-            min_value, max_value = 0, unsigned_iinfo.max
-
-        lower_bound = max(min_value, self.min_value)
-        upper_bound = min(max_value, self.max_value)
-        return lower_bound, upper_bound
-
-    def _get_type(self):
-        return OrderedDict([
-            ('name', 'int'),
-            ('isSigned', self.is_signed),
-            ('bitWidth', self.bit_width)
-        ])
-
-    def generate_column(self, size, name=None):
-        lower_bound, upper_bound = self._get_generated_data_bounds()
-        return self.generate_range(size, lower_bound, upper_bound,
-                                   name=name, include_extremes=True)
-
-    def generate_range(self, size, lower, upper, name=None,
-                       include_extremes=False):
-        values = np.random.randint(lower, upper, size=size, dtype=np.int64)
-        if include_extremes and size >= 2:
-            values[:2] = [lower, upper]
-        values = list(map(int if self.bit_width < 64 else str, values))
-
-        is_valid = self._make_is_valid(size)
-
-        if name is None:
-            name = self.name
-        return PrimitiveColumn(name, size, is_valid, values)
-
-
-class DateField(IntegerField):
-
-    DAY = 0
-    MILLISECOND = 1
-
-    # 1/1/1 to 12/31/9999
-    _ranges = {
-        DAY: [-719162, 2932896],
-        MILLISECOND: [-62135596800000, 253402214400000]
-    }
-
-    def __init__(self, name, unit, *, nullable=True, metadata=None):
-        bit_width = 32 if unit == self.DAY else 64
-
-        min_value, max_value = self._ranges[unit]
-        super().__init__(
-            name, True, bit_width,
-            nullable=nullable, metadata=metadata,
-            min_value=min_value, max_value=max_value
-        )
-        self.unit = unit
-
-    def _get_type(self):
-        return OrderedDict([
-            ('name', 'date'),
-            ('unit', 'DAY' if self.unit == self.DAY else 'MILLISECOND')
-        ])
-
-
-TIMEUNIT_NAMES = {
-    's': 'SECOND',
-    'ms': 'MILLISECOND',
-    'us': 'MICROSECOND',
-    'ns': 'NANOSECOND'
-}
-
-
-class TimeField(IntegerField):
-
-    BIT_WIDTHS = {
-        's': 32,
-        'ms': 32,
-        'us': 64,
-        'ns': 64
-    }
-
-    _ranges = {
-        's': [0, 86400],
-        'ms': [0, 86400000],
-        'us': [0, 86400000000],
-        'ns': [0, 86400000000000]
-    }
-
-    def __init__(self, name, unit='s', *, nullable=True,
-                 metadata=None):
-        min_val, max_val = self._ranges[unit]
-        super().__init__(name, True, self.BIT_WIDTHS[unit],
-                         nullable=nullable, metadata=metadata,
-                         min_value=min_val, max_value=max_val)
-        self.unit = unit
-
-    def _get_type(self):
-        return OrderedDict([
-            ('name', 'time'),
-            ('unit', TIMEUNIT_NAMES[self.unit]),
-            ('bitWidth', self.bit_width)
-        ])
-
-
-class TimestampField(IntegerField):
-
-    # 1/1/1 to 12/31/9999
-    _ranges = {
-        's': [-62135596800, 253402214400],
-        'ms': [-62135596800000, 253402214400000],
-        'us': [-62135596800000000, 253402214400000000],
-
-        # Physical range for int64, ~584 years and change
-        'ns': [np.iinfo('int64').min, np.iinfo('int64').max]
-    }
-
-    def __init__(self, name, unit='s', tz=None, *, nullable=True,
-                 metadata=None):
-        min_val, max_val = self._ranges[unit]
-        super().__init__(name, True, 64,
-                         nullable=nullable,
-                         metadata=metadata,
-                         min_value=min_val,
-                         max_value=max_val)
-        self.unit = unit
-        self.tz = tz
-
-    def _get_type(self):
-        fields = [
-            ('name', 'timestamp'),
-            ('unit', TIMEUNIT_NAMES[self.unit])
-        ]
-
-        if self.tz is not None:
-            fields.append(('timezone', self.tz))
-
-        return OrderedDict(fields)
-
-
-class DurationIntervalField(IntegerField):
-
-    def __init__(self, name, unit='s', *, nullable=True,
-                 metadata=None):
-        min_val, max_val = np.iinfo('int64').min, np.iinfo('int64').max,
-        super().__init__(
-            name, True, 64,
-            nullable=nullable, metadata=metadata,
-            min_value=min_val, max_value=max_val)
-        self.unit = unit
-
-    def _get_type(self):
-        fields = [
-            ('name', 'duration'),
-            ('unit', TIMEUNIT_NAMES[self.unit])
-        ]
-
-        return OrderedDict(fields)
-
-
-class YearMonthIntervalField(IntegerField):
-    def __init__(self, name, *, nullable=True, metadata=None):
-        min_val, max_val = [-10000*12, 10000*12]  # +/- 10000 years.
-        super().__init__(
-            name, True, 32,
-            nullable=nullable, metadata=metadata,
-            min_value=min_val, max_value=max_val)
-
-    def _get_type(self):
-        fields = [
-            ('name', 'interval'),
-            ('unit', 'YEAR_MONTH'),
-        ]
-
-        return OrderedDict(fields)
-
-
-class DayTimeIntervalField(PrimitiveField):
-    def __init__(self, name, *, nullable=True, metadata=None):
-        super().__init__(name,
-                         nullable=True,
-                         metadata=metadata)
-
-    @property
-    def numpy_type(self):
-        return object
-
-    def _get_type(self):
-
-        return OrderedDict([
-            ('name', 'interval'),
-            ('unit', 'DAY_TIME'),
-        ])
-
-    def generate_column(self, size, name=None):
-        min_day_value, max_day_value = -10000*366, 10000*366
-        values = [{'days': random.randint(min_day_value, max_day_value),
-                   'milliseconds': random.randint(-86400000, +86400000)}
-                  for _ in range(size)]
-
-        is_valid = self._make_is_valid(size)
-        if name is None:
-            name = self.name
-        return PrimitiveColumn(name, size, is_valid, values)
-
-
-class FloatingPointField(PrimitiveField):
-
-    def __init__(self, name, bit_width, *, nullable=True,
-                 metadata=None):
-        super().__init__(name,
-                         nullable=nullable,
-                         metadata=metadata)
-
-        self.bit_width = bit_width
-        self.precision = {
-            16: 'HALF',
-            32: 'SINGLE',
-            64: 'DOUBLE'
-        }[self.bit_width]
-
-    @property
-    def numpy_type(self):
-        return 'float' + str(self.bit_width)
-
-    def _get_type(self):
-        return OrderedDict([
-            ('name', 'floatingpoint'),
-            ('precision', self.precision)
-        ])
-
-    def generate_column(self, size, name=None):
-        values = np.random.randn(size) * 1000
-        values = np.round(values, 3)
-
-        is_valid = self._make_is_valid(size)
-        if name is None:
-            name = self.name
-        return PrimitiveColumn(name, size, is_valid, values)
-
-
-DECIMAL_PRECISION_TO_VALUE = {
-    key: (1 << (8 * i - 1)) - 1 for i, key in enumerate(
-        [1, 3, 5, 7, 10, 12, 15, 17, 19, 22, 24, 27, 29, 32, 34, 36,
-         40, 42, 44, 50, 60, 70],
-        start=1,
-    )
-}
-
-
-def decimal_range_from_precision(precision):
-    assert 1 <= precision <= 76
-    try:
-        max_value = DECIMAL_PRECISION_TO_VALUE[precision]
-    except KeyError:
-        return decimal_range_from_precision(precision - 1)
-    else:
-        return ~max_value, max_value
-
-
-class DecimalField(PrimitiveField):
-    def __init__(self, name, precision, scale, bit_width, *,
-                 nullable=True, metadata=None):
-        super().__init__(name, nullable=True,
-                         metadata=metadata)
-        self.precision = precision
-        self.scale = scale
-        self.bit_width = bit_width
-
-    @property
-    def numpy_type(self):
-        return object
-
-    def _get_type(self):
-        return OrderedDict([
-            ('name', 'decimal'),
-            ('precision', self.precision),
-            ('scale', self.scale),
-            ('bitWidth', self.bit_width),
-        ])
-
-    def generate_column(self, size, name=None):
-        min_value, max_value = decimal_range_from_precision(self.precision)
-        values = [random.randint(min_value, max_value) for _ in range(size)]
-
-        is_valid = self._make_is_valid(size)
-        if name is None:
-            name = self.name
-        return DecimalColumn(name, size, is_valid, values, self.bit_width)
-
-
-class DecimalColumn(PrimitiveColumn):
-
-    def __init__(self, name, count, is_valid, values, bit_width):
-        super().__init__(name, count, is_valid, values)
-        self.bit_width = bit_width
-
-    def _encode_value(self, x):
-        return str(x)
-
-
-class BooleanField(PrimitiveField):
-    bit_width = 1
-
-    def _get_type(self):
-        return OrderedDict([('name', 'bool')])
-
-    @property
-    def numpy_type(self):
-        return 'bool'
-
-    def generate_column(self, size, name=None):
-        values = list(map(bool, np.random.randint(0, 2, size=size)))
-        is_valid = self._make_is_valid(size)
-        if name is None:
-            name = self.name
-        return PrimitiveColumn(name, size, is_valid, values)
-
-
-class FixedSizeBinaryField(PrimitiveField):
-
-    def __init__(self, name, byte_width, *, nullable=True,
-                 metadata=None):
-        super().__init__(name, nullable=nullable,
-                         metadata=metadata)
-        self.byte_width = byte_width
-
-    @property
-    def numpy_type(self):
-        return object
-
-    @property
-    def column_class(self):
-        return FixedSizeBinaryColumn
-
-    def _get_type(self):
-        return OrderedDict([('name', 'fixedsizebinary'),
-                            ('byteWidth', self.byte_width)])
-
-    def generate_column(self, size, name=None):
-        is_valid = self._make_is_valid(size)
-        values = []
-
-        for i in range(size):
-            values.append(random_bytes(self.byte_width))
-
-        if name is None:
-            name = self.name
-        return self.column_class(name, size, is_valid, values)
-
-
-class BinaryField(PrimitiveField):
-
-    @property
-    def numpy_type(self):
-        return object
-
-    @property
-    def column_class(self):
-        return BinaryColumn
-
-    def _get_type(self):
-        return OrderedDict([('name', 'binary')])
-
-    def _random_sizes(self, size):
-        return np.random.exponential(scale=4, size=size).astype(np.int32)
-
-    def generate_column(self, size, name=None):
-        is_valid = self._make_is_valid(size)
-        values = []
-
-        sizes = self._random_sizes(size)
-
-        for i, nbytes in enumerate(sizes):
-            if is_valid[i]:
-                values.append(random_bytes(nbytes))
-            else:
-                values.append(b"")
-
-        if name is None:
-            name = self.name
-        return self.column_class(name, size, is_valid, values)
-
-
-class StringField(BinaryField):
-
-    @property
-    def column_class(self):
-        return StringColumn
-
-    def _get_type(self):
-        return OrderedDict([('name', 'utf8')])
-
-    def generate_column(self, size, name=None):
-        K = 7
-        is_valid = self._make_is_valid(size)
-        values = []
-
-        for i in range(size):
-            if is_valid[i]:
-                values.append(tobytes(random_utf8(K)))
-            else:
-                values.append(b"")
-
-        if name is None:
-            name = self.name
-        return self.column_class(name, size, is_valid, values)
-
-
-class LargeBinaryField(BinaryField):
-
-    @property
-    def column_class(self):
-        return LargeBinaryColumn
-
-    def _get_type(self):
-        return OrderedDict([('name', 'largebinary')])
-
-
-class LargeStringField(StringField):
-
-    @property
-    def column_class(self):
-        return LargeStringColumn
-
-    def _get_type(self):
-        return OrderedDict([('name', 'largeutf8')])
-
-
-class Schema(object):
-
-    def __init__(self, fields, metadata=None):
-        self.fields = fields
-        self.metadata = metadata
-
-    def get_json(self):
-        entries = [
-            ('fields', [field.get_json() for field in self.fields])
-        ]
-
-        if self.metadata is not None and len(self.metadata) > 0:
-            entries.append(('metadata', metadata_key_values(self.metadata)))
-
-        return OrderedDict(entries)
-
-
-class _NarrowOffsetsMixin:
-
-    def _encode_offsets(self, offsets):
-        return list(map(int, offsets))
-
-
-class _LargeOffsetsMixin:
-
-    def _encode_offsets(self, offsets):
-        # 64-bit offsets have to be represented as strings to roundtrip
-        # through JSON.
-        return list(map(str, offsets))
-
-
-class _BaseBinaryColumn(PrimitiveColumn):
-
-    def _encode_value(self, x):
-        return frombytes(binascii.hexlify(x).upper())
-
-    def _get_buffers(self):
-        offset = 0
-        offsets = [0]
-
-        data = []
-        for i, v in enumerate(self.values):
-            if self.is_valid[i]:
-                offset += len(v)
-            else:
-                v = b""
-
-            offsets.append(offset)
-            data.append(self._encode_value(v))
-
-        return [
-            ('VALIDITY', [int(x) for x in self.is_valid]),
-            ('OFFSET', self._encode_offsets(offsets)),
-            ('DATA', data)
-        ]
-
-
-class _BaseStringColumn(_BaseBinaryColumn):
-
-    def _encode_value(self, x):
-        return frombytes(x)
-
-
-class BinaryColumn(_BaseBinaryColumn, _NarrowOffsetsMixin):
-    pass
-
-
-class StringColumn(_BaseStringColumn, _NarrowOffsetsMixin):
-    pass
-
-
-class LargeBinaryColumn(_BaseBinaryColumn, _LargeOffsetsMixin):
-    pass
-
-
-class LargeStringColumn(_BaseStringColumn, _LargeOffsetsMixin):
-    pass
-
-
-class FixedSizeBinaryColumn(PrimitiveColumn):
-
-    def _encode_value(self, x):
-        return frombytes(binascii.hexlify(x).upper())
-
-    def _get_buffers(self):
-        data = []
-        for i, v in enumerate(self.values):
-            data.append(self._encode_value(v))
-
-        return [
-            ('VALIDITY', [int(x) for x in self.is_valid]),
-            ('DATA', data)
-        ]
-
-
-class ListField(Field):
-
-    def __init__(self, name, value_field, *, nullable=True,
-                 metadata=None):
-        super().__init__(name, nullable=nullable,
-                         metadata=metadata)
-        self.value_field = value_field
-
-    @property
-    def column_class(self):
-        return ListColumn
-
-    def _get_type(self):
-        return OrderedDict([
-            ('name', 'list')
-        ])
-
-    def _get_children(self):
-        return [self.value_field.get_json()]
-
-    def generate_column(self, size, name=None):
-        MAX_LIST_SIZE = 4
-
-        is_valid = self._make_is_valid(size)
-        list_sizes = np.random.randint(0, MAX_LIST_SIZE + 1, size=size)
-        offsets = [0]
-
-        offset = 0
-        for i in range(size):
-            if is_valid[i]:
-                offset += int(list_sizes[i])
-            offsets.append(offset)
-
-        # The offset now is the total number of elements in the child array
-        values = self.value_field.generate_column(offset)
-
-        if name is None:
-            name = self.name
-        return self.column_class(name, size, is_valid, offsets, values)
-
-
-class LargeListField(ListField):
-
-    @property
-    def column_class(self):
-        return LargeListColumn
-
-    def _get_type(self):
-        return OrderedDict([
-            ('name', 'largelist')
-        ])
-
-
-class _BaseListColumn(Column):
-
-    def __init__(self, name, count, is_valid, offsets, values):
-        super().__init__(name, count)
-        self.is_valid = is_valid
-        self.offsets = offsets
-        self.values = values
-
-    def _get_buffers(self):
-        return [
-            ('VALIDITY', [int(v) for v in self.is_valid]),
-            ('OFFSET', self._encode_offsets(self.offsets))
-        ]
-
-    def _get_children(self):
-        return [self.values.get_json()]
-
-
-class ListColumn(_BaseListColumn, _NarrowOffsetsMixin):
-    pass
-
-
-class LargeListColumn(_BaseListColumn, _LargeOffsetsMixin):
-    pass
-
-
-class MapField(Field):
-
-    def __init__(self, name, key_field, item_field, *, nullable=True,
-                 metadata=None, keys_sorted=False, entries_name='entries'):
-        super().__init__(name, nullable=nullable,
-                         metadata=metadata)
-
-        assert not key_field.nullable
-        self.key_field = key_field
-        self.item_field = item_field
-        self.pair_field = StructField(entries_name, [key_field, item_field],
-                                      nullable=False)
-        self.keys_sorted = keys_sorted
-
-    def _get_type(self):
-        return OrderedDict([
-            ('name', 'map'),
-            ('keysSorted', self.keys_sorted)
-        ])
-
-    def _get_children(self):
-        return [self.pair_field.get_json()]
-
-    def generate_column(self, size, name=None):
-        MAX_MAP_SIZE = 4
-
-        is_valid = self._make_is_valid(size)
-        map_sizes = np.random.randint(0, MAX_MAP_SIZE + 1, size=size)
-        offsets = [0]
-
-        offset = 0
-        for i in range(size):
-            if is_valid[i]:
-                offset += int(map_sizes[i])
-            offsets.append(offset)
-
-        # The offset now is the total number of elements in the child array
-        pairs = self.pair_field.generate_column(offset)
-        if name is None:
-            name = self.name
-
-        return MapColumn(name, size, is_valid, offsets, pairs)
-
-
-class MapColumn(Column):
-
-    def __init__(self, name, count, is_valid, offsets, pairs):
-        super().__init__(name, count)
-        self.is_valid = is_valid
-        self.offsets = offsets
-        self.pairs = pairs
-
-    def _get_buffers(self):
-        return [
-            ('VALIDITY', [int(v) for v in self.is_valid]),
-            ('OFFSET', list(self.offsets))
-        ]
-
-    def _get_children(self):
-        return [self.pairs.get_json()]
-
-
-class FixedSizeListField(Field):
-
-    def __init__(self, name, value_field, list_size, *, nullable=True,
-                 metadata=None):
-        super().__init__(name, nullable=nullable,
-                         metadata=metadata)
-        self.value_field = value_field
-        self.list_size = list_size
-
-    def _get_type(self):
-        return OrderedDict([
-            ('name', 'fixedsizelist'),
-            ('listSize', self.list_size)
-        ])
-
-    def _get_children(self):
-        return [self.value_field.get_json()]
-
-    def generate_column(self, size, name=None):
-        is_valid = self._make_is_valid(size)
-        values = self.value_field.generate_column(size * self.list_size)
-
-        if name is None:
-            name = self.name
-        return FixedSizeListColumn(name, size, is_valid, values)
-
-
-class FixedSizeListColumn(Column):
-
-    def __init__(self, name, count, is_valid, values):
-        super().__init__(name, count)
-        self.is_valid = is_valid
-        self.values = values
-
-    def _get_buffers(self):
-        return [
-            ('VALIDITY', [int(v) for v in self.is_valid])
-        ]
-
-    def _get_children(self):
-        return [self.values.get_json()]
-
-
-class StructField(Field):
-
-    def __init__(self, name, fields, *, nullable=True,
-                 metadata=None):
-        super().__init__(name, nullable=nullable,
-                         metadata=metadata)
-        self.fields = fields
-
-    def _get_type(self):
-        return OrderedDict([
-            ('name', 'struct')
-        ])
-
-    def _get_children(self):
-        return [field.get_json() for field in self.fields]
-
-    def generate_column(self, size, name=None):
-        is_valid = self._make_is_valid(size)
-
-        field_values = [field.generate_column(size) for field in self.fields]
-        if name is None:
-            name = self.name
-        return StructColumn(name, size, is_valid, field_values)
-
-
-class _BaseUnionField(Field):
-
-    def __init__(self, name, fields, type_ids=None, *, nullable=True,
-                 metadata=None):
-        super().__init__(name, nullable=nullable, metadata=metadata)
-        if type_ids is None:
-            type_ids = list(range(fields))
-        else:
-            assert len(fields) == len(type_ids)
-        self.fields = fields
-        self.type_ids = type_ids
-        assert all(x >= 0 for x in self.type_ids)
-
-    def _get_type(self):
-        return OrderedDict([
-            ('name', 'union'),
-            ('mode', self.mode),
-            ('typeIds', self.type_ids),
-        ])
-
-    def _get_children(self):
-        return [field.get_json() for field in self.fields]
-
-    def _make_type_ids(self, size):
-        return np.random.choice(self.type_ids, size)
-
-
-class SparseUnionField(_BaseUnionField):
-    mode = 'SPARSE'
-
-    def generate_column(self, size, name=None):
-        array_type_ids = self._make_type_ids(size)
-        field_values = [field.generate_column(size) for field in self.fields]
-
-        if name is None:
-            name = self.name
-        return SparseUnionColumn(name, size, array_type_ids, field_values)
-
-
-class DenseUnionField(_BaseUnionField):
-    mode = 'DENSE'
-
-    def generate_column(self, size, name=None):
-        # Reverse mapping {logical type id => physical child id}
-        child_ids = [None] * (max(self.type_ids) + 1)
-        for i, type_id in enumerate(self.type_ids):
-            child_ids[type_id] = i
-
-        array_type_ids = self._make_type_ids(size)
-        offsets = []
-        child_sizes = [0] * len(self.fields)
-
-        for i in range(size):
-            child_id = child_ids[array_type_ids[i]]
-            offset = child_sizes[child_id]
-            offsets.append(offset)
-            child_sizes[child_id] = offset + 1
-
-        field_values = [
-            field.generate_column(child_size)
-            for field, child_size in zip(self.fields, child_sizes)]
-
-        if name is None:
-            name = self.name
-        return DenseUnionColumn(name, size, array_type_ids, offsets,
-                                field_values)
-
-
-class Dictionary(object):
-
-    def __init__(self, id_, field, size, name=None, ordered=False):
-        self.id_ = id_
-        self.field = field
-        self.values = field.generate_column(size=size, name=name)
-        self.ordered = ordered
-
-    def __len__(self):
-        return len(self.values)
-
-    def get_json(self):
-        dummy_batch = RecordBatch(len(self.values), [self.values])
-        return OrderedDict([
-            ('id', self.id_),
-            ('data', dummy_batch.get_json())
-        ])
-
-
-class DictionaryField(Field):
-
-    def __init__(self, name, index_field, dictionary, *, nullable=True,
-                 metadata=None):
-        super().__init__(name, nullable=nullable,
-                         metadata=metadata)
-        assert index_field.name == ''
-        assert isinstance(index_field, IntegerField)
-        assert isinstance(dictionary, Dictionary)
-
-        self.index_field = index_field
-        self.dictionary = dictionary
-
-    def _get_type(self):
-        return self.dictionary.field._get_type()
-
-    def _get_children(self):
-        return self.dictionary.field._get_children()
-
-    def _get_dictionary(self):
-        return OrderedDict([
-            ('id', self.dictionary.id_),
-            ('indexType', self.index_field._get_type()),
-            ('isOrdered', self.dictionary.ordered)
-        ])
-
-    def generate_column(self, size, name=None):
-        if name is None:
-            name = self.name
-        return self.index_field.generate_range(size, 0, len(self.dictionary),
-                                               name=name)
-
-
-ExtensionType = namedtuple(
-    'ExtensionType', ['extension_name', 'serialized', 'storage_field'])
-
-
-class ExtensionField(Field):
-
-    def __init__(self, name, extension_type, *, nullable=True, metadata=None):
-        metadata = (metadata or []) + [
-            ('ARROW:extension:name', extension_type.extension_name),
-            ('ARROW:extension:metadata', extension_type.serialized),
-        ]
-        super().__init__(name, nullable=nullable, metadata=metadata)
-        self.extension_type = extension_type
-
-    def _get_type(self):
-        return self.extension_type.storage_field._get_type()
-
-    def _get_children(self):
-        return self.extension_type.storage_field._get_children()
-
-    def _get_dictionary(self):
-        return self.extension_type.storage_field._get_dictionary()
-
-    def generate_column(self, size, name=None):
-        if name is None:
-            name = self.name
-        return self.extension_type.storage_field.generate_column(size, name)
-
-
-class StructColumn(Column):
-
-    def __init__(self, name, count, is_valid, field_values):
-        super().__init__(name, count)
-        self.is_valid = is_valid
-        self.field_values = field_values
-
-    def _get_buffers(self):
-        return [
-            ('VALIDITY', [int(v) for v in self.is_valid])
-        ]
-
-    def _get_children(self):
-        return [field.get_json() for field in self.field_values]
-
-
-class SparseUnionColumn(Column):
-
-    def __init__(self, name, count, type_ids, field_values):
-        super().__init__(name, count)
-        self.type_ids = type_ids
-        self.field_values = field_values
-
-    def _get_buffers(self):
-        return [
-            ('TYPE_ID', [int(v) for v in self.type_ids])
-        ]
-
-    def _get_children(self):
-        return [field.get_json() for field in self.field_values]
-
-
-class DenseUnionColumn(Column):
-
-    def __init__(self, name, count, type_ids, offsets, field_values):
-        super().__init__(name, count)
-        self.type_ids = type_ids
-        self.offsets = offsets
-        self.field_values = field_values
-
-    def _get_buffers(self):
-        return [
-            ('TYPE_ID', [int(v) for v in self.type_ids]),
-            ('OFFSET', [int(v) for v in self.offsets]),
-        ]
-
-    def _get_children(self):
-        return [field.get_json() for field in self.field_values]
-
-
-class RecordBatch(object):
-
-    def __init__(self, count, columns):
-        self.count = count
-        self.columns = columns
-
-    def get_json(self):
-        return OrderedDict([
-            ('count', self.count),
-            ('columns', [col.get_json() for col in self.columns])
-        ])
-
-
-class File(object):
-
-    def __init__(self, name, schema, batches, dictionaries=None,
-                 skip=None, path=None):
-        self.name = name
-        self.schema = schema
-        self.dictionaries = dictionaries or []
-        self.batches = batches
-        self.skip = set()
-        self.path = path
-        if skip:
-            self.skip.update(skip)
-
-    def get_json(self):
-        entries = [
-            ('schema', self.schema.get_json())
-        ]
-
-        if len(self.dictionaries) > 0:
-            entries.append(('dictionaries',
-                            [dictionary.get_json()
-                             for dictionary in self.dictionaries]))
-
-        entries.append(('batches', [batch.get_json()
-                                    for batch in self.batches]))
-        return OrderedDict(entries)
-
-    def write(self, path):
-        with open(path, 'wb') as f:
-            f.write(json.dumps(self.get_json(), indent=2).encode('utf-8'))
-        self.path = path
-
-    def skip_category(self, category):
-        """Skip this test for the given category.
-
-        Category should be SKIP_ARROW or SKIP_FLIGHT.
-        """
-        self.skip.add(category)
-        return self
-
-
-def get_field(name, type_, **kwargs):
-    if type_ == 'binary':
-        return BinaryField(name, **kwargs)
-    elif type_ == 'utf8':
-        return StringField(name, **kwargs)
-    elif type_ == 'largebinary':
-        return LargeBinaryField(name, **kwargs)
-    elif type_ == 'largeutf8':
-        return LargeStringField(name, **kwargs)
-    elif type_.startswith('fixedsizebinary_'):
-        byte_width = int(type_.split('_')[1])
-        return FixedSizeBinaryField(name, byte_width=byte_width, **kwargs)
-
-    dtype = np.dtype(type_)
-
-    if dtype.kind in ('i', 'u'):
-        signed = dtype.kind == 'i'
-        bit_width = dtype.itemsize * 8
-        return IntegerField(name, signed, bit_width, **kwargs)
-    elif dtype.kind == 'f':
-        bit_width = dtype.itemsize * 8
-        return FloatingPointField(name, bit_width, **kwargs)
-    elif dtype.kind == 'b':
-        return BooleanField(name, **kwargs)
-    else:
-        raise TypeError(dtype)
-
-
-def _generate_file(name, fields, batch_sizes, dictionaries=None, skip=None,
-                   metadata=None):
-    schema = Schema(fields, metadata=metadata)
-    batches = []
-    for size in batch_sizes:
-        columns = []
-        for field in fields:
-            col = field.generate_column(size)
-            columns.append(col)
-
-        batches.append(RecordBatch(size, columns))
-
-    return File(name, schema, batches, dictionaries, skip=skip)
-
-
-def generate_custom_metadata_case():
-    def meta(items):
-        # Generate a simple block of metadata where each value is '{}'.
-        # Keys are delimited by whitespace in `items`.
-        return [(k, '{}') for k in items.split()]
-
-    fields = [
-        get_field('sort_of_pandas', 'int8', metadata=meta('pandas')),
-
-        get_field('lots_of_meta', 'int8', metadata=meta('a b c d .. w x y z')),
-
-        get_field(
-            'unregistered_extension', 'int8',
-            metadata=[
-                ('ARROW:extension:name', '!nonexistent'),
-                ('ARROW:extension:metadata', ''),
-                ('ARROW:integration:allow_unregistered_extension', 'true'),
-            ]),
-
-        ListField('list_with_odd_values',
-                  get_field('item', 'int32', metadata=meta('odd_values'))),
-    ]
-
-    batch_sizes = [1]
-    return _generate_file('custom_metadata', fields, batch_sizes,
-                          metadata=meta('schema_custom_0 schema_custom_1'))
-
-
-def generate_duplicate_fieldnames_case():
-    fields = [
-        get_field('ints', 'int8'),
-        get_field('ints', 'int32'),
-
-        StructField('struct', [get_field('', 'int32'), get_field('', 'utf8')]),
-    ]
-
-    batch_sizes = [1]
-    return _generate_file('duplicate_fieldnames', fields, batch_sizes)
-
-
-def generate_primitive_case(batch_sizes, name='primitive'):
-    types = ['bool', 'int8', 'int16', 'int32', 'int64',
-             'uint8', 'uint16', 'uint32', 'uint64',
-             'float32', 'float64', 'binary', 'utf8',
-             'fixedsizebinary_19', 'fixedsizebinary_120']
-
-    fields = []
-
-    for type_ in types:
-        fields.append(get_field(type_ + "_nullable", type_, nullable=True))
-        fields.append(get_field(type_ + "_nonnullable", type_, nullable=False))
-
-    return _generate_file(name, fields, batch_sizes)
-
-
-def generate_primitive_large_offsets_case(batch_sizes):
-    types = ['largebinary', 'largeutf8']
-
-    fields = []
-
-    for type_ in types:
-        fields.append(get_field(type_ + "_nullable", type_, nullable=True))
-        fields.append(get_field(type_ + "_nonnullable", type_, nullable=False))
-
-    return _generate_file('primitive_large_offsets', fields, batch_sizes)
-
-
-def generate_null_case(batch_sizes):
-    # Interleave null with non-null types to ensure the appropriate number of
-    # buffers (0) is read and written
-    fields = [
-        NullField(name='f0'),
-        get_field('f1', 'int32'),
-        NullField(name='f2'),
-        get_field('f3', 'float64'),
-        NullField(name='f4')
-    ]
-    return _generate_file('null', fields, batch_sizes)
-
-
-def generate_null_trivial_case(batch_sizes):
-    # Generate a case with no buffers
-    fields = [
-        NullField(name='f0'),
-    ]
-    return _generate_file('null_trivial', fields, batch_sizes)
-
-
-def generate_decimal128_case():
-    fields = [
-        DecimalField(name='f{}'.format(i), precision=precision, scale=2,
-                     bit_width=128)
-        for i, precision in enumerate(range(3, 39))
-    ]
-
-    possible_batch_sizes = 7, 10
-    batch_sizes = [possible_batch_sizes[i % 2] for i in range(len(fields))]
-    # 'decimal' is the original name for the test, and it must match
-    # provide "gold" files that test backwards compatibility, so they
-    # can be appropriately skipped.
-    return _generate_file('decimal', fields, batch_sizes)
-
-
-def generate_decimal256_case():
-    fields = [
-        DecimalField(name='f{}'.format(i), precision=precision, scale=5,
-                     bit_width=256)
-        for i, precision in enumerate(range(37, 70))
-    ]
-
-    possible_batch_sizes = 7, 10
-    batch_sizes = [possible_batch_sizes[i % 2] for i in range(len(fields))]
-    return _generate_file('decimal256', fields, batch_sizes)
-
-
-def generate_datetime_case():
-    fields = [
-        DateField('f0', DateField.DAY),
-        DateField('f1', DateField.MILLISECOND),
-        TimeField('f2', 's'),
-        TimeField('f3', 'ms'),
-        TimeField('f4', 'us'),
-        TimeField('f5', 'ns'),
-        TimestampField('f6', 's'),
-        TimestampField('f7', 'ms'),
-        TimestampField('f8', 'us'),
-        TimestampField('f9', 'ns'),
-        TimestampField('f10', 'ms', tz=None),
-        TimestampField('f11', 's', tz='UTC'),
-        TimestampField('f12', 'ms', tz='US/Eastern'),
-        TimestampField('f13', 'us', tz='Europe/Paris'),
-        TimestampField('f14', 'ns', tz='US/Pacific'),
-    ]
-
-    batch_sizes = [7, 10]
-    return _generate_file("datetime", fields, batch_sizes)
-
-
-def generate_interval_case():
-    fields = [
-        DurationIntervalField('f1', 's'),
-        DurationIntervalField('f2', 'ms'),
-        DurationIntervalField('f3', 'us'),
-        DurationIntervalField('f4', 'ns'),
-        YearMonthIntervalField('f5'),
-        DayTimeIntervalField('f6'),
-    ]
-
-    batch_sizes = [7, 10]
-    return _generate_file("interval", fields, batch_sizes)
-
-
-def generate_map_case():
-    fields = [
-        MapField('map_nullable', get_field('key', 'utf8', nullable=False),
-                 get_field('value', 'int32')),
-    ]
-
-    batch_sizes = [7, 10]
-    return _generate_file("map", fields, batch_sizes)
-
-
-def generate_non_canonical_map_case():
-    fields = [
-        MapField('map_other_names',
-                 get_field('some_key', 'utf8', nullable=False),
-                 get_field('some_value', 'int32'),
-                 entries_name='some_entries'),
-    ]
-
-    batch_sizes = [7]
-    return _generate_file("map_non_canonical", fields, batch_sizes)
-
-
-def generate_nested_case():
-    fields = [
-        ListField('list_nullable', get_field('item', 'int32')),
-        FixedSizeListField('fixedsizelist_nullable',
-                           get_field('item', 'int32'), 4),
-        StructField('struct_nullable', [get_field('f1', 'int32'),
-                                        get_field('f2', 'utf8')]),
-        # Fails on Go (ARROW-8452)
-        # ListField('list_nonnullable', get_field('item', 'int32'),
-        #           nullable=False),
-    ]
-
-    batch_sizes = [7, 10]
-    return _generate_file("nested", fields, batch_sizes)
-
-
-def generate_recursive_nested_case():
-    fields = [
-        ListField('lists_list',
-                  ListField('inner_list', get_field('item', 'int16'))),
-        ListField('structs_list',
-                  StructField('inner_struct',
-                              [get_field('f1', 'int32'),
-                               get_field('f2', 'utf8')])),
-    ]
-
-    batch_sizes = [7, 10]
-    return _generate_file("recursive_nested", fields, batch_sizes)
-
-
-def generate_nested_large_offsets_case():
-    fields = [
-        LargeListField('large_list_nullable', get_field('item', 'int32')),
-        LargeListField('large_list_nonnullable',
-                       get_field('item', 'int32'), nullable=False),
-        LargeListField('large_list_nested',
-                       ListField('inner_list', get_field('item', 'int16'))),
-    ]
-
-    batch_sizes = [0, 13]
-    return _generate_file("nested_large_offsets", fields, batch_sizes)
-
-
-def generate_unions_case():
-    fields = [
-        SparseUnionField('sparse', [get_field('f1', 'int32'),
-                                    get_field('f2', 'utf8')],
-                         type_ids=[5, 7]),
-        DenseUnionField('dense', [get_field('f1', 'int16'),
-                                  get_field('f2', 'binary')],
-                        type_ids=[10, 20]),
-        SparseUnionField('sparse', [get_field('f1', 'float32', nullable=False),
-                                    get_field('f2', 'bool')],
-                         type_ids=[5, 7], nullable=False),
-        DenseUnionField('dense', [get_field('f1', 'uint8', nullable=False),
-                                  get_field('f2', 'uint16'),
-                                  NullField('f3')],
-                        type_ids=[42, 43, 44], nullable=False),
-    ]
-
-    batch_sizes = [0, 11]
-    return _generate_file("union", fields, batch_sizes)
-
-
-def generate_dictionary_case():
-    dict0 = Dictionary(0, StringField('dictionary1'), size=10, name='DICT0')
-    dict1 = Dictionary(1, StringField('dictionary1'), size=5, name='DICT1')
-    dict2 = Dictionary(2, get_field('dictionary2', 'int64'),
-                       size=50, name='DICT2')
-
-    fields = [
-        DictionaryField('dict0', get_field('', 'int8'), dict0),
-        DictionaryField('dict1', get_field('', 'int32'), dict1),
-        DictionaryField('dict2', get_field('', 'int16'), dict2)
-    ]
-    batch_sizes = [7, 10]
-    return _generate_file("dictionary", fields, batch_sizes,
-                          dictionaries=[dict0, dict1, dict2])
-
-
-def generate_dictionary_unsigned_case():
-    dict0 = Dictionary(0, StringField('dictionary0'), size=5, name='DICT0')
-    dict1 = Dictionary(1, StringField('dictionary1'), size=5, name='DICT1')
-    dict2 = Dictionary(2, StringField('dictionary2'), size=5, name='DICT2')
-
-    # TODO: JavaScript does not support uint64 dictionary indices, so disabled
-    # for now
-
-    # dict3 = Dictionary(3, StringField('dictionary3'), size=5, name='DICT3')
-    fields = [
-        DictionaryField('f0', get_field('', 'uint8'), dict0),
-        DictionaryField('f1', get_field('', 'uint16'), dict1),
-        DictionaryField('f2', get_field('', 'uint32'), dict2),
-        # DictionaryField('f3', get_field('', 'uint64'), dict3)
-    ]
-    batch_sizes = [7, 10]
-    return _generate_file("dictionary_unsigned", fields, batch_sizes,
-                          dictionaries=[dict0, dict1, dict2])
-
-
-def generate_nested_dictionary_case():
-    dict0 = Dictionary(0, StringField('str'), size=10, name='DICT0')
-
-    list_of_dict = ListField(
-        'list',
-        DictionaryField('str_dict', get_field('', 'int8'), dict0))
-    dict1 = Dictionary(1, list_of_dict, size=30, name='DICT1')
-
-    struct_of_dict = StructField('struct', [
-        DictionaryField('str_dict_a', get_field('', 'int8'), dict0),
-        DictionaryField('str_dict_b', get_field('', 'int8'), dict0)
-    ])
-    dict2 = Dictionary(2, struct_of_dict, size=30, name='DICT2')
-
-    fields = [
-        DictionaryField('list_dict', get_field('', 'int8'), dict1),
-        DictionaryField('struct_dict', get_field('', 'int8'), dict2)
-    ]
-
-    batch_sizes = [10, 13]
-    return _generate_file("nested_dictionary", fields, batch_sizes,
-                          dictionaries=[dict0, dict1, dict2])
-
-
-def generate_extension_case():
-    dict0 = Dictionary(0, StringField('dictionary0'), size=5, name='DICT0')
-
-    uuid_type = ExtensionType('uuid', 'uuid-serialized',
-                              FixedSizeBinaryField('', 16))
-    dict_ext_type = ExtensionType(
-        'dict-extension', 'dict-extension-serialized',
-        DictionaryField('str_dict', get_field('', 'int8'), dict0))
-
-    fields = [
-        ExtensionField('uuids', uuid_type),
-        ExtensionField('dict_exts', dict_ext_type),
-    ]
-
-    batch_sizes = [0, 13]
-    return _generate_file("extension", fields, batch_sizes,
-                          dictionaries=[dict0])
-
-
-def get_generated_json_files(tempdir=None):
-    tempdir = tempdir or tempfile.mkdtemp(prefix='arrow-integration-')
-
-    def _temp_path():
-        return
-
-    file_objs = [
-        generate_primitive_case([], name='primitive_no_batches'),
-        generate_primitive_case([17, 20], name='primitive'),
-        generate_primitive_case([0, 0, 0], name='primitive_zerolength'),
-
-        generate_primitive_large_offsets_case([17, 20])
-        .skip_category('Go')
-        .skip_category('JS'),
-
-        generate_null_case([10, 0])
-        .skip_category('JS')   # TODO(ARROW-7900)
-        .skip_category('Go'),  # TODO(ARROW-7901)
-
-        generate_null_trivial_case([0, 0])
-        .skip_category('JS')   # TODO(ARROW-7900)
-        .skip_category('Go'),  # TODO(ARROW-7901)
-
-        generate_decimal128_case()
-        .skip_category('Go')  # TODO(ARROW-7948): Decimal + Go
-        .skip_category('Rust'),
-
-        generate_decimal256_case()
-        .skip_category('Go')  # TODO(ARROW-7948): Decimal + Go
-        .skip_category('JS')
-        .skip_category('Rust'),
-
-        generate_datetime_case(),
-
-        generate_interval_case()
-        .skip_category('JS')  # TODO(ARROW-5239): Intervals + JS
-        .skip_category('Rust'),
-
-        generate_map_case()
-        .skip_category('Go')  # TODO(ARROW-5620): Map + Go
-        .skip_category('Rust'),
-
-        generate_non_canonical_map_case()
-        .skip_category('Go')     # TODO(ARROW-5620)
-        .skip_category('Java')   # TODO(ARROW-8715)
-        .skip_category('JS')     # TODO(ARROW-8716)
-        .skip_category('Rust'),
-
-        generate_nested_case(),
-
-        generate_recursive_nested_case()
-        .skip_category('Go'),    # TODO(ARROW-8453)
-
-        generate_nested_large_offsets_case()
-        .skip_category('Go')
-        .skip_category('JS')
-        .skip_category('Rust'),
-
-        generate_unions_case()
-        .skip_category('Go')
-        .skip_category('JS')
-        .skip_category('Rust'),
-
-        generate_custom_metadata_case()
-        .skip_category('Go')
-        .skip_category('JS'),
-
-        generate_duplicate_fieldnames_case()
-        .skip_category('Go')
-        .skip_category('JS'),
-
-        # TODO(ARROW-3039, ARROW-5267): Dictionaries in GO
-        generate_dictionary_case()
-        .skip_category('Go'),
-
-        generate_dictionary_unsigned_case()
-        .skip_category('Go')     # TODO(ARROW-9378)
-        .skip_category('Java'),  # TODO(ARROW-9377)
-
-        generate_nested_dictionary_case()
-        .skip_category('Go')
-        .skip_category('Java')  # TODO(ARROW-7779)
-        .skip_category('JS')
-        .skip_category('Rust'),
-
-        generate_extension_case()
-        .skip_category('Go')
-        .skip_category('JS')
-        .skip_category('Rust'),
-    ]
-
-    generated_paths = []
-    for file_obj in file_objs:
-        out_path = os.path.join(tempdir, 'generated_' +
-                                file_obj.name + '.json')
-        file_obj.write(out_path)
-        generated_paths.append(file_obj)
-
-    return generated_paths
diff --git a/dev/archery/archery/integration/runner.py b/dev/archery/archery/integration/runner.py
deleted file mode 100644
index 8aef163..0000000
--- a/dev/archery/archery/integration/runner.py
+++ /dev/null
@@ -1,419 +0,0 @@
-# licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from collections import namedtuple
-from concurrent.futures import ThreadPoolExecutor
-from functools import partial
-import glob
-import gzip
-import itertools
-import os
-import sys
-import tempfile
-import traceback
-
-from .scenario import Scenario
-from .tester_cpp import CPPTester
-from .tester_go import GoTester
-from .tester_rust import RustTester
-from .tester_java import JavaTester
-from .tester_js import JSTester
-from .util import (ARROW_ROOT_DEFAULT, guid, SKIP_ARROW, SKIP_FLIGHT,
-                   printer)
-from . import datagen
-
-
-Failure = namedtuple('Failure',
-                     ('test_case', 'producer', 'consumer', 'exc_info'))
-
-log = printer.print
-
-
-class Outcome:
-    def __init__(self):
-        self.failure = None
-        self.skipped = False
-
-
-class IntegrationRunner(object):
-
-    def __init__(self, json_files, flight_scenarios, testers, tempdir=None,
-                 debug=False, stop_on_error=True, gold_dirs=None,
-                 serial=False, match=None, **unused_kwargs):
-        self.json_files = json_files
-        self.flight_scenarios = flight_scenarios
-        self.testers = testers
-        self.temp_dir = tempdir or tempfile.mkdtemp()
-        self.debug = debug
-        self.stop_on_error = stop_on_error
-        self.serial = serial
-        self.gold_dirs = gold_dirs
-        self.failures = []
-        self.match = match
-
-        if self.match is not None:
-            print("-- Only running tests with {} in their name"
-                  .format(self.match))
-            self.json_files = [json_file for json_file in self.json_files
-                               if self.match in json_file.name]
-
-    def run(self):
-        """
-        Run Arrow IPC integration tests for the matrix of enabled
-        implementations.
-        """
-        for producer, consumer in itertools.product(
-                filter(lambda t: t.PRODUCER, self.testers),
-                filter(lambda t: t.CONSUMER, self.testers)):
-            self._compare_implementations(
-                producer, consumer, self._produce_consume,
-                self.json_files)
-        if self.gold_dirs:
-            for gold_dir, consumer in itertools.product(
-                    self.gold_dirs,
-                    filter(lambda t: t.CONSUMER, self.testers)):
-                log('\n\n\n\n')
-                log('******************************************************')
-                log('Tests against golden files in {}'.format(gold_dir))
-                log('******************************************************')
-
-                def run_gold(producer, consumer, outcome, test_case):
-                    self._run_gold(gold_dir, producer, consumer, outcome,
-                                   test_case)
-                self._compare_implementations(
-                    consumer, consumer, run_gold,
-                    self._gold_tests(gold_dir))
-
-    def run_flight(self):
-        """
-        Run Arrow Flight integration tests for the matrix of enabled
-        implementations.
-        """
-        servers = filter(lambda t: t.FLIGHT_SERVER, self.testers)
-        clients = filter(lambda t: (t.FLIGHT_CLIENT and t.CONSUMER),
-                         self.testers)
-        for server, client in itertools.product(servers, clients):
-            self._compare_flight_implementations(server, client)
-
-    def _gold_tests(self, gold_dir):
-        prefix = os.path.basename(os.path.normpath(gold_dir))
-        SUFFIX = ".json.gz"
-        golds = [jf for jf in os.listdir(gold_dir) if jf.endswith(SUFFIX)]
-        for json_path in golds:
-            name = json_path[json_path.index('_')+1: -len(SUFFIX)]
-            base_name = prefix + "_" + name + ".gold.json"
-            out_path = os.path.join(self.temp_dir, base_name)
-            with gzip.open(os.path.join(gold_dir, json_path)) as i:
-                with open(out_path, "wb") as out:
-                    out.write(i.read())
-
-            try:
-                skip = next(f for f in self.json_files
-                            if f.name == name).skip
-            except StopIteration:
-                skip = set()
-            if name == 'union' and prefix == '0.17.1':
-                skip.add("Java")
-            if prefix == '1.0.0-bigendian' or prefix == '1.0.0-littleendian':
-                skip.add("Go")
-                skip.add("Java")
-                skip.add("JS")
-                skip.add("Rust")
-            if prefix == '2.0.0-compression':
-                skip.add("JS")
-                skip.add("Rust")
-
-            # See https://github.com/apache/arrow/pull/9822 for how to
-            # disable specific compression type tests.
-
-            if prefix == '4.0.0-shareddict':
-                skip.add("Go")
-
-            yield datagen.File(name, None, None, skip=skip, path=out_path)
-
-    def _run_test_cases(self, producer, consumer, case_runner,
-                        test_cases):
-        def case_wrapper(test_case):
-            with printer.cork():
-                return case_runner(test_case)
-
-        if self.failures and self.stop_on_error:
-            return
-
-        if self.serial:
-            for outcome in map(case_wrapper, test_cases):
-                if outcome.failure is not None:
-                    self.failures.append(outcome.failure)
-                    if self.stop_on_error:
-                        break
-
-        else:
-            with ThreadPoolExecutor() as executor:
-                for outcome in executor.map(case_wrapper, test_cases):
-                    if outcome.failure is not None:
-                        self.failures.append(outcome.failure)
-                        if self.stop_on_error:
-                            break
-
-    def _compare_implementations(
-            self, producer, consumer, run_binaries, test_cases):
-        """
-        Compare Arrow IPC for two implementations (one producer, one consumer).
-        """
-        log('##########################################################')
-        log('IPC: {0} producing, {1} consuming'
-            .format(producer.name, consumer.name))
-        log('##########################################################')
-
-        case_runner = partial(self._run_ipc_test_case,
-                              producer, consumer, run_binaries)
-        self._run_test_cases(producer, consumer, case_runner, test_cases)
-
-    def _run_ipc_test_case(self, producer, consumer, run_binaries, test_case):
-        """
-        Run one IPC test case.
-        """
-        outcome = Outcome()
-
-        json_path = test_case.path
-        log('==========================================================')
-        log('Testing file {0}'.format(json_path))
-        log('==========================================================')
-
-        if producer.name in test_case.skip:
-            log('-- Skipping test because producer {0} does '
-                'not support'.format(producer.name))
-            outcome.skipped = True
-
-        elif consumer.name in test_case.skip:
-            log('-- Skipping test because consumer {0} does '
-                'not support'.format(consumer.name))
-            outcome.skipped = True
-
-        elif SKIP_ARROW in test_case.skip:
-            log('-- Skipping test')
-            outcome.skipped = True
-
-        else:
-            try:
-                run_binaries(producer, consumer, outcome, test_case)
-            except Exception:
-                traceback.print_exc(file=printer.stdout)
-                outcome.failure = Failure(test_case, producer, consumer,
-                                          sys.exc_info())
-
-        return outcome
-
-    def _produce_consume(self, producer, consumer, outcome, test_case):
-        # Make the random access file
-        json_path = test_case.path
-        file_id = guid()[:8]
-        name = os.path.splitext(os.path.basename(json_path))[0]
-
-        producer_file_path = os.path.join(self.temp_dir, file_id + '_' +
-                                          name + '.json_as_file')
-        producer_stream_path = os.path.join(self.temp_dir, file_id + '_' +
-                                            name + '.producer_file_as_stream')
-        consumer_file_path = os.path.join(self.temp_dir, file_id + '_' +
-                                          name + '.consumer_stream_as_file')
-
-        log('-- Creating binary inputs')
-        producer.json_to_file(json_path, producer_file_path)
-
-        # Validate the file
-        log('-- Validating file')
-        consumer.validate(json_path, producer_file_path)
-
-        log('-- Validating stream')
-        producer.file_to_stream(producer_file_path, producer_stream_path)
-        consumer.stream_to_file(producer_stream_path, consumer_file_path)
-        consumer.validate(json_path, consumer_file_path)
-
-    def _run_gold(self, gold_dir, producer, consumer, outcome, test_case):
-        json_path = test_case.path
-
-        # Validate the file
-        log('-- Validating file')
-        producer_file_path = os.path.join(
-            gold_dir, "generated_" + test_case.name + ".arrow_file")
-        consumer.validate(json_path, producer_file_path)
-
-        log('-- Validating stream')
-        consumer_stream_path = os.path.join(
-            gold_dir, "generated_" + test_case.name + ".stream")
-        file_id = guid()[:8]
-        name = os.path.splitext(os.path.basename(json_path))[0]
-
-        consumer_file_path = os.path.join(self.temp_dir, file_id + '_' +
-                                          name + '.consumer_stream_as_file')
-
-        consumer.stream_to_file(consumer_stream_path, consumer_file_path)
-        consumer.validate(json_path, consumer_file_path)
-
-    def _compare_flight_implementations(self, producer, consumer):
-        log('##########################################################')
-        log('Flight: {0} serving, {1} requesting'
-            .format(producer.name, consumer.name))
-        log('##########################################################')
-
-        case_runner = partial(self._run_flight_test_case, producer, consumer)
-        self._run_test_cases(producer, consumer, case_runner,
-                             self.json_files + self.flight_scenarios)
-
-    def _run_flight_test_case(self, producer, consumer, test_case):
-        """
-        Run one Flight test case.
-        """
-        outcome = Outcome()
-
-        log('=' * 58)
-        log('Testing file {0}'.format(test_case.name))
-        log('=' * 58)
-
-        if producer.name in test_case.skip:
-            log('-- Skipping test because producer {0} does '
-                'not support'.format(producer.name))
-            outcome.skipped = True
-
-        elif consumer.name in test_case.skip:
-            log('-- Skipping test because consumer {0} does '
-                'not support'.format(consumer.name))
-            outcome.skipped = True
-
-        elif SKIP_FLIGHT in test_case.skip:
-            log('-- Skipping test')
-            outcome.skipped = True
-
-        else:
-            try:
-                if isinstance(test_case, Scenario):
-                    server = producer.flight_server(test_case.name)
-                    client_args = {'scenario_name': test_case.name}
-                else:
-                    server = producer.flight_server()
-                    client_args = {'json_path': test_case.path}
-
-                with server as port:
-                    # Have the client upload the file, then download and
-                    # compare
-                    consumer.flight_request(port, **client_args)
-            except Exception:
-                traceback.print_exc(file=printer.stdout)
-                outcome.failure = Failure(test_case, producer, consumer,
-                                          sys.exc_info())
-
-        return outcome
-
-
-def get_static_json_files():
-    glob_pattern = os.path.join(ARROW_ROOT_DEFAULT,
-                                'integration', 'data', '*.json')
-    return [
-        datagen.File(name=os.path.basename(p), path=p, skip=set(),
-                     schema=None, batches=None)
-        for p in glob.glob(glob_pattern)
-    ]
-
-
-def run_all_tests(with_cpp=True, with_java=True, with_js=True,
-                  with_go=True, with_rust=False, run_flight=False,
-                  tempdir=None, **kwargs):
-    tempdir = tempdir or tempfile.mkdtemp(prefix='arrow-integration-')
-
-    testers = []
-
-    if with_cpp:
-        testers.append(CPPTester(**kwargs))
-
-    if with_java:
-        testers.append(JavaTester(**kwargs))
-
-    if with_js:
-        testers.append(JSTester(**kwargs))
-
-    if with_go:
-        testers.append(GoTester(**kwargs))
-
-    if with_rust:
-        testers.append(RustTester(**kwargs))
-
-    static_json_files = get_static_json_files()
-    generated_json_files = datagen.get_generated_json_files(tempdir=tempdir)
-    json_files = static_json_files + generated_json_files
-
-    # Additional integration test cases for Arrow Flight.
-    flight_scenarios = [
-        Scenario(
-            "auth:basic_proto",
-            description="Authenticate using the BasicAuth protobuf."),
-        Scenario(
-            "middleware",
-            description="Ensure headers are propagated via middleware.",
-            skip={"Rust"}   # TODO(ARROW-10961): tonic upgrade needed
-        ),
-    ]
-
-    runner = IntegrationRunner(json_files, flight_scenarios, testers, **kwargs)
-    runner.run()
-    if run_flight:
-        runner.run_flight()
-
-    fail_count = 0
-    if runner.failures:
-        log("################# FAILURES #################")
-        for test_case, producer, consumer, exc_info in runner.failures:
-            fail_count += 1
-            log("FAILED TEST:", end=" ")
-            log(test_case.name, producer.name, "producing, ",
-                consumer.name, "consuming")
-            if exc_info:
-                traceback.print_exception(*exc_info)
-            log()
-
-    log(fail_count, "failures")
-    if fail_count > 0:
-        sys.exit(1)
-
-
-def write_js_test_json(directory):
-    datagen.generate_map_case().write(
-        os.path.join(directory, 'map.json')
-    )
-    datagen.generate_nested_case().write(
-        os.path.join(directory, 'nested.json')
-    )
-    datagen.generate_decimal_case().write(
-        os.path.join(directory, 'decimal.json')
-    )
-    datagen.generate_datetime_case().write(
-        os.path.join(directory, 'datetime.json')
-    )
-    datagen.generate_dictionary_case().write(
-        os.path.join(directory, 'dictionary.json')
-    )
-    datagen.generate_dictionary_unsigned_case().write(
-        os.path.join(directory, 'dictionary_unsigned.json')
-    )
-    datagen.generate_primitive_case([]).write(
-        os.path.join(directory, 'primitive_no_batches.json')
-    )
-    datagen.generate_primitive_case([7, 10]).write(
-        os.path.join(directory, 'primitive.json')
-    )
-    datagen.generate_primitive_case([0, 0, 0]).write(
-        os.path.join(directory, 'primitive-empty.json')
-    )
diff --git a/dev/archery/archery/integration/scenario.py b/dev/archery/archery/integration/scenario.py
deleted file mode 100644
index 1fcbca6..0000000
--- a/dev/archery/archery/integration/scenario.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-
-class Scenario:
-    """
-    An integration test scenario for Arrow Flight.
-
-    Does not correspond to a particular IPC JSON file.
-    """
-
-    def __init__(self, name, description, skip=None):
-        self.name = name
-        self.description = description
-        self.skip = skip or set()
diff --git a/dev/archery/archery/integration/tester.py b/dev/archery/archery/integration/tester.py
deleted file mode 100644
index 122e4f2..0000000
--- a/dev/archery/archery/integration/tester.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-# Base class for language-specific integration test harnesses
-
-import subprocess
-
-from .util import log
-
-
-class Tester(object):
-    PRODUCER = False
-    CONSUMER = False
-    FLIGHT_SERVER = False
-    FLIGHT_CLIENT = False
-
-    def __init__(self, debug=False, **args):
-        self.args = args
-        self.debug = debug
-
-    def run_shell_command(self, cmd):
-        cmd = ' '.join(cmd)
-        if self.debug:
-            log(cmd)
-        subprocess.check_call(cmd, shell=True)
-
-    def json_to_file(self, json_path, arrow_path):
-        raise NotImplementedError
-
-    def stream_to_file(self, stream_path, file_path):
-        raise NotImplementedError
-
-    def file_to_stream(self, file_path, stream_path):
-        raise NotImplementedError
-
-    def validate(self, json_path, arrow_path):
-        raise NotImplementedError
-
-    def flight_server(self, scenario_name=None):
-        """Start the Flight server on a free port.
-
-        This should be a context manager that returns the port as the
-        managed object, and cleans up the server on exit.
-        """
-        raise NotImplementedError
-
-    def flight_request(self, port, json_path=None, scenario_name=None):
-        raise NotImplementedError
diff --git a/dev/archery/archery/integration/tester_cpp.py b/dev/archery/archery/integration/tester_cpp.py
deleted file mode 100644
index d35c955..0000000
--- a/dev/archery/archery/integration/tester_cpp.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import contextlib
-import os
-import subprocess
-
-from .tester import Tester
-from .util import run_cmd, ARROW_ROOT_DEFAULT, log
-
-
-class CPPTester(Tester):
-    PRODUCER = True
-    CONSUMER = True
-    FLIGHT_SERVER = True
-    FLIGHT_CLIENT = True
-
-    EXE_PATH = os.environ.get(
-        'ARROW_CPP_EXE_PATH',
-        os.path.join(ARROW_ROOT_DEFAULT, 'cpp/build/debug'))
-
-    CPP_INTEGRATION_EXE = os.path.join(EXE_PATH, 'arrow-json-integration-test')
-    STREAM_TO_FILE = os.path.join(EXE_PATH, 'arrow-stream-to-file')
-    FILE_TO_STREAM = os.path.join(EXE_PATH, 'arrow-file-to-stream')
-
-    FLIGHT_SERVER_CMD = [
-        os.path.join(EXE_PATH, 'flight-test-integration-server')]
-    FLIGHT_CLIENT_CMD = [
-        os.path.join(EXE_PATH, 'flight-test-integration-client'),
-        "-host", "localhost"]
-
-    name = 'C++'
-
-    def _run(self, arrow_path=None, json_path=None, command='VALIDATE'):
-        cmd = [self.CPP_INTEGRATION_EXE, '--integration']
-
-        if arrow_path is not None:
-            cmd.append('--arrow=' + arrow_path)
-
-        if json_path is not None:
-            cmd.append('--json=' + json_path)
-
-        cmd.append('--mode=' + command)
-
-        if self.debug:
-            log(' '.join(cmd))
-
-        run_cmd(cmd)
-
-    def validate(self, json_path, arrow_path):
-        return self._run(arrow_path, json_path, 'VALIDATE')
-
-    def json_to_file(self, json_path, arrow_path):
-        return self._run(arrow_path, json_path, 'JSON_TO_ARROW')
-
-    def stream_to_file(self, stream_path, file_path):
-        cmd = [self.STREAM_TO_FILE, '<', stream_path, '>', file_path]
-        self.run_shell_command(cmd)
-
-    def file_to_stream(self, file_path, stream_path):
-        cmd = [self.FILE_TO_STREAM, file_path, '>', stream_path]
-        self.run_shell_command(cmd)
-
-    @contextlib.contextmanager
-    def flight_server(self, scenario_name=None):
-        cmd = self.FLIGHT_SERVER_CMD + ['-port=0']
-        if scenario_name:
-            cmd = cmd + ["-scenario", scenario_name]
-        if self.debug:
-            log(' '.join(cmd))
-        server = subprocess.Popen(cmd,
-                                  stdout=subprocess.PIPE,
-                                  stderr=subprocess.PIPE)
-        try:
-            output = server.stdout.readline().decode()
-            if not output.startswith("Server listening on localhost:"):
-                server.kill()
-                out, err = server.communicate()
-                raise RuntimeError(
-                    "Flight-C++ server did not start properly, "
-                    "stdout:\n{}\n\nstderr:\n{}\n"
-                    .format(output + out.decode(), err.decode()))
-            port = int(output.split(":")[1])
-            yield port
-        finally:
-            server.kill()
-            server.wait(5)
-
-    def flight_request(self, port, json_path=None, scenario_name=None):
-        cmd = self.FLIGHT_CLIENT_CMD + [
-            '-port=' + str(port),
-        ]
-        if json_path:
-            cmd.extend(('-path', json_path))
-        elif scenario_name:
-            cmd.extend(('-scenario', scenario_name))
-        else:
-            raise TypeError("Must provide one of json_path or scenario_name")
-
-        if self.debug:
-            log(' '.join(cmd))
-        run_cmd(cmd)
diff --git a/dev/archery/archery/integration/tester_go.py b/dev/archery/archery/integration/tester_go.py
deleted file mode 100644
index ea799c5..0000000
--- a/dev/archery/archery/integration/tester_go.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import os
-
-from .tester import Tester
-from .util import run_cmd, log
-
-
-class GoTester(Tester):
-    PRODUCER = True
-    CONSUMER = True
-
-    # FIXME(sbinet): revisit for Go modules
-    HOME = os.getenv('HOME', '~')
-    GOPATH = os.getenv('GOPATH', os.path.join(HOME, 'go'))
-    GOBIN = os.environ.get('GOBIN', os.path.join(GOPATH, 'bin'))
-
-    GO_INTEGRATION_EXE = os.path.join(GOBIN, 'arrow-json-integration-test')
-    STREAM_TO_FILE = os.path.join(GOBIN, 'arrow-stream-to-file')
-    FILE_TO_STREAM = os.path.join(GOBIN, 'arrow-file-to-stream')
-
-    name = 'Go'
-
-    def _run(self, arrow_path=None, json_path=None, command='VALIDATE'):
-        cmd = [self.GO_INTEGRATION_EXE]
-
-        if arrow_path is not None:
-            cmd.extend(['-arrow', arrow_path])
-
-        if json_path is not None:
-            cmd.extend(['-json', json_path])
-
-        cmd.extend(['-mode', command])
-
-        if self.debug:
-            log(' '.join(cmd))
-
-        run_cmd(cmd)
-
-    def validate(self, json_path, arrow_path):
-        return self._run(arrow_path, json_path, 'VALIDATE')
-
-    def json_to_file(self, json_path, arrow_path):
-        return self._run(arrow_path, json_path, 'JSON_TO_ARROW')
-
-    def stream_to_file(self, stream_path, file_path):
-        cmd = [self.STREAM_TO_FILE, '<', stream_path, '>', file_path]
-        self.run_shell_command(cmd)
-
-    def file_to_stream(self, file_path, stream_path):
-        cmd = [self.FILE_TO_STREAM, file_path, '>', stream_path]
-        self.run_shell_command(cmd)
diff --git a/dev/archery/archery/integration/tester_java.py b/dev/archery/archery/integration/tester_java.py
deleted file mode 100644
index f283f6c..0000000
--- a/dev/archery/archery/integration/tester_java.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import contextlib
-import os
-import subprocess
-
-from .tester import Tester
-from .util import run_cmd, ARROW_ROOT_DEFAULT, log
-
-
-def load_version_from_pom():
-    import xml.etree.ElementTree as ET
-    tree = ET.parse(os.path.join(ARROW_ROOT_DEFAULT, 'java', 'pom.xml'))
-    tag_pattern = '{http://maven.apache.org/POM/4.0.0}version'
-    version_tag = list(tree.getroot().findall(tag_pattern))[0]
-    return version_tag.text
-
-
-class JavaTester(Tester):
-    PRODUCER = True
-    CONSUMER = True
-    FLIGHT_SERVER = True
-    FLIGHT_CLIENT = True
-
-    JAVA_OPTS = ['-Dio.netty.tryReflectionSetAccessible=true',
-                 '-Darrow.struct.conflict.policy=CONFLICT_APPEND']
-
-    _arrow_version = load_version_from_pom()
-    ARROW_TOOLS_JAR = os.environ.get(
-        'ARROW_JAVA_INTEGRATION_JAR',
-        os.path.join(ARROW_ROOT_DEFAULT,
-                     'java/tools/target/arrow-tools-{}-'
-                     'jar-with-dependencies.jar'.format(_arrow_version)))
-    ARROW_FLIGHT_JAR = os.environ.get(
-        'ARROW_FLIGHT_JAVA_INTEGRATION_JAR',
-        os.path.join(ARROW_ROOT_DEFAULT,
-                     'java/flight/flight-core/target/flight-core-{}-'
-                     'jar-with-dependencies.jar'.format(_arrow_version)))
-    ARROW_FLIGHT_SERVER = ('org.apache.arrow.flight.example.integration.'
-                           'IntegrationTestServer')
-    ARROW_FLIGHT_CLIENT = ('org.apache.arrow.flight.example.integration.'
-                           'IntegrationTestClient')
-
-    name = 'Java'
-
-    def _run(self, arrow_path=None, json_path=None, command='VALIDATE'):
-        cmd = ['java'] + self.JAVA_OPTS + \
-            ['-cp', self.ARROW_TOOLS_JAR, 'org.apache.arrow.tools.Integration']
-
-        if arrow_path is not None:
-            cmd.extend(['-a', arrow_path])
-
-        if json_path is not None:
-            cmd.extend(['-j', json_path])
-
-        cmd.extend(['-c', command])
-
-        if self.debug:
-            log(' '.join(cmd))
-
-        run_cmd(cmd)
-
-    def validate(self, json_path, arrow_path):
-        return self._run(arrow_path, json_path, 'VALIDATE')
-
-    def json_to_file(self, json_path, arrow_path):
-        return self._run(arrow_path, json_path, 'JSON_TO_ARROW')
-
-    def stream_to_file(self, stream_path, file_path):
-        cmd = ['java'] + self.JAVA_OPTS + \
-            ['-cp', self.ARROW_TOOLS_JAR,
-             'org.apache.arrow.tools.StreamToFile', stream_path, file_path]
-        if self.debug:
-            log(' '.join(cmd))
-        run_cmd(cmd)
-
-    def file_to_stream(self, file_path, stream_path):
-        cmd = ['java'] + self.JAVA_OPTS + \
-            ['-cp', self.ARROW_TOOLS_JAR,
-             'org.apache.arrow.tools.FileToStream', file_path, stream_path]
-        if self.debug:
-            log(' '.join(cmd))
-        run_cmd(cmd)
-
-    def flight_request(self, port, json_path=None, scenario_name=None):
-        cmd = ['java'] + self.JAVA_OPTS + \
-            ['-cp', self.ARROW_FLIGHT_JAR, self.ARROW_FLIGHT_CLIENT,
-             '-port', str(port)]
-
-        if json_path:
-            cmd.extend(('-j', json_path))
-        elif scenario_name:
-            cmd.extend(('-scenario', scenario_name))
-        else:
-            raise TypeError("Must provide one of json_path or scenario_name")
-
-        if self.debug:
-            log(' '.join(cmd))
-        run_cmd(cmd)
-
-    @contextlib.contextmanager
-    def flight_server(self, scenario_name=None):
-        cmd = ['java'] + self.JAVA_OPTS + \
-            ['-cp', self.ARROW_FLIGHT_JAR, self.ARROW_FLIGHT_SERVER,
-             '-port', '0']
-        if scenario_name:
-            cmd.extend(('-scenario', scenario_name))
-        if self.debug:
-            log(' '.join(cmd))
-        server = subprocess.Popen(cmd, stdout=subprocess.PIPE,
-                                  stderr=subprocess.PIPE)
-        try:
-            output = server.stdout.readline().decode()
-            if not output.startswith("Server listening on localhost:"):
-                server.kill()
-                out, err = server.communicate()
-                raise RuntimeError(
-                    "Flight-Java server did not start properly, "
-                    "stdout:\n{}\n\nstderr:\n{}\n"
-                    .format(output + out.decode(), err.decode()))
-            port = int(output.split(":")[1])
-            yield port
-        finally:
-            server.kill()
-            server.wait(5)
diff --git a/dev/archery/archery/integration/tester_js.py b/dev/archery/archery/integration/tester_js.py
deleted file mode 100644
index e24eec0..0000000
--- a/dev/archery/archery/integration/tester_js.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import os
-
-from .tester import Tester
-from .util import run_cmd, ARROW_ROOT_DEFAULT, log
-
-
-class JSTester(Tester):
-    PRODUCER = True
-    CONSUMER = True
-
-    EXE_PATH = os.path.join(ARROW_ROOT_DEFAULT, 'js/bin')
-    VALIDATE = os.path.join(EXE_PATH, 'integration.js')
-    JSON_TO_ARROW = os.path.join(EXE_PATH, 'json-to-arrow.js')
-    STREAM_TO_FILE = os.path.join(EXE_PATH, 'stream-to-file.js')
-    FILE_TO_STREAM = os.path.join(EXE_PATH, 'file-to-stream.js')
-
-    name = 'JS'
-
-    def _run(self, exe_cmd, arrow_path=None, json_path=None,
-             command='VALIDATE'):
-        cmd = [exe_cmd]
-
-        if arrow_path is not None:
-            cmd.extend(['-a', arrow_path])
-
-        if json_path is not None:
-            cmd.extend(['-j', json_path])
-
-        cmd.extend(['--mode', command])
-
-        if self.debug:
-            log(' '.join(cmd))
-
-        run_cmd(cmd)
-
-    def validate(self, json_path, arrow_path):
-        return self._run(self.VALIDATE, arrow_path, json_path, 'VALIDATE')
-
-    def json_to_file(self, json_path, arrow_path):
-        cmd = ['node',
-               '--no-warnings', self.JSON_TO_ARROW,
-               '-a', arrow_path,
-               '-j', json_path]
-        self.run_shell_command(cmd)
-
-    def stream_to_file(self, stream_path, file_path):
-        cmd = ['node', '--no-warnings', self.STREAM_TO_FILE,
-               '<', stream_path,
-               '>', file_path]
-        self.run_shell_command(cmd)
-
-    def file_to_stream(self, file_path, stream_path):
-        cmd = ['node', '--no-warnings', self.FILE_TO_STREAM,
-               '<', file_path,
-               '>', stream_path]
-        self.run_shell_command(cmd)
diff --git a/dev/archery/archery/integration/tester_rust.py b/dev/archery/archery/integration/tester_rust.py
deleted file mode 100644
index bca80eb..0000000
--- a/dev/archery/archery/integration/tester_rust.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import contextlib
-import os
-import subprocess
-
-from .tester import Tester
-from .util import run_cmd, ARROW_ROOT_DEFAULT, log
-
-
-class RustTester(Tester):
-    PRODUCER = True
-    CONSUMER = True
-    FLIGHT_SERVER = True
-    FLIGHT_CLIENT = True
-
-    EXE_PATH = os.path.join(ARROW_ROOT_DEFAULT, 'rust/target/debug')
-
-    RUST_INTEGRATION_EXE = os.path.join(EXE_PATH,
-                                        'arrow-json-integration-test')
-    STREAM_TO_FILE = os.path.join(EXE_PATH, 'arrow-stream-to-file')
-    FILE_TO_STREAM = os.path.join(EXE_PATH, 'arrow-file-to-stream')
-
-    FLIGHT_SERVER_CMD = [
-        os.path.join(EXE_PATH, 'flight-test-integration-server')]
-    FLIGHT_CLIENT_CMD = [
-        os.path.join(EXE_PATH, 'flight-test-integration-client'),
-        "--host", "localhost"]
-
-    name = 'Rust'
-
-    def _run(self, arrow_path=None, json_path=None, command='VALIDATE'):
-        cmd = [self.RUST_INTEGRATION_EXE, '--integration']
-
-        if arrow_path is not None:
-            cmd.append('--arrow=' + arrow_path)
-
-        if json_path is not None:
-            cmd.append('--json=' + json_path)
-
-        cmd.append('--mode=' + command)
-
-        if self.debug:
-            log(' '.join(cmd))
-
-        run_cmd(cmd)
-
-    def validate(self, json_path, arrow_path):
-        return self._run(arrow_path, json_path, 'VALIDATE')
-
-    def json_to_file(self, json_path, arrow_path):
-        return self._run(arrow_path, json_path, 'JSON_TO_ARROW')
-
-    def stream_to_file(self, stream_path, file_path):
-        cmd = [self.STREAM_TO_FILE, '<', stream_path, '>', file_path]
-        self.run_shell_command(cmd)
-
-    def file_to_stream(self, file_path, stream_path):
-        cmd = [self.FILE_TO_STREAM, file_path, '>', stream_path]
-        self.run_shell_command(cmd)
-
-    @contextlib.contextmanager
-    def flight_server(self, scenario_name=None):
-        cmd = self.FLIGHT_SERVER_CMD + ['--port=0']
-        if scenario_name:
-            cmd = cmd + ["--scenario", scenario_name]
-        if self.debug:
-            log(' '.join(cmd))
-        server = subprocess.Popen(cmd,
-                                  stdout=subprocess.PIPE,
-                                  stderr=subprocess.PIPE)
-        try:
-            output = server.stdout.readline().decode()
-            if not output.startswith("Server listening on localhost:"):
-                server.kill()
-                out, err = server.communicate()
-                raise RuntimeError(
-                    "Flight-Rust server did not start properly, "
-                    "stdout:\n{}\n\nstderr:\n{}\n"
-                    .format(output + out.decode(), err.decode()))
-            port = int(output.split(":")[1])
-            yield port
-        finally:
-            server.kill()
-            server.wait(5)
-
-    def flight_request(self, port, json_path=None, scenario_name=None):
-        cmd = self.FLIGHT_CLIENT_CMD + [
-            '--port=' + str(port),
-        ]
-        if json_path:
-            cmd.extend(('--path', json_path))
-        elif scenario_name:
-            cmd.extend(('--scenario', scenario_name))
-        else:
-            raise TypeError("Must provide one of json_path or scenario_name")
-
-        if self.debug:
-            log(' '.join(cmd))
-        run_cmd(cmd)
diff --git a/dev/archery/archery/integration/util.py b/dev/archery/archery/integration/util.py
deleted file mode 100644
index a4c4982..0000000
--- a/dev/archery/archery/integration/util.py
+++ /dev/null
@@ -1,166 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import contextlib
-import io
-import os
-import random
-import socket
-import subprocess
-import sys
-import threading
-import uuid
-
-import numpy as np
-
-
-def guid():
-    return uuid.uuid4().hex
-
-
-# SKIP categories
-SKIP_ARROW = 'arrow'
-SKIP_FLIGHT = 'flight'
-
-ARROW_ROOT_DEFAULT = os.environ.get(
-    'ARROW_ROOT',
-    os.path.abspath(__file__).rsplit("/", 5)[0]
-)
-
-
-class _Printer:
-    """
-    A print()-providing object that can override the stream output on
-    a per-thread basis.
-    """
-
-    def __init__(self):
-        self._tls = threading.local()
-
-    def _get_stdout(self):
-        try:
-            return self._tls.stdout
-        except AttributeError:
-            self._tls.stdout = sys.stdout
-            self._tls.corked = False
-            return self._tls.stdout
-
-    def print(self, *args, **kwargs):
-        """
-        A variant of print() that writes to a thread-local stream.
-        """
-        print(*args, file=self._get_stdout(), **kwargs)
-
-    @property
-    def stdout(self):
-        """
-        A thread-local stdout wrapper that may be temporarily buffered
-        using `cork()`.
-        """
-        return self._get_stdout()
-
-    @contextlib.contextmanager
-    def cork(self):
-        """
-        Temporarily buffer this thread's stream and write out its contents
-        at the end of the context manager.  Useful to avoid interleaved
-        output when multiple threads output progress information.
-        """
-        outer_stdout = self._get_stdout()
-        assert not self._tls.corked, "reentrant call"
-        inner_stdout = self._tls.stdout = io.StringIO()
-        self._tls.corked = True
-        try:
-            yield
-        finally:
-            self._tls.stdout = outer_stdout
-            self._tls.corked = False
-            outer_stdout.write(inner_stdout.getvalue())
-            outer_stdout.flush()
-
-
-printer = _Printer()
-log = printer.print
-
-
-_RAND_CHARS = np.array(list("abcdefghijklmnop123456Ârrôwµ£°€矢"), dtype="U")
-
-
-def random_utf8(nchars):
-    """
-    Generate one random UTF8 string.
-    """
-    return ''.join(np.random.choice(_RAND_CHARS, nchars))
-
-
-def random_bytes(nbytes):
-    """
-    Generate one random binary string.
-    """
-    # NOTE getrandbits(0) fails
-    if nbytes > 0:
-        return random.getrandbits(nbytes * 8).to_bytes(nbytes,
-                                                       byteorder='little')
-    else:
-        return b""
-
-
-def tobytes(o):
-    if isinstance(o, str):
-        return o.encode('utf8')
-    return o
-
-
-def frombytes(o):
-    if isinstance(o, bytes):
-        return o.decode('utf8')
-    return o
-
-
-def run_cmd(cmd):
-    if isinstance(cmd, str):
-        cmd = cmd.split(' ')
-
-    try:
-        output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
-    except subprocess.CalledProcessError as e:
-        # this avoids hiding the stdout / stderr of failed processes
-        sio = io.StringIO()
-        print('Command failed:', " ".join(cmd), file=sio)
-        print('With output:', file=sio)
-        print('--------------', file=sio)
-        print(frombytes(e.output), file=sio)
-        print('--------------', file=sio)
-        raise RuntimeError(sio.getvalue())
-
-    return frombytes(output)
-
-
-# Adapted from CPython
-def find_unused_port(family=socket.AF_INET, socktype=socket.SOCK_STREAM):
-    """Returns an unused port that should be suitable for binding.  This is
-    achieved by creating a temporary socket with the same family and type as
-    the 'sock' parameter (default is AF_INET, SOCK_STREAM), and binding it to
-    the specified host address (defaults to 0.0.0.0) with the port set to 0,
-    eliciting an unused ephemeral port from the OS.  The temporary socket is
-    then closed and deleted, and the ephemeral port is returned.
-    """
-    with socket.socket(family, socktype) as tempsock:
-        tempsock.bind(('', 0))
-        port = tempsock.getsockname()[1]
-    del tempsock
-    return port
diff --git a/dev/archery/archery/lang/__init__.py b/dev/archery/archery/lang/__init__.py
deleted file mode 100644
index 13a8339..0000000
--- a/dev/archery/archery/lang/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
diff --git a/dev/archery/archery/lang/cpp.py b/dev/archery/archery/lang/cpp.py
deleted file mode 100644
index 045d23b..0000000
--- a/dev/archery/archery/lang/cpp.py
+++ /dev/null
@@ -1,295 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import os
-
-from ..utils.cmake import CMakeDefinition
-
-
-def truthifier(value):
-    return "ON" if value else "OFF"
-
-
-def or_else(value, default):
-    return value if value else default
-
-
-def coalesce(value, fallback):
-    return fallback if value is None else value
-
-
-LLVM_VERSION = 7
-
-
-class CppConfiguration:
-    def __init__(self,
-
-                 # toolchain
-                 cc=None, cxx=None, cxx_flags=None,
-                 build_type=None, warn_level=None,
-                 cpp_package_prefix=None, install_prefix=None, use_conda=None,
-                 build_static=False, build_shared=True,
-                 # tests & examples
-                 with_tests=None, with_benchmarks=None, with_examples=None,
-                 with_integration=None,
-                 # static checks
-                 use_asan=None, use_tsan=None, use_ubsan=None,
-                 with_fuzzing=None,
-                 # Components
-                 with_compute=None, with_csv=None, with_cuda=None,
-                 with_dataset=None, with_filesystem=None, with_flight=None,
-                 with_gandiva=None, with_hdfs=None, with_hiveserver2=None,
-                 with_ipc=True, with_json=None, with_jni=None,
-                 with_mimalloc=None,
-                 with_parquet=None, with_plasma=None, with_python=True,
-                 with_r=None, with_s3=None,
-                 # Compressions
-                 with_brotli=None, with_bz2=None, with_lz4=None,
-                 with_snappy=None, with_zlib=None, with_zstd=None,
-                 # extras
-                 with_lint_only=False,
-                 use_gold_linker=True,
-                 simd_level="SSE4_2",
-                 cmake_extras=None):
-        self._cc = cc
-        self._cxx = cxx
-        self.cxx_flags = cxx_flags
-
-        self._build_type = build_type
-        self.warn_level = warn_level
-        self._install_prefix = install_prefix
-        self._package_prefix = cpp_package_prefix
-        self._use_conda = use_conda
-        self.build_static = build_static
-        self.build_shared = build_shared
-
-        self.with_tests = with_tests
-        self.with_benchmarks = with_benchmarks
-        self.with_examples = with_examples
-        self.with_integration = with_integration
-
-        self.use_asan = use_asan
-        self.use_tsan = use_tsan
-        self.use_ubsan = use_ubsan
-        self.with_fuzzing = with_fuzzing
-
-        self.with_compute = with_compute
-        self.with_csv = with_csv
-        self.with_cuda = with_cuda
-        self.with_dataset = with_dataset
-        self.with_filesystem = with_filesystem
-        self.with_flight = with_flight
-        self.with_gandiva = with_gandiva
-        self.with_hdfs = with_hdfs
-        self.with_hiveserver2 = with_hiveserver2
-        self.with_ipc = with_ipc
-        self.with_json = with_json
-        self.with_jni = with_jni
-        self.with_mimalloc = with_mimalloc
-        self.with_parquet = with_parquet
-        self.with_plasma = with_plasma
-        self.with_python = with_python
-        self.with_r = with_r
-        self.with_s3 = with_s3
-
-        self.with_brotli = with_brotli
-        self.with_bz2 = with_bz2
-        self.with_lz4 = with_lz4
-        self.with_snappy = with_snappy
-        self.with_zlib = with_zlib
-        self.with_zstd = with_zstd
-
-        self.with_lint_only = with_lint_only
-        self.use_gold_linker = use_gold_linker
-        self.simd_level = simd_level
-
-        self.cmake_extras = cmake_extras
-
-        # Fixup required dependencies by providing sane defaults if the caller
-        # didn't specify the option.
-        if self.with_r:
-            self.with_csv = coalesce(with_csv, True)
-            self.with_dataset = coalesce(with_dataset, True)
-            self.with_filesystem = coalesce(with_filesystem, True)
-            self.with_ipc = coalesce(with_ipc, True)
-            self.with_json = coalesce(with_json, True)
-            self.with_parquet = coalesce(with_parquet, True)
-
-        if self.with_python:
-            self.with_zlib = coalesce(with_zlib, True)
-            self.with_lz4 = coalesce(with_lz4, True)
-
-        if self.with_dataset:
-            self.with_filesystem = coalesce(with_filesystem, True)
-            self.with_parquet = coalesce(with_parquet, True)
-
-        if self.with_parquet:
-            self.with_snappy = coalesce(with_snappy, True)
-
-    @property
-    def build_type(self):
-        if self._build_type:
-            return self._build_type
-
-        if self.with_fuzzing:
-            return "relwithdebinfo"
-
-        return "release"
-
-    @property
-    def cc(self):
-        if self._cc:
-            return self._cc
-
-        if self.with_fuzzing:
-            return "clang-{}".format(LLVM_VERSION)
-
-        return None
-
-    @property
-    def cxx(self):
-        if self._cxx:
-            return self._cxx
-
-        if self.with_fuzzing:
-            return "clang++-{}".format(LLVM_VERSION)
-
-        return None
-
-    def _gen_defs(self):
-        if self.cxx_flags:
-            yield ("ARROW_CXXFLAGS", self.cxx_flags)
-
-        yield ("CMAKE_EXPORT_COMPILE_COMMANDS", truthifier(True))
-        yield ("CMAKE_BUILD_TYPE", self.build_type)
-        yield ("CMAKE_UNITY_BUILD", True)
-
-        if not self.with_lint_only:
-            yield ("BUILD_WARNING_LEVEL",
-                   or_else(self.warn_level, "production"))
-
-        # if not ctx.quiet:
-        #   yield ("ARROW_VERBOSE_THIRDPARTY_BUILD", "ON")
-
-        maybe_prefix = self.install_prefix
-        if maybe_prefix:
-            yield ("CMAKE_INSTALL_PREFIX", maybe_prefix)
-
-        if self._package_prefix is not None:
-            yield ("ARROW_DEPENDENCY_SOURCE", "SYSTEM")
-            yield ("ARROW_PACKAGE_PREFIX", self._package_prefix)
-
-        yield ("ARROW_BUILD_STATIC", truthifier(self.build_static))
-        yield ("ARROW_BUILD_SHARED", truthifier(self.build_shared))
-
-        # Tests and benchmarks
-        yield ("ARROW_BUILD_TESTS", truthifier(self.with_tests))
-        yield ("ARROW_BUILD_BENCHMARKS", truthifier(self.with_benchmarks))
-        yield ("ARROW_BUILD_EXAMPLES", truthifier(self.with_examples))
-        yield ("ARROW_BUILD_INTEGRATION", truthifier(self.with_integration))
-
-        # Static checks
-        yield ("ARROW_USE_ASAN", truthifier(self.use_asan))
-        yield ("ARROW_USE_TSAN", truthifier(self.use_tsan))
-        yield ("ARROW_USE_UBSAN", truthifier(self.use_ubsan))
-        yield ("ARROW_FUZZING", truthifier(self.with_fuzzing))
-
-        # Components
-        yield ("ARROW_COMPUTE", truthifier(self.with_compute))
-        yield ("ARROW_CSV", truthifier(self.with_csv))
-        yield ("ARROW_CUDA", truthifier(self.with_cuda))
-        yield ("ARROW_DATASET", truthifier(self.with_dataset))
-        yield ("ARROW_FILESYSTEM", truthifier(self.with_filesystem))
-        yield ("ARROW_FLIGHT", truthifier(self.with_flight))
-        yield ("ARROW_GANDIVA", truthifier(self.with_gandiva))
-        yield ("ARROW_PARQUET", truthifier(self.with_parquet))
-        yield ("ARROW_HDFS", truthifier(self.with_hdfs))
-        yield ("ARROW_HIVESERVER2", truthifier(self.with_hiveserver2))
-        yield ("ARROW_IPC", truthifier(self.with_ipc))
-        yield ("ARROW_JSON", truthifier(self.with_json))
-        yield ("ARROW_JNI", truthifier(self.with_jni))
-        yield ("ARROW_MIMALLOC", truthifier(self.with_mimalloc))
-        yield ("ARROW_PLASMA", truthifier(self.with_plasma))
-        yield ("ARROW_PYTHON", truthifier(self.with_python))
-        yield ("ARROW_S3", truthifier(self.with_s3))
-
-        # Compressions
-        yield ("ARROW_WITH_BROTLI", truthifier(self.with_brotli))
-        yield ("ARROW_WITH_BZ2", truthifier(self.with_bz2))
-        yield ("ARROW_WITH_LZ4", truthifier(self.with_lz4))
-        yield ("ARROW_WITH_SNAPPY", truthifier(self.with_snappy))
-        yield ("ARROW_WITH_ZLIB", truthifier(self.with_zlib))
-        yield ("ARROW_WITH_ZSTD", truthifier(self.with_zstd))
-
-        yield ("ARROW_LINT_ONLY", truthifier(self.with_lint_only))
-
-        # Some configurations don't like gnu gold linker.
-        broken_with_gold_ld = [self.with_fuzzing, self.with_gandiva]
-        if self.use_gold_linker and not any(broken_with_gold_ld):
-            yield ("ARROW_USE_LD_GOLD", truthifier(self.use_gold_linker))
-        yield ("ARROW_SIMD_LEVEL", or_else(self.simd_level, "SSE4_2"))
-
-        # Detect custom conda toolchain
-        if self.use_conda:
-            for d, v in [('CMAKE_AR', 'AR'), ('CMAKE_RANLIB', 'RANLIB')]:
-                v = os.environ.get(v)
-                if v:
-                    yield (d, v)
-
-    @property
-    def install_prefix(self):
-        if self._install_prefix:
-            return self._install_prefix
-
-        if self.use_conda:
-            return os.environ.get("CONDA_PREFIX")
-
-        return None
-
-    @property
-    def use_conda(self):
-        # If the user didn't specify a preference, guess via environment
-        if self._use_conda is None:
-            return os.environ.get("CONDA_PREFIX") is not None
-
-        return self._use_conda
-
-    @property
-    def definitions(self):
-        extras = list(self.cmake_extras) if self.cmake_extras else []
-        definitions = ["-D{}={}".format(d[0], d[1]) for d in self._gen_defs()]
-        return definitions + extras
-
-    @property
-    def environment(self):
-        env = os.environ.copy()
-
-        if self.cc:
-            env["CC"] = self.cc
-
-        if self.cxx:
-            env["CXX"] = self.cxx
-
-        return env
-
-
-class CppCMakeDefinition(CMakeDefinition):
-    def __init__(self, source, conf, **kwargs):
-        self.configuration = conf
-        super().__init__(source, **kwargs,
-                         definitions=conf.definitions, env=conf.environment,
-                         build_type=conf.build_type)
diff --git a/dev/archery/archery/lang/java.py b/dev/archery/archery/lang/java.py
deleted file mode 100644
index 24743b6..0000000
--- a/dev/archery/archery/lang/java.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from ..utils.command import Command, CommandStackMixin, default_bin
-
-
-class Java(Command):
-    def __init__(self, java_bin=None):
-        self.bin = default_bin(java_bin, "java")
-
-
-class Jar(CommandStackMixin, Java):
-    def __init__(self, jar, *args, **kwargs):
-        self.jar = jar
-        self.argv = ("-jar", jar)
-        Java.__init__(self, *args, **kwargs)
diff --git a/dev/archery/archery/lang/python.py b/dev/archery/archery/lang/python.py
deleted file mode 100644
index 4952d5f..0000000
--- a/dev/archery/archery/lang/python.py
+++ /dev/null
@@ -1,218 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import inspect
-import tokenize
-from contextlib import contextmanager
-
-try:
-    from numpydoc.validate import Docstring, validate
-except ImportError:
-    have_numpydoc = False
-else:
-    have_numpydoc = True
-
-from ..utils.command import Command, capture_stdout, default_bin
-
-
-class Flake8(Command):
-    def __init__(self, flake8_bin=None):
-        self.bin = default_bin(flake8_bin, "flake8")
-
-
-class Autopep8(Command):
-    def __init__(self, autopep8_bin=None):
-        self.bin = default_bin(autopep8_bin, "autopep8")
-
-    @capture_stdout()
-    def run_captured(self, *args, **kwargs):
-        return self.run(*args, **kwargs)
-
-
-def _tokenize_signature(s):
-    lines = s.encode('ascii').splitlines()
-    generator = iter(lines).__next__
-    return tokenize.tokenize(generator)
-
-
-def _convert_typehint(tokens):
-    names = []
-    opening_bracket_reached = False
-    for token in tokens:
-        # omit the tokens before the opening bracket
-        if not opening_bracket_reached:
-            if token.string == '(':
-                opening_bracket_reached = True
-            else:
-                continue
-
-        if token.type == 1:  # type 1 means NAME token
-            names.append(token)
-        else:
-            if len(names) == 1:
-                yield (names[0].type, names[0].string)
-            elif len(names) == 2:
-                # two "NAME" tokens follow each other which means a cython
-                # typehint like `bool argument`, so remove the typehint
-                # note that we could convert it to python typehints, but hints
-                # are not supported by _signature_fromstr
-                yield (names[1].type, names[1].string)
-            elif len(names) > 2:
-                raise ValueError('More than two NAME tokens follow each other')
-            names = []
-            yield (token.type, token.string)
-
-
-def inspect_signature(obj):
-    """
-    Custom signature inspection primarily for cython generated callables.
-
-    Cython puts the signatures to the first line of the docstrings, which we
-    can reuse to parse the python signature from, but some gymnastics are
-    required, like removing the cython typehints.
-
-    It converts the cython signature:
-        array(obj, type=None, mask=None, size=None, from_pandas=None,
-              bool safe=True, MemoryPool memory_pool=None)
-    To:
-        <Signature (obj, type=None, mask=None, size=None, from_pandas=None,
-                    safe=True, memory_pool=None)>
-    """
-    cython_signature = obj.__doc__.splitlines()[0]
-    cython_tokens = _tokenize_signature(cython_signature)
-    python_tokens = _convert_typehint(cython_tokens)
-    python_signature = tokenize.untokenize(python_tokens)
-    return inspect._signature_fromstr(inspect.Signature, obj, python_signature)
-
-
-class NumpyDoc:
-
-    def __init__(self, symbols=None):
-        if not have_numpydoc:
-            raise RuntimeError(
-                'Numpydoc is not available, install the development version '
-                'with command: pip install '
-                'git+https://github.com/numpy/numpydoc'
-            )
-        self.symbols = set(symbols or {'pyarrow'})
-
-    def traverse(self, fn, obj, from_package):
-        """Apply a function on publicly exposed API components.
-
-        Recursively iterates over the members of the passed object. It omits
-        any '_' prefixed and thirdparty (non pyarrow) symbols.
-
-        Parameters
-        ----------
-        obj : Any
-        from_package : string, default 'pyarrow'
-            Predicate to only consider objects from this package.
-        """
-        todo = [obj]
-        seen = set()
-
-        while todo:
-            obj = todo.pop()
-            if obj in seen:
-                continue
-            else:
-                seen.add(obj)
-
-            fn(obj)
-
-            for name in dir(obj):
-                if name.startswith('_'):
-                    continue
-
-                member = getattr(obj, name)
-                module = getattr(member, '__module__', None)
-                if not (module and module.startswith(from_package)):
-                    continue
-
-                todo.append(member)
-
-    @contextmanager
-    def _apply_patches(self):
-        """
-        Patch Docstring class to bypass loading already loaded python objects.
-        """
-        orig_load_obj = Docstring._load_obj
-        orig_signature = inspect.signature
-
-        @staticmethod
-        def _load_obj(obj):
-            # By default it expects a qualname and import the object, but we
-            # have already loaded object after the API traversal.
-            if isinstance(obj, str):
-                return orig_load_obj(obj)
-            else:
-                return obj
-
-        def signature(obj):
-            # inspect.signature tries to parse __text_signature__ if other
-            # properties like __signature__ doesn't exists, but cython
-            # doesn't set that property despite that embedsignature cython
-            # directive is set. The only way to inspect a cython compiled
-            # callable's signature to parse it from __doc__ while
-            # embedsignature directive is set during the build phase.
-            # So path inspect.signature function to attempt to parse the first
-            # line of callable.__doc__ as a signature.
-            try:
-                return orig_signature(obj)
-            except Exception as orig_error:
-                try:
-                    return inspect_signature(obj)
-                except Exception:
-                    raise orig_error
-
-        try:
-            Docstring._load_obj = _load_obj
-            inspect.signature = signature
-            yield
-        finally:
-            Docstring._load_obj = orig_load_obj
-            inspect.signature = orig_signature
-
-    def validate(self, from_package='', allow_rules=None,
-                 disallow_rules=None):
-        results = []
-
-        def callback(obj):
-            result = validate(obj)
-
-            errors = []
-            for errcode, errmsg in result.get('errors', []):
-                if allow_rules and errcode not in allow_rules:
-                    continue
-                if disallow_rules and errcode in disallow_rules:
-                    continue
-                errors.append((errcode, errmsg))
-
-            if len(errors):
-                result['errors'] = errors
-                results.append((obj, result))
-
-        with self._apply_patches():
-            for symbol in self.symbols:
-                try:
-                    obj = Docstring._load_obj(symbol)
-                except (ImportError, AttributeError):
-                    print('{} is not available for import'.format(symbol))
-                else:
-                    self.traverse(callback, obj, from_package=from_package)
-
-        return results
diff --git a/dev/archery/archery/lang/rust.py b/dev/archery/archery/lang/rust.py
deleted file mode 100644
index b1d765b..0000000
--- a/dev/archery/archery/lang/rust.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from ..utils.command import Command, default_bin
-
-
-class Cargo(Command):
-    def __init__(self, cargo_bin=None):
-        self.bin = default_bin(cargo_bin, "cargo")
diff --git a/dev/archery/archery/release.py b/dev/archery/archery/release.py
deleted file mode 100644
index acfe3fc..0000000
--- a/dev/archery/archery/release.py
+++ /dev/null
@@ -1,535 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from collections import defaultdict
-import functools
-import os
-import re
-import pathlib
-import shelve
-import warnings
-
-from git import Repo
-from jira import JIRA
-from semver import VersionInfo as SemVer
-
-from .utils.source import ArrowSources
-from .utils.report import JinjaReport
-
-
-def cached_property(fn):
-    return property(functools.lru_cache(maxsize=1)(fn))
-
-
-class Version(SemVer):
-
-    __slots__ = ('released', 'release_date')
-
-    def __init__(self, released=False, release_date=None, **kwargs):
-        super().__init__(**kwargs)
-        self.released = released
-        self.release_date = release_date
-
-    @classmethod
-    def parse(cls, version, **kwargs):
-        return cls(**SemVer.parse(version).to_dict(), **kwargs)
-
-    @classmethod
-    def from_jira(cls, jira_version):
-        return cls.parse(
-            jira_version.name,
-            released=jira_version.released,
-            release_date=getattr(jira_version, 'releaseDate', None)
-        )
-
-
-class Issue:
-
-    def __init__(self, key, type, summary):
-        self.key = key
-        self.type = type
-        self.summary = summary
-
-    @classmethod
-    def from_jira(cls, jira_issue):
-        return cls(
-            key=jira_issue.key,
-            type=jira_issue.fields.issuetype.name,
-            summary=jira_issue.fields.summary
-        )
-
-    @property
-    def project(self):
-        return self.key.split('-')[0]
-
-    @property
-    def number(self):
-        return int(self.key.split('-')[1])
-
-
-class Jira(JIRA):
-
-    def __init__(self, user=None, password=None,
-                 url='https://issues.apache.org/jira'):
-        user = user or os.environ.get('APACHE_JIRA_USER')
-        password = password or os.environ.get('APACHE_JIRA_PASSWORD')
-        super().__init__(url, basic_auth=(user, password))
-
-    def project_version(self, version_string, project='ARROW'):
-        # query version from jira to populated with additional metadata
-        versions = {str(v): v for v in self.project_versions(project)}
-        return versions[version_string]
-
-    def project_versions(self, project):
-        versions = []
-        for v in super().project_versions(project):
-            try:
-                versions.append(Version.from_jira(v))
-            except ValueError:
-                # ignore invalid semantic versions like JS-0.4.0
-                continue
-        return sorted(versions, reverse=True)
-
-    def issue(self, key):
-        return Issue.from_jira(super().issue(key))
-
-    def project_issues(self, version, project='ARROW'):
-        query = "project={} AND fixVersion={}".format(project, version)
-        issues = super().search_issues(query, maxResults=False)
-        return list(map(Issue.from_jira, issues))
-
-
-class CachedJira:
-
-    def __init__(self, cache_path, jira=None):
-        self.jira = jira or Jira()
-        self.cache_path = cache_path
-
-    def __getattr__(self, name):
-        attr = getattr(self.jira, name)
-        return self._cached(name, attr) if callable(attr) else attr
-
-    def _cached(self, name, method):
-        def wrapper(*args, **kwargs):
-            key = str((name, args, kwargs))
-            with shelve.open(self.cache_path) as cache:
-                try:
-                    result = cache[key]
-                except KeyError:
-                    cache[key] = result = method(*args, **kwargs)
-            return result
-        return wrapper
-
-
-_TITLE_REGEX = re.compile(
-    r"(?P<issue>(?P<project>(ARROW|PARQUET))\-\d+)?\s*:?\s*"
-    r"(?P<components>\[.*\])?\s*(?P<summary>.*)"
-)
-_COMPONENT_REGEX = re.compile(r"\[([^\[\]]+)\]")
-
-
-class CommitTitle:
-
-    def __init__(self, summary, project=None, issue=None, components=None):
-        self.project = project
-        self.issue = issue
-        self.components = components or []
-        self.summary = summary
-
-    def __str__(self):
-        out = ""
-        if self.issue:
-            out += "{}: ".format(self.issue)
-        if self.components:
-            for component in self.components:
-                out += "[{}]".format(component)
-            out += " "
-        out += self.summary
-        return out
-
-    def __eq__(self, other):
-        return (
-            self.summary == other.summary and
-            self.project == other.project and
-            self.issue == other.issue and
-            self.components == other.components
-        )
-
-    def __hash__(self):
-        return hash(
-            (self.summary, self.project, self.issue, tuple(self.components))
-        )
-
-    @classmethod
-    def parse(cls, headline):
-        matches = _TITLE_REGEX.match(headline)
-        if matches is None:
-            warnings.warn(
-                "Unable to parse commit message `{}`".format(headline)
-            )
-            return CommitTitle(headline)
-
-        values = matches.groupdict()
-        components = values.get('components') or ''
-        components = _COMPONENT_REGEX.findall(components)
-
-        return CommitTitle(
-            values['summary'],
-            project=values.get('project'),
-            issue=values.get('issue'),
-            components=components
-        )
-
-
-class Commit:
-
-    def __init__(self, wrapped):
-        self._title = CommitTitle.parse(wrapped.summary)
-        self._wrapped = wrapped
-
-    def __getattr__(self, attr):
-        if hasattr(self._title, attr):
-            return getattr(self._title, attr)
-        else:
-            return getattr(self._wrapped, attr)
-
-    def __repr__(self):
-        template = '<Commit sha={!r} issue={!r} components={!r} summary={!r}>'
-        return template.format(self.hexsha, self.issue, self.components,
-                               self.summary)
-
-    @property
-    def url(self):
-        return 'https://github.com/apache/arrow/commit/{}'.format(self.hexsha)
-
-    @property
-    def title(self):
-        return self._title
-
-
-class ReleaseCuration(JinjaReport):
-    templates = {
-        'console': 'release_curation.txt.j2'
-    }
-    fields = [
-        'release',
-        'within',
-        'outside',
-        'nojira',
-        'parquet',
-        'nopatch'
-    ]
-
-
-class JiraChangelog(JinjaReport):
-    templates = {
-        'markdown': 'release_changelog.md.j2',
-        'html': 'release_changelog.html.j2'
-    }
-    fields = [
-        'release',
-        'categories'
-    ]
-
-
-class Release:
-
-    def __init__(self):
-        raise TypeError("Do not initialize Release class directly, use "
-                        "Release.from_jira(version) instead.")
-
-    def __repr__(self):
-        if self.version.released:
-            status = "released_at={!r}".format(self.version.release_date)
-        else:
-            status = "pending"
-        return "<{} {!r} {}>".format(self.__class__.__name__,
-                                     str(self.version), status)
-
-    @staticmethod
-    def from_jira(version, jira=None, repo=None):
-        if jira is None:
-            jira = Jira()
-        elif isinstance(jira, str):
-            jira = Jira(jira)
-        elif not isinstance(jira, (Jira, CachedJira)):
-            raise TypeError("`jira` argument must be a server url or a valid "
-                            "Jira instance")
-
-        if repo is None:
-            arrow = ArrowSources.find()
-            repo = Repo(arrow.path)
-        elif isinstance(repo, (str, pathlib.Path)):
-            repo = Repo(repo)
-        elif not isinstance(repo, Repo):
-            raise TypeError("`repo` argument must be a path or a valid Repo "
-                            "instance")
-
-        if isinstance(version, str):
-            version = jira.project_version(version, project='ARROW')
-        elif not isinstance(version, Version):
-            raise TypeError(version)
-
-        # decide the type of the release based on the version number
-        if version.patch == 0:
-            if version.minor == 0:
-                klass = MajorRelease
-            elif version.major == 0:
-                # handle minor releases before 1.0 as major releases
-                klass = MajorRelease
-            else:
-                klass = MinorRelease
-        else:
-            klass = PatchRelease
-
-        # prevent instantiating release object directly
-        obj = klass.__new__(klass)
-        obj.version = version
-        obj.jira = jira
-        obj.repo = repo
-
-        return obj
-
-    @property
-    def is_released(self):
-        return self.version.released
-
-    @property
-    def tag(self):
-        return "apache-arrow-{}".format(str(self.version))
-
-    @property
-    def branch(self):
-        raise NotImplementedError()
-
-    @property
-    def siblings(self):
-        """
-        Releases to consider when calculating previous and next releases.
-        """
-        raise NotImplementedError()
-
-    @cached_property
-    def previous(self):
-        # select all non-patch releases
-        position = self.siblings.index(self.version)
-        try:
-            previous = self.siblings[position + 1]
-        except IndexError:
-            # first release doesn't have a previous one
-            return None
-        else:
-            return Release.from_jira(previous, jira=self.jira, repo=self.repo)
-
-    @cached_property
-    def next(self):
-        # select all non-patch releases
-        position = self.siblings.index(self.version)
-        if position <= 0:
-            raise ValueError("There is no upcoming release set in JIRA after "
-                             "version {}".format(self.version))
-        upcoming = self.siblings[position - 1]
-        return Release.from_jira(upcoming, jira=self.jira, repo=self.repo)
-
-    @cached_property
-    def issues(self):
-        issues = self.jira.project_issues(self.version, project='ARROW')
-        return {i.key: i for i in issues}
-
-    @cached_property
-    def commits(self):
-        """
-        All commits applied between two versions.
-        """
-        if self.previous is None:
-            # first release
-            lower = ''
-        else:
-            lower = self.repo.tags[self.previous.tag]
-
-        if self.version.released:
-            upper = self.repo.tags[self.tag]
-        else:
-            try:
-                upper = self.repo.branches[self.branch]
-            except IndexError:
-                warnings.warn("Release branch `{}` doesn't exist."
-                              .format(self.branch))
-                return []
-
-        commit_range = "{}..{}".format(lower, upper)
-        return list(map(Commit, self.repo.iter_commits(commit_range)))
-
-    def curate(self):
-        # handle commits with parquet issue key specially and query them from
-        # jira and add it to the issues
-        release_issues = self.issues
-
-        within, outside, nojira, parquet = [], [], [], []
-        for c in self.commits:
-            if c.issue is None:
-                nojira.append(c)
-            elif c.issue in release_issues:
-                within.append((release_issues[c.issue], c))
-            elif c.project == 'PARQUET':
-                parquet.append((self.jira.issue(c.issue), c))
-            else:
-                outside.append((self.jira.issue(c.issue), c))
-
-        # remaining jira tickets
-        within_keys = {i.key for i, c in within}
-        nopatch = [issue for key, issue in release_issues.items()
-                   if key not in within_keys]
-
-        return ReleaseCuration(release=self, within=within, outside=outside,
-                               nojira=nojira, parquet=parquet, nopatch=nopatch)
-
-    def changelog(self):
-        release_issues = []
-
-        # get organized report for the release
-        curation = self.curate()
-
-        # jira tickets having patches in the release
-        for issue, _ in curation.within:
-            release_issues.append(issue)
-
-        # jira tickets without patches
-        for issue in curation.nopatch:
-            release_issues.append(issue)
-
-        # parquet patches in the release
-        for issue, _ in curation.parquet:
-            release_issues.append(issue)
-
-        # organize issues into categories
-        issue_types = {
-            'Bug': 'Bug Fixes',
-            'Improvement': 'New Features and Improvements',
-            'New Feature': 'New Features and Improvements',
-            'Sub-task': 'New Features and Improvements',
-            'Task': 'New Features and Improvements',
-            'Test': 'Bug Fixes',
-            'Wish': 'New Features and Improvements',
-        }
-        categories = defaultdict(list)
-        for issue in release_issues:
-            categories[issue_types[issue.type]].append(issue)
-
-        # sort issues by the issue key in ascending order
-        for name, issues in categories.items():
-            issues.sort(key=lambda issue: (issue.project, issue.number))
-
-        return JiraChangelog(release=self, categories=categories)
-
-
-class MaintenanceMixin:
-    """
-    Utility methods for cherry-picking commits from the main branch.
-    """
-
-    def commits_to_pick(self, exclude_already_applied=True):
-        # collect commits applied on the main branch since the root of the
-        # maintenance branch (the previous major release)
-        if self.version.major == 0:
-            # treat minor releases as major releases preceeding 1.0.0 release
-            commit_range = "apache-arrow-0.{}.0..master".format(
-                self.version.minor - 1
-            )
-        else:
-            commit_range = "apache-arrow-{}.0.0..master".format(
-                self.version.major
-            )
-
-        # keeping the original order of the commits helps to minimize the merge
-        # conflicts during cherry-picks
-        commits = map(Commit, self.repo.iter_commits(commit_range))
-
-        # exclude patches that have been already applied to the maintenance
-        # branch, we cannot identify patches based on sha because it changes
-        # after the cherry pick so use commit title instead
-        if exclude_already_applied:
-            already_applied = {c.title for c in self.commits}
-        else:
-            already_applied = set()
-
-        # iterate over the commits applied on the main branch and filter out
-        # the ones that are included in the jira release
-        patches_to_pick = [c for c in commits if
-                           c.issue in self.issues and
-                           c.title not in already_applied]
-
-        return reversed(patches_to_pick)
-
-    def cherry_pick_commits(self, recreate_branch=True):
-        if recreate_branch:
-            # delete, create and checkout the maintenance branch based off of
-            # the previous tag
-            if self.branch in self.repo.branches:
-                self.repo.git.branch('-D', self.branch)
-            self.repo.git.checkout(self.previous.tag, b=self.branch)
-        else:
-            # just checkout the already existing maintenance branch
-            self.repo.git.checkout(self.branch)
-
-        # cherry pick the commits based on the jira tickets
-        for commit in self.commits_to_pick():
-            self.repo.git.cherry_pick(commit.hexsha)
-
-
-class MajorRelease(Release):
-
-    @property
-    def branch(self):
-        return "master"
-
-    @cached_property
-    def siblings(self):
-        """
-        Filter only the major releases.
-        """
-        # handle minor releases before 1.0 as major releases
-        return [v for v in self.jira.project_versions('ARROW')
-                if v.patch == 0 and (v.major == 0 or v.minor == 0)]
-
-
-class MinorRelease(Release, MaintenanceMixin):
-
-    @property
-    def branch(self):
-        return "maint-{}.x.x".format(self.version.major)
-
-    @cached_property
-    def siblings(self):
-        """
-        Filter the major and minor releases.
-        """
-        return [v for v in self.jira.project_versions('ARROW') if v.patch == 0]
-
-
-class PatchRelease(Release, MaintenanceMixin):
-
-    @property
-    def branch(self):
-        return "maint-{}.{}.x".format(self.version.major, self.version.minor)
-
-    @cached_property
-    def siblings(self):
-        """
-        No filtering, consider all releases.
-        """
-        return self.jira.project_versions('ARROW')
diff --git a/dev/archery/archery/templates/release_changelog.md.j2 b/dev/archery/archery/templates/release_changelog.md.j2
deleted file mode 100644
index c0406dd..0000000
--- a/dev/archery/archery/templates/release_changelog.md.j2
+++ /dev/null
@@ -1,29 +0,0 @@
-{#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#}
-# Apache Arrow {{ release.version }} ({{ release.version.release_date or today() }})
-
-{% for category, issues in categories.items() -%}
-
-## {{ category }}
-
-{% for issue in issues -%}
-* [{{ issue.key }}](https://issues.apache.org/jira/browse/{{ issue.key }}) - {{ issue.summary | md }}
-{% endfor %}
-
-{% endfor %}
diff --git a/dev/archery/archery/templates/release_curation.txt.j2 b/dev/archery/archery/templates/release_curation.txt.j2
deleted file mode 100644
index a5d11e9..0000000
--- a/dev/archery/archery/templates/release_curation.txt.j2
+++ /dev/null
@@ -1,41 +0,0 @@
-{#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#}
-Total number of JIRA tickets assigned to version {{ release.version }}: {{ release.issues|length }}
-
-Total number of applied patches since version {{ release.previous.version }}: {{ release.commits|length }}
-
-Patches with assigned issue in version {{ release.version }}:
-{% for issue, commit in within -%}
- - {{ commit.url }} {{ commit.title }}
-{% endfor %}
-
-Patches with assigned issue outside of version {{ release.version }}:
-{% for issue, commit in outside -%}
- - {{ commit.url }} {{ commit.title }}
-{% endfor %}
-
-Patches in version {{ release.version }} without a linked issue:
-{% for commit in nojira -%}
- - {{ commit.url }} {{ commit.title }}
-{% endfor %}
-
-JIRA issues in version {{ release.version }} without a linked patch:
-{% for issue in nopatch -%}
- - https://issues.apache.org/jira/browse/{{ issue.key }}
-{% endfor %}
diff --git a/dev/archery/archery/testing.py b/dev/archery/archery/testing.py
deleted file mode 100644
index 471a54d..0000000
--- a/dev/archery/archery/testing.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from contextlib import contextmanager
-import os
-from unittest import mock
-import re
-
-
-class DotDict(dict):
-
-    def __getattr__(self, key):
-        try:
-            item = self[key]
-        except KeyError:
-            raise AttributeError(key)
-        if isinstance(item, dict):
-            return DotDict(item)
-        else:
-            return item
-
-
-class PartialEnv(dict):
-
-    def __eq__(self, other):
-        return self.items() <= other.items()
-
-
-_mock_call_type = type(mock.call())
-
-
-def _ensure_mock_call_object(obj, **kwargs):
-    if isinstance(obj, _mock_call_type):
-        return obj
-    elif isinstance(obj, str):
-        cmd = re.split(r"\s+", obj)
-        return mock.call(cmd, **kwargs)
-    elif isinstance(obj, list):
-        return mock.call(obj, **kwargs)
-    else:
-        raise TypeError(obj)
-
-
-class SuccessfulSubprocessResult:
-
-    def check_returncode(self):
-        return
-
-
-@contextmanager
-def assert_subprocess_calls(expected_commands_or_calls, **kwargs):
-    calls = [
-        _ensure_mock_call_object(obj, **kwargs)
-        for obj in expected_commands_or_calls
-    ]
-    with mock.patch('subprocess.run', autospec=True) as run:
-        run.return_value = SuccessfulSubprocessResult()
-        yield run
-        run.assert_has_calls(calls)
-
-
-@contextmanager
-def override_env(mapping):
-    original = os.environ
-    try:
-        os.environ = dict(os.environ, **mapping)
-        yield os.environ
-    finally:
-        os.environ = original
diff --git a/dev/archery/archery/tests/fixtures/archery-benchmark-diff-empty-lines.jsonl b/dev/archery/archery/tests/fixtures/archery-benchmark-diff-empty-lines.jsonl
deleted file mode 100644
index 5854eb7..0000000
--- a/dev/archery/archery/tests/fixtures/archery-benchmark-diff-empty-lines.jsonl
+++ /dev/null
@@ -1,6 +0,0 @@
-{"benchmark": "RegressionSumKernel/32768/10", "change": 0.0046756468886368545, "regression": false, "baseline": 13265442258.099466, "contender": 13327466781.91994, "unit": "bytes_per_second", "less_is_better": false, "suite": "arrow-compute-aggregate-benchmark"}
-{"benchmark": "RegressionSumKernel/32768/1", "change": 0.0025108399115900733, "regression": false, "baseline": 15181891659.539782, "contender": 15220010959.05199, "unit": "bytes_per_second", "less_is_better": false, "suite": "arrow-compute-aggregate-benchmark"}
-
-{"benchmark": "RegressionSumKernel/32768/50", "change": 0.00346735806287155, "regression": false, "baseline": 11471825667.817123, "contender": 11511602595.042286, "unit": "bytes_per_second", "less_is_better": false, "suite": "arrow-compute-aggregate-benchmark"}
-
-{"benchmark": "RegressionSumKernel/32768/0", "change": 0.010140954727954987, "regression": false, "baseline": 18316987019.994465, "contender": 18502738756.116768, "unit": "bytes_per_second", "less_is_better": false, "suite": "arrow-compute-aggregate-benchmark"}
diff --git a/dev/archery/archery/tests/fixtures/archery-benchmark-diff.jsonl b/dev/archery/archery/tests/fixtures/archery-benchmark-diff.jsonl
deleted file mode 100644
index 1e25810..0000000
--- a/dev/archery/archery/tests/fixtures/archery-benchmark-diff.jsonl
+++ /dev/null
@@ -1,4 +0,0 @@
-{"benchmark":"RegressionSumKernel/32768/50","change":-0.001550846227215492,"regression":false,"baseline":19241207435.428757,"contender":19211367281.47045,"unit":"bytes_per_second","less_is_better":false,"suite":"arrow-compute-aggregate-benchmark"}
-{"benchmark":"RegressionSumKernel/32768/1","change":0.0020681767923465765,"regression":true,"baseline":24823170673.777943,"contender":24771831968.277977,"unit":"bytes_per_second","less_is_better":false,"suite":"arrow-compute-aggregate-benchmark"}
-{"benchmark":"RegressionSumKernel/32768/10","change":0.0033323376378746905,"regression":false,"baseline":21902707565.968014,"contender":21975694782.76145,"unit":"bytes_per_second","less_is_better":false,"suite":"arrow-compute-aggregate-benchmark"}
-{"benchmark":"RegressionSumKernel/32768/0","change":-0.004918126090954414,"regression":true,"baseline":27685006611.446762,"contender":27821164964.790764,"unit":"bytes_per_second","less_is_better":false,"suite":"arrow-compute-aggregate-benchmark"}
diff --git a/dev/archery/archery/tests/fixtures/event-issue-comment-build-command.json b/dev/archery/archery/tests/fixtures/event-issue-comment-build-command.json
deleted file mode 100644
index d591105..0000000
--- a/dev/archery/archery/tests/fixtures/event-issue-comment-build-command.json
+++ /dev/null
@@ -1,212 +0,0 @@
-{
-    "action": "created",
-    "comment": {
-        "author_association": "MEMBER",
-        "body": "@ursabot build",
-        "created_at": "2019-04-05T11:55:43Z",
-        "html_url": "https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480248726",
-        "id": 480248726,
-        "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26",
-        "node_id": "MDEyOklzc3VlQ29tbWVudDQ4MDI0ODcyNg==",
-        "updated_at": "2019-04-05T11:55:43Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480248726",
-        "user": {
-            "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-            "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/kszucs/followers",
-            "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/kszucs",
-            "id": 961747,
-            "login": "kszucs",
-            "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-            "organizations_url": "https://api.github.com/users/kszucs/orgs",
-            "received_events_url": "https://api.github.com/users/kszucs/received_events",
-            "repos_url": "https://api.github.com/users/kszucs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-            "type": "User",
-            "url": "https://api.github.com/users/kszucs"
-        }
-    },
-    "issue": {
-        "assignee": null,
-        "assignees": [],
-        "author_association": "MEMBER",
-        "body": "",
-        "closed_at": null,
-        "comments": 3,
-        "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments",
-        "created_at": "2019-04-05T11:22:15Z",
-        "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/events",
-        "html_url": "https://github.com/ursa-labs/ursabot/pull/26",
-        "id": 429706959,
-        "labels": [],
-        "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}",
-        "locked": false,
-        "milestone": null,
-        "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy",
-        "number": 26,
-        "pull_request": {
-            "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff",
-            "html_url": "https://github.com/ursa-labs/ursabot/pull/26",
-            "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch",
-            "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26"
-        },
-        "repository_url": "https://api.github.com/repos/ursa-labs/ursabot",
-        "state": "open",
-        "title": "Unittests for GithubHook",
-        "updated_at": "2019-04-05T11:55:43Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26",
-        "user": {
-            "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-            "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/kszucs/followers",
-            "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/kszucs",
-            "id": 961747,
-            "login": "kszucs",
-            "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-            "organizations_url": "https://api.github.com/users/kszucs/orgs",
-            "received_events_url": "https://api.github.com/users/kszucs/received_events",
-            "repos_url": "https://api.github.com/users/kszucs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-            "type": "User",
-            "url": "https://api.github.com/users/kszucs"
-        }
-    },
-    "organization": {
-        "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-        "description": "Innovation lab for open source data science tools, powered by Apache Arrow",
-        "events_url": "https://api.github.com/orgs/ursa-labs/events",
-        "hooks_url": "https://api.github.com/orgs/ursa-labs/hooks",
-        "id": 46514972,
-        "issues_url": "https://api.github.com/orgs/ursa-labs/issues",
-        "login": "ursa-labs",
-        "members_url": "https://api.github.com/orgs/ursa-labs/members{/member}",
-        "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-        "public_members_url": "https://api.github.com/orgs/ursa-labs/public_members{/member}",
-        "repos_url": "https://api.github.com/orgs/ursa-labs/repos",
-        "url": "https://api.github.com/orgs/ursa-labs"
-    },
-    "repository": {
-        "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}",
-        "archived": false,
-        "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}",
-        "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}",
-        "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}",
-        "clone_url": "https://github.com/ursa-labs/ursabot.git",
-        "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}",
-        "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}",
-        "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}",
-        "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}",
-        "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}",
-        "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors",
-        "created_at": "2019-02-04T15:40:31Z",
-        "default_branch": "master",
-        "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments",
-        "description": null,
-        "disabled": false,
-        "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads",
-        "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events",
-        "fork": false,
-        "forks": 0,
-        "forks_count": 0,
-        "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks",
-        "full_name": "ursa-labs/ursabot",
-        "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}",
-        "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}",
-        "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}",
-        "git_url": "git://github.com/ursa-labs/ursabot.git",
-        "has_downloads": true,
-        "has_issues": true,
-        "has_pages": false,
-        "has_projects": true,
-        "has_wiki": true,
-        "homepage": null,
-        "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks",
-        "html_url": "https://github.com/ursa-labs/ursabot",
-        "id": 169101701,
-        "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}",
-        "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}",
-        "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}",
-        "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}",
-        "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}",
-        "language": "Jupyter Notebook",
-        "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages",
-        "license": null,
-        "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges",
-        "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}",
-        "mirror_url": null,
-        "name": "ursabot",
-        "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=",
-        "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}",
-        "open_issues": 19,
-        "open_issues_count": 19,
-        "owner": {
-            "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-            "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/ursa-labs/followers",
-            "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/ursa-labs",
-            "id": 46514972,
-            "login": "ursa-labs",
-            "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-            "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-            "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-            "repos_url": "https://api.github.com/users/ursa-labs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-            "type": "Organization",
-            "url": "https://api.github.com/users/ursa-labs"
-        },
-        "private": false,
-        "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}",
-        "pushed_at": "2019-04-05T11:22:16Z",
-        "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}",
-        "size": 892,
-        "ssh_url": "git@github.com:ursa-labs/ursabot.git",
-        "stargazers_count": 1,
-        "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers",
-        "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}",
-        "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers",
-        "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription",
-        "svn_url": "https://github.com/ursa-labs/ursabot",
-        "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags",
-        "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams",
-        "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}",
-        "updated_at": "2019-04-04T17:49:10Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot",
-        "watchers": 1,
-        "watchers_count": 1
-    },
-    "sender": {
-        "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-        "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-        "followers_url": "https://api.github.com/users/kszucs/followers",
-        "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-        "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-        "gravatar_id": "",
-        "html_url": "https://github.com/kszucs",
-        "id": 961747,
-        "login": "kszucs",
-        "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-        "organizations_url": "https://api.github.com/users/kszucs/orgs",
-        "received_events_url": "https://api.github.com/users/kszucs/received_events",
-        "repos_url": "https://api.github.com/users/kszucs/repos",
-        "site_admin": false,
-        "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-        "type": "User",
-        "url": "https://api.github.com/users/kszucs"
-    }
-}
\ No newline at end of file
diff --git a/dev/archery/archery/tests/fixtures/event-issue-comment-by-non-authorized-user.json b/dev/archery/archery/tests/fixtures/event-issue-comment-by-non-authorized-user.json
deleted file mode 100644
index 5a8f346..0000000
--- a/dev/archery/archery/tests/fixtures/event-issue-comment-by-non-authorized-user.json
+++ /dev/null
@@ -1,212 +0,0 @@
-{
-    "action": "created",
-    "comment": {
-        "author_association": "NONE",
-        "body": "Unknown command \"\"",
-        "created_at": "2019-04-05T11:35:47Z",
-        "html_url": "https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243815",
-        "id": 480243815,
-        "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26",
-        "node_id": "MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxNQ==",
-        "updated_at": "2019-04-05T11:35:47Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243815",
-        "user": {
-            "avatar_url": "https://avatars2.githubusercontent.com/u/49275095?v=4",
-            "events_url": "https://api.github.com/users/ursabot/events{/privacy}",
-            "followers_url": "https://api.github.com/users/ursabot/followers",
-            "following_url": "https://api.github.com/users/ursabot/following{/other_user}",
-            "gists_url": "https://api.github.com/users/ursabot/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/ursabot",
-            "id": 49275095,
-            "login": "someone",
-            "node_id": "MDQ6VXNlcjQ5Mjc1MDk1",
-            "organizations_url": "https://api.github.com/users/ursabot/orgs",
-            "received_events_url": "https://api.github.com/users/ursabot/received_events",
-            "repos_url": "https://api.github.com/users/ursabot/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/ursabot/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/ursabot/subscriptions",
-            "type": "User",
-            "url": "https://api.github.com/users/ursabot"
-        }
-    },
-    "issue": {
-        "assignee": null,
-        "assignees": [],
-        "author_association": "NONE",
-        "body": "",
-        "closed_at": null,
-        "comments": 2,
-        "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments",
-        "created_at": "2019-04-05T11:22:15Z",
-        "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/events",
-        "html_url": "https://github.com/ursa-labs/ursabot/pull/26",
-        "id": 429706959,
-        "labels": [],
-        "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}",
-        "locked": false,
-        "milestone": null,
-        "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy",
-        "number": 26,
-        "pull_request": {
-            "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff",
-            "html_url": "https://github.com/ursa-labs/ursabot/pull/26",
-            "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch",
-            "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26"
-        },
-        "repository_url": "https://api.github.com/repos/ursa-labs/ursabot",
-        "state": "open",
-        "title": "Unittests for GithubHook",
-        "updated_at": "2019-04-05T11:35:47Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26",
-        "user": {
-            "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-            "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/kszucs/followers",
-            "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/kszucs",
-            "id": 961747,
-            "login": "kszucs",
-            "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-            "organizations_url": "https://api.github.com/users/kszucs/orgs",
-            "received_events_url": "https://api.github.com/users/kszucs/received_events",
-            "repos_url": "https://api.github.com/users/kszucs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-            "type": "User",
-            "url": "https://api.github.com/users/kszucs"
-        }
-    },
-    "organization": {
-        "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-        "description": "Innovation lab for open source data science tools, powered by Apache Arrow",
-        "events_url": "https://api.github.com/orgs/ursa-labs/events",
-        "hooks_url": "https://api.github.com/orgs/ursa-labs/hooks",
-        "id": 46514972,
-        "issues_url": "https://api.github.com/orgs/ursa-labs/issues",
-        "login": "ursa-labs",
-        "members_url": "https://api.github.com/orgs/ursa-labs/members{/member}",
-        "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-        "public_members_url": "https://api.github.com/orgs/ursa-labs/public_members{/member}",
-        "repos_url": "https://api.github.com/orgs/ursa-labs/repos",
-        "url": "https://api.github.com/orgs/ursa-labs"
-    },
-    "repository": {
-        "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}",
-        "archived": false,
-        "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}",
-        "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}",
-        "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}",
-        "clone_url": "https://github.com/ursa-labs/ursabot.git",
-        "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}",
-        "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}",
-        "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}",
-        "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}",
-        "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}",
-        "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors",
-        "created_at": "2019-02-04T15:40:31Z",
-        "default_branch": "master",
-        "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments",
-        "description": null,
-        "disabled": false,
-        "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads",
-        "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events",
-        "fork": false,
-        "forks": 0,
-        "forks_count": 0,
-        "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks",
-        "full_name": "ursa-labs/ursabot",
-        "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}",
-        "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}",
-        "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}",
-        "git_url": "git://github.com/ursa-labs/ursabot.git",
-        "has_downloads": true,
-        "has_issues": true,
-        "has_pages": false,
-        "has_projects": true,
-        "has_wiki": true,
-        "homepage": null,
-        "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks",
-        "html_url": "https://github.com/ursa-labs/ursabot",
-        "id": 169101701,
-        "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}",
-        "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}",
-        "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}",
-        "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}",
-        "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}",
-        "language": "Jupyter Notebook",
-        "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages",
-        "license": null,
-        "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges",
-        "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}",
-        "mirror_url": null,
-        "name": "someone",
-        "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=",
-        "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}",
-        "open_issues": 19,
-        "open_issues_count": 19,
-        "owner": {
-            "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-            "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/ursa-labs/followers",
-            "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/ursa-labs",
-            "id": 46514972,
-            "login": "ursa-labs",
-            "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-            "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-            "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-            "repos_url": "https://api.github.com/users/ursa-labs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-            "type": "Organization",
-            "url": "https://api.github.com/users/ursa-labs"
-        },
-        "private": false,
-        "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}",
-        "pushed_at": "2019-04-05T11:22:16Z",
-        "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}",
-        "size": 892,
-        "ssh_url": "git@github.com:ursa-labs/ursabot.git",
-        "stargazers_count": 1,
-        "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers",
-        "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}",
-        "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers",
-        "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription",
-        "svn_url": "https://github.com/ursa-labs/ursabot",
-        "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags",
-        "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams",
-        "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}",
-        "updated_at": "2019-04-04T17:49:10Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot",
-        "watchers": 1,
-        "watchers_count": 1
-    },
-    "sender": {
-        "avatar_url": "https://avatars2.githubusercontent.com/u/49275095?v=4",
-        "events_url": "https://api.github.com/users/ursabot/events{/privacy}",
-        "followers_url": "https://api.github.com/users/ursabot/followers",
-        "following_url": "https://api.github.com/users/ursabot/following{/other_user}",
-        "gists_url": "https://api.github.com/users/ursabot/gists{/gist_id}",
-        "gravatar_id": "",
-        "html_url": "https://github.com/ursabot",
-        "id": 49275095,
-        "login": "someone",
-        "node_id": "MDQ6VXNlcjQ5Mjc1MDk1",
-        "organizations_url": "https://api.github.com/users/ursabot/orgs",
-        "received_events_url": "https://api.github.com/users/ursabot/received_events",
-        "repos_url": "https://api.github.com/users/ursabot/repos",
-        "site_admin": false,
-        "starred_url": "https://api.github.com/users/ursabot/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/ursabot/subscriptions",
-        "type": "User",
-        "url": "https://api.github.com/users/ursabot"
-    }
-}
\ No newline at end of file
diff --git a/dev/archery/archery/tests/fixtures/event-issue-comment-by-ursabot.json b/dev/archery/archery/tests/fixtures/event-issue-comment-by-ursabot.json
deleted file mode 100644
index bfb7210..0000000
--- a/dev/archery/archery/tests/fixtures/event-issue-comment-by-ursabot.json
+++ /dev/null
@@ -1,212 +0,0 @@
-{
-    "action": "created",
-    "comment": {
-        "author_association": "NONE",
-        "body": "Unknown command \"\"",
-        "created_at": "2019-04-05T11:35:47Z",
-        "html_url": "https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243815",
-        "id": 480243815,
-        "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26",
-        "node_id": "MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxNQ==",
-        "updated_at": "2019-04-05T11:35:47Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243815",
-        "user": {
-            "avatar_url": "https://avatars2.githubusercontent.com/u/49275095?v=4",
-            "events_url": "https://api.github.com/users/ursabot/events{/privacy}",
-            "followers_url": "https://api.github.com/users/ursabot/followers",
-            "following_url": "https://api.github.com/users/ursabot/following{/other_user}",
-            "gists_url": "https://api.github.com/users/ursabot/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/ursabot",
-            "id": 49275095,
-            "login": "ursabot",
-            "node_id": "MDQ6VXNlcjQ5Mjc1MDk1",
-            "organizations_url": "https://api.github.com/users/ursabot/orgs",
-            "received_events_url": "https://api.github.com/users/ursabot/received_events",
-            "repos_url": "https://api.github.com/users/ursabot/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/ursabot/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/ursabot/subscriptions",
-            "type": "User",
-            "url": "https://api.github.com/users/ursabot"
-        }
-    },
-    "issue": {
-        "assignee": null,
-        "assignees": [],
-        "author_association": "MEMBER",
-        "body": "",
-        "closed_at": null,
-        "comments": 2,
-        "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments",
-        "created_at": "2019-04-05T11:22:15Z",
-        "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/events",
-        "html_url": "https://github.com/ursa-labs/ursabot/pull/26",
-        "id": 429706959,
-        "labels": [],
-        "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}",
-        "locked": false,
-        "milestone": null,
-        "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy",
-        "number": 26,
-        "pull_request": {
-            "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff",
-            "html_url": "https://github.com/ursa-labs/ursabot/pull/26",
-            "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch",
-            "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26"
-        },
-        "repository_url": "https://api.github.com/repos/ursa-labs/ursabot",
-        "state": "open",
-        "title": "Unittests for GithubHook",
-        "updated_at": "2019-04-05T11:35:47Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26",
-        "user": {
-            "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-            "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/kszucs/followers",
-            "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/kszucs",
-            "id": 961747,
-            "login": "kszucs",
-            "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-            "organizations_url": "https://api.github.com/users/kszucs/orgs",
-            "received_events_url": "https://api.github.com/users/kszucs/received_events",
-            "repos_url": "https://api.github.com/users/kszucs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-            "type": "User",
-            "url": "https://api.github.com/users/kszucs"
-        }
-    },
-    "organization": {
-        "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-        "description": "Innovation lab for open source data science tools, powered by Apache Arrow",
-        "events_url": "https://api.github.com/orgs/ursa-labs/events",
-        "hooks_url": "https://api.github.com/orgs/ursa-labs/hooks",
-        "id": 46514972,
-        "issues_url": "https://api.github.com/orgs/ursa-labs/issues",
-        "login": "ursa-labs",
-        "members_url": "https://api.github.com/orgs/ursa-labs/members{/member}",
-        "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-        "public_members_url": "https://api.github.com/orgs/ursa-labs/public_members{/member}",
-        "repos_url": "https://api.github.com/orgs/ursa-labs/repos",
-        "url": "https://api.github.com/orgs/ursa-labs"
-    },
-    "repository": {
-        "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}",
-        "archived": false,
-        "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}",
-        "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}",
-        "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}",
-        "clone_url": "https://github.com/ursa-labs/ursabot.git",
-        "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}",
-        "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}",
-        "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}",
-        "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}",
-        "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}",
-        "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors",
-        "created_at": "2019-02-04T15:40:31Z",
-        "default_branch": "master",
-        "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments",
-        "description": null,
-        "disabled": false,
-        "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads",
-        "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events",
-        "fork": false,
-        "forks": 0,
-        "forks_count": 0,
-        "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks",
-        "full_name": "ursa-labs/ursabot",
-        "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}",
-        "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}",
-        "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}",
-        "git_url": "git://github.com/ursa-labs/ursabot.git",
-        "has_downloads": true,
-        "has_issues": true,
-        "has_pages": false,
-        "has_projects": true,
-        "has_wiki": true,
-        "homepage": null,
-        "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks",
-        "html_url": "https://github.com/ursa-labs/ursabot",
-        "id": 169101701,
-        "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}",
-        "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}",
-        "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}",
-        "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}",
-        "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}",
-        "language": "Jupyter Notebook",
-        "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages",
-        "license": null,
-        "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges",
-        "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}",
-        "mirror_url": null,
-        "name": "ursabot",
-        "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=",
-        "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}",
-        "open_issues": 19,
-        "open_issues_count": 19,
-        "owner": {
-            "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-            "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/ursa-labs/followers",
-            "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/ursa-labs",
-            "id": 46514972,
-            "login": "ursa-labs",
-            "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-            "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-            "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-            "repos_url": "https://api.github.com/users/ursa-labs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-            "type": "Organization",
-            "url": "https://api.github.com/users/ursa-labs"
-        },
-        "private": false,
-        "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}",
-        "pushed_at": "2019-04-05T11:22:16Z",
-        "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}",
-        "size": 892,
-        "ssh_url": "git@github.com:ursa-labs/ursabot.git",
-        "stargazers_count": 1,
-        "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers",
-        "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}",
-        "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers",
-        "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription",
-        "svn_url": "https://github.com/ursa-labs/ursabot",
-        "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags",
-        "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams",
-        "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}",
-        "updated_at": "2019-04-04T17:49:10Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot",
-        "watchers": 1,
-        "watchers_count": 1
-    },
-    "sender": {
-        "avatar_url": "https://avatars2.githubusercontent.com/u/49275095?v=4",
-        "events_url": "https://api.github.com/users/ursabot/events{/privacy}",
-        "followers_url": "https://api.github.com/users/ursabot/followers",
-        "following_url": "https://api.github.com/users/ursabot/following{/other_user}",
-        "gists_url": "https://api.github.com/users/ursabot/gists{/gist_id}",
-        "gravatar_id": "",
-        "html_url": "https://github.com/ursabot",
-        "id": 49275095,
-        "login": "ursabot",
-        "node_id": "MDQ6VXNlcjQ5Mjc1MDk1",
-        "organizations_url": "https://api.github.com/users/ursabot/orgs",
-        "received_events_url": "https://api.github.com/users/ursabot/received_events",
-        "repos_url": "https://api.github.com/users/ursabot/repos",
-        "site_admin": false,
-        "starred_url": "https://api.github.com/users/ursabot/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/ursabot/subscriptions",
-        "type": "User",
-        "url": "https://api.github.com/users/ursabot"
-    }
-}
\ No newline at end of file
diff --git a/dev/archery/archery/tests/fixtures/event-issue-comment-not-mentioning-ursabot.json b/dev/archery/archery/tests/fixtures/event-issue-comment-not-mentioning-ursabot.json
deleted file mode 100644
index a3d4500..0000000
--- a/dev/archery/archery/tests/fixtures/event-issue-comment-not-mentioning-ursabot.json
+++ /dev/null
@@ -1,212 +0,0 @@
-{
-    "action": "created",
-    "comment": {
-        "author_association": "MEMBER",
-        "body": "bear is no game",
-        "created_at": "2019-04-05T11:26:56Z",
-        "html_url": "https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480241727",
-        "id": 480241727,
-        "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26",
-        "node_id": "MDEyOklzc3VlQ29tbWVudDQ4MDI0MTcyNw==",
-        "updated_at": "2019-04-05T11:26:56Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480241727",
-        "user": {
-            "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-            "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/kszucs/followers",
-            "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/kszucs",
-            "id": 961747,
-            "login": "kszucs",
-            "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-            "organizations_url": "https://api.github.com/users/kszucs/orgs",
-            "received_events_url": "https://api.github.com/users/kszucs/received_events",
-            "repos_url": "https://api.github.com/users/kszucs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-            "type": "User",
-            "url": "https://api.github.com/users/kszucs"
-        }
-    },
-    "issue": {
-        "assignee": null,
-        "assignees": [],
-        "author_association": "MEMBER",
-        "body": "",
-        "closed_at": null,
-        "comments": 0,
-        "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments",
-        "created_at": "2019-04-05T11:22:15Z",
-        "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/events",
-        "html_url": "https://github.com/ursa-labs/ursabot/pull/26",
-        "id": 429706959,
-        "labels": [],
-        "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}",
-        "locked": false,
-        "milestone": null,
-        "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy",
-        "number": 26,
-        "pull_request": {
-            "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff",
-            "html_url": "https://github.com/ursa-labs/ursabot/pull/26",
-            "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch",
-            "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26"
-        },
-        "repository_url": "https://api.github.com/repos/ursa-labs/ursabot",
-        "state": "open",
-        "title": "Unittests for GithubHook",
-        "updated_at": "2019-04-05T11:26:56Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26",
-        "user": {
-            "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-            "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/kszucs/followers",
-            "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/kszucs",
-            "id": 961747,
-            "login": "kszucs",
-            "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-            "organizations_url": "https://api.github.com/users/kszucs/orgs",
-            "received_events_url": "https://api.github.com/users/kszucs/received_events",
-            "repos_url": "https://api.github.com/users/kszucs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-            "type": "User",
-            "url": "https://api.github.com/users/kszucs"
-        }
-    },
-    "organization": {
-        "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-        "description": "Innovation lab for open source data science tools, powered by Apache Arrow",
-        "events_url": "https://api.github.com/orgs/ursa-labs/events",
-        "hooks_url": "https://api.github.com/orgs/ursa-labs/hooks",
-        "id": 46514972,
-        "issues_url": "https://api.github.com/orgs/ursa-labs/issues",
-        "login": "ursa-labs",
-        "members_url": "https://api.github.com/orgs/ursa-labs/members{/member}",
-        "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-        "public_members_url": "https://api.github.com/orgs/ursa-labs/public_members{/member}",
-        "repos_url": "https://api.github.com/orgs/ursa-labs/repos",
-        "url": "https://api.github.com/orgs/ursa-labs"
-    },
-    "repository": {
-        "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}",
-        "archived": false,
-        "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}",
-        "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}",
-        "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}",
-        "clone_url": "https://github.com/ursa-labs/ursabot.git",
-        "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}",
-        "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}",
-        "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}",
-        "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}",
-        "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}",
-        "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors",
-        "created_at": "2019-02-04T15:40:31Z",
-        "default_branch": "master",
-        "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments",
-        "description": null,
-        "disabled": false,
-        "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads",
-        "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events",
-        "fork": false,
-        "forks": 0,
-        "forks_count": 0,
-        "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks",
-        "full_name": "ursa-labs/ursabot",
-        "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}",
-        "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}",
-        "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}",
-        "git_url": "git://github.com/ursa-labs/ursabot.git",
-        "has_downloads": true,
-        "has_issues": true,
-        "has_pages": false,
-        "has_projects": true,
-        "has_wiki": true,
-        "homepage": null,
-        "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks",
-        "html_url": "https://github.com/ursa-labs/ursabot",
-        "id": 169101701,
-        "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}",
-        "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}",
-        "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}",
-        "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}",
-        "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}",
-        "language": "Jupyter Notebook",
-        "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages",
-        "license": null,
-        "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges",
-        "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}",
-        "mirror_url": null,
-        "name": "ursabot",
-        "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=",
-        "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}",
-        "open_issues": 19,
-        "open_issues_count": 19,
-        "owner": {
-            "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-            "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/ursa-labs/followers",
-            "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/ursa-labs",
-            "id": 46514972,
-            "login": "ursa-labs",
-            "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-            "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-            "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-            "repos_url": "https://api.github.com/users/ursa-labs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-            "type": "Organization",
-            "url": "https://api.github.com/users/ursa-labs"
-        },
-        "private": false,
-        "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}",
-        "pushed_at": "2019-04-05T11:22:16Z",
-        "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}",
-        "size": 892,
-        "ssh_url": "git@github.com:ursa-labs/ursabot.git",
-        "stargazers_count": 1,
-        "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers",
-        "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}",
-        "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers",
-        "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription",
-        "svn_url": "https://github.com/ursa-labs/ursabot",
-        "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags",
-        "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams",
-        "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}",
-        "updated_at": "2019-04-04T17:49:10Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot",
-        "watchers": 1,
-        "watchers_count": 1
-    },
-    "sender": {
-        "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-        "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-        "followers_url": "https://api.github.com/users/kszucs/followers",
-        "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-        "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-        "gravatar_id": "",
-        "html_url": "https://github.com/kszucs",
-        "id": 961747,
-        "login": "kszucs",
-        "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-        "organizations_url": "https://api.github.com/users/kszucs/orgs",
-        "received_events_url": "https://api.github.com/users/kszucs/received_events",
-        "repos_url": "https://api.github.com/users/kszucs/repos",
-        "site_admin": false,
-        "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-        "type": "User",
-        "url": "https://api.github.com/users/kszucs"
-    }
-}
\ No newline at end of file
diff --git a/dev/archery/archery/tests/fixtures/event-issue-comment-with-empty-command.json b/dev/archery/archery/tests/fixtures/event-issue-comment-with-empty-command.json
deleted file mode 100644
index c88197c..0000000
--- a/dev/archery/archery/tests/fixtures/event-issue-comment-with-empty-command.json
+++ /dev/null
@@ -1,217 +0,0 @@
-{
-    "action": "created",
-    "comment": {
-        "author_association": "MEMBER",
-        "body": "@ursabot ",
-        "body_html": "",
-        "body_text": "",
-        "created_at": "2019-04-05T11:35:46Z",
-        "html_url": "https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243811",
-        "id": 480243811,
-        "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26",
-        "node_id": "MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxMQ==",
-        "updated_at": "2019-04-05T11:35:46Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243811",
-        "user": {
-            "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-            "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/kszucs/followers",
-            "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/kszucs",
-            "id": 961747,
-            "login": "kszucs",
-            "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-            "organizations_url": "https://api.github.com/users/kszucs/orgs",
-            "received_events_url": "https://api.github.com/users/kszucs/received_events",
-            "repos_url": "https://api.github.com/users/kszucs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-            "type": "User",
-            "url": "https://api.github.com/users/kszucs"
-        }
-    },
-    "issue": {
-        "assignee": null,
-        "assignees": [],
-        "author_association": "MEMBER",
-        "body": "",
-        "body_html": "",
-        "body_text": "",
-        "closed_at": null,
-        "closed_by": null,
-        "comments": 1,
-        "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments",
-        "created_at": "2019-04-05T11:22:15Z",
-        "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/events",
-        "html_url": "https://github.com/ursa-labs/ursabot/pull/26",
-        "id": 429706959,
-        "labels": [],
-        "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}",
-        "locked": false,
-        "milestone": null,
-        "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy",
-        "number": 26,
-        "pull_request": {
-            "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff",
-            "html_url": "https://github.com/ursa-labs/ursabot/pull/26",
-            "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch",
-            "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26"
-        },
-        "repository_url": "https://api.github.com/repos/ursa-labs/ursabot",
-        "state": "open",
-        "title": "Unittests for GithubHook",
-        "updated_at": "2019-04-05T11:35:46Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26",
-        "user": {
-            "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-            "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/kszucs/followers",
-            "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/kszucs",
-            "id": 961747,
-            "login": "kszucs",
-            "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-            "organizations_url": "https://api.github.com/users/kszucs/orgs",
-            "received_events_url": "https://api.github.com/users/kszucs/received_events",
-            "repos_url": "https://api.github.com/users/kszucs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-            "type": "User",
-            "url": "https://api.github.com/users/kszucs"
-        }
-    },
-    "organization": {
-        "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-        "description": "Innovation lab for open source data science tools, powered by Apache Arrow",
-        "events_url": "https://api.github.com/orgs/ursa-labs/events",
-        "hooks_url": "https://api.github.com/orgs/ursa-labs/hooks",
-        "id": 46514972,
-        "issues_url": "https://api.github.com/orgs/ursa-labs/issues",
-        "login": "ursa-labs",
-        "members_url": "https://api.github.com/orgs/ursa-labs/members{/member}",
-        "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-        "public_members_url": "https://api.github.com/orgs/ursa-labs/public_members{/member}",
-        "repos_url": "https://api.github.com/orgs/ursa-labs/repos",
-        "url": "https://api.github.com/orgs/ursa-labs"
-    },
-    "repository": {
-        "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}",
-        "archived": false,
-        "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}",
-        "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}",
-        "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}",
-        "clone_url": "https://github.com/ursa-labs/ursabot.git",
-        "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}",
-        "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}",
-        "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}",
-        "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}",
-        "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}",
-        "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors",
-        "created_at": "2019-02-04T15:40:31Z",
-        "default_branch": "master",
-        "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments",
-        "description": null,
-        "disabled": false,
-        "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads",
-        "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events",
-        "fork": false,
-        "forks": 0,
-        "forks_count": 0,
-        "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks",
-        "full_name": "ursa-labs/ursabot",
-        "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}",
-        "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}",
-        "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}",
-        "git_url": "git://github.com/ursa-labs/ursabot.git",
-        "has_downloads": true,
-        "has_issues": true,
-        "has_pages": false,
-        "has_projects": true,
-        "has_wiki": true,
-        "homepage": null,
-        "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks",
-        "html_url": "https://github.com/ursa-labs/ursabot",
-        "id": 169101701,
-        "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}",
-        "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}",
-        "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}",
-        "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}",
-        "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}",
-        "language": "Jupyter Notebook",
-        "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages",
-        "license": null,
-        "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges",
-        "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}",
-        "mirror_url": null,
-        "name": "ursabot",
-        "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=",
-        "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}",
-        "open_issues": 19,
-        "open_issues_count": 19,
-        "owner": {
-            "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-            "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/ursa-labs/followers",
-            "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/ursa-labs",
-            "id": 46514972,
-            "login": "ursa-labs",
-            "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-            "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-            "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-            "repos_url": "https://api.github.com/users/ursa-labs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-            "type": "Organization",
-            "url": "https://api.github.com/users/ursa-labs"
-        },
-        "private": false,
-        "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}",
-        "pushed_at": "2019-04-05T11:22:16Z",
-        "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}",
-        "size": 892,
-        "ssh_url": "git@github.com:ursa-labs/ursabot.git",
-        "stargazers_count": 1,
-        "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers",
-        "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}",
-        "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers",
-        "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription",
-        "svn_url": "https://github.com/ursa-labs/ursabot",
-        "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags",
-        "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams",
-        "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}",
-        "updated_at": "2019-04-04T17:49:10Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot",
-        "watchers": 1,
-        "watchers_count": 1
-    },
-    "sender": {
-        "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-        "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-        "followers_url": "https://api.github.com/users/kszucs/followers",
-        "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-        "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-        "gravatar_id": "",
-        "html_url": "https://github.com/kszucs",
-        "id": 961747,
-        "login": "kszucs",
-        "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-        "organizations_url": "https://api.github.com/users/kszucs/orgs",
-        "received_events_url": "https://api.github.com/users/kszucs/received_events",
-        "repos_url": "https://api.github.com/users/kszucs/repos",
-        "site_admin": false,
-        "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-        "type": "User",
-        "url": "https://api.github.com/users/kszucs"
-    }
-}
\ No newline at end of file
diff --git a/dev/archery/archery/tests/fixtures/event-issue-comment-without-pull-request.json b/dev/archery/archery/tests/fixtures/event-issue-comment-without-pull-request.json
deleted file mode 100644
index 9e362fc..0000000
--- a/dev/archery/archery/tests/fixtures/event-issue-comment-without-pull-request.json
+++ /dev/null
@@ -1,206 +0,0 @@
-{
-    "action": "created",
-    "comment": {
-        "author_association": "MEMBER",
-        "body": "@ursabot build",
-        "created_at": "2019-04-05T13:07:57Z",
-        "html_url": "https://github.com/ursa-labs/ursabot/issues/19#issuecomment-480268708",
-        "id": 480268708,
-        "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19",
-        "node_id": "MDEyOklzc3VlQ29tbWVudDQ4MDI2ODcwOA==",
-        "updated_at": "2019-04-05T13:07:57Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480268708",
-        "user": {
-            "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-            "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/kszucs/followers",
-            "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/kszucs",
-            "id": 961747,
-            "login": "kszucs",
-            "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-            "organizations_url": "https://api.github.com/users/kszucs/orgs",
-            "received_events_url": "https://api.github.com/users/kszucs/received_events",
-            "repos_url": "https://api.github.com/users/kszucs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-            "type": "User",
-            "url": "https://api.github.com/users/kszucs"
-        }
-    },
-    "issue": {
-        "assignee": null,
-        "assignees": [],
-        "author_association": "MEMBER",
-        "body": "",
-        "closed_at": null,
-        "comments": 5,
-        "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19/comments",
-        "created_at": "2019-04-02T09:56:41Z",
-        "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19/events",
-        "html_url": "https://github.com/ursa-labs/ursabot/issues/19",
-        "id": 428131685,
-        "labels": [],
-        "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19/labels{/name}",
-        "locked": false,
-        "milestone": null,
-        "node_id": "MDU6SXNzdWU0MjgxMzE2ODU=",
-        "number": 19,
-        "repository_url": "https://api.github.com/repos/ursa-labs/ursabot",
-        "state": "open",
-        "title": "Build ursabot itself via ursabot",
-        "updated_at": "2019-04-05T13:07:57Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19",
-        "user": {
-            "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-            "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/kszucs/followers",
-            "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/kszucs",
-            "id": 961747,
-            "login": "kszucs",
-            "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-            "organizations_url": "https://api.github.com/users/kszucs/orgs",
-            "received_events_url": "https://api.github.com/users/kszucs/received_events",
-            "repos_url": "https://api.github.com/users/kszucs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-            "type": "User",
-            "url": "https://api.github.com/users/kszucs"
-        }
-    },
-    "organization": {
-        "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-        "description": "Innovation lab for open source data science tools, powered by Apache Arrow",
-        "events_url": "https://api.github.com/orgs/ursa-labs/events",
-        "hooks_url": "https://api.github.com/orgs/ursa-labs/hooks",
-        "id": 46514972,
-        "issues_url": "https://api.github.com/orgs/ursa-labs/issues",
-        "login": "ursa-labs",
-        "members_url": "https://api.github.com/orgs/ursa-labs/members{/member}",
-        "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-        "public_members_url": "https://api.github.com/orgs/ursa-labs/public_members{/member}",
-        "repos_url": "https://api.github.com/orgs/ursa-labs/repos",
-        "url": "https://api.github.com/orgs/ursa-labs"
-    },
-    "repository": {
-        "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}",
-        "archived": false,
-        "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}",
-        "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}",
-        "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}",
-        "clone_url": "https://github.com/ursa-labs/ursabot.git",
-        "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}",
-        "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}",
-        "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}",
-        "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}",
-        "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}",
-        "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors",
-        "created_at": "2019-02-04T15:40:31Z",
-        "default_branch": "master",
-        "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments",
-        "description": null,
-        "disabled": false,
-        "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads",
-        "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events",
-        "fork": false,
-        "forks": 0,
-        "forks_count": 0,
-        "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks",
-        "full_name": "ursa-labs/ursabot",
-        "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}",
-        "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}",
-        "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}",
-        "git_url": "git://github.com/ursa-labs/ursabot.git",
-        "has_downloads": true,
-        "has_issues": true,
-        "has_pages": false,
-        "has_projects": true,
-        "has_wiki": true,
-        "homepage": null,
-        "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks",
-        "html_url": "https://github.com/ursa-labs/ursabot",
-        "id": 169101701,
-        "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}",
-        "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}",
-        "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}",
-        "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}",
-        "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}",
-        "language": "Jupyter Notebook",
-        "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages",
-        "license": null,
-        "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges",
-        "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}",
-        "mirror_url": null,
-        "name": "ursabot",
-        "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=",
-        "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}",
-        "open_issues": 19,
-        "open_issues_count": 19,
-        "owner": {
-            "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-            "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-            "followers_url": "https://api.github.com/users/ursa-labs/followers",
-            "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-            "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-            "gravatar_id": "",
-            "html_url": "https://github.com/ursa-labs",
-            "id": 46514972,
-            "login": "ursa-labs",
-            "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-            "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-            "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-            "repos_url": "https://api.github.com/users/ursa-labs/repos",
-            "site_admin": false,
-            "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-            "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-            "type": "Organization",
-            "url": "https://api.github.com/users/ursa-labs"
-        },
-        "private": false,
-        "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}",
-        "pushed_at": "2019-04-05T12:01:40Z",
-        "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}",
-        "size": 898,
-        "ssh_url": "git@github.com:ursa-labs/ursabot.git",
-        "stargazers_count": 1,
-        "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers",
-        "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}",
-        "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers",
-        "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription",
-        "svn_url": "https://github.com/ursa-labs/ursabot",
-        "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags",
-        "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams",
-        "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}",
-        "updated_at": "2019-04-04T17:49:10Z",
-        "url": "https://api.github.com/repos/ursa-labs/ursabot",
-        "watchers": 1,
-        "watchers_count": 1
-    },
-    "sender": {
-        "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-        "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-        "followers_url": "https://api.github.com/users/kszucs/followers",
-        "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-        "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-        "gravatar_id": "",
-        "html_url": "https://github.com/kszucs",
-        "id": 961747,
-        "login": "kszucs",
-        "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-        "organizations_url": "https://api.github.com/users/kszucs/orgs",
-        "received_events_url": "https://api.github.com/users/kszucs/received_events",
-        "repos_url": "https://api.github.com/users/kszucs/repos",
-        "site_admin": false,
-        "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-        "type": "User",
-        "url": "https://api.github.com/users/kszucs"
-    }
-}
\ No newline at end of file
diff --git a/dev/archery/archery/tests/fixtures/event-pull-request-opened.json b/dev/archery/archery/tests/fixtures/event-pull-request-opened.json
deleted file mode 100644
index 9cf5c0d..0000000
--- a/dev/archery/archery/tests/fixtures/event-pull-request-opened.json
+++ /dev/null
@@ -1,445 +0,0 @@
-{
-  "action": "opened",
-  "number": 26,
-  "pull_request": {
-    "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26",
-    "id": 267785552,
-    "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy",
-    "html_url": "https://github.com/ursa-labs/ursabot/pull/26",
-    "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff",
-    "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch",
-    "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26",
-    "number": 26,
-    "state": "open",
-    "locked": false,
-    "title": "Unittests for GithubHook",
-    "user": {
-      "login": "kszucs",
-      "id": 961747,
-      "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-      "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-      "gravatar_id": "",
-      "url": "https://api.github.com/users/kszucs",
-      "html_url": "https://github.com/kszucs",
-      "followers_url": "https://api.github.com/users/kszucs/followers",
-      "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-      "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-      "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-      "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-      "organizations_url": "https://api.github.com/users/kszucs/orgs",
-      "repos_url": "https://api.github.com/users/kszucs/repos",
-      "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-      "received_events_url": "https://api.github.com/users/kszucs/received_events",
-      "type": "User",
-      "site_admin": false
-    },
-    "body": "",
-    "created_at": "2019-04-05T11:22:15Z",
-    "updated_at": "2019-04-05T12:01:40Z",
-    "closed_at": null,
-    "merged_at": null,
-    "merge_commit_sha": "cc5dc3606988b3824be54df779ed2028776113cb",
-    "assignee": null,
-    "assignees": [],
-    "requested_reviewers": [],
-    "requested_teams": [],
-    "labels": [],
-    "milestone": null,
-    "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/commits",
-    "review_comments_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/comments",
-    "review_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/comments{/number}",
-    "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments",
-    "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/2705da2b616b98fa6010a25813c5a7a27456f71d",
-    "head": {
-      "label": "ursa-labs:test-hook",
-      "ref": "test-hook",
-      "sha": "2705da2b616b98fa6010a25813c5a7a27456f71d",
-      "user": {
-        "login": "ursa-labs",
-        "id": 46514972,
-        "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-        "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-        "gravatar_id": "",
-        "url": "https://api.github.com/users/ursa-labs",
-        "html_url": "https://github.com/ursa-labs",
-        "followers_url": "https://api.github.com/users/ursa-labs/followers",
-        "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-        "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-        "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-        "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-        "repos_url": "https://api.github.com/users/ursa-labs/repos",
-        "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-        "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-        "type": "Organization",
-        "site_admin": false
-      },
-      "repo": {
-        "id": 169101701,
-        "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=",
-        "name": "ursabot",
-        "full_name": "ursa-labs/ursabot",
-        "private": false,
-        "owner": {
-          "login": "ursa-labs",
-          "id": 46514972,
-          "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-          "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-          "gravatar_id": "",
-          "url": "https://api.github.com/users/ursa-labs",
-          "html_url": "https://github.com/ursa-labs",
-          "followers_url": "https://api.github.com/users/ursa-labs/followers",
-          "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-          "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-          "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-          "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-          "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-          "repos_url": "https://api.github.com/users/ursa-labs/repos",
-          "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-          "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-          "type": "Organization",
-          "site_admin": false
-        },
-        "html_url": "https://github.com/ursa-labs/ursabot",
-        "description": null,
-        "fork": false,
-        "url": "https://api.github.com/repos/ursa-labs/ursabot",
-        "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks",
-        "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}",
-        "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}",
-        "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams",
-        "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks",
-        "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}",
-        "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events",
-        "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}",
-        "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}",
-        "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags",
-        "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}",
-        "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}",
-        "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}",
-        "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}",
-        "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}",
-        "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages",
-        "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers",
-        "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors",
-        "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers",
-        "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription",
-        "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}",
-        "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}",
-        "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}",
-        "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}",
-        "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}",
-        "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}",
-        "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges",
-        "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}",
-        "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads",
-        "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}",
-        "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}",
-        "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}",
-        "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}",
-        "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}",
-        "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}",
-        "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments",
-        "created_at": "2019-02-04T15:40:31Z",
-        "updated_at": "2019-04-04T17:49:10Z",
-        "pushed_at": "2019-04-05T12:01:40Z",
-        "git_url": "git://github.com/ursa-labs/ursabot.git",
-        "ssh_url": "git@github.com:ursa-labs/ursabot.git",
-        "clone_url": "https://github.com/ursa-labs/ursabot.git",
-        "svn_url": "https://github.com/ursa-labs/ursabot",
-        "homepage": null,
-        "size": 898,
-        "stargazers_count": 1,
-        "watchers_count": 1,
-        "language": "Jupyter Notebook",
-        "has_issues": true,
-        "has_projects": true,
-        "has_downloads": true,
-        "has_wiki": true,
-        "has_pages": false,
-        "forks_count": 0,
-        "mirror_url": null,
-        "archived": false,
-        "disabled": false,
-        "open_issues_count": 19,
-        "license": null,
-        "forks": 0,
-        "open_issues": 19,
-        "watchers": 1,
-        "default_branch": "master"
-      }
-    },
-    "base": {
-      "label": "ursa-labs:master",
-      "ref": "master",
-      "sha": "a162ad254b589b924db47e057791191b39613fd5",
-      "user": {
-        "login": "ursa-labs",
-        "id": 46514972,
-        "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-        "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-        "gravatar_id": "",
-        "url": "https://api.github.com/users/ursa-labs",
-        "html_url": "https://github.com/ursa-labs",
-        "followers_url": "https://api.github.com/users/ursa-labs/followers",
-        "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-        "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-        "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-        "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-        "repos_url": "https://api.github.com/users/ursa-labs/repos",
-        "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-        "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-        "type": "Organization",
-        "site_admin": false
-      },
-      "repo": {
-        "id": 169101701,
-        "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=",
-        "name": "ursabot",
-        "full_name": "ursa-labs/ursabot",
-        "private": false,
-        "owner": {
-          "login": "ursa-labs",
-          "id": 46514972,
-          "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-          "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-          "gravatar_id": "",
-          "url": "https://api.github.com/users/ursa-labs",
-          "html_url": "https://github.com/ursa-labs",
-          "followers_url": "https://api.github.com/users/ursa-labs/followers",
-          "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-          "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-          "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-          "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-          "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-          "repos_url": "https://api.github.com/users/ursa-labs/repos",
-          "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-          "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-          "type": "Organization",
-          "site_admin": false
-        },
-        "html_url": "https://github.com/ursa-labs/ursabot",
-        "description": null,
-        "fork": false,
-        "url": "https://api.github.com/repos/ursa-labs/ursabot",
-        "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks",
-        "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}",
-        "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}",
-        "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams",
-        "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks",
-        "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}",
-        "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events",
-        "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}",
-        "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}",
-        "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags",
-        "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}",
-        "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}",
-        "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}",
-        "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}",
-        "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}",
-        "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages",
-        "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers",
-        "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors",
-        "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers",
-        "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription",
-        "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}",
-        "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}",
-        "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}",
-        "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}",
-        "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}",
-        "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}",
-        "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges",
-        "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}",
-        "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads",
-        "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}",
-        "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}",
-        "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}",
-        "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}",
-        "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}",
-        "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}",
-        "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments",
-        "created_at": "2019-02-04T15:40:31Z",
-        "updated_at": "2019-04-04T17:49:10Z",
-        "pushed_at": "2019-04-05T12:01:40Z",
-        "git_url": "git://github.com/ursa-labs/ursabot.git",
-        "ssh_url": "git@github.com:ursa-labs/ursabot.git",
-        "clone_url": "https://github.com/ursa-labs/ursabot.git",
-        "svn_url": "https://github.com/ursa-labs/ursabot",
-        "homepage": null,
-        "size": 898,
-        "stargazers_count": 1,
-        "watchers_count": 1,
-        "language": "Jupyter Notebook",
-        "has_issues": true,
-        "has_projects": true,
-        "has_downloads": true,
-        "has_wiki": true,
-        "has_pages": false,
-        "forks_count": 0,
-        "mirror_url": null,
-        "archived": false,
-        "disabled": false,
-        "open_issues_count": 19,
-        "license": null,
-        "forks": 0,
-        "open_issues": 19,
-        "watchers": 1,
-        "default_branch": "master"
-      }
-    },
-    "_links": {
-      "self": {
-        "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26"
-      },
-      "html": {
-        "href": "https://github.com/ursa-labs/ursabot/pull/26"
-      },
-      "issue": {
-        "href": "https://api.github.com/repos/ursa-labs/ursabot/issues/26"
-      },
-      "comments": {
-        "href": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments"
-      },
-      "review_comments": {
-        "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/comments"
-      },
-      "review_comment": {
-        "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/comments{/number}"
-      },
-      "commits": {
-        "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/commits"
-      },
-      "statuses": {
-        "href": "https://api.github.com/repos/ursa-labs/ursabot/statuses/2705da2b616b98fa6010a25813c5a7a27456f71d"
-      }
-    },
-    "author_association": "MEMBER",
-    "merged": false,
-    "mergeable": true,
-    "rebaseable": true,
-    "mergeable_state": "unstable",
-    "merged_by": null,
-    "comments": 5,
-    "review_comments": 0,
-    "maintainer_can_modify": false,
-    "commits": 2,
-    "additions": 1124,
-    "deletions": 0,
-    "changed_files": 7
-  },
-  "repository": {
-    "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}",
-    "archived": false,
-    "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}",
-    "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}",
-    "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}",
-    "clone_url": "https://github.com/ursa-labs/ursabot.git",
-    "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}",
-    "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}",
-    "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}",
-    "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}",
-    "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors",
-    "created_at": "2019-02-04T15:40:31Z",
-    "default_branch": "master",
-    "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments",
-    "description": null,
-    "disabled": false,
-    "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads",
-    "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events",
-    "fork": false,
-    "forks": 0,
-    "forks_count": 0,
-    "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks",
-    "full_name": "ursa-labs/ursabot",
-    "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}",
-    "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}",
-    "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}",
-    "git_url": "git://github.com/ursa-labs/ursabot.git",
-    "has_downloads": true,
-    "has_issues": true,
-    "has_pages": false,
-    "has_projects": true,
-    "has_wiki": true,
-    "homepage": null,
-    "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks",
-    "html_url": "https://github.com/ursa-labs/ursabot",
-    "id": 169101701,
-    "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}",
-    "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}",
-    "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}",
-    "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}",
-    "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}",
-    "language": "Jupyter Notebook",
-    "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages",
-    "license": null,
-    "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges",
-    "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}",
-    "mirror_url": null,
-    "name": "ursabot",
-    "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=",
-    "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}",
-    "open_issues": 19,
-    "open_issues_count": 19,
-    "owner": {
-      "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-      "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-      "followers_url": "https://api.github.com/users/ursa-labs/followers",
-      "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-      "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-      "gravatar_id": "",
-      "html_url": "https://github.com/ursa-labs",
-      "id": 46514972,
-      "login": "ursa-labs",
-      "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-      "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-      "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-      "repos_url": "https://api.github.com/users/ursa-labs/repos",
-      "site_admin": false,
-      "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-      "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-      "type": "Organization",
-      "url": "https://api.github.com/users/ursa-labs"
-    },
-    "private": false,
-    "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}",
-    "pushed_at": "2019-04-05T11:22:16Z",
-    "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}",
-    "size": 892,
-    "ssh_url": "git@github.com:ursa-labs/ursabot.git",
-    "stargazers_count": 1,
-    "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers",
-    "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}",
-    "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers",
-    "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription",
-    "svn_url": "https://github.com/ursa-labs/ursabot",
-    "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags",
-    "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams",
-    "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}",
-    "updated_at": "2019-04-04T17:49:10Z",
-    "url": "https://api.github.com/repos/ursa-labs/ursabot",
-    "watchers": 1,
-    "watchers_count": 1
-  },
-  "sender": {
-    "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-    "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-    "followers_url": "https://api.github.com/users/kszucs/followers",
-    "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-    "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-    "gravatar_id": "",
-    "html_url": "https://github.com/kszucs",
-    "id": 961747,
-    "login": "kszucs",
-    "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-    "organizations_url": "https://api.github.com/users/kszucs/orgs",
-    "received_events_url": "https://api.github.com/users/kszucs/received_events",
-    "repos_url": "https://api.github.com/users/kszucs/repos",
-    "site_admin": false,
-    "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-    "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-    "type": "User",
-    "url": "https://api.github.com/users/kszucs"
-  }
-}
\ No newline at end of file
diff --git a/dev/archery/archery/tests/fixtures/issue-19.json b/dev/archery/archery/tests/fixtures/issue-19.json
deleted file mode 100644
index 1e49397..0000000
--- a/dev/archery/archery/tests/fixtures/issue-19.json
+++ /dev/null
@@ -1,64 +0,0 @@
-{
-    "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19",
-    "repository_url": "https://api.github.com/repos/ursa-labs/ursabot",
-    "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19/labels{/name}",
-    "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19/comments",
-    "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/19/events",
-    "html_url": "https://github.com/ursa-labs/ursabot/issues/19",
-    "id": 428131685,
-    "node_id": "MDU6SXNzdWU0MjgxMzE2ODU=",
-    "number": 19,
-    "title": "Build ursabot itself via ursabot",
-    "user": {
-        "login": "kszucs",
-        "id": 961747,
-        "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-        "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-        "gravatar_id": "",
-        "url": "https://api.github.com/users/kszucs",
-        "html_url": "https://github.com/kszucs",
-        "followers_url": "https://api.github.com/users/kszucs/followers",
-        "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-        "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-        "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-        "organizations_url": "https://api.github.com/users/kszucs/orgs",
-        "repos_url": "https://api.github.com/users/kszucs/repos",
-        "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-        "received_events_url": "https://api.github.com/users/kszucs/received_events",
-        "type": "User",
-        "site_admin": false
-    },
-    "labels": [],
-    "state": "closed",
-    "locked": false,
-    "assignee": null,
-    "assignees": [],
-    "milestone": null,
-    "comments": 8,
-    "created_at": "2019-04-02T09:56:41Z",
-    "updated_at": "2019-04-05T13:30:49Z",
-    "closed_at": "2019-04-05T13:30:49Z",
-    "author_association": "MEMBER",
-    "body": "",
-    "closed_by": {
-        "login": "kszucs",
-        "id": 961747,
-        "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-        "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-        "gravatar_id": "",
-        "url": "https://api.github.com/users/kszucs",
-        "html_url": "https://github.com/kszucs",
-        "followers_url": "https://api.github.com/users/kszucs/followers",
-        "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-        "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-        "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-        "organizations_url": "https://api.github.com/users/kszucs/orgs",
-        "repos_url": "https://api.github.com/users/kszucs/repos",
-        "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-        "received_events_url": "https://api.github.com/users/kszucs/received_events",
-        "type": "User",
-        "site_admin": false
-    }
-}
\ No newline at end of file
diff --git a/dev/archery/archery/tests/fixtures/issue-26.json b/dev/archery/archery/tests/fixtures/issue-26.json
deleted file mode 100644
index 44c4d3b..0000000
--- a/dev/archery/archery/tests/fixtures/issue-26.json
+++ /dev/null
@@ -1,70 +0,0 @@
-{
-    "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26",
-    "repository_url": "https://api.github.com/repos/ursa-labs/ursabot",
-    "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}",
-    "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments",
-    "events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/events",
-    "html_url": "https://github.com/ursa-labs/ursabot/pull/26",
-    "id": 429706959,
-    "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy",
-    "number": 26,
-    "title": "Unittests for GithubHook + native asyncio syntax",
-    "user": {
-        "login": "kszucs",
-        "id": 961747,
-        "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-        "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-        "gravatar_id": "",
-        "url": "https://api.github.com/users/kszucs",
-        "html_url": "https://github.com/kszucs",
-        "followers_url": "https://api.github.com/users/kszucs/followers",
-        "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-        "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-        "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-        "organizations_url": "https://api.github.com/users/kszucs/orgs",
-        "repos_url": "https://api.github.com/users/kszucs/repos",
-        "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-        "received_events_url": "https://api.github.com/users/kszucs/received_events",
-        "type": "User",
-        "site_admin": false
-    },
-    "labels": [],
-    "state": "closed",
-    "locked": false,
-    "assignee": null,
-    "assignees": [],
-    "milestone": null,
-    "comments": 9,
-    "created_at": "2019-04-05T11:22:15Z",
-    "updated_at": "2019-08-28T00:34:19Z",
-    "closed_at": "2019-04-05T13:54:34Z",
-    "author_association": "MEMBER",
-    "pull_request": {
-        "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26",
-        "html_url": "https://github.com/ursa-labs/ursabot/pull/26",
-        "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff",
-        "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch"
-    },
-    "body": "Resolves:\r\n- #26 Unittests for GithubHook + native asyncio syntax\r\n- #27 Use native async/await keywords instead of @inlineCallbacks and yield\r\n",
-    "closed_by": {
-        "login": "kszucs",
-        "id": 961747,
-        "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-        "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-        "gravatar_id": "",
-        "url": "https://api.github.com/users/kszucs",
-        "html_url": "https://github.com/kszucs",
-        "followers_url": "https://api.github.com/users/kszucs/followers",
-        "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-        "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-        "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-        "organizations_url": "https://api.github.com/users/kszucs/orgs",
-        "repos_url": "https://api.github.com/users/kszucs/repos",
-        "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-        "received_events_url": "https://api.github.com/users/kszucs/received_events",
-        "type": "User",
-        "site_admin": false
-    }
-}
\ No newline at end of file
diff --git a/dev/archery/archery/tests/fixtures/issue-comment-480243811.json b/dev/archery/archery/tests/fixtures/issue-comment-480243811.json
deleted file mode 100644
index 93ee4b1..0000000
--- a/dev/archery/archery/tests/fixtures/issue-comment-480243811.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
-    "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/479081273",
-    "html_url": "https://github.com/ursa-labs/ursabot/pull/21#issuecomment-479081273",
-    "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/21",
-    "id": 480243811,
-    "node_id": "MDEyOklzc3VlQ29tbWVudDQ3OTA4MTI3Mw==",
-    "user": {
-        "login": "kszucs",
-        "id": 961747,
-        "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-        "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-        "gravatar_id": "",
-        "url": "https://api.github.com/users/kszucs",
-        "html_url": "https://github.com/kszucs",
-        "followers_url": "https://api.github.com/users/kszucs/followers",
-        "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-        "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-        "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-        "organizations_url": "https://api.github.com/users/kszucs/orgs",
-        "repos_url": "https://api.github.com/users/kszucs/repos",
-        "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-        "received_events_url": "https://api.github.com/users/kszucs/received_events",
-        "type": "User",
-        "site_admin": false
-    },
-    "created_at": "2019-04-02T16:29:46Z",
-    "updated_at": "2019-04-02T16:29:46Z",
-    "author_association": "MEMBER",
-    "body": "@ursabot"
-}
\ No newline at end of file
diff --git a/dev/archery/archery/tests/fixtures/issue-comment-480248726.json b/dev/archery/archery/tests/fixtures/issue-comment-480248726.json
deleted file mode 100644
index f3cd340..0000000
--- a/dev/archery/archery/tests/fixtures/issue-comment-480248726.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
-    "url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480248726",
-    "html_url": "https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480248726",
-    "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26",
-    "id": 480248726,
-    "node_id": "MDEyOklzc3VlQ29tbWVudDQ4MDI0ODcyNg==",
-    "user": {
-        "login": "kszucs",
-        "id": 961747,
-        "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-        "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-        "gravatar_id": "",
-        "url": "https://api.github.com/users/kszucs",
-        "html_url": "https://github.com/kszucs",
-        "followers_url": "https://api.github.com/users/kszucs/followers",
-        "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-        "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-        "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-        "organizations_url": "https://api.github.com/users/kszucs/orgs",
-        "repos_url": "https://api.github.com/users/kszucs/repos",
-        "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-        "received_events_url": "https://api.github.com/users/kszucs/received_events",
-        "type": "User",
-        "site_admin": false
-    },
-    "created_at": "2019-04-05T11:55:43Z",
-    "updated_at": "2019-04-05T11:55:43Z",
-    "author_association": "MEMBER",
-    "body": "@ursabot build"
-}
\ No newline at end of file
diff --git a/dev/archery/archery/tests/fixtures/pull-request-26-commit.json b/dev/archery/archery/tests/fixtures/pull-request-26-commit.json
deleted file mode 100644
index ffc4894..0000000
--- a/dev/archery/archery/tests/fixtures/pull-request-26-commit.json
+++ /dev/null
@@ -1,158 +0,0 @@
-{
-  "sha": "2705da2b616b98fa6010a25813c5a7a27456f71d",
-  "node_id": "MDY6Q29tbWl0MTY5MTAxNzAxOjI3MDVkYTJiNjE2Yjk4ZmE2MDEwYTI1ODEzYzVhN2EyNzQ1NmY3MWQ=",
-  "commit": {
-    "author": {
-      "name": "Krisztián Szűcs",
-      "email": "szucs.krisztian@gmail.com",
-      "date": "2019-04-05T12:01:31Z"
-    },
-    "committer": {
-      "name": "Krisztián Szűcs",
-      "email": "szucs.krisztian@gmail.com",
-      "date": "2019-04-05T12:01:31Z"
-    },
-    "message": "add recorded event requests",
-    "tree": {
-      "sha": "16a7bb186833a67e9c2d84a58393503b85500ceb",
-      "url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees/16a7bb186833a67e9c2d84a58393503b85500ceb"
-    },
-    "url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits/2705da2b616b98fa6010a25813c5a7a27456f71d",
-    "comment_count": 0,
-    "verification": {
-      "verified": true,
-      "reason": "valid",
-      "signature": "-----BEGIN PGP SIGNATURE-----\n\niQFOBAABCAA4FiEEOOW2r8dr6sA77zHlgjqBKYe1QKUFAlynQ58aHHN6dWNzLmty\naXN6dGlhbkBnbWFpbC5jb20ACgkQgjqBKYe1QKUYKwf6AiXDMaLqNLNSjRY7lIXX\nudioewz0hSb4bgIXBv30nswu9CoOA0+mHCokEVtZhYbXzXDsZ1KJrilSC4j+Ws4q\nkRGA6iEmrne2HcSKNZXzcVnwV9zpwKxlVh2QCTNb1PuOYFBLH0kwE704uWIWMGDN\nbo8cjQPwegePCRguCvPh/5wa5J3uiq5gmJLG6bC/d1XYE+FJVtlnyzqzLMIryGKe\ntIciw+wwkF413Q/YVbZ49vLUeCX9H8PHC4mZYGDWuvjFW1WTfkjK5bAH+oaTVM6h\n350I5ZFloHmMA/QeRge5qFxXoEBMDGiXHHktzYZDXnliFOQNxzqwirA5lQQ6LRSS\naQ==\n=7rqi\n-----END PGP SIGNATURE-----",
-      "payload": "tree 16a7bb186833a67e9c2d84a58393503b85500ceb\nparent 446ae69b9385e8d0f40aa9595f723d34383af2f7\nauthor Krisztián Szűcs <szucs.krisztian@gmail.com> 1554465691 +0200\ncommitter Krisztián Szűcs <szucs.krisztian@gmail.com> 1554465691 +0200\n\nadd recorded event requests\n"
-    }
-  },
-  "url": "https://api.github.com/repos/ursa-labs/ursabot/commits/2705da2b616b98fa6010a25813c5a7a27456f71d",
-  "html_url": "https://github.com/ursa-labs/ursabot/commit/2705da2b616b98fa6010a25813c5a7a27456f71d",
-  "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/commits/2705da2b616b98fa6010a25813c5a7a27456f71d/comments",
-  "author": {
-    "login": "kszucs",
-    "id": 961747,
-    "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-    "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-    "gravatar_id": "",
-    "url": "https://api.github.com/users/kszucs",
-    "html_url": "https://github.com/kszucs",
-    "followers_url": "https://api.github.com/users/kszucs/followers",
-    "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-    "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-    "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-    "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-    "organizations_url": "https://api.github.com/users/kszucs/orgs",
-    "repos_url": "https://api.github.com/users/kszucs/repos",
-    "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-    "received_events_url": "https://api.github.com/users/kszucs/received_events",
-    "type": "User",
-    "site_admin": false
-  },
-  "committer": {
-    "login": "kszucs",
-    "id": 961747,
-    "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-    "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-    "gravatar_id": "",
-    "url": "https://api.github.com/users/kszucs",
-    "html_url": "https://github.com/kszucs",
-    "followers_url": "https://api.github.com/users/kszucs/followers",
-    "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-    "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-    "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-    "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-    "organizations_url": "https://api.github.com/users/kszucs/orgs",
-    "repos_url": "https://api.github.com/users/kszucs/repos",
-    "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-    "received_events_url": "https://api.github.com/users/kszucs/received_events",
-    "type": "User",
-    "site_admin": false
-  },
-  "parents": [
-    {
-      "sha": "446ae69b9385e8d0f40aa9595f723d34383af2f7",
-      "url": "https://api.github.com/repos/ursa-labs/ursabot/commits/446ae69b9385e8d0f40aa9595f723d34383af2f7",
-      "html_url": "https://github.com/ursa-labs/ursabot/commit/446ae69b9385e8d0f40aa9595f723d34383af2f7"
-    }
-  ],
-  "stats": {
-    "total": 1062,
-    "additions": 1058,
-    "deletions": 4
-  },
-  "files": [
-    {
-      "sha": "dfae6eeaef384ae6180c6302a58b49e39982dc33",
-      "filename": "ursabot/tests/fixtures/issue-comment-build-command.json",
-      "status": "added",
-      "additions": 212,
-      "deletions": 0,
-      "changes": 212,
-      "blob_url": "https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-build-command.json",
-      "raw_url": "https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-build-command.json",
-      "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-build-command.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d",
-      "patch": "@@ -0,0 +1,212 @@\n+{\n+    \"action\": \"created\",\n+    \"comment\": {\n+        \"author_association\": \"NONE\",\n+        \"body\": \"I've successfully started builds for this PR\",\n+        \"created_at\": \"2019-04-05T11:55:44Z\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480248730\",\n+        \"id\": 480248730,\n+        \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0ODczMA==\",\n+        \"updated_at\": \"2019-04-05T11:55:44Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480248730\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+            \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+            \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/ursabot\",\n+            \"id\": 49275095,\n+            \"login\": \"ursabot\",\n+            \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+            \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/ursabot\"\n+        }\n+    },\n+    \"issue\": {\n+        \"assignee\": null,\n+        \"assignees\": [],\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"\",\n+        \"closed_at\": null,\n+        \"comments\": 4,\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+        \"created_at\": \"2019-04-05T11:22:15Z\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+        \"id\": 429706959,\n+        \"labels\": [],\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+        \"locked\": false,\n+        \"milestone\": null,\n+        \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+        \"number\": 26,\n+        \"pull_request\": {\n+            \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+            \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+            \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+            \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+        },\n+        \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"state\": \"open\",\n+        \"title\": \"Unittests for GithubHook\",\n+        \"updated_at\": \"2019-04-05T11:55:44Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"organization\": {\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+        \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+        \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+        \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+        \"id\": 46514972,\n+        \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+        \"login\": \"ursa-labs\",\n+        \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+        \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+        \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+        \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+        \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+    },\n+    \"repository\": {\n+        \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+        \"archived\": false,\n+        \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+        \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+        \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+        \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+        \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+        \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+        \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+        \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+        \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+        \"created_at\": \"2019-02-04T15:40:31Z\",\n+        \"default_branch\": \"master\",\n+        \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+        \"description\": null,\n+        \"disabled\": false,\n+        \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+        \"fork\": false,\n+        \"forks\": 0,\n+        \"forks_count\": 0,\n+        \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+        \"full_name\": \"ursa-labs/ursabot\",\n+        \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+        \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+        \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+        \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+        \"has_downloads\": true,\n+        \"has_issues\": true,\n+        \"has_pages\": false,\n+        \"has_projects\": true,\n+        \"has_wiki\": true,\n+        \"homepage\": null,\n+        \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"id\": 169101701,\n+        \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+        \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+        \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+        \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+        \"language\": \"Jupyter Notebook\",\n+        \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+        \"license\": null,\n+        \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+        \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+        \"mirror_url\": null,\n+        \"name\": \"ursabot\",\n+        \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+        \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+        \"open_issues\": 19,\n+        \"open_issues_count\": 19,\n+        \"owner\": {\n+            \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+            \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+            \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/ursa-labs\",\n+            \"id\": 46514972,\n+            \"login\": \"ursa-labs\",\n+            \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+            \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+            \"type\": \"Organization\",\n+            \"url\": \"https://api.github.com/users/ursa-labs\"\n+        },\n+        \"private\": false,\n+        \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+        \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+        \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+        \"size\": 892,\n+        \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+        \"stargazers_count\": 1,\n+        \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+        \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+        \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+        \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+        \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+        \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+        \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+        \"updated_at\": \"2019-04-04T17:49:10Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"watchers\": 1,\n+        \"watchers_count\": 1\n+    },\n+    \"sender\": {\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+        \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+        \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+        \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+        \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+        \"gravatar_id\": \"\",\n+        \"html_url\": \"https://github.com/ursabot\",\n+        \"id\": 49275095,\n+        \"login\": \"ursabot\",\n+        \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+        \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+        \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+        \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+        \"site_admin\": false,\n+        \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+        \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+        \"type\": \"User\",\n+        \"url\": \"https://api.github.com/users/ursabot\"\n+    }\n+}"
-    },
-    {
-      "sha": "7ef554e333327f0e62aa1fd76b4b17844a39adeb",
-      "filename": "ursabot/tests/fixtures/issue-comment-by-ursabot.json",
-      "status": "added",
-      "additions": 212,
-      "deletions": 0,
-      "changes": 212,
-      "blob_url": "https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-by-ursabot.json",
-      "raw_url": "https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-by-ursabot.json",
-      "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-by-ursabot.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d",
-      "patch": "@@ -0,0 +1,212 @@\n+{\n+    \"action\": \"created\",\n+    \"comment\": {\n+        \"author_association\": \"NONE\",\n+        \"body\": \"Unknown command \\\"\\\"\",\n+        \"created_at\": \"2019-04-05T11:35:47Z\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243815\",\n+        \"id\": 480243815,\n+        \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxNQ==\",\n+        \"updated_at\": \"2019-04-05T11:35:47Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243815\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+            \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+            \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/ursabot\",\n+            \"id\": 49275095,\n+            \"login\": \"ursabot\",\n+            \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+            \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/ursabot\"\n+        }\n+    },\n+    \"issue\": {\n+        \"assignee\": null,\n+        \"assignees\": [],\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"\",\n+        \"closed_at\": null,\n+        \"comments\": 2,\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+        \"created_at\": \"2019-04-05T11:22:15Z\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+        \"id\": 429706959,\n+        \"labels\": [],\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+        \"locked\": false,\n+        \"milestone\": null,\n+        \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+        \"number\": 26,\n+        \"pull_request\": {\n+            \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+            \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+            \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+            \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+        },\n+        \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"state\": \"open\",\n+        \"title\": \"Unittests for GithubHook\",\n+        \"updated_at\": \"2019-04-05T11:35:47Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"organization\": {\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+        \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+        \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+        \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+        \"id\": 46514972,\n+        \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+        \"login\": \"ursa-labs\",\n+        \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+        \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+        \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+        \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+        \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+    },\n+    \"repository\": {\n+        \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+        \"archived\": false,\n+        \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+        \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+        \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+        \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+        \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+        \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+        \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+        \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+        \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+        \"created_at\": \"2019-02-04T15:40:31Z\",\n+        \"default_branch\": \"master\",\n+        \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+        \"description\": null,\n+        \"disabled\": false,\n+        \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+        \"fork\": false,\n+        \"forks\": 0,\n+        \"forks_count\": 0,\n+        \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+        \"full_name\": \"ursa-labs/ursabot\",\n+        \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+        \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+        \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+        \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+        \"has_downloads\": true,\n+        \"has_issues\": true,\n+        \"has_pages\": false,\n+        \"has_projects\": true,\n+        \"has_wiki\": true,\n+        \"homepage\": null,\n+        \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"id\": 169101701,\n+        \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+        \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+        \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+        \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+        \"language\": \"Jupyter Notebook\",\n+        \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+        \"license\": null,\n+        \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+        \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+        \"mirror_url\": null,\n+        \"name\": \"ursabot\",\n+        \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+        \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+        \"open_issues\": 19,\n+        \"open_issues_count\": 19,\n+        \"owner\": {\n+            \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+            \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+            \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/ursa-labs\",\n+            \"id\": 46514972,\n+            \"login\": \"ursa-labs\",\n+            \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+            \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+            \"type\": \"Organization\",\n+            \"url\": \"https://api.github.com/users/ursa-labs\"\n+        },\n+        \"private\": false,\n+        \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+        \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+        \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+        \"size\": 892,\n+        \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+        \"stargazers_count\": 1,\n+        \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+        \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+        \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+        \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+        \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+        \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+        \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+        \"updated_at\": \"2019-04-04T17:49:10Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"watchers\": 1,\n+        \"watchers_count\": 1\n+    },\n+    \"sender\": {\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+        \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+        \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+        \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+        \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+        \"gravatar_id\": \"\",\n+        \"html_url\": \"https://github.com/ursabot\",\n+        \"id\": 49275095,\n+        \"login\": \"ursabot\",\n+        \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+        \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+        \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+        \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+        \"site_admin\": false,\n+        \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+        \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+        \"type\": \"User\",\n+        \"url\": \"https://api.github.com/users/ursabot\"\n+    }\n+}"
-    },
-    {
-      "sha": "a8082dbc91fdfe815b795e49ec10e49000771ef5",
-      "filename": "ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json",
-      "status": "added",
-      "additions": 212,
-      "deletions": 0,
-      "changes": 212,
-      "blob_url": "https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json",
-      "raw_url": "https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json",
-      "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d",
-      "patch": "@@ -0,0 +1,212 @@\n+{\n+    \"action\": \"created\",\n+    \"comment\": {\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"bear is no game\",\n+        \"created_at\": \"2019-04-05T11:26:56Z\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480241727\",\n+        \"id\": 480241727,\n+        \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0MTcyNw==\",\n+        \"updated_at\": \"2019-04-05T11:26:56Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480241727\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"issue\": {\n+        \"assignee\": null,\n+        \"assignees\": [],\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"\",\n+        \"closed_at\": null,\n+        \"comments\": 0,\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+        \"created_at\": \"2019-04-05T11:22:15Z\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+        \"id\": 429706959,\n+        \"labels\": [],\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+        \"locked\": false,\n+        \"milestone\": null,\n+        \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+        \"number\": 26,\n+        \"pull_request\": {\n+            \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+            \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+            \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+            \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+        },\n+        \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"state\": \"open\",\n+        \"title\": \"Unittests for GithubHook\",\n+        \"updated_at\": \"2019-04-05T11:26:56Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"organization\": {\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+        \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+        \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+        \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+        \"id\": 46514972,\n+        \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+        \"login\": \"ursa-labs\",\n+        \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+        \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+        \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+        \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+        \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+    },\n+    \"repository\": {\n+        \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+        \"archived\": false,\n+        \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+        \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+        \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+        \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+        \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+        \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+        \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+        \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+        \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+        \"created_at\": \"2019-02-04T15:40:31Z\",\n+        \"default_branch\": \"master\",\n+        \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+        \"description\": null,\n+        \"disabled\": false,\n+        \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+        \"fork\": false,\n+        \"forks\": 0,\n+        \"forks_count\": 0,\n+        \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+        \"full_name\": \"ursa-labs/ursabot\",\n+        \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+        \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+        \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+        \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+        \"has_downloads\": true,\n+        \"has_issues\": true,\n+        \"has_pages\": false,\n+        \"has_projects\": true,\n+        \"has_wiki\": true,\n+        \"homepage\": null,\n+        \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"id\": 169101701,\n+        \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+        \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+        \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+        \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+        \"language\": \"Jupyter Notebook\",\n+        \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+        \"license\": null,\n+        \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+        \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+        \"mirror_url\": null,\n+        \"name\": \"ursabot\",\n+        \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+        \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+        \"open_issues\": 19,\n+        \"open_issues_count\": 19,\n+        \"owner\": {\n+            \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+            \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+            \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/ursa-labs\",\n+            \"id\": 46514972,\n+            \"login\": \"ursa-labs\",\n+            \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+            \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+            \"type\": \"Organization\",\n+            \"url\": \"https://api.github.com/users/ursa-labs\"\n+        },\n+        \"private\": false,\n+        \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+        \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+        \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+        \"size\": 892,\n+        \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+        \"stargazers_count\": 1,\n+        \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+        \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+        \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+        \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+        \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+        \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+        \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+        \"updated_at\": \"2019-04-04T17:49:10Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"watchers\": 1,\n+        \"watchers_count\": 1\n+    },\n+    \"sender\": {\n+        \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+        \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+        \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+        \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+        \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+        \"gravatar_id\": \"\",\n+        \"html_url\": \"https://github.com/kszucs\",\n+        \"id\": 961747,\n+        \"login\": \"kszucs\",\n+        \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+        \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+        \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+        \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+        \"site_admin\": false,\n+        \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+        \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+        \"type\": \"User\",\n+        \"url\": \"https://api.github.com/users/kszucs\"\n+    }\n+}"
-    },
-    {
-      "sha": "2770e29ba9086394455315e590c0b433d08e437e",
-      "filename": "ursabot/tests/fixtures/issue-comment-with-empty-command.json",
-      "status": "added",
-      "additions": 212,
-      "deletions": 0,
-      "changes": 212,
-      "blob_url": "https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-with-empty-command.json",
-      "raw_url": "https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-with-empty-command.json",
-      "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-with-empty-command.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d",
-      "patch": "@@ -0,0 +1,212 @@\n+{\n+    \"action\": \"created\",\n+    \"comment\": {\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"@ursabot \",\n+        \"created_at\": \"2019-04-05T11:35:46Z\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243811\",\n+        \"id\": 480243811,\n+        \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxMQ==\",\n+        \"updated_at\": \"2019-04-05T11:35:46Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243811\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"issue\": {\n+        \"assignee\": null,\n+        \"assignees\": [],\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"\",\n+        \"closed_at\": null,\n+        \"comments\": 1,\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+        \"created_at\": \"2019-04-05T11:22:15Z\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+        \"id\": 429706959,\n+        \"labels\": [],\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+        \"locked\": false,\n+        \"milestone\": null,\n+        \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+        \"number\": 26,\n+        \"pull_request\": {\n+            \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+            \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+            \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+            \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+        },\n+        \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"state\": \"open\",\n+        \"title\": \"Unittests for GithubHook\",\n+        \"updated_at\": \"2019-04-05T11:35:46Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"organization\": {\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+        \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+        \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+        \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+        \"id\": 46514972,\n+        \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+        \"login\": \"ursa-labs\",\n+        \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+        \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+        \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+        \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+        \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+    },\n+    \"repository\": {\n+        \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+        \"archived\": false,\n+        \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+        \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+        \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+        \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+        \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+        \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+        \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+        \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+        \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+        \"created_at\": \"2019-02-04T15:40:31Z\",\n+        \"default_branch\": \"master\",\n+        \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+        \"description\": null,\n+        \"disabled\": false,\n+        \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+        \"fork\": false,\n+        \"forks\": 0,\n+        \"forks_count\": 0,\n+        \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+        \"full_name\": \"ursa-labs/ursabot\",\n+        \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+        \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+        \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+        \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+        \"has_downloads\": true,\n+        \"has_issues\": true,\n+        \"has_pages\": false,\n+        \"has_projects\": true,\n+        \"has_wiki\": true,\n+        \"homepage\": null,\n+        \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"id\": 169101701,\n+        \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+        \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+        \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+        \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+        \"language\": \"Jupyter Notebook\",\n+        \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+        \"license\": null,\n+        \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+        \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+        \"mirror_url\": null,\n+        \"name\": \"ursabot\",\n+        \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+        \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+        \"open_issues\": 19,\n+        \"open_issues_count\": 19,\n+        \"owner\": {\n+            \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+            \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+            \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/ursa-labs\",\n+            \"id\": 46514972,\n+            \"login\": \"ursa-labs\",\n+            \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+            \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+            \"type\": \"Organization\",\n+            \"url\": \"https://api.github.com/users/ursa-labs\"\n+        },\n+        \"private\": false,\n+        \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+        \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+        \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+        \"size\": 892,\n+        \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+        \"stargazers_count\": 1,\n+        \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+        \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+        \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+        \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+        \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+        \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+        \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+        \"updated_at\": \"2019-04-04T17:49:10Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"watchers\": 1,\n+        \"watchers_count\": 1\n+    },\n+    \"sender\": {\n+        \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+        \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+        \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+        \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+        \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+        \"gravatar_id\": \"\",\n+        \"html_url\": \"https://github.com/kszucs\",\n+        \"id\": 961747,\n+        \"login\": \"kszucs\",\n+        \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+        \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+        \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+        \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+        \"site_admin\": false,\n+        \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+        \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+        \"type\": \"User\",\n+        \"url\": \"https://api.github.com/users/kszucs\"\n+    }\n+}"
-    },
-    {
-      "sha": "80ff46510a2f39ae60f7c3a98e5fdaef8e688784",
-      "filename": "ursabot/tests/fixtures/issue-comment-without-pull-request.json",
-      "status": "added",
-      "additions": 206,
-      "deletions": 0,
-      "changes": 206,
-      "blob_url": "https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-without-pull-request.json",
-      "raw_url": "https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-without-pull-request.json",
-      "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-without-pull-request.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d",
-      "patch": "@@ -0,0 +1,206 @@\n+{\n+    \"action\": \"created\",\n+    \"comment\": {\n+        \"author_association\": \"NONE\",\n+        \"body\": \"Ursabot only listens to pull request comments!\",\n+        \"created_at\": \"2019-04-05T11:53:43Z\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/issues/19#issuecomment-480248217\",\n+        \"id\": 480248217,\n+        \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19\",\n+        \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0ODIxNw==\",\n+        \"updated_at\": \"2019-04-05T11:53:43Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480248217\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+            \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+            \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/ursabot\",\n+            \"id\": 49275095,\n+            \"login\": \"ursabot\",\n+            \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+            \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/ursabot\"\n+        }\n+    },\n+    \"issue\": {\n+        \"assignee\": null,\n+        \"assignees\": [],\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"\",\n+        \"closed_at\": null,\n+        \"comments\": 4,\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19/comments\",\n+        \"created_at\": \"2019-04-02T09:56:41Z\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19/events\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/issues/19\",\n+        \"id\": 428131685,\n+        \"labels\": [],\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19/labels{/name}\",\n+        \"locked\": false,\n+        \"milestone\": null,\n+        \"node_id\": \"MDU6SXNzdWU0MjgxMzE2ODU=\",\n+        \"number\": 19,\n+        \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"state\": \"open\",\n+        \"title\": \"Build ursabot itself via ursabot\",\n+        \"updated_at\": \"2019-04-05T11:53:43Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"organization\": {\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+        \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+        \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+        \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+        \"id\": 46514972,\n+        \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+        \"login\": \"ursa-labs\",\n+        \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+        \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+        \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+        \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+        \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+    },\n+    \"repository\": {\n+        \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+        \"archived\": false,\n+        \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+        \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+        \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+        \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+        \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+        \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+        \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+        \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+        \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+        \"created_at\": \"2019-02-04T15:40:31Z\",\n+        \"default_branch\": \"master\",\n+        \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+        \"description\": null,\n+        \"disabled\": false,\n+        \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+        \"fork\": false,\n+        \"forks\": 0,\n+        \"forks_count\": 0,\n+        \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+        \"full_name\": \"ursa-labs/ursabot\",\n+        \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+        \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+        \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+        \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+        \"has_downloads\": true,\n+        \"has_issues\": true,\n+        \"has_pages\": false,\n+        \"has_projects\": true,\n+        \"has_wiki\": true,\n+        \"homepage\": null,\n+        \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"id\": 169101701,\n+        \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+        \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+        \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+        \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+        \"language\": \"Jupyter Notebook\",\n+        \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+        \"license\": null,\n+        \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+        \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+        \"mirror_url\": null,\n+        \"name\": \"ursabot\",\n+        \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+        \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+        \"open_issues\": 19,\n+        \"open_issues_count\": 19,\n+        \"owner\": {\n+            \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+            \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+            \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/ursa-labs\",\n+            \"id\": 46514972,\n+            \"login\": \"ursa-labs\",\n+            \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+            \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+            \"type\": \"Organization\",\n+            \"url\": \"https://api.github.com/users/ursa-labs\"\n+        },\n+        \"private\": false,\n+        \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+        \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+        \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+        \"size\": 892,\n+        \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+        \"stargazers_count\": 1,\n+        \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+        \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+        \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+        \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+        \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+        \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+        \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+        \"updated_at\": \"2019-04-04T17:49:10Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"watchers\": 1,\n+        \"watchers_count\": 1\n+    },\n+    \"sender\": {\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+        \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+        \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+        \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+        \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+        \"gravatar_id\": \"\",\n+        \"html_url\": \"https://github.com/ursabot\",\n+        \"id\": 49275095,\n+        \"login\": \"ursabot\",\n+        \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+        \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+        \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+        \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+        \"site_admin\": false,\n+        \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+        \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+        \"type\": \"User\",\n+        \"url\": \"https://api.github.com/users/ursabot\"\n+    }\n+}"
-    },
-    {
-      "sha": "c738bb0eb54c87ba0f23e97e827d77c2be74d0b6",
-      "filename": "ursabot/tests/test_hooks.py",
-      "status": "modified",
-      "additions": 4,
-      "deletions": 4,
-      "changes": 8,
-      "blob_url": "https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/test_hooks.py",
-      "raw_url": "https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/test_hooks.py",
-      "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/test_hooks.py?ref=2705da2b616b98fa6010a25813c5a7a27456f71d",
-      "patch": "@@ -54,7 +54,7 @@ class TestGithubHook(ChangeHookTestCase):\n         await self.request('ping', {})\n         assert len(self.hook.master.data.updates.changesAdded) == 0\n \n-    @ensure_deferred\n-    async def test_issue_comment(self):\n-        payload = {}\n-        await self.request('issue_comment', payload)\n+    # @ensure_deferred\n+    # async def test_issue_comment(self):\n+    #     payload = {}\n+    #     await self.request('issue_comment', payload)"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/dev/archery/archery/tests/fixtures/pull-request-26-files.json b/dev/archery/archery/tests/fixtures/pull-request-26-files.json
deleted file mode 100644
index b039b3d..0000000
--- a/dev/archery/archery/tests/fixtures/pull-request-26-files.json
+++ /dev/null
@@ -1,170 +0,0 @@
-[
-  {
-    "sha": "ebfe3f6c5e98723f9751c99ce8ce798f1ba529c5",
-    "filename": ".travis.yml",
-    "status": "modified",
-    "additions": 4,
-    "deletions": 1,
-    "changes": 5,
-    "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/.travis.yml",
-    "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/.travis.yml",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/.travis.yml?ref=70267dee34884e4b972388e1b30d57f6248c58d0",
-    "patch": "@@ -4,7 +4,10 @@ services:\n python:\n   - 3.6\n script:\n-  - pip install \"pytest>=3.9\" flake8 -e .\n+  # --no-binary buildbot is required because buildbot doesn't bundle its tests\n+  # to binary wheels, but ursabot's test suite depends on buildbot's so install\n+  # it from source\n+  - pip install --no-binary buildbot \"pytest>=3.9\" mock flake8 -e .\n \n   # run linter\n   - flake8 ursabot"
-  },
-  {
-    "sha": "86ad809d3f74c175b92ac58c6c645b0fbf5fa2c5",
-    "filename": "setup.py",
-    "status": "modified",
-    "additions": 6,
-    "deletions": 1,
-    "changes": 7,
-    "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/setup.py",
-    "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/setup.py",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/setup.py?ref=70267dee34884e4b972388e1b30d57f6248c58d0",
-    "patch": "@@ -1,8 +1,13 @@\n #!/usr/bin/env python\n \n+import sys\n from setuptools import setup\n \n \n+if sys.version_info < (3, 6):\n+    sys.exit('Python < 3.6 is not supported due to missing asyncio support')\n+\n+\n # TODO(kszucs): add package data, change maintainer\n setup(\n     name='ursabot',\n@@ -15,7 +20,7 @@\n     setup_requires=['setuptools_scm'],\n     install_requires=['click', 'dask', 'docker', 'docker-map', 'toolz',\n                       'buildbot', 'treq'],\n-    tests_require=['pytest>=3.9'],\n+    tests_require=['pytest>=3.9', 'mock'],\n     entry_points='''\n         [console_scripts]\n         ursabot=ursabot.cli:ursabot"
-  },
-  {
-    "sha": "c884f3f85bba499d77d9ad28bcd0ff5edf80f957",
-    "filename": "ursabot/factories.py",
-    "status": "modified",
-    "additions": 6,
-    "deletions": 2,
-    "changes": 8,
-    "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/factories.py",
-    "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/factories.py",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/factories.py?ref=70267dee34884e4b972388e1b30d57f6248c58d0",
-    "patch": "@@ -79,8 +79,12 @@ def prepend_step(self, step):\n                repourl='https://github.com/ursa-labs/ursabot',\n                mode='full'),\n     ShellCommand(command=['ls', '-lah']),\n-    ShellCommand(command=['pip', 'install', 'pytest', 'flake8']),\n-    ShellCommand(command=['pip', 'install', '-e', '.']),\n+    ShellCommand(command=['pip', 'install', 'pytest', 'flake8', 'mock']),\n+    # --no-binary buildbot is required because buildbot doesn't bundle its\n+    # tests to binary wheels, but ursabot's test suite depends on buildbot's\n+    # so install it from source\n+    ShellCommand(command=['pip', 'install', '--no-binary', 'buildbot',\n+                          '-e', '.']),\n     ShellCommand(command=['flake8']),\n     ShellCommand(command=['pytest', '-v', '-m', 'not docker', 'ursabot']),\n     ShellCommand(command=['buildbot', 'checkconfig', '.'])"
-  },
-  {
-    "sha": "0265cfbd9c2882f492469882a7bf513a1c1b5af4",
-    "filename": "ursabot/hooks.py",
-    "status": "modified",
-    "additions": 17,
-    "deletions": 19,
-    "changes": 36,
-    "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/hooks.py",
-    "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/hooks.py",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/hooks.py?ref=70267dee34884e4b972388e1b30d57f6248c58d0",
-    "patch": "@@ -1,11 +1,11 @@\n from urllib.parse import urlparse\n \n from twisted.python import log\n-from twisted.internet import defer\n \n from buildbot.www.hooks.github import GitHubEventHandler\n from buildbot.util.httpclientservice import HTTPClientService\n \n+from .utils import ensure_deferred\n \n BOTNAME = 'ursabot'\n \n@@ -22,20 +22,18 @@ def _client(self):\n             self.master, self.github_api_endpoint, headers=headers,\n             debug=self.debug, verify=self.verify)\n \n-    @defer.inlineCallbacks\n-    def _get(self, url):\n+    async def _get(self, url):\n         url = urlparse(url)\n-        client = yield self._client()\n-        response = yield client.get(url.path)\n-        result = yield response.json()\n+        client = await self._client()\n+        response = await client.get(url.path)\n+        result = await response.json()\n         return result\n \n-    @defer.inlineCallbacks\n-    def _post(self, url, data):\n+    async def _post(self, url, data):\n         url = urlparse(url)\n-        client = yield self._client()\n-        response = yield client.post(url.path, json=data)\n-        result = yield response.json()\n+        client = await self._client()\n+        response = await client.post(url.path, json=data)\n+        result = await response.json()\n         log.msg(f'POST to {url} with the following result: {result}')\n         return result\n \n@@ -46,8 +44,8 @@ def _parse_command(self, message):\n             return message.split(mention)[-1].lower().strip()\n         return None\n \n-    @defer.inlineCallbacks\n-    def handle_issue_comment(self, payload, event):\n+    @ensure_deferred\n+    async def handle_issue_comment(self, payload, event):\n         issue = payload['issue']\n         comments_url = issue['comments_url']\n         command = self._parse_command(payload['comment']['body'])\n@@ -64,16 +62,16 @@ def handle_issue_comment(self, payload, event):\n         elif command == 'build':\n             if 'pull_request' not in issue:\n                 message = 'Ursabot only listens to pull request comments!'\n-                yield self._post(comments_url, {'body': message})\n+                await self._post(comments_url, {'body': message})\n                 return [], 'git'\n         else:\n             message = f'Unknown command \"{command}\"'\n-            yield self._post(comments_url, {'body': message})\n+            await self._post(comments_url, {'body': message})\n             return [], 'git'\n \n         try:\n-            pull_request = yield self._get(issue['pull_request']['url'])\n-            changes, _ = yield self.handle_pull_request({\n+            pull_request = await self._get(issue['pull_request']['url'])\n+            changes, _ = await self.handle_pull_request({\n                 'action': 'synchronize',\n                 'sender': payload['sender'],\n                 'repository': payload['repository'],\n@@ -82,11 +80,11 @@ def handle_issue_comment(self, payload, event):\n             }, event)\n         except Exception as e:\n             message = \"I've failed to start builds for this PR\"\n-            yield self._post(comments_url, {'body': message})\n+            await self._post(comments_url, {'body': message})\n             raise e\n         else:\n             message = \"I've successfully started builds for this PR\"\n-            yield self._post(comments_url, {'body': message})\n+            await self._post(comments_url, {'body': message})\n             return changes, 'git'\n \n     # TODO(kszucs):"
-  },
-  {
-    "sha": "1e1ecf2ce47da929dbf1b93632640e7e6ae1cfe0",
-    "filename": "ursabot/steps.py",
-    "status": "modified",
-    "additions": 13,
-    "deletions": 13,
-    "changes": 26,
-    "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/steps.py",
-    "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/steps.py",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/steps.py?ref=70267dee34884e4b972388e1b30d57f6248c58d0",
-    "patch": "@@ -1,9 +1,9 @@\n-from twisted.internet import defer\n-\n from buildbot.plugins import steps, util\n from buildbot.process import buildstep\n from buildbot.process.results import SUCCESS\n \n+from .utils import ensure_deferred\n+\n \n class ShellMixin(buildstep.ShellMixin):\n     \"\"\"Run command in a login bash shell\n@@ -49,10 +49,10 @@ def __init__(self, **kwargs):\n         kwargs = self.setupShellMixin(kwargs)\n         super().__init__(**kwargs)\n \n-    @defer.inlineCallbacks\n-    def run(self):\n-        cmd = yield self.makeRemoteShellCommand(command=self.command)\n-        yield self.runCommand(cmd)\n+    @ensure_deferred\n+    async def run(self):\n+        cmd = await self.makeRemoteShellCommand(command=self.command)\n+        await self.runCommand(cmd)\n         return cmd.results()\n \n \n@@ -71,8 +71,8 @@ class CMake(ShellMixin, steps.CMake):\n \n     name = 'CMake'\n \n-    @defer.inlineCallbacks\n-    def run(self):\n+    @ensure_deferred\n+    async def run(self):\n         \"\"\"Create and run CMake command\n \n         Copied from the original CMake implementation to handle None values as\n@@ -94,8 +94,8 @@ def run(self):\n         if self.options is not None:\n             command.extend(self.options)\n \n-        cmd = yield self.makeRemoteShellCommand(command=command)\n-        yield self.runCommand(cmd)\n+        cmd = await self.makeRemoteShellCommand(command=command)\n+        await self.runCommand(cmd)\n \n         return cmd.results()\n \n@@ -117,8 +117,8 @@ def __init__(self, variables, source='WorkerEnvironment', **kwargs):\n         self.source = source\n         super().__init__(**kwargs)\n \n-    @defer.inlineCallbacks\n-    def run(self):\n+    @ensure_deferred\n+    async def run(self):\n         # on Windows, environment variables are case-insensitive, but we have\n         # a case-sensitive dictionary in worker_environ.  Fortunately, that\n         # dictionary is also folded to uppercase, so we can simply fold the\n@@ -139,7 +139,7 @@ def run(self):\n                 # TODO(kszucs) try with self.setProperty similarly like in\n                 # SetProperties\n                 properties.setProperty(prop, value, self.source, runtime=True)\n-                yield self.addCompleteLog('set-prop', f'{prop}: {value}')\n+                await self.addCompleteLog('set-prop', f'{prop}: {value}')\n \n         return SUCCESS\n "
-  },
-  {
-    "sha": "6a7d5308be6608f542a810d410f9240157a1340f",
-    "filename": "ursabot/tests/fixtures/issue-comment-build-command.json",
-    "status": "added",
-    "additions": 212,
-    "deletions": 0,
-    "changes": 212,
-    "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-build-command.json",
-    "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-build-command.json",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-build-command.json?ref=70267dee34884e4b972388e1b30d57f6248c58d0",
-    "patch": "@@ -0,0 +1,212 @@\n+{\n+    \"action\": \"created\",\n+    \"comment\": {\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"@ursabot build\",\n+        \"created_at\": \"2019-04-05T11:55:43Z\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480248726\",\n+        \"id\": 480248726,\n+        \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0ODcyNg==\",\n+        \"updated_at\": \"2019-04-05T11:55:43Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480248726\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"issue\": {\n+        \"assignee\": null,\n+        \"assignees\": [],\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"\",\n+        \"closed_at\": null,\n+        \"comments\": 3,\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+        \"created_at\": \"2019-04-05T11:22:15Z\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+        \"id\": 429706959,\n+        \"labels\": [],\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+        \"locked\": false,\n+        \"milestone\": null,\n+        \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+        \"number\": 26,\n+        \"pull_request\": {\n+            \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+            \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+            \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+            \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+        },\n+        \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"state\": \"open\",\n+        \"title\": \"Unittests for GithubHook\",\n+        \"updated_at\": \"2019-04-05T11:55:43Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"organization\": {\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+        \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+        \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+        \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+        \"id\": 46514972,\n+        \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+        \"login\": \"ursa-labs\",\n+        \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+        \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+        \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+        \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+        \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+    },\n+    \"repository\": {\n+        \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+        \"archived\": false,\n+        \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+        \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+        \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+        \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+        \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+        \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+        \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+        \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+        \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+        \"created_at\": \"2019-02-04T15:40:31Z\",\n+        \"default_branch\": \"master\",\n+        \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+        \"description\": null,\n+        \"disabled\": false,\n+        \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+        \"fork\": false,\n+        \"forks\": 0,\n+        \"forks_count\": 0,\n+        \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+        \"full_name\": \"ursa-labs/ursabot\",\n+        \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+        \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+        \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+        \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+        \"has_downloads\": true,\n+        \"has_issues\": true,\n+        \"has_pages\": false,\n+        \"has_projects\": true,\n+        \"has_wiki\": true,\n+        \"homepage\": null,\n+        \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"id\": 169101701,\n+        \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+        \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+        \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+        \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+        \"language\": \"Jupyter Notebook\",\n+        \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+        \"license\": null,\n+        \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+        \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+        \"mirror_url\": null,\n+        \"name\": \"ursabot\",\n+        \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+        \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+        \"open_issues\": 19,\n+        \"open_issues_count\": 19,\n+        \"owner\": {\n+            \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+            \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+            \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/ursa-labs\",\n+            \"id\": 46514972,\n+            \"login\": \"ursa-labs\",\n+            \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+            \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+            \"type\": \"Organization\",\n+            \"url\": \"https://api.github.com/users/ursa-labs\"\n+        },\n+        \"private\": false,\n+        \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+        \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+        \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+        \"size\": 892,\n+        \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+        \"stargazers_count\": 1,\n+        \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+        \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+        \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+        \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+        \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+        \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+        \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+        \"updated_at\": \"2019-04-04T17:49:10Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"watchers\": 1,\n+        \"watchers_count\": 1\n+    },\n+    \"sender\": {\n+        \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+        \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+        \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+        \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+        \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+        \"gravatar_id\": \"\",\n+        \"html_url\": \"https://github.com/kszucs\",\n+        \"id\": 961747,\n+        \"login\": \"kszucs\",\n+        \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+        \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+        \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+        \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+        \"site_admin\": false,\n+        \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+        \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+        \"type\": \"User\",\n+        \"url\": \"https://api.github.com/users/kszucs\"\n+    }\n+}"
-  },
-  {
-    "sha": "7ef554e333327f0e62aa1fd76b4b17844a39adeb",
-    "filename": "ursabot/tests/fixtures/issue-comment-by-ursabot.json",
-    "status": "added",
-    "additions": 212,
-    "deletions": 0,
-    "changes": 212,
-    "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-by-ursabot.json",
-    "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-by-ursabot.json",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-by-ursabot.json?ref=70267dee34884e4b972388e1b30d57f6248c58d0",
-    "patch": "@@ -0,0 +1,212 @@\n+{\n+    \"action\": \"created\",\n+    \"comment\": {\n+        \"author_association\": \"NONE\",\n+        \"body\": \"Unknown command \\\"\\\"\",\n+        \"created_at\": \"2019-04-05T11:35:47Z\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243815\",\n+        \"id\": 480243815,\n+        \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxNQ==\",\n+        \"updated_at\": \"2019-04-05T11:35:47Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243815\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+            \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+            \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/ursabot\",\n+            \"id\": 49275095,\n+            \"login\": \"ursabot\",\n+            \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+            \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/ursabot\"\n+        }\n+    },\n+    \"issue\": {\n+        \"assignee\": null,\n+        \"assignees\": [],\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"\",\n+        \"closed_at\": null,\n+        \"comments\": 2,\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+        \"created_at\": \"2019-04-05T11:22:15Z\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+        \"id\": 429706959,\n+        \"labels\": [],\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+        \"locked\": false,\n+        \"milestone\": null,\n+        \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+        \"number\": 26,\n+        \"pull_request\": {\n+            \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+            \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+            \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+            \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+        },\n+        \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"state\": \"open\",\n+        \"title\": \"Unittests for GithubHook\",\n+        \"updated_at\": \"2019-04-05T11:35:47Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"organization\": {\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+        \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+        \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+        \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+        \"id\": 46514972,\n+        \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+        \"login\": \"ursa-labs\",\n+        \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+        \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+        \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+        \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+        \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+    },\n+    \"repository\": {\n+        \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+        \"archived\": false,\n+        \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+        \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+        \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+        \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+        \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+        \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+        \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+        \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+        \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+        \"created_at\": \"2019-02-04T15:40:31Z\",\n+        \"default_branch\": \"master\",\n+        \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+        \"description\": null,\n+        \"disabled\": false,\n+        \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+        \"fork\": false,\n+        \"forks\": 0,\n+        \"forks_count\": 0,\n+        \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+        \"full_name\": \"ursa-labs/ursabot\",\n+        \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+        \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+        \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+        \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+        \"has_downloads\": true,\n+        \"has_issues\": true,\n+        \"has_pages\": false,\n+        \"has_projects\": true,\n+        \"has_wiki\": true,\n+        \"homepage\": null,\n+        \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"id\": 169101701,\n+        \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+        \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+        \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+        \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+        \"language\": \"Jupyter Notebook\",\n+        \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+        \"license\": null,\n+        \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+        \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+        \"mirror_url\": null,\n+        \"name\": \"ursabot\",\n+        \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+        \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+        \"open_issues\": 19,\n+        \"open_issues_count\": 19,\n+        \"owner\": {\n+            \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+            \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+            \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/ursa-labs\",\n+            \"id\": 46514972,\n+            \"login\": \"ursa-labs\",\n+            \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+            \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+            \"type\": \"Organization\",\n+            \"url\": \"https://api.github.com/users/ursa-labs\"\n+        },\n+        \"private\": false,\n+        \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+        \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+        \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+        \"size\": 892,\n+        \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+        \"stargazers_count\": 1,\n+        \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+        \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+        \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+        \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+        \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+        \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+        \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+        \"updated_at\": \"2019-04-04T17:49:10Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"watchers\": 1,\n+        \"watchers_count\": 1\n+    },\n+    \"sender\": {\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/49275095?v=4\",\n+        \"events_url\": \"https://api.github.com/users/ursabot/events{/privacy}\",\n+        \"followers_url\": \"https://api.github.com/users/ursabot/followers\",\n+        \"following_url\": \"https://api.github.com/users/ursabot/following{/other_user}\",\n+        \"gists_url\": \"https://api.github.com/users/ursabot/gists{/gist_id}\",\n+        \"gravatar_id\": \"\",\n+        \"html_url\": \"https://github.com/ursabot\",\n+        \"id\": 49275095,\n+        \"login\": \"ursabot\",\n+        \"node_id\": \"MDQ6VXNlcjQ5Mjc1MDk1\",\n+        \"organizations_url\": \"https://api.github.com/users/ursabot/orgs\",\n+        \"received_events_url\": \"https://api.github.com/users/ursabot/received_events\",\n+        \"repos_url\": \"https://api.github.com/users/ursabot/repos\",\n+        \"site_admin\": false,\n+        \"starred_url\": \"https://api.github.com/users/ursabot/starred{/owner}{/repo}\",\n+        \"subscriptions_url\": \"https://api.github.com/users/ursabot/subscriptions\",\n+        \"type\": \"User\",\n+        \"url\": \"https://api.github.com/users/ursabot\"\n+    }\n+}"
-  },
-  {
-    "sha": "a8082dbc91fdfe815b795e49ec10e49000771ef5",
-    "filename": "ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json",
-    "status": "added",
-    "additions": 212,
-    "deletions": 0,
-    "changes": 212,
-    "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json",
-    "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json?ref=70267dee34884e4b972388e1b30d57f6248c58d0",
-    "patch": "@@ -0,0 +1,212 @@\n+{\n+    \"action\": \"created\",\n+    \"comment\": {\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"bear is no game\",\n+        \"created_at\": \"2019-04-05T11:26:56Z\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480241727\",\n+        \"id\": 480241727,\n+        \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0MTcyNw==\",\n+        \"updated_at\": \"2019-04-05T11:26:56Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480241727\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"issue\": {\n+        \"assignee\": null,\n+        \"assignees\": [],\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"\",\n+        \"closed_at\": null,\n+        \"comments\": 0,\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+        \"created_at\": \"2019-04-05T11:22:15Z\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+        \"id\": 429706959,\n+        \"labels\": [],\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+        \"locked\": false,\n+        \"milestone\": null,\n+        \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+        \"number\": 26,\n+        \"pull_request\": {\n+            \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+            \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+            \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+            \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+        },\n+        \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"state\": \"open\",\n+        \"title\": \"Unittests for GithubHook\",\n+        \"updated_at\": \"2019-04-05T11:26:56Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"organization\": {\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+        \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+        \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+        \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+        \"id\": 46514972,\n+        \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+        \"login\": \"ursa-labs\",\n+        \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+        \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+        \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+        \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+        \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+    },\n+    \"repository\": {\n+        \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+        \"archived\": false,\n+        \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+        \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+        \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+        \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+        \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+        \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+        \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+        \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+        \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+        \"created_at\": \"2019-02-04T15:40:31Z\",\n+        \"default_branch\": \"master\",\n+        \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+        \"description\": null,\n+        \"disabled\": false,\n+        \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+        \"fork\": false,\n+        \"forks\": 0,\n+        \"forks_count\": 0,\n+        \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+        \"full_name\": \"ursa-labs/ursabot\",\n+        \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+        \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+        \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+        \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+        \"has_downloads\": true,\n+        \"has_issues\": true,\n+        \"has_pages\": false,\n+        \"has_projects\": true,\n+        \"has_wiki\": true,\n+        \"homepage\": null,\n+        \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"id\": 169101701,\n+        \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+        \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+        \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+        \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+        \"language\": \"Jupyter Notebook\",\n+        \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+        \"license\": null,\n+        \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+        \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+        \"mirror_url\": null,\n+        \"name\": \"ursabot\",\n+        \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+        \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+        \"open_issues\": 19,\n+        \"open_issues_count\": 19,\n+        \"owner\": {\n+            \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+            \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+            \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/ursa-labs\",\n+            \"id\": 46514972,\n+            \"login\": \"ursa-labs\",\n+            \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+            \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+            \"type\": \"Organization\",\n+            \"url\": \"https://api.github.com/users/ursa-labs\"\n+        },\n+        \"private\": false,\n+        \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+        \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+        \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+        \"size\": 892,\n+        \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+        \"stargazers_count\": 1,\n+        \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+        \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+        \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+        \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+        \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+        \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+        \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+        \"updated_at\": \"2019-04-04T17:49:10Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"watchers\": 1,\n+        \"watchers_count\": 1\n+    },\n+    \"sender\": {\n+        \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+        \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+        \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+        \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+        \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+        \"gravatar_id\": \"\",\n+        \"html_url\": \"https://github.com/kszucs\",\n+        \"id\": 961747,\n+        \"login\": \"kszucs\",\n+        \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+        \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+        \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+        \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+        \"site_admin\": false,\n+        \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+        \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+        \"type\": \"User\",\n+        \"url\": \"https://api.github.com/users/kszucs\"\n+    }\n+}"
-  },
-  {
-    "sha": "2770e29ba9086394455315e590c0b433d08e437e",
-    "filename": "ursabot/tests/fixtures/issue-comment-with-empty-command.json",
-    "status": "added",
-    "additions": 212,
-    "deletions": 0,
-    "changes": 212,
-    "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-with-empty-command.json",
-    "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-with-empty-command.json",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-with-empty-command.json?ref=70267dee34884e4b972388e1b30d57f6248c58d0",
-    "patch": "@@ -0,0 +1,212 @@\n+{\n+    \"action\": \"created\",\n+    \"comment\": {\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"@ursabot \",\n+        \"created_at\": \"2019-04-05T11:35:46Z\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243811\",\n+        \"id\": 480243811,\n+        \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxMQ==\",\n+        \"updated_at\": \"2019-04-05T11:35:46Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243811\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"issue\": {\n+        \"assignee\": null,\n+        \"assignees\": [],\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"\",\n+        \"closed_at\": null,\n+        \"comments\": 1,\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+        \"created_at\": \"2019-04-05T11:22:15Z\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+        \"id\": 429706959,\n+        \"labels\": [],\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\",\n+        \"locked\": false,\n+        \"milestone\": null,\n+        \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+        \"number\": 26,\n+        \"pull_request\": {\n+            \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+            \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+            \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+            \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+        },\n+        \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"state\": \"open\",\n+        \"title\": \"Unittests for GithubHook\",\n+        \"updated_at\": \"2019-04-05T11:35:46Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"organization\": {\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+        \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+        \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+        \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+        \"id\": 46514972,\n+        \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+        \"login\": \"ursa-labs\",\n+        \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+        \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+        \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+        \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+        \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+    },\n+    \"repository\": {\n+        \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+        \"archived\": false,\n+        \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+        \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+        \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+        \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+        \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+        \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+        \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+        \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+        \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+        \"created_at\": \"2019-02-04T15:40:31Z\",\n+        \"default_branch\": \"master\",\n+        \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+        \"description\": null,\n+        \"disabled\": false,\n+        \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+        \"fork\": false,\n+        \"forks\": 0,\n+        \"forks_count\": 0,\n+        \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+        \"full_name\": \"ursa-labs/ursabot\",\n+        \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+        \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+        \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+        \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+        \"has_downloads\": true,\n+        \"has_issues\": true,\n+        \"has_pages\": false,\n+        \"has_projects\": true,\n+        \"has_wiki\": true,\n+        \"homepage\": null,\n+        \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"id\": 169101701,\n+        \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+        \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+        \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+        \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+        \"language\": \"Jupyter Notebook\",\n+        \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+        \"license\": null,\n+        \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+        \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+        \"mirror_url\": null,\n+        \"name\": \"ursabot\",\n+        \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+        \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+        \"open_issues\": 19,\n+        \"open_issues_count\": 19,\n+        \"owner\": {\n+            \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+            \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+            \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/ursa-labs\",\n+            \"id\": 46514972,\n+            \"login\": \"ursa-labs\",\n+            \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+            \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+            \"type\": \"Organization\",\n+            \"url\": \"https://api.github.com/users/ursa-labs\"\n+        },\n+        \"private\": false,\n+        \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+        \"pushed_at\": \"2019-04-05T11:22:16Z\",\n+        \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+        \"size\": 892,\n+        \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+        \"stargazers_count\": 1,\n+        \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+        \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+        \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+        \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+        \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+        \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+        \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+        \"updated_at\": \"2019-04-04T17:49:10Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"watchers\": 1,\n+        \"watchers_count\": 1\n+    },\n+    \"sender\": {\n+        \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+        \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+        \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+        \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+        \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+        \"gravatar_id\": \"\",\n+        \"html_url\": \"https://github.com/kszucs\",\n+        \"id\": 961747,\n+        \"login\": \"kszucs\",\n+        \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+        \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+        \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+        \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+        \"site_admin\": false,\n+        \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+        \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+        \"type\": \"User\",\n+        \"url\": \"https://api.github.com/users/kszucs\"\n+    }\n+}"
-  },
-  {
-    "sha": "b7de8d838332944101812ee2a46c08dd0144efe3",
-    "filename": "ursabot/tests/fixtures/issue-comment-without-pull-request.json",
-    "status": "added",
-    "additions": 206,
-    "deletions": 0,
-    "changes": 206,
-    "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-without-pull-request.json",
-    "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/issue-comment-without-pull-request.json",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-without-pull-request.json?ref=70267dee34884e4b972388e1b30d57f6248c58d0",
-    "patch": "@@ -0,0 +1,206 @@\n+{\n+    \"action\": \"created\",\n+    \"comment\": {\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"@ursabot build\",\n+        \"created_at\": \"2019-04-05T13:07:57Z\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/issues/19#issuecomment-480268708\",\n+        \"id\": 480268708,\n+        \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19\",\n+        \"node_id\": \"MDEyOklzc3VlQ29tbWVudDQ4MDI2ODcwOA==\",\n+        \"updated_at\": \"2019-04-05T13:07:57Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480268708\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"issue\": {\n+        \"assignee\": null,\n+        \"assignees\": [],\n+        \"author_association\": \"MEMBER\",\n+        \"body\": \"\",\n+        \"closed_at\": null,\n+        \"comments\": 5,\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19/comments\",\n+        \"created_at\": \"2019-04-02T09:56:41Z\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19/events\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot/issues/19\",\n+        \"id\": 428131685,\n+        \"labels\": [],\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19/labels{/name}\",\n+        \"locked\": false,\n+        \"milestone\": null,\n+        \"node_id\": \"MDU6SXNzdWU0MjgxMzE2ODU=\",\n+        \"number\": 19,\n+        \"repository_url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"state\": \"open\",\n+        \"title\": \"Build ursabot itself via ursabot\",\n+        \"updated_at\": \"2019-04-05T13:07:57Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/19\",\n+        \"user\": {\n+            \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+            \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+            \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/kszucs\",\n+            \"id\": 961747,\n+            \"login\": \"kszucs\",\n+            \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+            \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+            \"type\": \"User\",\n+            \"url\": \"https://api.github.com/users/kszucs\"\n+        }\n+    },\n+    \"organization\": {\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+        \"description\": \"Innovation lab for open source data science tools, powered by Apache Arrow\",\n+        \"events_url\": \"https://api.github.com/orgs/ursa-labs/events\",\n+        \"hooks_url\": \"https://api.github.com/orgs/ursa-labs/hooks\",\n+        \"id\": 46514972,\n+        \"issues_url\": \"https://api.github.com/orgs/ursa-labs/issues\",\n+        \"login\": \"ursa-labs\",\n+        \"members_url\": \"https://api.github.com/orgs/ursa-labs/members{/member}\",\n+        \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+        \"public_members_url\": \"https://api.github.com/orgs/ursa-labs/public_members{/member}\",\n+        \"repos_url\": \"https://api.github.com/orgs/ursa-labs/repos\",\n+        \"url\": \"https://api.github.com/orgs/ursa-labs\"\n+    },\n+    \"repository\": {\n+        \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+        \"archived\": false,\n+        \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+        \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+        \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+        \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+        \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+        \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+        \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+        \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+        \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+        \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+        \"created_at\": \"2019-02-04T15:40:31Z\",\n+        \"default_branch\": \"master\",\n+        \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+        \"description\": null,\n+        \"disabled\": false,\n+        \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+        \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+        \"fork\": false,\n+        \"forks\": 0,\n+        \"forks_count\": 0,\n+        \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+        \"full_name\": \"ursa-labs/ursabot\",\n+        \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+        \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+        \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+        \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+        \"has_downloads\": true,\n+        \"has_issues\": true,\n+        \"has_pages\": false,\n+        \"has_projects\": true,\n+        \"has_wiki\": true,\n+        \"homepage\": null,\n+        \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+        \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"id\": 169101701,\n+        \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+        \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+        \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+        \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+        \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+        \"language\": \"Jupyter Notebook\",\n+        \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+        \"license\": null,\n+        \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+        \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+        \"mirror_url\": null,\n+        \"name\": \"ursabot\",\n+        \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+        \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+        \"open_issues\": 19,\n+        \"open_issues_count\": 19,\n+        \"owner\": {\n+            \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+            \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+            \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+            \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+            \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+            \"gravatar_id\": \"\",\n+            \"html_url\": \"https://github.com/ursa-labs\",\n+            \"id\": 46514972,\n+            \"login\": \"ursa-labs\",\n+            \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+            \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+            \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+            \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+            \"site_admin\": false,\n+            \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+            \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+            \"type\": \"Organization\",\n+            \"url\": \"https://api.github.com/users/ursa-labs\"\n+        },\n+        \"private\": false,\n+        \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+        \"pushed_at\": \"2019-04-05T12:01:40Z\",\n+        \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+        \"size\": 898,\n+        \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+        \"stargazers_count\": 1,\n+        \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+        \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+        \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+        \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+        \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+        \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+        \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+        \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+        \"updated_at\": \"2019-04-04T17:49:10Z\",\n+        \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+        \"watchers\": 1,\n+        \"watchers_count\": 1\n+    },\n+    \"sender\": {\n+        \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+        \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+        \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+        \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+        \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+        \"gravatar_id\": \"\",\n+        \"html_url\": \"https://github.com/kszucs\",\n+        \"id\": 961747,\n+        \"login\": \"kszucs\",\n+        \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+        \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+        \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+        \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+        \"site_admin\": false,\n+        \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+        \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+        \"type\": \"User\",\n+        \"url\": \"https://api.github.com/users/kszucs\"\n+    }\n+}"
-  },
-  {
-    "sha": "33e051455e866fb4774a16ae02ad40dcf9e6a7fd",
-    "filename": "ursabot/tests/fixtures/pull-request-26-commit.json",
-    "status": "added",
-    "additions": 158,
-    "deletions": 0,
-    "changes": 158,
-    "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/pull-request-26-commit.json",
-    "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/pull-request-26-commit.json",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/pull-request-26-commit.json?ref=70267dee34884e4b972388e1b30d57f6248c58d0",
-    "patch": "@@ -0,0 +1,158 @@\n+{\n+  \"sha\": \"2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+  \"node_id\": \"MDY6Q29tbWl0MTY5MTAxNzAxOjI3MDVkYTJiNjE2Yjk4ZmE2MDEwYTI1ODEzYzVhN2EyNzQ1NmY3MWQ=\",\n+  \"commit\": {\n+    \"author\": {\n+      \"name\": \"Krisztián Szűcs\",\n+      \"email\": \"szucs.krisztian@gmail.com\",\n+      \"date\": \"2019-04-05T12:01:31Z\"\n+    },\n+    \"committer\": {\n+      \"name\": \"Krisztián Szűcs\",\n+      \"email\": \"szucs.krisztian@gmail.com\",\n+      \"date\": \"2019-04-05T12:01:31Z\"\n+    },\n+    \"message\": \"add recorded event requests\",\n+    \"tree\": {\n+      \"sha\": \"16a7bb186833a67e9c2d84a58393503b85500ceb\",\n+      \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees/16a7bb186833a67e9c2d84a58393503b85500ceb\"\n+    },\n+    \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits/2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+    \"comment_count\": 0,\n+    \"verification\": {\n+      \"verified\": true,\n+      \"reason\": \"valid\",\n+      \"signature\": \"-----BEGIN PGP SIGNATURE-----\\n\\niQFOBAABCAA4FiEEOOW2r8dr6sA77zHlgjqBKYe1QKUFAlynQ58aHHN6dWNzLmty\\naXN6dGlhbkBnbWFpbC5jb20ACgkQgjqBKYe1QKUYKwf6AiXDMaLqNLNSjRY7lIXX\\nudioewz0hSb4bgIXBv30nswu9CoOA0+mHCokEVtZhYbXzXDsZ1KJrilSC4j+Ws4q\\nkRGA6iEmrne2HcSKNZXzcVnwV9zpwKxlVh2QCTNb1PuOYFBLH0kwE704uWIWMGDN\\nbo8cjQPwegePCRguCvPh/5wa5J3uiq5gmJLG6bC/d1XYE+FJVtlnyzqzLMIryGKe\\ntIciw+wwkF413Q/YVbZ49vLUeCX9H8PHC4mZYGDWuvjFW1WTfkjK5bAH+oaTVM6h\\n350I5ZFloHmMA/QeRge5qFxXoEBMDGiXHHktzYZDXnliFOQNxzqwirA5lQQ6LRSS\\naQ==\\n=7rqi\\n-----END PGP SIGNATURE-----\",\n+      \"payload\": \"tree 16a7bb186833a67e9c2d84a58393503b85500ceb\\nparent 446ae69b9385e8d0f40aa9595f723d34383af2f7\\nauthor Krisztián Szűcs <szucs.krisztian@gmail.com> 1554465691 +0200\\ncommitter Krisztián Szűcs <szucs.krisztian@gmail.com> 1554465691 +0200\\n\\nadd recorded event requests\\n\"\n+    }\n+  },\n+  \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits/2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+  \"html_url\": \"https://github.com/ursa-labs/ursabot/commit/2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+  \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits/2705da2b616b98fa6010a25813c5a7a27456f71d/comments\",\n+  \"author\": {\n+    \"login\": \"kszucs\",\n+    \"id\": 961747,\n+    \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+    \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+    \"gravatar_id\": \"\",\n+    \"url\": \"https://api.github.com/users/kszucs\",\n+    \"html_url\": \"https://github.com/kszucs\",\n+    \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+    \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+    \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+    \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+    \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+    \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+    \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+    \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+    \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+    \"type\": \"User\",\n+    \"site_admin\": false\n+  },\n+  \"committer\": {\n+    \"login\": \"kszucs\",\n+    \"id\": 961747,\n+    \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+    \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+    \"gravatar_id\": \"\",\n+    \"url\": \"https://api.github.com/users/kszucs\",\n+    \"html_url\": \"https://github.com/kszucs\",\n+    \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+    \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+    \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+    \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+    \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+    \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+    \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+    \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+    \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+    \"type\": \"User\",\n+    \"site_admin\": false\n+  },\n+  \"parents\": [\n+    {\n+      \"sha\": \"446ae69b9385e8d0f40aa9595f723d34383af2f7\",\n+      \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits/446ae69b9385e8d0f40aa9595f723d34383af2f7\",\n+      \"html_url\": \"https://github.com/ursa-labs/ursabot/commit/446ae69b9385e8d0f40aa9595f723d34383af2f7\"\n+    }\n+  ],\n+  \"stats\": {\n+    \"total\": 1062,\n+    \"additions\": 1058,\n+    \"deletions\": 4\n+  },\n+  \"files\": [\n+    {\n+      \"sha\": \"dfae6eeaef384ae6180c6302a58b49e39982dc33\",\n+      \"filename\": \"ursabot/tests/fixtures/issue-comment-build-command.json\",\n+      \"status\": \"added\",\n+      \"additions\": 212,\n+      \"deletions\": 0,\n+      \"changes\": 212,\n+      \"blob_url\": \"https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-build-command.json\",\n+      \"raw_url\": \"https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-build-command.json\",\n+      \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-build-command.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+      \"patch\": \"@@ -0,0 +1,212 @@\\n+{\\n+    \\\"action\\\": \\\"created\\\",\\n+    \\\"comment\\\": {\\n+        \\\"author_association\\\": \\\"NONE\\\",\\n+        \\\"body\\\": \\\"I've successfully started builds for this PR\\\",\\n+        \\\"created_at\\\": \\\"2019-04-05T11:55:44Z\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480248730\\\",\\n+        \\\"id\\\": 480248730,\\n+        \\\"issue_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+        \\\"node_id\\\": \\\"MDEyOklzc3VlQ29tbWVudDQ4MDI0ODczMA==\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-05T11:55:44Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480248730\\\",\\n+        \\\"user\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/49275095?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/ursabot/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/ursabot/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/ursabot/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/ursabot/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/ursabot\\\",\\n+            \\\"id\\\": 49275095,\\n+            \\\"login\\\": \\\"ursabot\\\",\\n+            \\\"node_id\\\": \\\"MDQ6VXNlcjQ5Mjc1MDk1\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/ursabot/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/ursabot/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/ursabot/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/ursabot/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursabot/subscriptions\\\",\\n+            \\\"type\\\": \\\"User\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/ursabot\\\"\\n+        }\\n+    },\\n+    \\\"issue\\\": {\\n+        \\\"assignee\\\": null,\\n+        \\\"assignees\\\": [],\\n+        \\\"author_association\\\": \\\"MEMBER\\\",\\n+        \\\"body\\\": \\\"\\\",\\n+        \\\"closed_at\\\": null,\\n+        \\\"comments\\\": 4,\\n+        \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\\\",\\n+        \\\"created_at\\\": \\\"2019-04-05T11:22:15Z\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+        \\\"id\\\": 429706959,\\n+        \\\"labels\\\": [],\\n+        \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\\\",\\n+        \\\"locked\\\": false,\\n+        \\\"milestone\\\": null,\\n+        \\\"node_id\\\": \\\"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\\\",\\n+        \\\"number\\\": 26,\\n+        \\\"pull_request\\\": {\\n+            \\\"diff_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.diff\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+            \\\"patch_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.patch\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\\\"\\n+        },\\n+        \\\"repository_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+        \\\"state\\\": \\\"open\\\",\\n+        \\\"title\\\": \\\"Unittests for GithubHook\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-05T11:55:44Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+        \\\"user\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+            \\\"id\\\": 961747,\\n+            \\\"login\\\": \\\"kszucs\\\",\\n+            \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+            \\\"type\\\": \\\"User\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+        }\\n+    },\\n+    \\\"organization\\\": {\\n+        \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+        \\\"description\\\": \\\"Innovation lab for open source data science tools, powered by Apache Arrow\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/orgs/ursa-labs/events\\\",\\n+        \\\"hooks_url\\\": \\\"https://api.github.com/orgs/ursa-labs/hooks\\\",\\n+        \\\"id\\\": 46514972,\\n+        \\\"issues_url\\\": \\\"https://api.github.com/orgs/ursa-labs/issues\\\",\\n+        \\\"login\\\": \\\"ursa-labs\\\",\\n+        \\\"members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/members{/member}\\\",\\n+        \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+        \\\"public_members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/public_members{/member}\\\",\\n+        \\\"repos_url\\\": \\\"https://api.github.com/orgs/ursa-labs/repos\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/orgs/ursa-labs\\\"\\n+    },\\n+    \\\"repository\\\": {\\n+        \\\"archive_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\\\",\\n+        \\\"archived\\\": false,\\n+        \\\"assignees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\\\",\\n+        \\\"blobs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\\\",\\n+        \\\"branches_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\\\",\\n+        \\\"clone_url\\\": \\\"https://github.com/ursa-labs/ursabot.git\\\",\\n+        \\\"collaborators_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\\\",\\n+        \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\\\",\\n+        \\\"commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\\\",\\n+        \\\"compare_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\\\",\\n+        \\\"contents_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\\\",\\n+        \\\"contributors_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contributors\\\",\\n+        \\\"created_at\\\": \\\"2019-02-04T15:40:31Z\\\",\\n+        \\\"default_branch\\\": \\\"master\\\",\\n+        \\\"deployments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/deployments\\\",\\n+        \\\"description\\\": null,\\n+        \\\"disabled\\\": false,\\n+        \\\"downloads_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/downloads\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/events\\\",\\n+        \\\"fork\\\": false,\\n+        \\\"forks\\\": 0,\\n+        \\\"forks_count\\\": 0,\\n+        \\\"forks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/forks\\\",\\n+        \\\"full_name\\\": \\\"ursa-labs/ursabot\\\",\\n+        \\\"git_commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\\\",\\n+        \\\"git_refs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\\\",\\n+        \\\"git_tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\\\",\\n+        \\\"git_url\\\": \\\"git://github.com/ursa-labs/ursabot.git\\\",\\n+        \\\"has_downloads\\\": true,\\n+        \\\"has_issues\\\": true,\\n+        \\\"has_pages\\\": false,\\n+        \\\"has_projects\\\": true,\\n+        \\\"has_wiki\\\": true,\\n+        \\\"homepage\\\": null,\\n+        \\\"hooks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/hooks\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+        \\\"id\\\": 169101701,\\n+        \\\"issue_comment_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\\\",\\n+        \\\"issue_events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\\\",\\n+        \\\"issues_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\\\",\\n+        \\\"keys_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\\\",\\n+        \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\\\",\\n+        \\\"language\\\": \\\"Jupyter Notebook\\\",\\n+        \\\"languages_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/languages\\\",\\n+        \\\"license\\\": null,\\n+        \\\"merges_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/merges\\\",\\n+        \\\"milestones_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\\\",\\n+        \\\"mirror_url\\\": null,\\n+        \\\"name\\\": \\\"ursabot\\\",\\n+        \\\"node_id\\\": \\\"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\\\",\\n+        \\\"notifications_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\\\",\\n+        \\\"open_issues\\\": 19,\\n+        \\\"open_issues_count\\\": 19,\\n+        \\\"owner\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/ursa-labs/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/ursa-labs/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/ursa-labs/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/ursa-labs/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/ursa-labs\\\",\\n+            \\\"id\\\": 46514972,\\n+            \\\"login\\\": \\\"ursa-labs\\\",\\n+            \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/ursa-labs/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/ursa-labs/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/ursa-labs/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursa-labs/subscriptions\\\",\\n+            \\\"type\\\": \\\"Organization\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/ursa-labs\\\"\\n+        },\\n+        \\\"private\\\": false,\\n+        \\\"pulls_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\\\",\\n+        \\\"pushed_at\\\": \\\"2019-04-05T11:22:16Z\\\",\\n+        \\\"releases_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\\\",\\n+        \\\"size\\\": 892,\\n+        \\\"ssh_url\\\": \\\"git@github.com:ursa-labs/ursabot.git\\\",\\n+        \\\"stargazers_count\\\": 1,\\n+        \\\"stargazers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/stargazers\\\",\\n+        \\\"statuses_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\\\",\\n+        \\\"subscribers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscribers\\\",\\n+        \\\"subscription_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscription\\\",\\n+        \\\"svn_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+        \\\"tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/tags\\\",\\n+        \\\"teams_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/teams\\\",\\n+        \\\"trees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-04T17:49:10Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+        \\\"watchers\\\": 1,\\n+        \\\"watchers_count\\\": 1\\n+    },\\n+    \\\"sender\\\": {\\n+        \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/49275095?v=4\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/users/ursabot/events{/privacy}\\\",\\n+        \\\"followers_url\\\": \\\"https://api.github.com/users/ursabot/followers\\\",\\n+        \\\"following_url\\\": \\\"https://api.github.com/users/ursabot/following{/other_user}\\\",\\n+        \\\"gists_url\\\": \\\"https://api.github.com/users/ursabot/gists{/gist_id}\\\",\\n+        \\\"gravatar_id\\\": \\\"\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursabot\\\",\\n+        \\\"id\\\": 49275095,\\n+        \\\"login\\\": \\\"ursabot\\\",\\n+        \\\"node_id\\\": \\\"MDQ6VXNlcjQ5Mjc1MDk1\\\",\\n+        \\\"organizations_url\\\": \\\"https://api.github.com/users/ursabot/orgs\\\",\\n+        \\\"received_events_url\\\": \\\"https://api.github.com/users/ursabot/received_events\\\",\\n+        \\\"repos_url\\\": \\\"https://api.github.com/users/ursabot/repos\\\",\\n+        \\\"site_admin\\\": false,\\n+        \\\"starred_url\\\": \\\"https://api.github.com/users/ursabot/starred{/owner}{/repo}\\\",\\n+        \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursabot/subscriptions\\\",\\n+        \\\"type\\\": \\\"User\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/users/ursabot\\\"\\n+    }\\n+}\"\n+    },\n+    {\n+      \"sha\": \"7ef554e333327f0e62aa1fd76b4b17844a39adeb\",\n+      \"filename\": \"ursabot/tests/fixtures/issue-comment-by-ursabot.json\",\n+      \"status\": \"added\",\n+      \"additions\": 212,\n+      \"deletions\": 0,\n+      \"changes\": 212,\n+      \"blob_url\": \"https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-by-ursabot.json\",\n+      \"raw_url\": \"https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-by-ursabot.json\",\n+      \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-by-ursabot.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+      \"patch\": \"@@ -0,0 +1,212 @@\\n+{\\n+    \\\"action\\\": \\\"created\\\",\\n+    \\\"comment\\\": {\\n+        \\\"author_association\\\": \\\"NONE\\\",\\n+        \\\"body\\\": \\\"Unknown command \\\\\\\"\\\\\\\"\\\",\\n+        \\\"created_at\\\": \\\"2019-04-05T11:35:47Z\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243815\\\",\\n+        \\\"id\\\": 480243815,\\n+        \\\"issue_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+        \\\"node_id\\\": \\\"MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxNQ==\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-05T11:35:47Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243815\\\",\\n+        \\\"user\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/49275095?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/ursabot/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/ursabot/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/ursabot/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/ursabot/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/ursabot\\\",\\n+            \\\"id\\\": 49275095,\\n+            \\\"login\\\": \\\"ursabot\\\",\\n+            \\\"node_id\\\": \\\"MDQ6VXNlcjQ5Mjc1MDk1\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/ursabot/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/ursabot/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/ursabot/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/ursabot/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursabot/subscriptions\\\",\\n+            \\\"type\\\": \\\"User\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/ursabot\\\"\\n+        }\\n+    },\\n+    \\\"issue\\\": {\\n+        \\\"assignee\\\": null,\\n+        \\\"assignees\\\": [],\\n+        \\\"author_association\\\": \\\"MEMBER\\\",\\n+        \\\"body\\\": \\\"\\\",\\n+        \\\"closed_at\\\": null,\\n+        \\\"comments\\\": 2,\\n+        \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\\\",\\n+        \\\"created_at\\\": \\\"2019-04-05T11:22:15Z\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+        \\\"id\\\": 429706959,\\n+        \\\"labels\\\": [],\\n+        \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\\\",\\n+        \\\"locked\\\": false,\\n+        \\\"milestone\\\": null,\\n+        \\\"node_id\\\": \\\"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\\\",\\n+        \\\"number\\\": 26,\\n+        \\\"pull_request\\\": {\\n+            \\\"diff_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.diff\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+            \\\"patch_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.patch\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\\\"\\n+        },\\n+        \\\"repository_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+        \\\"state\\\": \\\"open\\\",\\n+        \\\"title\\\": \\\"Unittests for GithubHook\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-05T11:35:47Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+        \\\"user\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+            \\\"id\\\": 961747,\\n+            \\\"login\\\": \\\"kszucs\\\",\\n+            \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+            \\\"type\\\": \\\"User\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+        }\\n+    },\\n+    \\\"organization\\\": {\\n+        \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+        \\\"description\\\": \\\"Innovation lab for open source data science tools, powered by Apache Arrow\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/orgs/ursa-labs/events\\\",\\n+        \\\"hooks_url\\\": \\\"https://api.github.com/orgs/ursa-labs/hooks\\\",\\n+        \\\"id\\\": 46514972,\\n+        \\\"issues_url\\\": \\\"https://api.github.com/orgs/ursa-labs/issues\\\",\\n+        \\\"login\\\": \\\"ursa-labs\\\",\\n+        \\\"members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/members{/member}\\\",\\n+        \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+        \\\"public_members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/public_members{/member}\\\",\\n+        \\\"repos_url\\\": \\\"https://api.github.com/orgs/ursa-labs/repos\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/orgs/ursa-labs\\\"\\n+    },\\n+    \\\"repository\\\": {\\n+        \\\"archive_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\\\",\\n+        \\\"archived\\\": false,\\n+        \\\"assignees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\\\",\\n+        \\\"blobs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\\\",\\n+        \\\"branches_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\\\",\\n+        \\\"clone_url\\\": \\\"https://github.com/ursa-labs/ursabot.git\\\",\\n+        \\\"collaborators_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\\\",\\n+        \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\\\",\\n+        \\\"commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\\\",\\n+        \\\"compare_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\\\",\\n+        \\\"contents_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\\\",\\n+        \\\"contributors_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contributors\\\",\\n+        \\\"created_at\\\": \\\"2019-02-04T15:40:31Z\\\",\\n+        \\\"default_branch\\\": \\\"master\\\",\\n+        \\\"deployments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/deployments\\\",\\n+        \\\"description\\\": null,\\n+        \\\"disabled\\\": false,\\n+        \\\"downloads_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/downloads\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/events\\\",\\n+        \\\"fork\\\": false,\\n+        \\\"forks\\\": 0,\\n+        \\\"forks_count\\\": 0,\\n+        \\\"forks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/forks\\\",\\n+        \\\"full_name\\\": \\\"ursa-labs/ursabot\\\",\\n+        \\\"git_commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\\\",\\n+        \\\"git_refs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\\\",\\n+        \\\"git_tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\\\",\\n+        \\\"git_url\\\": \\\"git://github.com/ursa-labs/ursabot.git\\\",\\n+        \\\"has_downloads\\\": true,\\n+        \\\"has_issues\\\": true,\\n+        \\\"has_pages\\\": false,\\n+        \\\"has_projects\\\": true,\\n+        \\\"has_wiki\\\": true,\\n+        \\\"homepage\\\": null,\\n+        \\\"hooks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/hooks\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+        \\\"id\\\": 169101701,\\n+        \\\"issue_comment_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\\\",\\n+        \\\"issue_events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\\\",\\n+        \\\"issues_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\\\",\\n+        \\\"keys_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\\\",\\n+        \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\\\",\\n+        \\\"language\\\": \\\"Jupyter Notebook\\\",\\n+        \\\"languages_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/languages\\\",\\n+        \\\"license\\\": null,\\n+        \\\"merges_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/merges\\\",\\n+        \\\"milestones_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\\\",\\n+        \\\"mirror_url\\\": null,\\n+        \\\"name\\\": \\\"ursabot\\\",\\n+        \\\"node_id\\\": \\\"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\\\",\\n+        \\\"notifications_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\\\",\\n+        \\\"open_issues\\\": 19,\\n+        \\\"open_issues_count\\\": 19,\\n+        \\\"owner\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/ursa-labs/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/ursa-labs/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/ursa-labs/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/ursa-labs/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/ursa-labs\\\",\\n+            \\\"id\\\": 46514972,\\n+            \\\"login\\\": \\\"ursa-labs\\\",\\n+            \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/ursa-labs/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/ursa-labs/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/ursa-labs/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursa-labs/subscriptions\\\",\\n+            \\\"type\\\": \\\"Organization\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/ursa-labs\\\"\\n+        },\\n+        \\\"private\\\": false,\\n+        \\\"pulls_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\\\",\\n+        \\\"pushed_at\\\": \\\"2019-04-05T11:22:16Z\\\",\\n+        \\\"releases_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\\\",\\n+        \\\"size\\\": 892,\\n+        \\\"ssh_url\\\": \\\"git@github.com:ursa-labs/ursabot.git\\\",\\n+        \\\"stargazers_count\\\": 1,\\n+        \\\"stargazers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/stargazers\\\",\\n+        \\\"statuses_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\\\",\\n+        \\\"subscribers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscribers\\\",\\n+        \\\"subscription_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscription\\\",\\n+        \\\"svn_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+        \\\"tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/tags\\\",\\n+        \\\"teams_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/teams\\\",\\n+        \\\"trees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-04T17:49:10Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+        \\\"watchers\\\": 1,\\n+        \\\"watchers_count\\\": 1\\n+    },\\n+    \\\"sender\\\": {\\n+        \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/49275095?v=4\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/users/ursabot/events{/privacy}\\\",\\n+        \\\"followers_url\\\": \\\"https://api.github.com/users/ursabot/followers\\\",\\n+        \\\"following_url\\\": \\\"https://api.github.com/users/ursabot/following{/other_user}\\\",\\n+        \\\"gists_url\\\": \\\"https://api.github.com/users/ursabot/gists{/gist_id}\\\",\\n+        \\\"gravatar_id\\\": \\\"\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursabot\\\",\\n+        \\\"id\\\": 49275095,\\n+        \\\"login\\\": \\\"ursabot\\\",\\n+        \\\"node_id\\\": \\\"MDQ6VXNlcjQ5Mjc1MDk1\\\",\\n+        \\\"organizations_url\\\": \\\"https://api.github.com/users/ursabot/orgs\\\",\\n+        \\\"received_events_url\\\": \\\"https://api.github.com/users/ursabot/received_events\\\",\\n+        \\\"repos_url\\\": \\\"https://api.github.com/users/ursabot/repos\\\",\\n+        \\\"site_admin\\\": false,\\n+        \\\"starred_url\\\": \\\"https://api.github.com/users/ursabot/starred{/owner}{/repo}\\\",\\n+        \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursabot/subscriptions\\\",\\n+        \\\"type\\\": \\\"User\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/users/ursabot\\\"\\n+    }\\n+}\"\n+    },\n+    {\n+      \"sha\": \"a8082dbc91fdfe815b795e49ec10e49000771ef5\",\n+      \"filename\": \"ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json\",\n+      \"status\": \"added\",\n+      \"additions\": 212,\n+      \"deletions\": 0,\n+      \"changes\": 212,\n+      \"blob_url\": \"https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json\",\n+      \"raw_url\": \"https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json\",\n+      \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-not-mentioning-ursabot.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+      \"patch\": \"@@ -0,0 +1,212 @@\\n+{\\n+    \\\"action\\\": \\\"created\\\",\\n+    \\\"comment\\\": {\\n+        \\\"author_association\\\": \\\"MEMBER\\\",\\n+        \\\"body\\\": \\\"bear is no game\\\",\\n+        \\\"created_at\\\": \\\"2019-04-05T11:26:56Z\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480241727\\\",\\n+        \\\"id\\\": 480241727,\\n+        \\\"issue_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+        \\\"node_id\\\": \\\"MDEyOklzc3VlQ29tbWVudDQ4MDI0MTcyNw==\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-05T11:26:56Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480241727\\\",\\n+        \\\"user\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+            \\\"id\\\": 961747,\\n+            \\\"login\\\": \\\"kszucs\\\",\\n+            \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+            \\\"type\\\": \\\"User\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+        }\\n+    },\\n+    \\\"issue\\\": {\\n+        \\\"assignee\\\": null,\\n+        \\\"assignees\\\": [],\\n+        \\\"author_association\\\": \\\"MEMBER\\\",\\n+        \\\"body\\\": \\\"\\\",\\n+        \\\"closed_at\\\": null,\\n+        \\\"comments\\\": 0,\\n+        \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\\\",\\n+        \\\"created_at\\\": \\\"2019-04-05T11:22:15Z\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+        \\\"id\\\": 429706959,\\n+        \\\"labels\\\": [],\\n+        \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\\\",\\n+        \\\"locked\\\": false,\\n+        \\\"milestone\\\": null,\\n+        \\\"node_id\\\": \\\"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\\\",\\n+        \\\"number\\\": 26,\\n+        \\\"pull_request\\\": {\\n+            \\\"diff_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.diff\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+            \\\"patch_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.patch\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\\\"\\n+        },\\n+        \\\"repository_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+        \\\"state\\\": \\\"open\\\",\\n+        \\\"title\\\": \\\"Unittests for GithubHook\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-05T11:26:56Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+        \\\"user\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+            \\\"id\\\": 961747,\\n+            \\\"login\\\": \\\"kszucs\\\",\\n+            \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+            \\\"type\\\": \\\"User\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+        }\\n+    },\\n+    \\\"organization\\\": {\\n+        \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+        \\\"description\\\": \\\"Innovation lab for open source data science tools, powered by Apache Arrow\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/orgs/ursa-labs/events\\\",\\n+        \\\"hooks_url\\\": \\\"https://api.github.com/orgs/ursa-labs/hooks\\\",\\n+        \\\"id\\\": 46514972,\\n+        \\\"issues_url\\\": \\\"https://api.github.com/orgs/ursa-labs/issues\\\",\\n+        \\\"login\\\": \\\"ursa-labs\\\",\\n+        \\\"members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/members{/member}\\\",\\n+        \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+        \\\"public_members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/public_members{/member}\\\",\\n+        \\\"repos_url\\\": \\\"https://api.github.com/orgs/ursa-labs/repos\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/orgs/ursa-labs\\\"\\n+    },\\n+    \\\"repository\\\": {\\n+        \\\"archive_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\\\",\\n+        \\\"archived\\\": false,\\n+        \\\"assignees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\\\",\\n+        \\\"blobs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\\\",\\n+        \\\"branches_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\\\",\\n+        \\\"clone_url\\\": \\\"https://github.com/ursa-labs/ursabot.git\\\",\\n+        \\\"collaborators_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\\\",\\n+        \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\\\",\\n+        \\\"commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\\\",\\n+        \\\"compare_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\\\",\\n+        \\\"contents_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\\\",\\n+        \\\"contributors_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contributors\\\",\\n+        \\\"created_at\\\": \\\"2019-02-04T15:40:31Z\\\",\\n+        \\\"default_branch\\\": \\\"master\\\",\\n+        \\\"deployments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/deployments\\\",\\n+        \\\"description\\\": null,\\n+        \\\"disabled\\\": false,\\n+        \\\"downloads_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/downloads\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/events\\\",\\n+        \\\"fork\\\": false,\\n+        \\\"forks\\\": 0,\\n+        \\\"forks_count\\\": 0,\\n+        \\\"forks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/forks\\\",\\n+        \\\"full_name\\\": \\\"ursa-labs/ursabot\\\",\\n+        \\\"git_commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\\\",\\n+        \\\"git_refs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\\\",\\n+        \\\"git_tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\\\",\\n+        \\\"git_url\\\": \\\"git://github.com/ursa-labs/ursabot.git\\\",\\n+        \\\"has_downloads\\\": true,\\n+        \\\"has_issues\\\": true,\\n+        \\\"has_pages\\\": false,\\n+        \\\"has_projects\\\": true,\\n+        \\\"has_wiki\\\": true,\\n+        \\\"homepage\\\": null,\\n+        \\\"hooks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/hooks\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+        \\\"id\\\": 169101701,\\n+        \\\"issue_comment_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\\\",\\n+        \\\"issue_events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\\\",\\n+        \\\"issues_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\\\",\\n+        \\\"keys_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\\\",\\n+        \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\\\",\\n+        \\\"language\\\": \\\"Jupyter Notebook\\\",\\n+        \\\"languages_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/languages\\\",\\n+        \\\"license\\\": null,\\n+        \\\"merges_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/merges\\\",\\n+        \\\"milestones_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\\\",\\n+        \\\"mirror_url\\\": null,\\n+        \\\"name\\\": \\\"ursabot\\\",\\n+        \\\"node_id\\\": \\\"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\\\",\\n+        \\\"notifications_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\\\",\\n+        \\\"open_issues\\\": 19,\\n+        \\\"open_issues_count\\\": 19,\\n+        \\\"owner\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/ursa-labs/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/ursa-labs/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/ursa-labs/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/ursa-labs/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/ursa-labs\\\",\\n+            \\\"id\\\": 46514972,\\n+            \\\"login\\\": \\\"ursa-labs\\\",\\n+            \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/ursa-labs/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/ursa-labs/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/ursa-labs/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursa-labs/subscriptions\\\",\\n+            \\\"type\\\": \\\"Organization\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/ursa-labs\\\"\\n+        },\\n+        \\\"private\\\": false,\\n+        \\\"pulls_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\\\",\\n+        \\\"pushed_at\\\": \\\"2019-04-05T11:22:16Z\\\",\\n+        \\\"releases_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\\\",\\n+        \\\"size\\\": 892,\\n+        \\\"ssh_url\\\": \\\"git@github.com:ursa-labs/ursabot.git\\\",\\n+        \\\"stargazers_count\\\": 1,\\n+        \\\"stargazers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/stargazers\\\",\\n+        \\\"statuses_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\\\",\\n+        \\\"subscribers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscribers\\\",\\n+        \\\"subscription_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscription\\\",\\n+        \\\"svn_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+        \\\"tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/tags\\\",\\n+        \\\"teams_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/teams\\\",\\n+        \\\"trees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-04T17:49:10Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+        \\\"watchers\\\": 1,\\n+        \\\"watchers_count\\\": 1\\n+    },\\n+    \\\"sender\\\": {\\n+        \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+        \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+        \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+        \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+        \\\"gravatar_id\\\": \\\"\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+        \\\"id\\\": 961747,\\n+        \\\"login\\\": \\\"kszucs\\\",\\n+        \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+        \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+        \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+        \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+        \\\"site_admin\\\": false,\\n+        \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+        \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+        \\\"type\\\": \\\"User\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+    }\\n+}\"\n+    },\n+    {\n+      \"sha\": \"2770e29ba9086394455315e590c0b433d08e437e\",\n+      \"filename\": \"ursabot/tests/fixtures/issue-comment-with-empty-command.json\",\n+      \"status\": \"added\",\n+      \"additions\": 212,\n+      \"deletions\": 0,\n+      \"changes\": 212,\n+      \"blob_url\": \"https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-with-empty-command.json\",\n+      \"raw_url\": \"https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-with-empty-command.json\",\n+      \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-with-empty-command.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+      \"patch\": \"@@ -0,0 +1,212 @@\\n+{\\n+    \\\"action\\\": \\\"created\\\",\\n+    \\\"comment\\\": {\\n+        \\\"author_association\\\": \\\"MEMBER\\\",\\n+        \\\"body\\\": \\\"@ursabot \\\",\\n+        \\\"created_at\\\": \\\"2019-04-05T11:35:46Z\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26#issuecomment-480243811\\\",\\n+        \\\"id\\\": 480243811,\\n+        \\\"issue_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+        \\\"node_id\\\": \\\"MDEyOklzc3VlQ29tbWVudDQ4MDI0MzgxMQ==\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-05T11:35:46Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480243811\\\",\\n+        \\\"user\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+            \\\"id\\\": 961747,\\n+            \\\"login\\\": \\\"kszucs\\\",\\n+            \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+            \\\"type\\\": \\\"User\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+        }\\n+    },\\n+    \\\"issue\\\": {\\n+        \\\"assignee\\\": null,\\n+        \\\"assignees\\\": [],\\n+        \\\"author_association\\\": \\\"MEMBER\\\",\\n+        \\\"body\\\": \\\"\\\",\\n+        \\\"closed_at\\\": null,\\n+        \\\"comments\\\": 1,\\n+        \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\\\",\\n+        \\\"created_at\\\": \\\"2019-04-05T11:22:15Z\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/events\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+        \\\"id\\\": 429706959,\\n+        \\\"labels\\\": [],\\n+        \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26/labels{/name}\\\",\\n+        \\\"locked\\\": false,\\n+        \\\"milestone\\\": null,\\n+        \\\"node_id\\\": \\\"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\\\",\\n+        \\\"number\\\": 26,\\n+        \\\"pull_request\\\": {\\n+            \\\"diff_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.diff\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26\\\",\\n+            \\\"patch_url\\\": \\\"https://github.com/ursa-labs/ursabot/pull/26.patch\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\\\"\\n+        },\\n+        \\\"repository_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+        \\\"state\\\": \\\"open\\\",\\n+        \\\"title\\\": \\\"Unittests for GithubHook\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-05T11:35:46Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/26\\\",\\n+        \\\"user\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+            \\\"id\\\": 961747,\\n+            \\\"login\\\": \\\"kszucs\\\",\\n+            \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+            \\\"type\\\": \\\"User\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+        }\\n+    },\\n+    \\\"organization\\\": {\\n+        \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+        \\\"description\\\": \\\"Innovation lab for open source data science tools, powered by Apache Arrow\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/orgs/ursa-labs/events\\\",\\n+        \\\"hooks_url\\\": \\\"https://api.github.com/orgs/ursa-labs/hooks\\\",\\n+        \\\"id\\\": 46514972,\\n+        \\\"issues_url\\\": \\\"https://api.github.com/orgs/ursa-labs/issues\\\",\\n+        \\\"login\\\": \\\"ursa-labs\\\",\\n+        \\\"members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/members{/member}\\\",\\n+        \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+        \\\"public_members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/public_members{/member}\\\",\\n+        \\\"repos_url\\\": \\\"https://api.github.com/orgs/ursa-labs/repos\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/orgs/ursa-labs\\\"\\n+    },\\n+    \\\"repository\\\": {\\n+        \\\"archive_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\\\",\\n+        \\\"archived\\\": false,\\n+        \\\"assignees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\\\",\\n+        \\\"blobs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\\\",\\n+        \\\"branches_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\\\",\\n+        \\\"clone_url\\\": \\\"https://github.com/ursa-labs/ursabot.git\\\",\\n+        \\\"collaborators_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\\\",\\n+        \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\\\",\\n+        \\\"commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\\\",\\n+        \\\"compare_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\\\",\\n+        \\\"contents_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\\\",\\n+        \\\"contributors_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contributors\\\",\\n+        \\\"created_at\\\": \\\"2019-02-04T15:40:31Z\\\",\\n+        \\\"default_branch\\\": \\\"master\\\",\\n+        \\\"deployments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/deployments\\\",\\n+        \\\"description\\\": null,\\n+        \\\"disabled\\\": false,\\n+        \\\"downloads_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/downloads\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/events\\\",\\n+        \\\"fork\\\": false,\\n+        \\\"forks\\\": 0,\\n+        \\\"forks_count\\\": 0,\\n+        \\\"forks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/forks\\\",\\n+        \\\"full_name\\\": \\\"ursa-labs/ursabot\\\",\\n+        \\\"git_commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\\\",\\n+        \\\"git_refs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\\\",\\n+        \\\"git_tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\\\",\\n+        \\\"git_url\\\": \\\"git://github.com/ursa-labs/ursabot.git\\\",\\n+        \\\"has_downloads\\\": true,\\n+        \\\"has_issues\\\": true,\\n+        \\\"has_pages\\\": false,\\n+        \\\"has_projects\\\": true,\\n+        \\\"has_wiki\\\": true,\\n+        \\\"homepage\\\": null,\\n+        \\\"hooks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/hooks\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+        \\\"id\\\": 169101701,\\n+        \\\"issue_comment_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\\\",\\n+        \\\"issue_events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\\\",\\n+        \\\"issues_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\\\",\\n+        \\\"keys_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\\\",\\n+        \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\\\",\\n+        \\\"language\\\": \\\"Jupyter Notebook\\\",\\n+        \\\"languages_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/languages\\\",\\n+        \\\"license\\\": null,\\n+        \\\"merges_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/merges\\\",\\n+        \\\"milestones_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\\\",\\n+        \\\"mirror_url\\\": null,\\n+        \\\"name\\\": \\\"ursabot\\\",\\n+        \\\"node_id\\\": \\\"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\\\",\\n+        \\\"notifications_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\\\",\\n+        \\\"open_issues\\\": 19,\\n+        \\\"open_issues_count\\\": 19,\\n+        \\\"owner\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/ursa-labs/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/ursa-labs/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/ursa-labs/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/ursa-labs/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/ursa-labs\\\",\\n+            \\\"id\\\": 46514972,\\n+            \\\"login\\\": \\\"ursa-labs\\\",\\n+            \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/ursa-labs/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/ursa-labs/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/ursa-labs/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursa-labs/subscriptions\\\",\\n+            \\\"type\\\": \\\"Organization\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/ursa-labs\\\"\\n+        },\\n+        \\\"private\\\": false,\\n+        \\\"pulls_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\\\",\\n+        \\\"pushed_at\\\": \\\"2019-04-05T11:22:16Z\\\",\\n+        \\\"releases_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\\\",\\n+        \\\"size\\\": 892,\\n+        \\\"ssh_url\\\": \\\"git@github.com:ursa-labs/ursabot.git\\\",\\n+        \\\"stargazers_count\\\": 1,\\n+        \\\"stargazers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/stargazers\\\",\\n+        \\\"statuses_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\\\",\\n+        \\\"subscribers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscribers\\\",\\n+        \\\"subscription_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscription\\\",\\n+        \\\"svn_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+        \\\"tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/tags\\\",\\n+        \\\"teams_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/teams\\\",\\n+        \\\"trees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-04T17:49:10Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+        \\\"watchers\\\": 1,\\n+        \\\"watchers_count\\\": 1\\n+    },\\n+    \\\"sender\\\": {\\n+        \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+        \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+        \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+        \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+        \\\"gravatar_id\\\": \\\"\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+        \\\"id\\\": 961747,\\n+        \\\"login\\\": \\\"kszucs\\\",\\n+        \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+        \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+        \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+        \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+        \\\"site_admin\\\": false,\\n+        \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+        \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+        \\\"type\\\": \\\"User\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+    }\\n+}\"\n+    },\n+    {\n+      \"sha\": \"80ff46510a2f39ae60f7c3a98e5fdaef8e688784\",\n+      \"filename\": \"ursabot/tests/fixtures/issue-comment-without-pull-request.json\",\n+      \"status\": \"added\",\n+      \"additions\": 206,\n+      \"deletions\": 0,\n+      \"changes\": 206,\n+      \"blob_url\": \"https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-without-pull-request.json\",\n+      \"raw_url\": \"https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/fixtures/issue-comment-without-pull-request.json\",\n+      \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/issue-comment-without-pull-request.json?ref=2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+      \"patch\": \"@@ -0,0 +1,206 @@\\n+{\\n+    \\\"action\\\": \\\"created\\\",\\n+    \\\"comment\\\": {\\n+        \\\"author_association\\\": \\\"NONE\\\",\\n+        \\\"body\\\": \\\"Ursabot only listens to pull request comments!\\\",\\n+        \\\"created_at\\\": \\\"2019-04-05T11:53:43Z\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/issues/19#issuecomment-480248217\\\",\\n+        \\\"id\\\": 480248217,\\n+        \\\"issue_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/19\\\",\\n+        \\\"node_id\\\": \\\"MDEyOklzc3VlQ29tbWVudDQ4MDI0ODIxNw==\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-05T11:53:43Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments/480248217\\\",\\n+        \\\"user\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/49275095?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/ursabot/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/ursabot/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/ursabot/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/ursabot/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/ursabot\\\",\\n+            \\\"id\\\": 49275095,\\n+            \\\"login\\\": \\\"ursabot\\\",\\n+            \\\"node_id\\\": \\\"MDQ6VXNlcjQ5Mjc1MDk1\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/ursabot/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/ursabot/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/ursabot/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/ursabot/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursabot/subscriptions\\\",\\n+            \\\"type\\\": \\\"User\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/ursabot\\\"\\n+        }\\n+    },\\n+    \\\"issue\\\": {\\n+        \\\"assignee\\\": null,\\n+        \\\"assignees\\\": [],\\n+        \\\"author_association\\\": \\\"MEMBER\\\",\\n+        \\\"body\\\": \\\"\\\",\\n+        \\\"closed_at\\\": null,\\n+        \\\"comments\\\": 4,\\n+        \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/19/comments\\\",\\n+        \\\"created_at\\\": \\\"2019-04-02T09:56:41Z\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/19/events\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot/issues/19\\\",\\n+        \\\"id\\\": 428131685,\\n+        \\\"labels\\\": [],\\n+        \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/19/labels{/name}\\\",\\n+        \\\"locked\\\": false,\\n+        \\\"milestone\\\": null,\\n+        \\\"node_id\\\": \\\"MDU6SXNzdWU0MjgxMzE2ODU=\\\",\\n+        \\\"number\\\": 19,\\n+        \\\"repository_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+        \\\"state\\\": \\\"open\\\",\\n+        \\\"title\\\": \\\"Build ursabot itself via ursabot\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-05T11:53:43Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/19\\\",\\n+        \\\"user\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars1.githubusercontent.com/u/961747?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/kszucs/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/kszucs/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/kszucs/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/kszucs/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/kszucs\\\",\\n+            \\\"id\\\": 961747,\\n+            \\\"login\\\": \\\"kszucs\\\",\\n+            \\\"node_id\\\": \\\"MDQ6VXNlcjk2MTc0Nw==\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/kszucs/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/kszucs/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/kszucs/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/kszucs/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/kszucs/subscriptions\\\",\\n+            \\\"type\\\": \\\"User\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/kszucs\\\"\\n+        }\\n+    },\\n+    \\\"organization\\\": {\\n+        \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+        \\\"description\\\": \\\"Innovation lab for open source data science tools, powered by Apache Arrow\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/orgs/ursa-labs/events\\\",\\n+        \\\"hooks_url\\\": \\\"https://api.github.com/orgs/ursa-labs/hooks\\\",\\n+        \\\"id\\\": 46514972,\\n+        \\\"issues_url\\\": \\\"https://api.github.com/orgs/ursa-labs/issues\\\",\\n+        \\\"login\\\": \\\"ursa-labs\\\",\\n+        \\\"members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/members{/member}\\\",\\n+        \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+        \\\"public_members_url\\\": \\\"https://api.github.com/orgs/ursa-labs/public_members{/member}\\\",\\n+        \\\"repos_url\\\": \\\"https://api.github.com/orgs/ursa-labs/repos\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/orgs/ursa-labs\\\"\\n+    },\\n+    \\\"repository\\\": {\\n+        \\\"archive_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\\\",\\n+        \\\"archived\\\": false,\\n+        \\\"assignees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\\\",\\n+        \\\"blobs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\\\",\\n+        \\\"branches_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\\\",\\n+        \\\"clone_url\\\": \\\"https://github.com/ursa-labs/ursabot.git\\\",\\n+        \\\"collaborators_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\\\",\\n+        \\\"comments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\\\",\\n+        \\\"commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\\\",\\n+        \\\"compare_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\\\",\\n+        \\\"contents_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\\\",\\n+        \\\"contributors_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/contributors\\\",\\n+        \\\"created_at\\\": \\\"2019-02-04T15:40:31Z\\\",\\n+        \\\"default_branch\\\": \\\"master\\\",\\n+        \\\"deployments_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/deployments\\\",\\n+        \\\"description\\\": null,\\n+        \\\"disabled\\\": false,\\n+        \\\"downloads_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/downloads\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/events\\\",\\n+        \\\"fork\\\": false,\\n+        \\\"forks\\\": 0,\\n+        \\\"forks_count\\\": 0,\\n+        \\\"forks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/forks\\\",\\n+        \\\"full_name\\\": \\\"ursa-labs/ursabot\\\",\\n+        \\\"git_commits_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\\\",\\n+        \\\"git_refs_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\\\",\\n+        \\\"git_tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\\\",\\n+        \\\"git_url\\\": \\\"git://github.com/ursa-labs/ursabot.git\\\",\\n+        \\\"has_downloads\\\": true,\\n+        \\\"has_issues\\\": true,\\n+        \\\"has_pages\\\": false,\\n+        \\\"has_projects\\\": true,\\n+        \\\"has_wiki\\\": true,\\n+        \\\"homepage\\\": null,\\n+        \\\"hooks_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/hooks\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+        \\\"id\\\": 169101701,\\n+        \\\"issue_comment_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\\\",\\n+        \\\"issue_events_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\\\",\\n+        \\\"issues_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\\\",\\n+        \\\"keys_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\\\",\\n+        \\\"labels_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\\\",\\n+        \\\"language\\\": \\\"Jupyter Notebook\\\",\\n+        \\\"languages_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/languages\\\",\\n+        \\\"license\\\": null,\\n+        \\\"merges_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/merges\\\",\\n+        \\\"milestones_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\\\",\\n+        \\\"mirror_url\\\": null,\\n+        \\\"name\\\": \\\"ursabot\\\",\\n+        \\\"node_id\\\": \\\"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\\\",\\n+        \\\"notifications_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\\\",\\n+        \\\"open_issues\\\": 19,\\n+        \\\"open_issues_count\\\": 19,\\n+        \\\"owner\\\": {\\n+            \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/46514972?v=4\\\",\\n+            \\\"events_url\\\": \\\"https://api.github.com/users/ursa-labs/events{/privacy}\\\",\\n+            \\\"followers_url\\\": \\\"https://api.github.com/users/ursa-labs/followers\\\",\\n+            \\\"following_url\\\": \\\"https://api.github.com/users/ursa-labs/following{/other_user}\\\",\\n+            \\\"gists_url\\\": \\\"https://api.github.com/users/ursa-labs/gists{/gist_id}\\\",\\n+            \\\"gravatar_id\\\": \\\"\\\",\\n+            \\\"html_url\\\": \\\"https://github.com/ursa-labs\\\",\\n+            \\\"id\\\": 46514972,\\n+            \\\"login\\\": \\\"ursa-labs\\\",\\n+            \\\"node_id\\\": \\\"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\\\",\\n+            \\\"organizations_url\\\": \\\"https://api.github.com/users/ursa-labs/orgs\\\",\\n+            \\\"received_events_url\\\": \\\"https://api.github.com/users/ursa-labs/received_events\\\",\\n+            \\\"repos_url\\\": \\\"https://api.github.com/users/ursa-labs/repos\\\",\\n+            \\\"site_admin\\\": false,\\n+            \\\"starred_url\\\": \\\"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\\\",\\n+            \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursa-labs/subscriptions\\\",\\n+            \\\"type\\\": \\\"Organization\\\",\\n+            \\\"url\\\": \\\"https://api.github.com/users/ursa-labs\\\"\\n+        },\\n+        \\\"private\\\": false,\\n+        \\\"pulls_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\\\",\\n+        \\\"pushed_at\\\": \\\"2019-04-05T11:22:16Z\\\",\\n+        \\\"releases_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\\\",\\n+        \\\"size\\\": 892,\\n+        \\\"ssh_url\\\": \\\"git@github.com:ursa-labs/ursabot.git\\\",\\n+        \\\"stargazers_count\\\": 1,\\n+        \\\"stargazers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/stargazers\\\",\\n+        \\\"statuses_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\\\",\\n+        \\\"subscribers_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscribers\\\",\\n+        \\\"subscription_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/subscription\\\",\\n+        \\\"svn_url\\\": \\\"https://github.com/ursa-labs/ursabot\\\",\\n+        \\\"tags_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/tags\\\",\\n+        \\\"teams_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/teams\\\",\\n+        \\\"trees_url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\\\",\\n+        \\\"updated_at\\\": \\\"2019-04-04T17:49:10Z\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/repos/ursa-labs/ursabot\\\",\\n+        \\\"watchers\\\": 1,\\n+        \\\"watchers_count\\\": 1\\n+    },\\n+    \\\"sender\\\": {\\n+        \\\"avatar_url\\\": \\\"https://avatars2.githubusercontent.com/u/49275095?v=4\\\",\\n+        \\\"events_url\\\": \\\"https://api.github.com/users/ursabot/events{/privacy}\\\",\\n+        \\\"followers_url\\\": \\\"https://api.github.com/users/ursabot/followers\\\",\\n+        \\\"following_url\\\": \\\"https://api.github.com/users/ursabot/following{/other_user}\\\",\\n+        \\\"gists_url\\\": \\\"https://api.github.com/users/ursabot/gists{/gist_id}\\\",\\n+        \\\"gravatar_id\\\": \\\"\\\",\\n+        \\\"html_url\\\": \\\"https://github.com/ursabot\\\",\\n+        \\\"id\\\": 49275095,\\n+        \\\"login\\\": \\\"ursabot\\\",\\n+        \\\"node_id\\\": \\\"MDQ6VXNlcjQ5Mjc1MDk1\\\",\\n+        \\\"organizations_url\\\": \\\"https://api.github.com/users/ursabot/orgs\\\",\\n+        \\\"received_events_url\\\": \\\"https://api.github.com/users/ursabot/received_events\\\",\\n+        \\\"repos_url\\\": \\\"https://api.github.com/users/ursabot/repos\\\",\\n+        \\\"site_admin\\\": false,\\n+        \\\"starred_url\\\": \\\"https://api.github.com/users/ursabot/starred{/owner}{/repo}\\\",\\n+        \\\"subscriptions_url\\\": \\\"https://api.github.com/users/ursabot/subscriptions\\\",\\n+        \\\"type\\\": \\\"User\\\",\\n+        \\\"url\\\": \\\"https://api.github.com/users/ursabot\\\"\\n+    }\\n+}\"\n+    },\n+    {\n+      \"sha\": \"c738bb0eb54c87ba0f23e97e827d77c2be74d0b6\",\n+      \"filename\": \"ursabot/tests/test_hooks.py\",\n+      \"status\": \"modified\",\n+      \"additions\": 4,\n+      \"deletions\": 4,\n+      \"changes\": 8,\n+      \"blob_url\": \"https://github.com/ursa-labs/ursabot/blob/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/test_hooks.py\",\n+      \"raw_url\": \"https://github.com/ursa-labs/ursabot/raw/2705da2b616b98fa6010a25813c5a7a27456f71d/ursabot/tests/test_hooks.py\",\n+      \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/test_hooks.py?ref=2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+      \"patch\": \"@@ -54,7 +54,7 @@ class TestGithubHook(ChangeHookTestCase):\\n         await self.request('ping', {})\\n         assert len(self.hook.master.data.updates.changesAdded) == 0\\n \\n-    @ensure_deferred\\n-    async def test_issue_comment(self):\\n-        payload = {}\\n-        await self.request('issue_comment', payload)\\n+    # @ensure_deferred\\n+    # async def test_issue_comment(self):\\n+    #     payload = {}\\n+    #     await self.request('issue_comment', payload)\"\n+    }\n+  ]\n+}"
-  },
-  {
-    "sha": "ad061d7244b917e6ea3853698dc3bc2a8c9c6857",
-    "filename": "ursabot/tests/fixtures/pull-request-26.json",
-    "status": "added",
-    "additions": 335,
-    "deletions": 0,
-    "changes": 335,
-    "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/pull-request-26.json",
-    "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/fixtures/pull-request-26.json",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/fixtures/pull-request-26.json?ref=70267dee34884e4b972388e1b30d57f6248c58d0",
-    "patch": "@@ -0,0 +1,335 @@\n+{\n+  \"url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\",\n+  \"id\": 267785552,\n+  \"node_id\": \"MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy\",\n+  \"html_url\": \"https://github.com/ursa-labs/ursabot/pull/26\",\n+  \"diff_url\": \"https://github.com/ursa-labs/ursabot/pull/26.diff\",\n+  \"patch_url\": \"https://github.com/ursa-labs/ursabot/pull/26.patch\",\n+  \"issue_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\",\n+  \"number\": 26,\n+  \"state\": \"open\",\n+  \"locked\": false,\n+  \"title\": \"Unittests for GithubHook\",\n+  \"user\": {\n+    \"login\": \"kszucs\",\n+    \"id\": 961747,\n+    \"node_id\": \"MDQ6VXNlcjk2MTc0Nw==\",\n+    \"avatar_url\": \"https://avatars1.githubusercontent.com/u/961747?v=4\",\n+    \"gravatar_id\": \"\",\n+    \"url\": \"https://api.github.com/users/kszucs\",\n+    \"html_url\": \"https://github.com/kszucs\",\n+    \"followers_url\": \"https://api.github.com/users/kszucs/followers\",\n+    \"following_url\": \"https://api.github.com/users/kszucs/following{/other_user}\",\n+    \"gists_url\": \"https://api.github.com/users/kszucs/gists{/gist_id}\",\n+    \"starred_url\": \"https://api.github.com/users/kszucs/starred{/owner}{/repo}\",\n+    \"subscriptions_url\": \"https://api.github.com/users/kszucs/subscriptions\",\n+    \"organizations_url\": \"https://api.github.com/users/kszucs/orgs\",\n+    \"repos_url\": \"https://api.github.com/users/kszucs/repos\",\n+    \"events_url\": \"https://api.github.com/users/kszucs/events{/privacy}\",\n+    \"received_events_url\": \"https://api.github.com/users/kszucs/received_events\",\n+    \"type\": \"User\",\n+    \"site_admin\": false\n+  },\n+  \"body\": \"\",\n+  \"created_at\": \"2019-04-05T11:22:15Z\",\n+  \"updated_at\": \"2019-04-05T12:01:40Z\",\n+  \"closed_at\": null,\n+  \"merged_at\": null,\n+  \"merge_commit_sha\": \"cc5dc3606988b3824be54df779ed2028776113cb\",\n+  \"assignee\": null,\n+  \"assignees\": [\n+\n+  ],\n+  \"requested_reviewers\": [\n+\n+  ],\n+  \"requested_teams\": [\n+\n+  ],\n+  \"labels\": [\n+\n+  ],\n+  \"milestone\": null,\n+  \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26/commits\",\n+  \"review_comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26/comments\",\n+  \"review_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/comments{/number}\",\n+  \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\",\n+  \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+  \"head\": {\n+    \"label\": \"ursa-labs:test-hook\",\n+    \"ref\": \"test-hook\",\n+    \"sha\": \"2705da2b616b98fa6010a25813c5a7a27456f71d\",\n+    \"user\": {\n+      \"login\": \"ursa-labs\",\n+      \"id\": 46514972,\n+      \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+      \"gravatar_id\": \"\",\n+      \"url\": \"https://api.github.com/users/ursa-labs\",\n+      \"html_url\": \"https://github.com/ursa-labs\",\n+      \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+      \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+      \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+      \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+      \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+      \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+      \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+      \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+      \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+      \"type\": \"Organization\",\n+      \"site_admin\": false\n+    },\n+    \"repo\": {\n+      \"id\": 169101701,\n+      \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+      \"name\": \"ursabot\",\n+      \"full_name\": \"ursa-labs/ursabot\",\n+      \"private\": false,\n+      \"owner\": {\n+        \"login\": \"ursa-labs\",\n+        \"id\": 46514972,\n+        \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+        \"gravatar_id\": \"\",\n+        \"url\": \"https://api.github.com/users/ursa-labs\",\n+        \"html_url\": \"https://github.com/ursa-labs\",\n+        \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+        \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+        \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+        \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+        \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+        \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+        \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+        \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+        \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+        \"type\": \"Organization\",\n+        \"site_admin\": false\n+      },\n+      \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+      \"description\": null,\n+      \"fork\": false,\n+      \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+      \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+      \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+      \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+      \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+      \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+      \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+      \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+      \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+      \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+      \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+      \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+      \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+      \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+      \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+      \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+      \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+      \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+      \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+      \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+      \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+      \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+      \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+      \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+      \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+      \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+      \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+      \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+      \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+      \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+      \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+      \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+      \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+      \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+      \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+      \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+      \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+      \"created_at\": \"2019-02-04T15:40:31Z\",\n+      \"updated_at\": \"2019-04-04T17:49:10Z\",\n+      \"pushed_at\": \"2019-04-05T12:01:40Z\",\n+      \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+      \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+      \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+      \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+      \"homepage\": null,\n+      \"size\": 898,\n+      \"stargazers_count\": 1,\n+      \"watchers_count\": 1,\n+      \"language\": \"Jupyter Notebook\",\n+      \"has_issues\": true,\n+      \"has_projects\": true,\n+      \"has_downloads\": true,\n+      \"has_wiki\": true,\n+      \"has_pages\": false,\n+      \"forks_count\": 0,\n+      \"mirror_url\": null,\n+      \"archived\": false,\n+      \"disabled\": false,\n+      \"open_issues_count\": 19,\n+      \"license\": null,\n+      \"forks\": 0,\n+      \"open_issues\": 19,\n+      \"watchers\": 1,\n+      \"default_branch\": \"master\"\n+    }\n+  },\n+  \"base\": {\n+    \"label\": \"ursa-labs:master\",\n+    \"ref\": \"master\",\n+    \"sha\": \"a162ad254b589b924db47e057791191b39613fd5\",\n+    \"user\": {\n+      \"login\": \"ursa-labs\",\n+      \"id\": 46514972,\n+      \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+      \"gravatar_id\": \"\",\n+      \"url\": \"https://api.github.com/users/ursa-labs\",\n+      \"html_url\": \"https://github.com/ursa-labs\",\n+      \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+      \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+      \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+      \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+      \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+      \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+      \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+      \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+      \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+      \"type\": \"Organization\",\n+      \"site_admin\": false\n+    },\n+    \"repo\": {\n+      \"id\": 169101701,\n+      \"node_id\": \"MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=\",\n+      \"name\": \"ursabot\",\n+      \"full_name\": \"ursa-labs/ursabot\",\n+      \"private\": false,\n+      \"owner\": {\n+        \"login\": \"ursa-labs\",\n+        \"id\": 46514972,\n+        \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy\",\n+        \"avatar_url\": \"https://avatars2.githubusercontent.com/u/46514972?v=4\",\n+        \"gravatar_id\": \"\",\n+        \"url\": \"https://api.github.com/users/ursa-labs\",\n+        \"html_url\": \"https://github.com/ursa-labs\",\n+        \"followers_url\": \"https://api.github.com/users/ursa-labs/followers\",\n+        \"following_url\": \"https://api.github.com/users/ursa-labs/following{/other_user}\",\n+        \"gists_url\": \"https://api.github.com/users/ursa-labs/gists{/gist_id}\",\n+        \"starred_url\": \"https://api.github.com/users/ursa-labs/starred{/owner}{/repo}\",\n+        \"subscriptions_url\": \"https://api.github.com/users/ursa-labs/subscriptions\",\n+        \"organizations_url\": \"https://api.github.com/users/ursa-labs/orgs\",\n+        \"repos_url\": \"https://api.github.com/users/ursa-labs/repos\",\n+        \"events_url\": \"https://api.github.com/users/ursa-labs/events{/privacy}\",\n+        \"received_events_url\": \"https://api.github.com/users/ursa-labs/received_events\",\n+        \"type\": \"Organization\",\n+        \"site_admin\": false\n+      },\n+      \"html_url\": \"https://github.com/ursa-labs/ursabot\",\n+      \"description\": null,\n+      \"fork\": false,\n+      \"url\": \"https://api.github.com/repos/ursa-labs/ursabot\",\n+      \"forks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/forks\",\n+      \"keys_url\": \"https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}\",\n+      \"collaborators_url\": \"https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}\",\n+      \"teams_url\": \"https://api.github.com/repos/ursa-labs/ursabot/teams\",\n+      \"hooks_url\": \"https://api.github.com/repos/ursa-labs/ursabot/hooks\",\n+      \"issue_events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}\",\n+      \"events_url\": \"https://api.github.com/repos/ursa-labs/ursabot/events\",\n+      \"assignees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}\",\n+      \"branches_url\": \"https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}\",\n+      \"tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/tags\",\n+      \"blobs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}\",\n+      \"git_tags_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}\",\n+      \"git_refs_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}\",\n+      \"trees_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}\",\n+      \"statuses_url\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}\",\n+      \"languages_url\": \"https://api.github.com/repos/ursa-labs/ursabot/languages\",\n+      \"stargazers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/stargazers\",\n+      \"contributors_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contributors\",\n+      \"subscribers_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscribers\",\n+      \"subscription_url\": \"https://api.github.com/repos/ursa-labs/ursabot/subscription\",\n+      \"commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}\",\n+      \"git_commits_url\": \"https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}\",\n+      \"comments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/comments{/number}\",\n+      \"issue_comment_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}\",\n+      \"contents_url\": \"https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}\",\n+      \"compare_url\": \"https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}\",\n+      \"merges_url\": \"https://api.github.com/repos/ursa-labs/ursabot/merges\",\n+      \"archive_url\": \"https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}\",\n+      \"downloads_url\": \"https://api.github.com/repos/ursa-labs/ursabot/downloads\",\n+      \"issues_url\": \"https://api.github.com/repos/ursa-labs/ursabot/issues{/number}\",\n+      \"pulls_url\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}\",\n+      \"milestones_url\": \"https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}\",\n+      \"notifications_url\": \"https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}\",\n+      \"labels_url\": \"https://api.github.com/repos/ursa-labs/ursabot/labels{/name}\",\n+      \"releases_url\": \"https://api.github.com/repos/ursa-labs/ursabot/releases{/id}\",\n+      \"deployments_url\": \"https://api.github.com/repos/ursa-labs/ursabot/deployments\",\n+      \"created_at\": \"2019-02-04T15:40:31Z\",\n+      \"updated_at\": \"2019-04-04T17:49:10Z\",\n+      \"pushed_at\": \"2019-04-05T12:01:40Z\",\n+      \"git_url\": \"git://github.com/ursa-labs/ursabot.git\",\n+      \"ssh_url\": \"git@github.com:ursa-labs/ursabot.git\",\n+      \"clone_url\": \"https://github.com/ursa-labs/ursabot.git\",\n+      \"svn_url\": \"https://github.com/ursa-labs/ursabot\",\n+      \"homepage\": null,\n+      \"size\": 898,\n+      \"stargazers_count\": 1,\n+      \"watchers_count\": 1,\n+      \"language\": \"Jupyter Notebook\",\n+      \"has_issues\": true,\n+      \"has_projects\": true,\n+      \"has_downloads\": true,\n+      \"has_wiki\": true,\n+      \"has_pages\": false,\n+      \"forks_count\": 0,\n+      \"mirror_url\": null,\n+      \"archived\": false,\n+      \"disabled\": false,\n+      \"open_issues_count\": 19,\n+      \"license\": null,\n+      \"forks\": 0,\n+      \"open_issues\": 19,\n+      \"watchers\": 1,\n+      \"default_branch\": \"master\"\n+    }\n+  },\n+  \"_links\": {\n+    \"self\": {\n+      \"href\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26\"\n+    },\n+    \"html\": {\n+      \"href\": \"https://github.com/ursa-labs/ursabot/pull/26\"\n+    },\n+    \"issue\": {\n+      \"href\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26\"\n+    },\n+    \"comments\": {\n+      \"href\": \"https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments\"\n+    },\n+    \"review_comments\": {\n+      \"href\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26/comments\"\n+    },\n+    \"review_comment\": {\n+      \"href\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/comments{/number}\"\n+    },\n+    \"commits\": {\n+      \"href\": \"https://api.github.com/repos/ursa-labs/ursabot/pulls/26/commits\"\n+    },\n+    \"statuses\": {\n+      \"href\": \"https://api.github.com/repos/ursa-labs/ursabot/statuses/2705da2b616b98fa6010a25813c5a7a27456f71d\"\n+    }\n+  },\n+  \"author_association\": \"MEMBER\",\n+  \"merged\": false,\n+  \"mergeable\": true,\n+  \"rebaseable\": true,\n+  \"mergeable_state\": \"unstable\",\n+  \"merged_by\": null,\n+  \"comments\": 5,\n+  \"review_comments\": 0,\n+  \"maintainer_can_modify\": false,\n+  \"commits\": 2,\n+  \"additions\": 1124,\n+  \"deletions\": 0,\n+  \"changed_files\": 7\n+}"
-  },
-  {
-    "sha": "e87b27d2d7b4956d15f7468488b96cf6a06686f4",
-    "filename": "ursabot/tests/test_hooks.py",
-    "status": "added",
-    "additions": 116,
-    "deletions": 0,
-    "changes": 116,
-    "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/test_hooks.py",
-    "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/tests/test_hooks.py",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/tests/test_hooks.py?ref=70267dee34884e4b972388e1b30d57f6248c58d0",
-    "patch": "@@ -0,0 +1,116 @@\n+import json\n+from pathlib import Path\n+from twisted.trial import unittest\n+\n+from buildbot.test.util.misc import TestReactorMixin\n+from buildbot.test.fake.httpclientservice import \\\n+    HTTPClientService as FakeHTTPClientService\n+from buildbot.test.unit.test_www_hooks_github import (\n+    _prepare_request, _prepare_github_change_hook)\n+\n+from ursabot.utils import ensure_deferred\n+from ursabot.hooks import GithubHook\n+\n+\n+class ChangeHookTestCase(unittest.TestCase, TestReactorMixin):\n+\n+    klass = None\n+\n+    @ensure_deferred\n+    async def setUp(self):\n+        self.setUpTestReactor()\n+\n+        assert self.klass is not None\n+        self.hook = _prepare_github_change_hook(self, **{'class': self.klass})\n+        self.master = self.hook.master\n+        self.http = await FakeHTTPClientService.getFakeService(\n+            self.master, self, 'https://api.github.com',\n+            headers={'User-Agent': 'Buildbot'}, debug=False, verify=False)\n+\n+        await self.master.startService()\n+\n+    @ensure_deferred\n+    async def tearDown(self):\n+        await self.master.stopService()\n+\n+    async def trigger(self, event, payload, headers=None, _secret=None):\n+        payload = json.dumps(payload).encode()\n+        request = _prepare_request(event, payload, _secret=_secret,\n+                                   headers=headers)\n+        await request.test_render(self.hook)\n+        return request\n+\n+    def load_fixture(self, name):\n+        path = Path(__file__).parent / 'fixtures' / f'{name}.json'\n+        with path.open('r') as fp:\n+            return json.load(fp)\n+\n+\n+class TestGithubHook(ChangeHookTestCase):\n+\n+    klass = GithubHook\n+\n+    @ensure_deferred\n+    async def test_ping(self):\n+        await self.trigger('ping', {})\n+        assert len(self.hook.master.data.updates.changesAdded) == 0\n+\n+    @ensure_deferred\n+    async def test_issue_comment_not_mentioning_ursabot(self):\n+        payload = self.load_fixture('issue-comment-not-mentioning-ursabot')\n+        await self.trigger('issue_comment', payload=payload)\n+        assert len(self.hook.master.data.updates.changesAdded) == 0\n+\n+    @ensure_deferred\n+    async def test_issue_comment_by_ursabot(self):\n+        payload = self.load_fixture('issue-comment-by-ursabot')\n+        await self.trigger('issue_comment', payload=payload)\n+        assert len(self.hook.master.data.updates.changesAdded) == 0\n+\n+    @ensure_deferred\n+    async def test_issue_comment_with_empty_command(self):\n+        # responds to the comment\n+        request_json = {'body': 'Unknown command \"\"'}\n+        response_json = ''\n+        self.http.expect('post', '/repos/ursa-labs/ursabot/issues/26/comments',\n+                         json=request_json, content_json=response_json)\n+\n+        payload = self.load_fixture('issue-comment-with-empty-command')\n+        await self.trigger('issue_comment', payload=payload)\n+        assert len(self.hook.master.data.updates.changesAdded) == 0\n+\n+    @ensure_deferred\n+    async def test_issue_comment_without_pull_request(self):\n+        # responds to the comment\n+        request_json = {\n+            'body': 'Ursabot only listens to pull request comments!'\n+        }\n+        response_json = ''\n+        self.http.expect('post', '/repos/ursa-labs/ursabot/issues/19/comments',\n+                         json=request_json, content_json=response_json)\n+\n+        payload = self.load_fixture('issue-comment-without-pull-request')\n+        await self.trigger('issue_comment', payload=payload)\n+        assert len(self.hook.master.data.updates.changesAdded) == 0\n+\n+    @ensure_deferred\n+    async def test_issue_comment_build_command(self):\n+        # handle_issue_comment queries the pull request\n+        request_json = self.load_fixture('pull-request-26')\n+        self.http.expect('get', '/repos/ursa-labs/ursabot/pulls/26',\n+                         content_json=request_json)\n+        # tigger handle_pull_request which fetches the commit\n+        request_json = self.load_fixture('pull-request-26-commit')\n+        commit = '2705da2b616b98fa6010a25813c5a7a27456f71d'\n+        self.http.expect('get', f'/repos/ursa-labs/ursabot/commits/{commit}',\n+                         content_json=request_json)\n+\n+        # then responds to the comment\n+        request_json = {'body': \"I've successfully started builds for this PR\"}\n+        response_json = ''\n+        self.http.expect('post', '/repos/ursa-labs/ursabot/issues/26/comments',\n+                         json=request_json, content_json=response_json)\n+\n+        payload = self.load_fixture('issue-comment-build-command')\n+        await self.trigger('issue_comment', payload=payload)\n+        assert len(self.hook.master.data.updates.changesAdded) == 1"
-  },
-  {
-    "sha": "3ff0e88660cf186420e8bc672735e4d446963192",
-    "filename": "ursabot/utils.py",
-    "status": "added",
-    "additions": 10,
-    "deletions": 0,
-    "changes": 10,
-    "blob_url": "https://github.com/ursa-labs/ursabot/blob/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/utils.py",
-    "raw_url": "https://github.com/ursa-labs/ursabot/raw/70267dee34884e4b972388e1b30d57f6248c58d0/ursabot/utils.py",
-    "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/ursabot/utils.py?ref=70267dee34884e4b972388e1b30d57f6248c58d0",
-    "patch": "@@ -0,0 +1,10 @@\n+import functools\n+from twisted.internet import defer\n+\n+\n+def ensure_deferred(f):\n+    @functools.wraps(f)\n+    def wrapper(*args, **kwargs):\n+        result = f(*args, **kwargs)\n+        return defer.ensureDeferred(result)\n+    return wrapper"
-  }
-]
\ No newline at end of file
diff --git a/dev/archery/archery/tests/fixtures/pull-request-26.json b/dev/archery/archery/tests/fixtures/pull-request-26.json
deleted file mode 100644
index d295afb..0000000
--- a/dev/archery/archery/tests/fixtures/pull-request-26.json
+++ /dev/null
@@ -1,329 +0,0 @@
-{
-  "url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26",
-  "id": 267785552,
-  "node_id": "MDExOlB1bGxSZXF1ZXN0MjY3Nzg1NTUy",
-  "html_url": "https://github.com/ursa-labs/ursabot/pull/26",
-  "diff_url": "https://github.com/ursa-labs/ursabot/pull/26.diff",
-  "patch_url": "https://github.com/ursa-labs/ursabot/pull/26.patch",
-  "issue_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26",
-  "number": 26,
-  "state": "open",
-  "locked": false,
-  "title": "Unittests for GithubHook",
-  "user": {
-    "login": "kszucs",
-    "id": 961747,
-    "node_id": "MDQ6VXNlcjk2MTc0Nw==",
-    "avatar_url": "https://avatars1.githubusercontent.com/u/961747?v=4",
-    "gravatar_id": "",
-    "url": "https://api.github.com/users/kszucs",
-    "html_url": "https://github.com/kszucs",
-    "followers_url": "https://api.github.com/users/kszucs/followers",
-    "following_url": "https://api.github.com/users/kszucs/following{/other_user}",
-    "gists_url": "https://api.github.com/users/kszucs/gists{/gist_id}",
-    "starred_url": "https://api.github.com/users/kszucs/starred{/owner}{/repo}",
-    "subscriptions_url": "https://api.github.com/users/kszucs/subscriptions",
-    "organizations_url": "https://api.github.com/users/kszucs/orgs",
-    "repos_url": "https://api.github.com/users/kszucs/repos",
-    "events_url": "https://api.github.com/users/kszucs/events{/privacy}",
-    "received_events_url": "https://api.github.com/users/kszucs/received_events",
-    "type": "User",
-    "site_admin": false
-  },
-  "body": "",
-  "body_html": "",
-  "body_text": "",
-  "created_at": "2019-04-05T11:22:15Z",
-  "updated_at": "2019-04-05T12:01:40Z",
-  "closed_at": null,
-  "merged_at": null,
-  "merge_commit_sha": "cc5dc3606988b3824be54df779ed2028776113cb",
-  "assignee": null,
-  "assignees": [],
-  "requested_reviewers": [],
-  "requested_teams": [],
-  "labels": [],
-  "milestone": null,
-  "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/commits",
-  "review_comments_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/comments",
-  "review_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls/comments{/number}",
-  "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments",
-  "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/2705da2b616b98fa6010a25813c5a7a27456f71d",
-  "head": {
-    "label": "ursa-labs:test-hook",
-    "ref": "test-hook",
-    "sha": "2705da2b616b98fa6010a25813c5a7a27456f71d",
-    "user": {
-      "login": "ursa-labs",
-      "id": 46514972,
-      "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-      "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-      "gravatar_id": "",
-      "url": "https://api.github.com/users/ursa-labs",
-      "html_url": "https://github.com/ursa-labs",
-      "followers_url": "https://api.github.com/users/ursa-labs/followers",
-      "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-      "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-      "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-      "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-      "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-      "repos_url": "https://api.github.com/users/ursa-labs/repos",
-      "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-      "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-      "type": "Organization",
-      "site_admin": false
-    },
-    "repo": {
-      "id": 169101701,
-      "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=",
-      "name": "ursabot",
-      "full_name": "ursa-labs/ursabot",
-      "private": false,
-      "owner": {
-        "login": "ursa-labs",
-        "id": 46514972,
-        "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-        "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-        "gravatar_id": "",
-        "url": "https://api.github.com/users/ursa-labs",
-        "html_url": "https://github.com/ursa-labs",
-        "followers_url": "https://api.github.com/users/ursa-labs/followers",
-        "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-        "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-        "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-        "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-        "repos_url": "https://api.github.com/users/ursa-labs/repos",
-        "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-        "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-        "type": "Organization",
-        "site_admin": false
-      },
-      "html_url": "https://github.com/ursa-labs/ursabot",
-      "description": null,
-      "fork": false,
-      "url": "https://api.github.com/repos/ursa-labs/ursabot",
-      "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks",
-      "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}",
-      "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}",
-      "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams",
-      "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks",
-      "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}",
-      "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events",
-      "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}",
-      "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}",
-      "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags",
-      "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}",
-      "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}",
-      "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}",
-      "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}",
-      "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}",
-      "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages",
-      "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers",
-      "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors",
-      "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers",
-      "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription",
-      "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}",
-      "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}",
-      "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}",
-      "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}",
-      "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}",
-      "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}",
-      "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges",
-      "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}",
-      "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads",
-      "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}",
-      "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}",
-      "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}",
-      "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}",
-      "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}",
-      "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}",
-      "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments",
-      "created_at": "2019-02-04T15:40:31Z",
-      "updated_at": "2019-04-04T17:49:10Z",
-      "pushed_at": "2019-04-05T12:01:40Z",
-      "git_url": "git://github.com/ursa-labs/ursabot.git",
-      "ssh_url": "git@github.com:ursa-labs/ursabot.git",
-      "clone_url": "https://github.com/ursa-labs/ursabot.git",
-      "svn_url": "https://github.com/ursa-labs/ursabot",
-      "homepage": null,
-      "size": 898,
-      "stargazers_count": 1,
-      "watchers_count": 1,
-      "language": "Jupyter Notebook",
-      "has_issues": true,
-      "has_projects": true,
-      "has_downloads": true,
-      "has_wiki": true,
-      "has_pages": false,
-      "forks_count": 0,
-      "mirror_url": null,
-      "archived": false,
-      "disabled": false,
-      "open_issues_count": 19,
-      "license": null,
-      "forks": 0,
-      "open_issues": 19,
-      "watchers": 1,
-      "default_branch": "master"
-    }
-  },
-  "base": {
-    "label": "ursa-labs:master",
-    "ref": "master",
-    "sha": "a162ad254b589b924db47e057791191b39613fd5",
-    "user": {
-      "login": "ursa-labs",
-      "id": 46514972,
-      "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-      "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-      "gravatar_id": "",
-      "url": "https://api.github.com/users/ursa-labs",
-      "html_url": "https://github.com/ursa-labs",
-      "followers_url": "https://api.github.com/users/ursa-labs/followers",
-      "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-      "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-      "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-      "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-      "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-      "repos_url": "https://api.github.com/users/ursa-labs/repos",
-      "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-      "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-      "type": "Organization",
-      "site_admin": false
-    },
-    "repo": {
-      "id": 169101701,
-      "node_id": "MDEwOlJlcG9zaXRvcnkxNjkxMDE3MDE=",
-      "name": "ursabot",
-      "full_name": "ursa-labs/ursabot",
-      "private": false,
-      "owner": {
-        "login": "ursa-labs",
-        "id": 46514972,
-        "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2NTE0OTcy",
-        "avatar_url": "https://avatars2.githubusercontent.com/u/46514972?v=4",
-        "gravatar_id": "",
-        "url": "https://api.github.com/users/ursa-labs",
-        "html_url": "https://github.com/ursa-labs",
-        "followers_url": "https://api.github.com/users/ursa-labs/followers",
-        "following_url": "https://api.github.com/users/ursa-labs/following{/other_user}",
-        "gists_url": "https://api.github.com/users/ursa-labs/gists{/gist_id}",
-        "starred_url": "https://api.github.com/users/ursa-labs/starred{/owner}{/repo}",
-        "subscriptions_url": "https://api.github.com/users/ursa-labs/subscriptions",
-        "organizations_url": "https://api.github.com/users/ursa-labs/orgs",
-        "repos_url": "https://api.github.com/users/ursa-labs/repos",
-        "events_url": "https://api.github.com/users/ursa-labs/events{/privacy}",
-        "received_events_url": "https://api.github.com/users/ursa-labs/received_events",
-        "type": "Organization",
-        "site_admin": false
-      },
-      "html_url": "https://github.com/ursa-labs/ursabot",
-      "description": null,
-      "fork": false,
-      "url": "https://api.github.com/repos/ursa-labs/ursabot",
-      "forks_url": "https://api.github.com/repos/ursa-labs/ursabot/forks",
-      "keys_url": "https://api.github.com/repos/ursa-labs/ursabot/keys{/key_id}",
-      "collaborators_url": "https://api.github.com/repos/ursa-labs/ursabot/collaborators{/collaborator}",
-      "teams_url": "https://api.github.com/repos/ursa-labs/ursabot/teams",
-      "hooks_url": "https://api.github.com/repos/ursa-labs/ursabot/hooks",
-      "issue_events_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/events{/number}",
-      "events_url": "https://api.github.com/repos/ursa-labs/ursabot/events",
-      "assignees_url": "https://api.github.com/repos/ursa-labs/ursabot/assignees{/user}",
-      "branches_url": "https://api.github.com/repos/ursa-labs/ursabot/branches{/branch}",
-      "tags_url": "https://api.github.com/repos/ursa-labs/ursabot/tags",
-      "blobs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/blobs{/sha}",
-      "git_tags_url": "https://api.github.com/repos/ursa-labs/ursabot/git/tags{/sha}",
-      "git_refs_url": "https://api.github.com/repos/ursa-labs/ursabot/git/refs{/sha}",
-      "trees_url": "https://api.github.com/repos/ursa-labs/ursabot/git/trees{/sha}",
-      "statuses_url": "https://api.github.com/repos/ursa-labs/ursabot/statuses/{sha}",
-      "languages_url": "https://api.github.com/repos/ursa-labs/ursabot/languages",
-      "stargazers_url": "https://api.github.com/repos/ursa-labs/ursabot/stargazers",
-      "contributors_url": "https://api.github.com/repos/ursa-labs/ursabot/contributors",
-      "subscribers_url": "https://api.github.com/repos/ursa-labs/ursabot/subscribers",
-      "subscription_url": "https://api.github.com/repos/ursa-labs/ursabot/subscription",
-      "commits_url": "https://api.github.com/repos/ursa-labs/ursabot/commits{/sha}",
-      "git_commits_url": "https://api.github.com/repos/ursa-labs/ursabot/git/commits{/sha}",
-      "comments_url": "https://api.github.com/repos/ursa-labs/ursabot/comments{/number}",
-      "issue_comment_url": "https://api.github.com/repos/ursa-labs/ursabot/issues/comments{/number}",
-      "contents_url": "https://api.github.com/repos/ursa-labs/ursabot/contents/{+path}",
-      "compare_url": "https://api.github.com/repos/ursa-labs/ursabot/compare/{base}...{head}",
-      "merges_url": "https://api.github.com/repos/ursa-labs/ursabot/merges",
-      "archive_url": "https://api.github.com/repos/ursa-labs/ursabot/{archive_format}{/ref}",
-      "downloads_url": "https://api.github.com/repos/ursa-labs/ursabot/downloads",
-      "issues_url": "https://api.github.com/repos/ursa-labs/ursabot/issues{/number}",
-      "pulls_url": "https://api.github.com/repos/ursa-labs/ursabot/pulls{/number}",
-      "milestones_url": "https://api.github.com/repos/ursa-labs/ursabot/milestones{/number}",
-      "notifications_url": "https://api.github.com/repos/ursa-labs/ursabot/notifications{?since,all,participating}",
-      "labels_url": "https://api.github.com/repos/ursa-labs/ursabot/labels{/name}",
-      "releases_url": "https://api.github.com/repos/ursa-labs/ursabot/releases{/id}",
-      "deployments_url": "https://api.github.com/repos/ursa-labs/ursabot/deployments",
-      "created_at": "2019-02-04T15:40:31Z",
-      "updated_at": "2019-04-04T17:49:10Z",
-      "pushed_at": "2019-04-05T12:01:40Z",
-      "git_url": "git://github.com/ursa-labs/ursabot.git",
-      "ssh_url": "git@github.com:ursa-labs/ursabot.git",
-      "clone_url": "https://github.com/ursa-labs/ursabot.git",
-      "svn_url": "https://github.com/ursa-labs/ursabot",
-      "homepage": null,
-      "size": 898,
-      "stargazers_count": 1,
-      "watchers_count": 1,
-      "language": "Jupyter Notebook",
-      "has_issues": true,
-      "has_projects": true,
-      "has_downloads": true,
-      "has_wiki": true,
-      "has_pages": false,
-      "forks_count": 0,
-      "mirror_url": null,
-      "archived": false,
-      "disabled": false,
-      "open_issues_count": 19,
-      "license": null,
-      "forks": 0,
-      "open_issues": 19,
-      "watchers": 1,
-      "default_branch": "master"
-    }
-  },
-  "_links": {
-    "self": {
-      "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26"
-    },
-    "html": {
-      "href": "https://github.com/ursa-labs/ursabot/pull/26"
-    },
-    "issue": {
-      "href": "https://api.github.com/repos/ursa-labs/ursabot/issues/26"
-    },
-    "comments": {
-      "href": "https://api.github.com/repos/ursa-labs/ursabot/issues/26/comments"
-    },
-    "review_comments": {
-      "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/comments"
-    },
-    "review_comment": {
-      "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/comments{/number}"
-    },
-    "commits": {
-      "href": "https://api.github.com/repos/ursa-labs/ursabot/pulls/26/commits"
-    },
-    "statuses": {
-      "href": "https://api.github.com/repos/ursa-labs/ursabot/statuses/2705da2b616b98fa6010a25813c5a7a27456f71d"
-    }
-  },
-  "author_association": "MEMBER",
-  "merged": false,
-  "mergeable": true,
-  "rebaseable": true,
-  "mergeable_state": "unstable",
-  "merged_by": null,
-  "comments": 5,
-  "review_comments": 0,
-  "maintainer_can_modify": false,
-  "commits": 2,
-  "additions": 1124,
-  "deletions": 0,
-  "changed_files": 7
-}
\ No newline at end of file
diff --git a/dev/archery/archery/tests/test_benchmarks.py b/dev/archery/archery/tests/test_benchmarks.py
deleted file mode 100644
index fab1e8d..0000000
--- a/dev/archery/archery/tests/test_benchmarks.py
+++ /dev/null
@@ -1,383 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import json
-
-from archery.benchmark.codec import JsonEncoder
-from archery.benchmark.core import Benchmark, median
-from archery.benchmark.compare import (
-    BenchmarkComparator, RunnerComparator
-)
-from archery.benchmark.google import (
-    GoogleBenchmark, GoogleBenchmarkObservation
-)
-from archery.benchmark.runner import StaticBenchmarkRunner
-
-
-def test_benchmark_comparator():
-    unit = "micros"
-
-    assert not BenchmarkComparator(
-        Benchmark("contender", unit, True, [10], unit, [1]),
-        Benchmark("baseline", unit, True, [20], unit, [1]),
-    ).regression
-
-    assert BenchmarkComparator(
-        Benchmark("contender", unit, False, [10], unit, [1]),
-        Benchmark("baseline", unit, False, [20], unit, [1]),
-    ).regression
-
-    assert BenchmarkComparator(
-        Benchmark("contender", unit, True, [20], unit, [1]),
-        Benchmark("baseline", unit, True, [10], unit, [1]),
-    ).regression
-
-    assert not BenchmarkComparator(
-        Benchmark("contender", unit, False, [20], unit, [1]),
-        Benchmark("baseline", unit, False, [10], unit, [1]),
-    ).regression
-
-
-def test_static_runner_from_json_not_a_regression():
-    archery_result = {
-        "suites": [
-            {
-                "name": "arrow-value-parsing-benchmark",
-                "benchmarks": [
-                    {
-                        "name": "FloatParsing<DoubleType>",
-                        "unit": "items_per_second",
-                        "less_is_better": False,
-                        "values": [
-                            109941112.87296811
-                        ],
-                        "time_unit": "ns",
-                        "times": [
-                            9095.800104330105
-                        ]
-                    },
-                ]
-            }
-        ]
-    }
-
-    contender = StaticBenchmarkRunner.from_json(json.dumps(archery_result))
-    baseline = StaticBenchmarkRunner.from_json(json.dumps(archery_result))
-    [comparison] = RunnerComparator(contender, baseline).comparisons
-    assert not comparison.regression
-
-
-def test_static_runner_from_json_regression():
-    archery_result = {
-        "suites": [
-            {
-                "name": "arrow-value-parsing-benchmark",
-                "benchmarks": [
-                    {
-                        "name": "FloatParsing<DoubleType>",
-                        "unit": "items_per_second",
-                        "less_is_better": False,
-                        "values": [
-                            109941112.87296811
-                        ],
-                        "time_unit": "ns",
-                        "times": [
-                            9095.800104330105
-                        ]
-                    },
-                ]
-            }
-        ]
-    }
-
-    contender = StaticBenchmarkRunner.from_json(json.dumps(archery_result))
-
-    # introduce artificial regression
-    archery_result['suites'][0]['benchmarks'][0]['values'][0] *= 2
-    baseline = StaticBenchmarkRunner.from_json(json.dumps(archery_result))
-
-    [comparison] = RunnerComparator(contender, baseline).comparisons
-    assert comparison.regression
-
-
-def test_benchmark_median():
-    assert median([10]) == 10
-    assert median([1, 2, 3]) == 2
-    assert median([1, 2]) == 1.5
-    assert median([1, 2, 3, 4]) == 2.5
-    assert median([1, 1, 1, 1]) == 1
-    try:
-        median([])
-        assert False
-    except ValueError:
-        pass
-
-
-def assert_benchmark(name, google_result, archery_result):
-    observation = GoogleBenchmarkObservation(**google_result)
-    benchmark = GoogleBenchmark(name, [observation])
-    result = json.dumps(benchmark, cls=JsonEncoder)
-    assert json.loads(result) == archery_result
-
-
-def test_items_per_second():
-    name = "ArrayArrayKernel<AddChecked, UInt8Type>/32768/0"
-    google_result = {
-        "cpu_time": 116292.58886653671,
-        "items_per_second": 281772039.9844759,
-        "iterations": 5964,
-        "name": name,
-        "null_percent": 0.0,
-        "real_time": 119811.77313729875,
-        "repetition_index": 0,
-        "repetitions": 0,
-        "run_name": name,
-        "run_type": "iteration",
-        "size": 32768.0,
-        "threads": 1,
-        "time_unit": "ns",
-    }
-    archery_result = {
-        "counters": {"iterations": 5964,
-                     "null_percent": 0.0,
-                     "repetition_index": 0,
-                     "repetitions": 0,
-                     "run_name": name,
-                     "threads": 1},
-        "name": name,
-        "unit": "items_per_second",
-        "less_is_better": False,
-        "values": [281772039.9844759],
-        "time_unit": "ns",
-        "times": [119811.77313729875],
-    }
-    assert "items_per_second" in google_result
-    assert "bytes_per_second" not in google_result
-    assert_benchmark(name, google_result, archery_result)
-
-
-def test_bytes_per_second():
-    name = "BufferOutputStreamLargeWrites/real_time"
-    google_result = {
-        "bytes_per_second": 1890209037.3405428,
-        "cpu_time": 17018127.659574457,
-        "iterations": 47,
-        "name": name,
-        "real_time": 17458386.53190963,
-        "repetition_index": 1,
-        "repetitions": 0,
-        "run_name": name,
-        "run_type": "iteration",
-        "threads": 1,
-        "time_unit": "ns",
-    }
-    archery_result = {
-        "counters": {"iterations": 47,
-                     "repetition_index": 1,
-                     "repetitions": 0,
-                     "run_name": name,
-                     "threads": 1},
-        "name": name,
-        "unit": "bytes_per_second",
-        "less_is_better": False,
-        "values": [1890209037.3405428],
-        "time_unit": "ns",
-        "times": [17458386.53190963],
-    }
-    assert "items_per_second" not in google_result
-    assert "bytes_per_second" in google_result
-    assert_benchmark(name, google_result, archery_result)
-
-
-def test_both_items_and_bytes_per_second():
-    name = "ArrayArrayKernel<AddChecked, UInt8Type>/32768/0"
-    google_result = {
-        "bytes_per_second": 281772039.9844759,
-        "cpu_time": 116292.58886653671,
-        "items_per_second": 281772039.9844759,
-        "iterations": 5964,
-        "name": name,
-        "null_percent": 0.0,
-        "real_time": 119811.77313729875,
-        "repetition_index": 0,
-        "repetitions": 0,
-        "run_name": name,
-        "run_type": "iteration",
-        "size": 32768.0,
-        "threads": 1,
-        "time_unit": "ns",
-    }
-    # Note that bytes_per_second trumps items_per_second
-    archery_result = {
-        "counters": {"iterations": 5964,
-                     "null_percent": 0.0,
-                     "repetition_index": 0,
-                     "repetitions": 0,
-                     "run_name": name,
-                     "threads": 1},
-        "name": name,
-        "unit": "bytes_per_second",
-        "less_is_better": False,
-        "values": [281772039.9844759],
-        "time_unit": "ns",
-        "times": [119811.77313729875],
-    }
-    assert "items_per_second" in google_result
-    assert "bytes_per_second" in google_result
-    assert_benchmark(name, google_result, archery_result)
-
-
-def test_neither_items_nor_bytes_per_second():
-    name = "AllocateDeallocate<Jemalloc>/size:1048576/real_time"
-    google_result = {
-        "cpu_time": 1778.6004847419827,
-        "iterations": 352765,
-        "name": name,
-        "real_time": 1835.3137357788837,
-        "repetition_index": 0,
-        "repetitions": 0,
-        "run_name": name,
-        "run_type": "iteration",
-        "threads": 1,
-        "time_unit": "ns",
-    }
-    archery_result = {
-        "counters": {"iterations": 352765,
-                     "repetition_index": 0,
-                     "repetitions": 0,
-                     "run_name": name,
-                     "threads": 1},
-        "name": name,
-        "unit": "ns",
-        "less_is_better": True,
-        "values": [1835.3137357788837],
-        "time_unit": "ns",
-        "times": [1835.3137357788837],
-    }
-    assert "items_per_second" not in google_result
-    assert "bytes_per_second" not in google_result
-    assert_benchmark(name, google_result, archery_result)
-
-
-def test_prefer_real_time():
-    name = "AllocateDeallocate<Jemalloc>/size:1048576/real_time"
-    google_result = {
-        "cpu_time": 1778.6004847419827,
-        "iterations": 352765,
-        "name": name,
-        "real_time": 1835.3137357788837,
-        "repetition_index": 0,
-        "repetitions": 0,
-        "run_name": name,
-        "run_type": "iteration",
-        "threads": 1,
-        "time_unit": "ns",
-    }
-    archery_result = {
-        "counters": {"iterations": 352765,
-                     "repetition_index": 0,
-                     "repetitions": 0,
-                     "run_name": name,
-                     "threads": 1},
-        "name": name,
-        "unit": "ns",
-        "less_is_better": True,
-        "values": [1835.3137357788837],
-        "time_unit": "ns",
-        "times": [1835.3137357788837],
-    }
-    assert name.endswith("/real_time")
-    assert_benchmark(name, google_result, archery_result)
-
-
-def test_prefer_cpu_time():
-    name = "AllocateDeallocate<Jemalloc>/size:1048576"
-    google_result = {
-        "cpu_time": 1778.6004847419827,
-        "iterations": 352765,
-        "name": name,
-        "real_time": 1835.3137357788837,
-        "repetition_index": 0,
-        "repetitions": 0,
-        "run_name": name,
-        "run_type": "iteration",
-        "threads": 1,
-        "time_unit": "ns",
-    }
-    archery_result = {
-        "counters": {"iterations": 352765,
-                     "repetition_index": 0,
-                     "repetitions": 0,
-                     "run_name": name,
-                     "threads": 1},
-        "name": name,
-        "unit": "ns",
-        "less_is_better": True,
-        "values": [1778.6004847419827],
-        "time_unit": "ns",
-        "times": [1835.3137357788837],
-    }
-    assert not name.endswith("/real_time")
-    assert_benchmark(name, google_result, archery_result)
-
-
-def test_omits_aggregates():
-    name = "AllocateDeallocate<Jemalloc>/size:1048576/real_time"
-    google_aggregate = {
-        "aggregate_name": "mean",
-        "cpu_time": 1757.428694267678,
-        "iterations": 3,
-        "name": "AllocateDeallocate<Jemalloc>/size:1048576/real_time_mean",
-        "real_time": 1849.3869337041162,
-        "repetitions": 0,
-        "run_name": name,
-        "run_type": "aggregate",
-        "threads": 1,
-        "time_unit": "ns",
-    }
-    google_result = {
-        "cpu_time": 1778.6004847419827,
-        "iterations": 352765,
-        "name": name,
-        "real_time": 1835.3137357788837,
-        "repetition_index": 0,
-        "repetitions": 0,
-        "run_name": name,
-        "run_type": "iteration",
-        "threads": 1,
-        "time_unit": "ns",
-    }
-    archery_result = {
-        "counters": {"iterations": 352765,
-                     "repetition_index": 0,
-                     "repetitions": 0,
-                     "run_name": name,
-                     "threads": 1},
-        "name": name,
-        "unit": "ns",
-        "less_is_better": True,
-        "values": [1835.3137357788837],
-        "time_unit": "ns",
-        "times": [1835.3137357788837],
-    }
-    assert google_aggregate["run_type"] == "aggregate"
-    assert google_result["run_type"] == "iteration"
-    observation1 = GoogleBenchmarkObservation(**google_aggregate)
-    observation2 = GoogleBenchmarkObservation(**google_result)
-    benchmark = GoogleBenchmark(name, [observation1, observation2])
-    result = json.dumps(benchmark, cls=JsonEncoder)
-    assert json.loads(result) == archery_result
diff --git a/dev/archery/archery/tests/test_bot.py b/dev/archery/archery/tests/test_bot.py
deleted file mode 100644
index e00853c..0000000
--- a/dev/archery/archery/tests/test_bot.py
+++ /dev/null
@@ -1,201 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import json
-from unittest.mock import Mock
-
-import click
-import pytest
-import responses as rsps
-
-from archery.bot import CommentBot, CommandError, group
-
-
-@pytest.fixture
-def responses():
-    with rsps.RequestsMock() as mock:
-        yield mock
-
-
-def github_url(path):
-    return 'https://api.github.com:443/{}'.format(path.strip('/'))
-
-
-@group()
-def custom_handler():
-    pass
-
-
-@custom_handler.command()
-@click.pass_obj
-def extra(obj):
-    return obj
-
-
-@custom_handler.command()
-@click.option('--force', '-f', is_flag=True)
-def build(force):
-    return force
-
-
-@custom_handler.command()
-@click.option('--name', required=True)
-def benchmark(name):
-    return name
-
-
-def test_click_based_commands():
-    assert custom_handler('build') is False
-    assert custom_handler('build -f') is True
-
-    assert custom_handler('benchmark --name strings') == 'strings'
-    with pytest.raises(CommandError):
-        assert custom_handler('benchmark')
-
-    assert custom_handler('extra', extra='data') == {'extra': 'data'}
-
-
-@pytest.mark.parametrize('fixture_name', [
-    # the bot is not mentioned, nothing to do
-    'event-issue-comment-not-mentioning-ursabot.json',
-    # don't respond to itself, it prevents recursive comment storms!
-    'event-issue-comment-by-ursabot.json',
-    # non-authorized user sent the comment, do not respond
-    'event-issue-comment-by-non-authorized-user.json',
-])
-def test_noop_events(load_fixture, fixture_name):
-    payload = load_fixture(fixture_name)
-
-    handler = Mock()
-    bot = CommentBot(name='ursabot', token='', handler=handler)
-    bot.handle('issue_comment', payload)
-
-    handler.assert_not_called()
-
-
-def test_issue_comment_without_pull_request(load_fixture, responses):
-    responses.add(
-        responses.GET,
-        github_url('/repositories/169101701/issues/19'),
-        json=load_fixture('issue-19.json'),
-        status=200
-    )
-    responses.add(
-        responses.GET,
-        github_url('repos/ursa-labs/ursabot/pulls/19'),
-        json={},
-        status=404
-    )
-    responses.add(
-        responses.POST,
-        github_url('/repos/ursa-labs/ursabot/issues/19/comments'),
-        json={}
-    )
-
-    def handler(command, **kwargs):
-        pass
-
-    payload = load_fixture('event-issue-comment-without-pull-request.json')
-    bot = CommentBot(name='ursabot', token='', handler=handler)
-    bot.handle('issue_comment', payload)
-
-    post = responses.calls[2]
-    assert json.loads(post.request.body) == {
-        'body': "The comment bot only listens to pull request comments!"
-    }
-
-
-def test_respond_with_usage(load_fixture, responses):
-    responses.add(
-        responses.GET,
-        github_url('/repositories/169101701/issues/26'),
-        json=load_fixture('issue-26.json'),
-        status=200
-    )
-    responses.add(
-        responses.GET,
-        github_url('/repos/ursa-labs/ursabot/pulls/26'),
-        json=load_fixture('pull-request-26.json'),
-        status=200
-    )
-    responses.add(
-        responses.GET,
-        github_url('/repos/ursa-labs/ursabot/issues/comments/480243811'),
-        json=load_fixture('issue-comment-480243811.json')
-    )
-    responses.add(
-        responses.POST,
-        github_url('/repos/ursa-labs/ursabot/issues/26/comments'),
-        json={}
-    )
-
-    def handler(command, **kwargs):
-        raise CommandError('test-usage')
-
-    payload = load_fixture('event-issue-comment-with-empty-command.json')
-    bot = CommentBot(name='ursabot', token='', handler=handler)
-    bot.handle('issue_comment', payload)
-
-    post = responses.calls[3]
-    assert json.loads(post.request.body) == {'body': '```\ntest-usage\n```'}
-
-
-@pytest.mark.parametrize(('command', 'reaction'), [
-    ('@ursabot build', '+1'),
-    ('@ursabot listen', '-1'),
-])
-def test_issue_comment_with_commands(load_fixture, responses, command,
-                                     reaction):
-    responses.add(
-        responses.GET,
-        github_url('/repositories/169101701/issues/26'),
-        json=load_fixture('issue-26.json'),
-        status=200
-    )
-    responses.add(
-        responses.GET,
-        github_url('/repos/ursa-labs/ursabot/pulls/26'),
-        json=load_fixture('pull-request-26.json'),
-        status=200
-    )
-    responses.add(
-        responses.GET,
-        github_url('/repos/ursa-labs/ursabot/issues/comments/480248726'),
-        json=load_fixture('issue-comment-480248726.json')
-    )
-    responses.add(
-        responses.POST,
-        github_url(
-            '/repos/ursa-labs/ursabot/issues/comments/480248726/reactions'
-        ),
-        json={}
-    )
-
-    def handler(command, **kwargs):
-        if command == 'build':
-            return True
-        else:
-            raise ValueError('Only `build` command is supported.')
-
-    payload = load_fixture('event-issue-comment-build-command.json')
-    payload["comment"]["body"] = command
-
-    bot = CommentBot(name='ursabot', token='', handler=handler)
-    bot.handle('issue_comment', payload)
-
-    post = responses.calls[3]
-    assert json.loads(post.request.body) == {'content': reaction}
diff --git a/dev/archery/archery/tests/test_cli.py b/dev/archery/archery/tests/test_cli.py
deleted file mode 100644
index b3199df..0000000
--- a/dev/archery/archery/tests/test_cli.py
+++ /dev/null
@@ -1,162 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from unittest.mock import patch
-
-from click.testing import CliRunner
-
-from archery.cli import archery
-from archery.docker import DockerCompose
-
-
-@patch.object(DockerCompose, "pull")
-@patch.object(DockerCompose, "build")
-@patch.object(DockerCompose, "run")
-def test_docker_run_with_custom_command(run, build, pull):
-    # with custom command
-    args = ["docker", "run", "ubuntu-cpp", "bash"]
-    result = CliRunner().invoke(archery, args)
-    assert result.exit_code == 0
-    pull.assert_called_once_with(
-        "ubuntu-cpp", pull_leaf=True, using_docker=False
-    )
-    build.assert_called_once_with(
-        "ubuntu-cpp",
-        use_cache=True,
-        use_leaf_cache=True,
-        using_docker=False,
-        using_buildx=False
-    )
-    run.assert_called_once_with(
-        "ubuntu-cpp",
-        command="bash",
-        env={},
-        user=None,
-        using_docker=False,
-        volumes=(),
-    )
-
-
-@patch.object(DockerCompose, "pull")
-@patch.object(DockerCompose, "build")
-@patch.object(DockerCompose, "run")
-def test_docker_run_options(run, build, pull):
-    # environment variables and volumes
-    args = [
-        "docker",
-        "run",
-        "-e",
-        "ARROW_GANDIVA=OFF",
-        "-e",
-        "ARROW_FLIGHT=ON",
-        "--volume",
-        "./build:/build",
-        "-v",
-        "./ccache:/ccache:delegated",
-        "-u",
-        "root",
-        "ubuntu-cpp",
-    ]
-    result = CliRunner().invoke(archery, args)
-    assert result.exit_code == 0
-    pull.assert_called_once_with(
-        "ubuntu-cpp", pull_leaf=True, using_docker=False
-    )
-    build.assert_called_once_with(
-        "ubuntu-cpp",
-        use_cache=True,
-        use_leaf_cache=True,
-        using_docker=False,
-        using_buildx=False
-    )
-    run.assert_called_once_with(
-        "ubuntu-cpp",
-        command=None,
-        env={"ARROW_GANDIVA": "OFF", "ARROW_FLIGHT": "ON"},
-        user="root",
-        using_docker=False,
-        volumes=(
-            "./build:/build",
-            "./ccache:/ccache:delegated",
-        ),
-    )
-
-
-@patch.object(DockerCompose, "run")
-def test_docker_run_without_pulling_or_building(run):
-    args = ["docker", "run", "--no-pull", "--no-build", "ubuntu-cpp"]
-    result = CliRunner().invoke(archery, args)
-    assert result.exit_code == 0
-    run.assert_called_once_with(
-        "ubuntu-cpp",
-        command=None,
-        env={},
-        user=None,
-        using_docker=False,
-        volumes=(),
-    )
-
-
-@patch.object(DockerCompose, "pull")
-@patch.object(DockerCompose, "build")
-def test_docker_run_only_pulling_and_building(build, pull):
-    args = ["docker", "run", "ubuntu-cpp", "--build-only"]
-    result = CliRunner().invoke(archery, args)
-    assert result.exit_code == 0
-    pull.assert_called_once_with(
-        "ubuntu-cpp", pull_leaf=True, using_docker=False
-    )
-    build.assert_called_once_with(
-        "ubuntu-cpp",
-        use_cache=True,
-        use_leaf_cache=True,
-        using_docker=False,
-        using_buildx=False
-    )
-
-
-@patch.object(DockerCompose, "build")
-@patch.object(DockerCompose, "run")
-def test_docker_run_without_build_cache(run, build):
-    args = [
-        "docker",
-        "run",
-        "--no-pull",
-        "--force-build",
-        "--user",
-        "me",
-        "--no-cache",
-        "--no-leaf-cache",
-        "ubuntu-cpp",
-    ]
-    result = CliRunner().invoke(archery, args)
-    assert result.exit_code == 0
-    build.assert_called_once_with(
-        "ubuntu-cpp",
-        use_cache=False,
-        use_leaf_cache=False,
-        using_docker=False,
-        using_buildx=False
-    )
-    run.assert_called_once_with(
-        "ubuntu-cpp",
-        command=None,
-        env={},
-        user="me",
-        using_docker=False,
-        volumes=(),
-    )
diff --git a/dev/archery/archery/tests/test_docker.py b/dev/archery/archery/tests/test_docker.py
deleted file mode 100644
index 09dcd27..0000000
--- a/dev/archery/archery/tests/test_docker.py
+++ /dev/null
@@ -1,512 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import collections
-import os
-import re
-import subprocess
-from unittest import mock
-
-import pytest
-
-from archery.docker import DockerCompose
-from archery.testing import assert_subprocess_calls, override_env, PartialEnv
-
-
-missing_service_compose_yml = """
-version: '3.5'
-
-x-hierarchy:
-  - foo:
-    - sub-foo:
-      - sub-sub-foo
-      - another-sub-sub-foo
-  - bar:
-    - sub-bar
-  - baz
-
-services:
-  foo:
-    image: org/foo
-  sub-sub-foo:
-    image: org/sub-sub-foo
-  another-sub-sub-foo:
-    image: org/another-sub-sub-foo
-  bar:
-    image: org/bar
-  sub-bar:
-    image: org/sub-bar
-  baz:
-    image: org/baz
-"""
-
-missing_node_compose_yml = """
-version: '3.5'
-
-x-hierarchy:
-  - foo:
-    - sub-foo:
-      - sub-sub-foo
-      - another-sub-sub-foo
-  - bar
-  - baz
-
-services:
-  foo:
-    image: org/foo
-  sub-foo:
-    image: org/sub-foo
-  sub-sub-foo:
-    image: org/sub-foo-foo
-  another-sub-sub-foo:
-    image: org/another-sub-sub-foo
-  bar:
-    image: org/bar
-  sub-bar:
-    image: org/sub-bar
-  baz:
-    image: org/baz
-"""
-
-ok_compose_yml = """
-version: '3.5'
-
-x-hierarchy:
-  - foo:
-    - sub-foo:
-      - sub-sub-foo
-      - another-sub-sub-foo
-  - bar:
-    - sub-bar
-  - baz
-
-services:
-  foo:
-    image: org/foo
-  sub-foo:
-    image: org/sub-foo
-  sub-sub-foo:
-    image: org/sub-sub-foo
-  another-sub-sub-foo:
-    image: org/another-sub-sub-foo
-  bar:
-    image: org/bar
-  sub-bar:
-    image: org/sub-bar
-  baz:
-    image: org/baz
-"""
-
-arrow_compose_yml = """
-version: '3.5'
-
-x-with-gpus:
-  - ubuntu-cuda
-
-x-hierarchy:
-  - conda-cpp:
-    - conda-python:
-      - conda-python-pandas
-      - conda-python-dask
-  - ubuntu-cpp:
-    - ubuntu-cpp-cmake32
-    - ubuntu-c-glib:
-      - ubuntu-ruby
-  - ubuntu-cuda
-
-services:
-  conda-cpp:
-    image: org/conda-cpp
-    build:
-      context: .
-      dockerfile: ci/docker/conda-cpp.dockerfile
-  conda-python:
-    image: org/conda-python
-    build:
-      context: .
-      dockerfile: ci/docker/conda-cpp.dockerfile
-      args:
-        python: 3.6
-  conda-python-pandas:
-    image: org/conda-python-pandas
-    build:
-      context: .
-      dockerfile: ci/docker/conda-python-pandas.dockerfile
-  conda-python-dask:
-    image: org/conda-python-dask
-  ubuntu-cpp:
-    image: org/ubuntu-cpp
-    build:
-      context: .
-      dockerfile: ci/docker/ubuntu-${UBUNTU}-cpp.dockerfile
-  ubuntu-cpp-cmake32:
-    image: org/ubuntu-cpp-cmake32
-  ubuntu-c-glib:
-    image: org/ubuntu-c-glib
-  ubuntu-ruby:
-    image: org/ubuntu-ruby
-  ubuntu-cuda:
-    image: org/ubuntu-cuda
-    environment:
-      CUDA_ENV: 1
-      OTHER_ENV: 2
-    volumes:
-     - /host:/container
-    command: /bin/bash -c "echo 1 > /tmp/dummy && cat /tmp/dummy"
-"""
-
-arrow_compose_env = {
-    'UBUNTU': '20.04',  # overridden below
-    'PYTHON': '3.6',
-    'PANDAS': 'latest',
-    'DASK': 'latest',  # overridden below
-}
-
-
-def create_config(directory, yml_content, env_content=None):
-    env_path = directory / '.env'
-    config_path = directory / 'docker-compose.yml'
-
-    with config_path.open('w') as fp:
-        fp.write(yml_content)
-
-    if env_content is not None:
-        with env_path.open('w') as fp:
-            for k, v in env_content.items():
-                fp.write("{}={}\n".format(k, v))
-
-    return config_path
-
-
-def format_run(args):
-    cmd = ["run", "--rm"]
-    if isinstance(args, str):
-        return " ".join(cmd + [args])
-    else:
-        return cmd + args
-
-
-@pytest.fixture
-def arrow_compose_path(tmpdir):
-    return create_config(tmpdir, arrow_compose_yml, arrow_compose_env)
-
-
-def test_config_validation(tmpdir):
-    config_path = create_config(tmpdir, missing_service_compose_yml)
-    msg = "`sub-foo` is defined in `x-hierarchy` bot not in `services`"
-    with pytest.raises(ValueError, match=msg):
-        DockerCompose(config_path)
-
-    config_path = create_config(tmpdir, missing_node_compose_yml)
-    msg = "`sub-bar` is defined in `services` but not in `x-hierarchy`"
-    with pytest.raises(ValueError, match=msg):
-        DockerCompose(config_path)
-
-    config_path = create_config(tmpdir, ok_compose_yml)
-    DockerCompose(config_path)  # no issue
-
-
-def assert_docker_calls(compose, expected_args):
-    base_command = ['docker']
-    expected_commands = []
-    for args in expected_args:
-        if isinstance(args, str):
-            args = re.split(r"\s", args)
-        expected_commands.append(base_command + args)
-    return assert_subprocess_calls(expected_commands, check=True)
-
-
-def assert_compose_calls(compose, expected_args, env=mock.ANY):
-    base_command = ['docker-compose', '--file', str(compose.config.path)]
-    expected_commands = []
-    for args in expected_args:
-        if isinstance(args, str):
-            args = re.split(r"\s", args)
-        expected_commands.append(base_command + args)
-    return assert_subprocess_calls(expected_commands, check=True, env=env)
-
-
-def test_arrow_example_validation_passes(arrow_compose_path):
-    DockerCompose(arrow_compose_path)
-
-
-def test_compose_default_params_and_env(arrow_compose_path):
-    compose = DockerCompose(arrow_compose_path, params=dict(
-        UBUNTU='18.04',
-        DASK='master'
-    ))
-    assert compose.config.dotenv == arrow_compose_env
-    assert compose.config.params == {
-        'UBUNTU': '18.04',
-        'DASK': 'master',
-    }
-
-
-def test_forwarding_env_variables(arrow_compose_path):
-    expected_calls = [
-        "pull --ignore-pull-failures conda-cpp",
-        "build conda-cpp",
-    ]
-    expected_env = PartialEnv(
-        MY_CUSTOM_VAR_A='a',
-        MY_CUSTOM_VAR_B='b'
-    )
-    with override_env({'MY_CUSTOM_VAR_A': 'a', 'MY_CUSTOM_VAR_B': 'b'}):
-        compose = DockerCompose(arrow_compose_path)
-        with assert_compose_calls(compose, expected_calls, env=expected_env):
-            assert os.environ['MY_CUSTOM_VAR_A'] == 'a'
-            assert os.environ['MY_CUSTOM_VAR_B'] == 'b'
-            compose.pull('conda-cpp')
-            compose.build('conda-cpp')
-
-
-def test_compose_pull(arrow_compose_path):
-    compose = DockerCompose(arrow_compose_path)
-
-    expected_calls = [
-        "pull --ignore-pull-failures conda-cpp",
-    ]
-    with assert_compose_calls(compose, expected_calls):
-        compose.clear_pull_memory()
-        compose.pull('conda-cpp')
-
-    expected_calls = [
-        "pull --ignore-pull-failures conda-cpp",
-        "pull --ignore-pull-failures conda-python",
-        "pull --ignore-pull-failures conda-python-pandas"
-    ]
-    with assert_compose_calls(compose, expected_calls):
-        compose.clear_pull_memory()
-        compose.pull('conda-python-pandas')
-
-    expected_calls = [
-        "pull --ignore-pull-failures conda-cpp",
-        "pull --ignore-pull-failures conda-python",
-    ]
-    with assert_compose_calls(compose, expected_calls):
-        compose.clear_pull_memory()
-        compose.pull('conda-python-pandas', pull_leaf=False)
-
-
-def test_compose_pull_params(arrow_compose_path):
-    expected_calls = [
-        "pull --ignore-pull-failures conda-cpp",
-        "pull --ignore-pull-failures conda-python",
-    ]
-    compose = DockerCompose(arrow_compose_path, params=dict(UBUNTU='18.04'))
-    expected_env = PartialEnv(PYTHON='3.6', PANDAS='latest')
-    with assert_compose_calls(compose, expected_calls, env=expected_env):
-        compose.clear_pull_memory()
-        compose.pull('conda-python-pandas', pull_leaf=False)
-
-
-def test_compose_build(arrow_compose_path):
-    compose = DockerCompose(arrow_compose_path)
-
-    expected_calls = [
-        "build conda-cpp",
-    ]
-    with assert_compose_calls(compose, expected_calls):
-        compose.build('conda-cpp')
-
-    expected_calls = [
-        "build --no-cache conda-cpp"
-    ]
-    with assert_compose_calls(compose, expected_calls):
-        compose.build('conda-cpp', use_cache=False)
-
-    expected_calls = [
-        "build conda-cpp",
-        "build conda-python",
-        "build conda-python-pandas"
-    ]
-    with assert_compose_calls(compose, expected_calls):
-        compose.build('conda-python-pandas')
-
-    expected_calls = [
-        "build --no-cache conda-cpp",
-        "build --no-cache conda-python",
-        "build --no-cache conda-python-pandas",
-    ]
-    with assert_compose_calls(compose, expected_calls):
-        compose.build('conda-python-pandas', use_cache=False)
-
-    expected_calls = [
-        "build conda-cpp",
-        "build conda-python",
-        "build --no-cache conda-python-pandas",
-    ]
-    with assert_compose_calls(compose, expected_calls):
-        compose.build('conda-python-pandas', use_cache=True,
-                      use_leaf_cache=False)
-
-
-@mock.patch.dict(os.environ, {"BUILDKIT_INLINE_CACHE": "1"})
-def test_compose_buildkit_inline_cache(arrow_compose_path):
-    compose = DockerCompose(arrow_compose_path)
-
-    expected_calls = [
-        "build --build-arg BUILDKIT_INLINE_CACHE=1 conda-cpp",
-    ]
-    with assert_compose_calls(compose, expected_calls):
-        compose.build('conda-cpp')
-
-
-def test_compose_build_params(arrow_compose_path):
-    expected_calls = [
-        "build ubuntu-cpp",
-    ]
-
-    compose = DockerCompose(arrow_compose_path, params=dict(UBUNTU='18.04'))
-    expected_env = PartialEnv(UBUNTU="18.04")
-    with assert_compose_calls(compose, expected_calls, env=expected_env):
-        compose.build('ubuntu-cpp')
-
-    compose = DockerCompose(arrow_compose_path, params=dict(UBUNTU='16.04'))
-    expected_env = PartialEnv(UBUNTU="16.04")
-    with assert_compose_calls(compose, expected_calls, env=expected_env):
-        compose.build('ubuntu-cpp')
-
-    expected_calls = [
-        "build --no-cache conda-cpp",
-        "build --no-cache conda-python",
-        "build --no-cache conda-python-pandas",
-    ]
-    compose = DockerCompose(arrow_compose_path, params=dict(UBUNTU='18.04'))
-    expected_env = PartialEnv(PYTHON='3.6', PANDAS='latest')
-    with assert_compose_calls(compose, expected_calls, env=expected_env):
-        compose.build('conda-python-pandas', use_cache=False)
-
-
-def test_compose_run(arrow_compose_path):
-    expected_calls = [
-        format_run("conda-cpp"),
-    ]
-    compose = DockerCompose(arrow_compose_path)
-    with assert_compose_calls(compose, expected_calls):
-        compose.run('conda-cpp')
-
-    expected_calls = [
-        format_run("conda-python")
-    ]
-    expected_env = PartialEnv(PYTHON='3.6')
-    with assert_compose_calls(compose, expected_calls, env=expected_env):
-        compose.run('conda-python')
-
-    compose = DockerCompose(arrow_compose_path, params=dict(PYTHON='3.8'))
-    expected_env = PartialEnv(PYTHON='3.8')
-    with assert_compose_calls(compose, expected_calls, env=expected_env):
-        compose.run('conda-python')
-
-    compose = DockerCompose(arrow_compose_path, params=dict(PYTHON='3.8'))
-    for command in ["bash", "echo 1"]:
-        expected_calls = [
-            format_run(["conda-python", command]),
-        ]
-        expected_env = PartialEnv(PYTHON='3.8')
-        with assert_compose_calls(compose, expected_calls, env=expected_env):
-            compose.run('conda-python', command)
-
-    expected_calls = [
-        (
-            format_run("-e CONTAINER_ENV_VAR_A=a -e CONTAINER_ENV_VAR_B=b "
-                       "conda-python")
-        )
-    ]
-    compose = DockerCompose(arrow_compose_path)
-    expected_env = PartialEnv(PYTHON='3.6')
-    with assert_compose_calls(compose, expected_calls, env=expected_env):
-        env = collections.OrderedDict([
-            ("CONTAINER_ENV_VAR_A", "a"),
-            ("CONTAINER_ENV_VAR_B", "b")
-        ])
-        compose.run('conda-python', env=env)
-
-    expected_calls = [
-        (
-            format_run("--volume /host/build:/build --volume "
-                       "/host/ccache:/ccache:delegated conda-python")
-        )
-    ]
-    compose = DockerCompose(arrow_compose_path)
-    with assert_compose_calls(compose, expected_calls):
-        volumes = ("/host/build:/build", "/host/ccache:/ccache:delegated")
-        compose.run('conda-python', volumes=volumes)
-
-
-def test_compose_push(arrow_compose_path):
-    compose = DockerCompose(arrow_compose_path, params=dict(PYTHON='3.8'))
-    expected_env = PartialEnv(PYTHON="3.8")
-    expected_calls = [
-        mock.call(["docker", "login", "-u", "user", "-p", "pass"], check=True),
-    ]
-    for image in ["conda-cpp", "conda-python", "conda-python-pandas"]:
-        expected_calls.append(
-            mock.call(["docker-compose", "--file", str(compose.config.path),
-                       "push", image], check=True, env=expected_env)
-        )
-    with assert_subprocess_calls(expected_calls):
-        compose.push('conda-python-pandas', user='user', password='pass')
-
-
-def test_compose_error(arrow_compose_path):
-    compose = DockerCompose(arrow_compose_path, params=dict(
-        PYTHON='3.8',
-        PANDAS='master'
-    ))
-
-    error = subprocess.CalledProcessError(99, [])
-    with mock.patch('subprocess.run', side_effect=error):
-        with pytest.raises(RuntimeError) as exc:
-            compose.run('conda-cpp')
-
-    exception_message = str(exc.value)
-    assert "exited with a non-zero exit code 99" in exception_message
-    assert "PANDAS: latest" in exception_message
-    assert "export PANDAS=master" in exception_message
-
-
-def test_image_with_gpu(arrow_compose_path):
-    compose = DockerCompose(arrow_compose_path)
-
-    expected_calls = [
-        [
-            "run", "--rm", "--gpus", "all",
-            "-e", "CUDA_ENV=1",
-            "-e", "OTHER_ENV=2",
-            "-v", "/host:/container:rw",
-            "org/ubuntu-cuda",
-            '/bin/bash -c "echo 1 > /tmp/dummy && cat /tmp/dummy"'
-        ]
-    ]
-    with assert_docker_calls(compose, expected_calls):
-        compose.run('ubuntu-cuda')
-
-
-def test_listing_images(arrow_compose_path):
-    compose = DockerCompose(arrow_compose_path)
-    assert sorted(compose.images()) == [
-        'conda-cpp',
-        'conda-python',
-        'conda-python-dask',
-        'conda-python-pandas',
-        'ubuntu-c-glib',
-        'ubuntu-cpp',
-        'ubuntu-cpp-cmake32',
-        'ubuntu-cuda',
-        'ubuntu-ruby',
-    ]
diff --git a/dev/archery/archery/tests/test_release.py b/dev/archery/archery/tests/test_release.py
deleted file mode 100644
index 75aac89..0000000
--- a/dev/archery/archery/tests/test_release.py
+++ /dev/null
@@ -1,333 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import pytest
-
-from archery.release import (
-    Release, MajorRelease, MinorRelease, PatchRelease,
-    Jira, Version, Issue, CommitTitle, Commit
-)
-from archery.testing import DotDict
-
-
-# subset of issues per revision
-_issues = {
-    "1.0.1": [
-        Issue("ARROW-9684", type="Bug", summary="[C++] Title"),
-        Issue("ARROW-9667", type="New Feature", summary="[Crossbow] Title"),
-        Issue("ARROW-9659", type="Bug", summary="[C++] Title"),
-        Issue("ARROW-9644", type="Bug", summary="[C++][Dataset] Title"),
-        Issue("ARROW-9643", type="Bug", summary="[C++] Title"),
-        Issue("ARROW-9609", type="Bug", summary="[C++] Title"),
-        Issue("ARROW-9606", type="Bug", summary="[C++][Dataset] Title")
-    ],
-    "1.0.0": [
-        Issue("ARROW-300", type="New Feature", summary="[Format] Title"),
-        Issue("ARROW-4427", type="Task", summary="[Doc] Title"),
-        Issue("ARROW-5035", type="Improvement", summary="[C#] Title"),
-        Issue("ARROW-8473", type="Bug", summary="[Rust] Title"),
-        Issue("ARROW-8472", type="Bug", summary="[Go][Integration] Title"),
-        Issue("ARROW-8471", type="Bug", summary="[C++][Integration] Title"),
-        Issue("ARROW-8974", type="Improvement", summary="[C++] Title"),
-        Issue("ARROW-8973", type="New Feature", summary="[Java] Title")
-    ],
-    "0.17.1": [
-        Issue("ARROW-8684", type="Bug", summary="[Python] Title"),
-        Issue("ARROW-8657", type="Bug", summary="[C++][Parquet] Title"),
-        Issue("ARROW-8641", type="Bug", summary="[Python] Title"),
-        Issue("ARROW-8609", type="Bug", summary="[C++] Title"),
-    ],
-    "0.17.0": [
-        Issue("ARROW-2882", type="New Feature", summary="[C++][Python] Title"),
-        Issue("ARROW-2587", type="Bug", summary="[Python] Title"),
-        Issue("ARROW-2447", type="Improvement", summary="[C++] Title"),
-        Issue("ARROW-2255", type="Bug", summary="[Integration] Title"),
-        Issue("ARROW-1907", type="Bug", summary="[C++/Python] Title"),
-        Issue("ARROW-1636", type="New Feature", summary="[Format] Title")
-    ]
-}
-
-
-class FakeJira(Jira):
-
-    def __init__(self):
-        pass
-
-    def project_versions(self, project='ARROW'):
-        return [
-            Version.parse("3.0.0", released=False),
-            Version.parse("2.0.0", released=False),
-            Version.parse("1.1.0", released=False),
-            Version.parse("1.0.1", released=False),
-            Version.parse("1.0.0", released=True),
-            Version.parse("0.17.1", released=True),
-            Version.parse("0.17.0", released=True),
-            Version.parse("0.16.0", released=True),
-            Version.parse("0.15.2", released=True),
-            Version.parse("0.15.1", released=True),
-            Version.parse("0.15.0", released=True),
-        ]
-
-    def project_issues(self, version, project='ARROW'):
-        return _issues[str(version)]
-
-
-@pytest.fixture
-def fake_jira():
-    return FakeJira()
-
-
-def test_version(fake_jira):
-    v = Version.parse("1.2.5")
-    assert str(v) == "1.2.5"
-    assert v.major == 1
-    assert v.minor == 2
-    assert v.patch == 5
-    assert v.released is False
-    assert v.release_date is None
-
-    v = Version.parse("1.0.0", released=True, release_date="2020-01-01")
-    assert str(v) == "1.0.0"
-    assert v.major == 1
-    assert v.minor == 0
-    assert v.patch == 0
-    assert v.released is True
-    assert v.release_date == "2020-01-01"
-
-
-def test_issue(fake_jira):
-    i = Issue("ARROW-1234", type='Bug', summary="title")
-    assert i.key == "ARROW-1234"
-    assert i.type == "Bug"
-    assert i.summary == "title"
-    assert i.project == "ARROW"
-    assert i.number == 1234
-
-    i = Issue("PARQUET-1111", type='Improvement', summary="another title")
-    assert i.key == "PARQUET-1111"
-    assert i.type == "Improvement"
-    assert i.summary == "another title"
-    assert i.project == "PARQUET"
-    assert i.number == 1111
-
-    fake_jira_issue = DotDict({
-        'key': 'ARROW-2222',
-        'fields': {
-            'issuetype': {
-                'name': 'Feature'
-            },
-            'summary': 'Issue title'
-        }
-    })
-    i = Issue.from_jira(fake_jira_issue)
-    assert i.key == "ARROW-2222"
-    assert i.type == "Feature"
-    assert i.summary == "Issue title"
-    assert i.project == "ARROW"
-    assert i.number == 2222
-
-
-def test_commit_title():
-    t = CommitTitle.parse(
-        "ARROW-9598: [C++][Parquet] Fix writing nullable structs"
-    )
-    assert t.project == "ARROW"
-    assert t.issue == "ARROW-9598"
-    assert t.components == ["C++", "Parquet"]
-    assert t.summary == "Fix writing nullable structs"
-
-    t = CommitTitle.parse(
-        "ARROW-8002: [C++][Dataset][R] Support partitioned dataset writing"
-    )
-    assert t.project == "ARROW"
-    assert t.issue == "ARROW-8002"
-    assert t.components == ["C++", "Dataset", "R"]
-    assert t.summary == "Support partitioned dataset writing"
-
-    t = CommitTitle.parse(
-        "ARROW-9600: [Rust][Arrow] pin older version of proc-macro2 during "
-        "build"
-    )
-    assert t.project == "ARROW"
-    assert t.issue == "ARROW-9600"
-    assert t.components == ["Rust", "Arrow"]
-    assert t.summary == "pin older version of proc-macro2 during build"
-
-    t = CommitTitle.parse("[Release] Update versions for 1.0.0")
-    assert t.project is None
-    assert t.issue is None
-    assert t.components == ["Release"]
-    assert t.summary == "Update versions for 1.0.0"
-
-    t = CommitTitle.parse("[Python][Doc] Fix rst role dataset.rst (#7725)")
-    assert t.project is None
-    assert t.issue is None
-    assert t.components == ["Python", "Doc"]
-    assert t.summary == "Fix rst role dataset.rst (#7725)"
-
-    t = CommitTitle.parse(
-        "PARQUET-1882: [C++] Buffered Reads should allow for 0 length"
-    )
-    assert t.project == 'PARQUET'
-    assert t.issue == 'PARQUET-1882'
-    assert t.components == ["C++"]
-    assert t.summary == "Buffered Reads should allow for 0 length"
-
-    t = CommitTitle.parse(
-        "ARROW-9340 [R] Use CRAN version of decor package "
-        "\nsomething else\n"
-        "\nwhich should be truncated"
-    )
-    assert t.project == 'ARROW'
-    assert t.issue == 'ARROW-9340'
-    assert t.components == ["R"]
-    assert t.summary == "Use CRAN version of decor package "
-
-
-def test_release_basics(fake_jira):
-    r = Release.from_jira("1.0.0", jira=fake_jira)
-    assert isinstance(r, MajorRelease)
-    assert r.is_released is True
-    assert r.branch == 'master'
-    assert r.tag == 'apache-arrow-1.0.0'
-
-    r = Release.from_jira("1.1.0", jira=fake_jira)
-    assert isinstance(r, MinorRelease)
-    assert r.is_released is False
-    assert r.branch == 'maint-1.x.x'
-    assert r.tag == 'apache-arrow-1.1.0'
-
-    # minor releases before 1.0 are treated as major releases
-    r = Release.from_jira("0.17.0", jira=fake_jira)
-    assert isinstance(r, MajorRelease)
-    assert r.is_released is True
-    assert r.branch == 'master'
-    assert r.tag == 'apache-arrow-0.17.0'
-
-    r = Release.from_jira("0.17.1", jira=fake_jira)
-    assert isinstance(r, PatchRelease)
-    assert r.is_released is True
-    assert r.branch == 'maint-0.17.x'
-    assert r.tag == 'apache-arrow-0.17.1'
-
-
-def test_previous_and_next_release(fake_jira):
-    r = Release.from_jira("3.0.0", jira=fake_jira)
-    assert isinstance(r.previous, MajorRelease)
-    assert r.previous.version == Version.parse("2.0.0")
-    with pytest.raises(ValueError, match="There is no upcoming release set"):
-        assert r.next
-
-    r = Release.from_jira("2.0.0", jira=fake_jira)
-    assert isinstance(r.previous, MajorRelease)
-    assert isinstance(r.next, MajorRelease)
-    assert r.previous.version == Version.parse("1.0.0")
-    assert r.next.version == Version.parse("3.0.0")
-
-    r = Release.from_jira("1.1.0", jira=fake_jira)
-    assert isinstance(r.previous, MajorRelease)
-    assert isinstance(r.next, MajorRelease)
-    assert r.previous.version == Version.parse("1.0.0")
-    assert r.next.version == Version.parse("2.0.0")
-
-    r = Release.from_jira("1.0.0", jira=fake_jira)
-    assert isinstance(r.next, MajorRelease)
-    assert isinstance(r.previous, MajorRelease)
-    assert r.previous.version == Version.parse("0.17.0")
-    assert r.next.version == Version.parse("2.0.0")
-
-    r = Release.from_jira("0.17.0", jira=fake_jira)
-    assert isinstance(r.previous, MajorRelease)
-    assert r.previous.version == Version.parse("0.16.0")
-
-    r = Release.from_jira("0.15.2", jira=fake_jira)
-    assert isinstance(r.previous, PatchRelease)
-    assert isinstance(r.next, MajorRelease)
-    assert r.previous.version == Version.parse("0.15.1")
-    assert r.next.version == Version.parse("0.16.0")
-
-    r = Release.from_jira("0.15.1", jira=fake_jira)
-    assert isinstance(r.previous, MajorRelease)
-    assert isinstance(r.next, PatchRelease)
-    assert r.previous.version == Version.parse("0.15.0")
-    assert r.next.version == Version.parse("0.15.2")
-
-
-def test_release_issues(fake_jira):
-    # major release issues
-    r = Release.from_jira("1.0.0", jira=fake_jira)
-    assert r.issues.keys() == set([
-        "ARROW-300",
-        "ARROW-4427",
-        "ARROW-5035",
-        "ARROW-8473",
-        "ARROW-8472",
-        "ARROW-8471",
-        "ARROW-8974",
-        "ARROW-8973"
-    ])
-    # minor release issues
-    r = Release.from_jira("0.17.0", jira=fake_jira)
-    assert r.issues.keys() == set([
-        "ARROW-2882",
-        "ARROW-2587",
-        "ARROW-2447",
-        "ARROW-2255",
-        "ARROW-1907",
-        "ARROW-1636",
-    ])
-    # patch release issues
-    r = Release.from_jira("1.0.1", jira=fake_jira)
-    assert r.issues.keys() == set([
-        "ARROW-9684",
-        "ARROW-9667",
-        "ARROW-9659",
-        "ARROW-9644",
-        "ARROW-9643",
-        "ARROW-9609",
-        "ARROW-9606"
-    ])
-
-
-@pytest.mark.parametrize(('version', 'ncommits'), [
-    ("1.0.0", 771),
-    ("0.17.1", 27),
-    ("0.17.0", 569),
-    ("0.15.1", 41)
-])
-def test_release_commits(fake_jira, version, ncommits):
-    r = Release.from_jira(version, jira=fake_jira)
-    assert len(r.commits) == ncommits
-    for c in r.commits:
-        assert isinstance(c, Commit)
-        assert isinstance(c.title, CommitTitle)
-        assert c.url.endswith(c.hexsha)
-
-
-def test_maintenance_patch_selection(fake_jira):
-    r = Release.from_jira("0.17.1", jira=fake_jira)
-
-    shas_to_pick = [
-        c.hexsha for c in r.commits_to_pick(exclude_already_applied=False)
-    ]
-    expected = [
-        '8939b4bd446ee406d5225c79d563a27d30fd7d6d',
-        'bcef6c95a324417e85e0140f9745d342cd8784b3',
-        '6002ec388840de5622e39af85abdc57a2cccc9b2',
-        '9123dadfd123bca7af4eaa9455f5b0d1ca8b929d',
-    ]
-    assert shas_to_pick == expected
diff --git a/dev/archery/archery/tests/test_testing.py b/dev/archery/archery/tests/test_testing.py
deleted file mode 100644
index 117b928..0000000
--- a/dev/archery/archery/tests/test_testing.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import subprocess
-
-import pytest
-
-from archery.testing import PartialEnv, assert_subprocess_calls
-
-
-def test_partial_env():
-    assert PartialEnv(a=1, b=2) == {'a': 1, 'b': 2, 'c': 3}
-    assert PartialEnv(a=1) == {'a': 1, 'b': 2, 'c': 3}
-    assert PartialEnv(a=1, b=2) == {'a': 1, 'b': 2}
-    assert PartialEnv(a=1, b=2) != {'b': 2, 'c': 3}
-    assert PartialEnv(a=1, b=2) != {'a': 1, 'c': 3}
-
-
-def test_assert_subprocess_calls():
-    expected_calls = [
-        "echo Hello",
-        ["echo", "World"]
-    ]
-    with assert_subprocess_calls(expected_calls):
-        subprocess.run(['echo', 'Hello'])
-        subprocess.run(['echo', 'World'])
-
-    expected_env = PartialEnv(
-        CUSTOM_ENV_A='a',
-        CUSTOM_ENV_C='c'
-    )
-    with assert_subprocess_calls(expected_calls, env=expected_env):
-        env = {
-            'CUSTOM_ENV_A': 'a',
-            'CUSTOM_ENV_B': 'b',
-            'CUSTOM_ENV_C': 'c'
-        }
-        subprocess.run(['echo', 'Hello'], env=env)
-        subprocess.run(['echo', 'World'], env=env)
-
-    with pytest.raises(AssertionError):
-        with assert_subprocess_calls(expected_calls, env=expected_env):
-            env = {
-                'CUSTOM_ENV_B': 'b',
-                'CUSTOM_ENV_C': 'c'
-            }
-            subprocess.run(['echo', 'Hello'], env=env)
-            subprocess.run(['echo', 'World'], env=env)
diff --git a/dev/archery/archery/utils/__init__.py b/dev/archery/archery/utils/__init__.py
deleted file mode 100644
index 13a8339..0000000
--- a/dev/archery/archery/utils/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
diff --git a/dev/archery/archery/utils/cache.py b/dev/archery/archery/utils/cache.py
deleted file mode 100644
index d92c5f3..0000000
--- a/dev/archery/archery/utils/cache.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from pathlib import Path
-import os
-from urllib.request import urlopen
-
-from .logger import logger
-
-ARCHERY_CACHE_DIR = Path.home() / ".cache" / "archery"
-
-
-class Cache:
-    """ Cache stores downloaded objects, notably apache-rat.jar. """
-
-    def __init__(self, path=ARCHERY_CACHE_DIR):
-        self.root = path
-
-        if not path.exists():
-            os.makedirs(path)
-
-    def key_path(self, key):
-        """ Return the full path of a key. """
-        return self.root/key
-
-    def get(self, key):
-        """ Return the full path of a key if cached, None otherwise. """
-        path = self.key_path(key)
-        return path if path.exists() else None
-
-    def delete(self, key):
-        """ Remove a key (and the file) from the cache. """
-        path = self.get(key)
-        if path:
-            path.unlink()
-
-    def get_or_insert(self, key, create):
-        """
-        Get or Insert a key from the cache. If the key is not found, the
-        `create` closure will be evaluated.
-
-        The `create` closure takes a single parameter, the path where the
-        object should be store. The file should only be created upon success.
-        """
-        path = self.key_path(key)
-
-        if not path.exists():
-            create(path)
-
-        return path
-
-    def get_or_insert_from_url(self, key, url):
-        """
-        Get or Insert a key from the cache. If the key is not found, the file
-        is downloaded from `url`.
-        """
-        def download(path):
-            """ Tiny wrapper that download a file and save as key. """
-            logger.debug("Downloading {} as {}".format(url, path))
-            conn = urlopen(url)
-            # Ensure the download is completed before writing to disks.
-            content = conn.read()
-            with open(path, "wb") as path_fd:
-                path_fd.write(content)
-
-        return self.get_or_insert(key, download)
diff --git a/dev/archery/archery/utils/cmake.py b/dev/archery/archery/utils/cmake.py
deleted file mode 100644
index f93895b..0000000
--- a/dev/archery/archery/utils/cmake.py
+++ /dev/null
@@ -1,215 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import os
-import re
-from shutil import rmtree, which
-
-from .command import Command, default_bin
-
-
-class CMake(Command):
-    def __init__(self, cmake_bin=None):
-        self.bin = default_bin(cmake_bin, "cmake")
-
-    @staticmethod
-    def default_generator():
-        """ Infer default generator.
-
-        Gives precedence to ninja if there exists an executable named `ninja`
-        in the search path.
-        """
-        found_ninja = which("ninja")
-        return "Ninja" if found_ninja else "Unix Makefiles"
-
-
-cmake = CMake()
-
-
-class CMakeDefinition:
-    """ CMakeDefinition captures the cmake invocation arguments.
-
-    It allows creating build directories with the same definition, e.g.
-    ```
-    build_1 = cmake_def.build("/tmp/build-1")
-    build_2 = cmake_def.build("/tmp/build-2")
-
-    ...
-
-    build1.all()
-    build2.all()
-    """
-
-    def __init__(self, source, build_type="release", generator=None,
-                 definitions=None, env=None):
-        """ Initialize a CMakeDefinition
-
-        Parameters
-        ----------
-        source : str
-                 Source directory where the top-level CMakeLists.txt is
-                 located. This is usually the root of the project.
-        generator : str, optional
-        definitions: list(str), optional
-        env : dict(str,str), optional
-              Environment to use when invoking cmake. This can be required to
-              work around cmake deficiencies, e.g. CC and CXX.
-        """
-        self.source = os.path.abspath(source)
-        self.build_type = build_type
-        self.generator = generator if generator else cmake.default_generator()
-        self.definitions = definitions if definitions else []
-        self.env = env
-
-    @property
-    def arguments(self):
-        """" Return the arguments to cmake invocation. """
-        arguments = [
-            "-G{}".format(self.generator),
-        ] + self.definitions + [
-            self.source
-        ]
-        return arguments
-
-    def build(self, build_dir, force=False, cmd_kwargs=None, **kwargs):
-        """ Invoke cmake into a build directory.
-
-        Parameters
-        ----------
-        build_dir : str
-                    Directory in which the CMake build will be instantiated.
-        force : bool
-                If the build folder exists, delete it before. Otherwise if it's
-                present, an error will be returned.
-        """
-        if os.path.exists(build_dir):
-            # Extra safety to ensure we're deleting a build folder.
-            if not CMakeBuild.is_build_dir(build_dir):
-                raise FileExistsError(
-                    "{} is not a cmake build".format(build_dir)
-                )
-            if not force:
-                raise FileExistsError(
-                    "{} exists use force=True".format(build_dir)
-                )
-            rmtree(build_dir)
-
-        os.mkdir(build_dir)
-
-        cmd_kwargs = cmd_kwargs if cmd_kwargs else {}
-        cmake(*self.arguments, cwd=build_dir, env=self.env, **cmd_kwargs)
-        return CMakeBuild(build_dir, self.build_type, definition=self,
-                          **kwargs)
-
-    def __repr__(self):
-        return "CMakeDefinition[source={}]".format(self.source)
-
-
-CMAKE_BUILD_TYPE_RE = re.compile("CMAKE_BUILD_TYPE:STRING=([a-zA-Z]+)")
-
-
-class CMakeBuild(CMake):
-    """ CMakeBuild represents a build directory initialized by cmake.
-
-    The build instance can be used to build/test/install. It alleviates the
-    user to know which generator is used.
-    """
-
-    def __init__(self, build_dir, build_type, definition=None):
-        """ Initialize a CMakeBuild.
-
-        The caller must ensure that cmake was invoked in the build directory.
-
-        Parameters
-        ----------
-        definition : CMakeDefinition
-                     The definition to build from.
-        build_dir : str
-                    The build directory to setup into.
-        """
-        assert CMakeBuild.is_build_dir(build_dir)
-        super().__init__()
-        self.build_dir = os.path.abspath(build_dir)
-        self.build_type = build_type
-        self.definition = definition
-
-    @property
-    def binaries_dir(self):
-        return os.path.join(self.build_dir, self.build_type)
-
-    def run(self, *argv, verbose=False, **kwargs):
-        cmake_args = ["--build", self.build_dir, "--"]
-        extra = []
-        if verbose:
-            extra.append("-v" if self.bin.endswith("ninja") else "VERBOSE=1")
-        # Commands must be ran under the build directory
-        return super().run(*cmake_args, *extra,
-                           *argv, **kwargs, cwd=self.build_dir)
-
-    def all(self):
-        return self.run("all")
-
-    def clean(self):
-        return self.run("clean")
-
-    def install(self):
-        return self.run("install")
-
-    def test(self):
-        return self.run("test")
-
-    @staticmethod
-    def is_build_dir(path):
-        """ Indicate if a path is CMake build directory.
-
-        This method only checks for the existence of paths and does not do any
-        validation whatsoever.
-        """
-        cmake_cache = os.path.join(path, "CMakeCache.txt")
-        cmake_files = os.path.join(path, "CMakeFiles")
-        return os.path.exists(cmake_cache) and os.path.exists(cmake_files)
-
-    @staticmethod
-    def from_path(path):
-        """ Instantiate a CMakeBuild from a path.
-
-        This is used to recover from an existing physical directory (created
-        with or without CMakeBuild).
-
-        Note that this method is not idempotent as the original definition will
-        be lost. Only build_type is recovered.
-        """
-        if not CMakeBuild.is_build_dir(path):
-            raise ValueError("Not a valid CMakeBuild path: {}".format(path))
-
-        build_type = None
-        # Infer build_type by looking at CMakeCache.txt and looking for a magic
-        # definition
-        cmake_cache_path = os.path.join(path, "CMakeCache.txt")
-        with open(cmake_cache_path, "r") as cmake_cache:
-            candidates = CMAKE_BUILD_TYPE_RE.findall(cmake_cache.read())
-            build_type = candidates[0].lower() if candidates else "release"
-
-        return CMakeBuild(path, build_type)
-
-    def __repr__(self):
-        return ("CMakeBuild["
-                "build = {},"
-                "build_type = {},"
-                "definition = {}]".format(self.build_dir,
-                                          self.build_type,
-                                          self.definition))
diff --git a/dev/archery/archery/utils/command.py b/dev/archery/archery/utils/command.py
deleted file mode 100644
index 84d2842..0000000
--- a/dev/archery/archery/utils/command.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import os
-import shlex
-import shutil
-import subprocess
-
-from .logger import logger, ctx
-
-
-def default_bin(name, default):
-    assert(default)
-    env_name = "ARCHERY_{0}_BIN".format(default.upper())
-    return name if name else os.environ.get(env_name, default)
-
-
-# Decorator running a command and returning stdout
-class capture_stdout:
-    def __init__(self, strip=False, listify=False):
-        self.strip = strip
-        self.listify = listify
-
-    def __call__(self, f):
-        def strip_it(x):
-            return x.strip() if self.strip else x
-
-        def list_it(x):
-            return x.decode('utf-8').splitlines() if self.listify else x
-
-        def wrapper(*argv, **kwargs):
-            # Ensure stdout is captured
-            kwargs["stdout"] = subprocess.PIPE
-            return list_it(strip_it(f(*argv, **kwargs).stdout))
-        return wrapper
-
-
-class Command:
-    """ A runnable command.
-
-    Class inheriting from the Command class must provide the bin
-    property/attribute.
-    """
-
-    def __init__(self, bin):
-        self.bin = bin
-
-    def run(self, *argv, **kwargs):
-        assert hasattr(self, "bin")
-        invocation = shlex.split(self.bin)
-        invocation.extend(argv)
-
-        for key in ["stdout", "stderr"]:
-            # Preserve caller intention, otherwise silence
-            if key not in kwargs and ctx.quiet:
-                kwargs[key] = subprocess.PIPE
-
-        # Prefer safe by default
-        if "check" not in kwargs:
-            kwargs["check"] = True
-
-        logger.debug("Executing `{}`".format(invocation))
-        return subprocess.run(invocation, **kwargs)
-
-    @property
-    def available(self):
-        """ Indicate if the command binary is found in PATH. """
-        binary = shlex.split(self.bin)[0]
-        return shutil.which(binary) is not None
-
-    def __call__(self, *argv, **kwargs):
-        return self.run(*argv, **kwargs)
-
-
-class CommandStackMixin:
-    def run(self, *argv, **kwargs):
-        stacked_args = self.argv + argv
-        return super(CommandStackMixin, self).run(*stacked_args, **kwargs)
-
-
-class Bash(Command):
-    def __init__(self, bash_bin=None):
-        self.bin = default_bin(bash_bin, "bash")
diff --git a/dev/archery/archery/utils/git.py b/dev/archery/archery/utils/git.py
deleted file mode 100644
index 798bc5d..0000000
--- a/dev/archery/archery/utils/git.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from .command import Command, capture_stdout, default_bin
-from ..compat import _stringify_path
-
-
-# Decorator prepending argv with the git sub-command found with the method
-# name.
-def git_cmd(fn):
-    # function name is the subcommand
-    sub_cmd = fn.__name__.replace("_", "-")
-
-    def wrapper(self, *argv, **kwargs):
-        return fn(self, sub_cmd, *argv, **kwargs)
-    return wrapper
-
-
-class Git(Command):
-    def __init__(self, git_bin=None):
-        self.bin = default_bin(git_bin, "git")
-
-    def run_cmd(self, cmd, *argv, git_dir=None, **kwargs):
-        """ Inject flags before sub-command in argv. """
-        opts = []
-        if git_dir is not None:
-            opts.extend(["-C", _stringify_path(git_dir)])
-
-        return self.run(*opts, cmd, *argv, **kwargs)
-
-    @capture_stdout(strip=False)
-    @git_cmd
-    def archive(self, *argv, **kwargs):
-        return self.run_cmd(*argv, **kwargs)
-
-    @git_cmd
-    def clone(self, *argv, **kwargs):
-        return self.run_cmd(*argv, **kwargs)
-
-    @git_cmd
-    def fetch(self, *argv, **kwargs):
-        return self.run_cmd(*argv, **kwargs)
-
-    @git_cmd
-    def checkout(self, *argv, **kwargs):
-        return self.run_cmd(*argv, **kwargs)
-
-    def dirty(self, **kwargs):
-        return len(self.status("--short", **kwargs)) > 0
-
-    @git_cmd
-    def log(self, *argv, **kwargs):
-        return self.run_cmd(*argv, **kwargs)
-
-    @capture_stdout(strip=True, listify=True)
-    @git_cmd
-    def ls_files(self, *argv, listify=False, **kwargs):
-        stdout = self.run_cmd(*argv, **kwargs)
-        return stdout
-
-    @capture_stdout(strip=True)
-    @git_cmd
-    def rev_parse(self, *argv, **kwargs):
-        return self.run_cmd(*argv, **kwargs)
-
-    @capture_stdout(strip=True)
-    @git_cmd
-    def status(self, *argv, **kwargs):
-        return self.run_cmd(*argv, **kwargs)
-
-    @capture_stdout(strip=True)
-    def head(self, **kwargs):
-        """ Return commit pointed by HEAD. """
-        return self.rev_parse("HEAD", **kwargs)
-
-    @capture_stdout(strip=True)
-    def current_branch(self, **kwargs):
-        return self.rev_parse("--abbrev-ref", "HEAD", **kwargs)
-
-    def repository_root(self, git_dir=None, **kwargs):
-        """ Locates the repository's root path from a subdirectory. """
-        stdout = self.rev_parse("--show-toplevel", git_dir=git_dir, **kwargs)
-        return stdout.decode('utf-8')
-
-
-git = Git()
diff --git a/dev/archery/archery/utils/lint.py b/dev/archery/archery/utils/lint.py
deleted file mode 100644
index e81d6ac..0000000
--- a/dev/archery/archery/utils/lint.py
+++ /dev/null
@@ -1,383 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import gzip
-import os
-from pathlib import Path
-
-import click
-
-from .command import Bash, Command, default_bin
-from .cmake import CMake
-from .git import git
-from .logger import logger
-from ..lang.cpp import CppCMakeDefinition, CppConfiguration
-from ..lang.rust import Cargo
-from ..lang.python import Autopep8, Flake8, NumpyDoc
-from .rat import Rat, exclusion_from_globs
-from .tmpdir import tmpdir
-
-
-class LintValidationException(Exception):
-    pass
-
-
-class LintResult:
-    def __init__(self, success, reason=None):
-        self.success = success
-
-    def ok(self):
-        if not self.success:
-            raise LintValidationException
-
-    @staticmethod
-    def from_cmd(command_result):
-        return LintResult(command_result.returncode == 0)
-
-
-def cpp_linter(src, build_dir, clang_format=True, cpplint=True,
-               clang_tidy=False, iwyu=False, iwyu_all=False,
-               fix=False):
-    """ Run clang-format, cpplint and clang-tidy on cpp/ codebase. """
-    logger.info("Running C++ linters")
-
-    cmake = CMake()
-    if not cmake.available:
-        logger.error("cpp linter requested but cmake binary not found.")
-        return
-
-    # A cmake build directory is required to populate `compile_commands.json`
-    # which in turn is required by clang-tidy. It also provides a convenient
-    # way to hide clang-format/clang-tidy invocation via the Generate
-    # (ninja/make) targets.
-
-    # ARROW_LINT_ONLY exits early but ignore building compile_command.json
-    lint_only = not (iwyu or clang_tidy)
-    cmake_args = {"with_python": False, "with_lint_only": lint_only}
-    cmake_def = CppCMakeDefinition(src.cpp, CppConfiguration(**cmake_args))
-
-    build = cmake_def.build(build_dir)
-    if clang_format:
-        target = "format" if fix else "check-format"
-        yield LintResult.from_cmd(build.run(target, check=False))
-
-    if cpplint:
-        yield LintResult.from_cmd(build.run("lint", check=False))
-        yield LintResult.from_cmd(build.run("lint_cpp_cli", check=False))
-
-    if clang_tidy:
-        yield LintResult.from_cmd(build.run("check-clang-tidy", check=False))
-
-    if iwyu:
-        if iwyu_all:
-            iwyu_cmd = "iwyu-all"
-        else:
-            iwyu_cmd = "iwyu"
-        yield LintResult.from_cmd(build.run(iwyu_cmd, check=False))
-
-
-class CMakeFormat(Command):
-    def __init__(self, cmake_format_bin):
-        self.bin = cmake_format_bin
-
-
-def cmake_linter(src, fix=False):
-    """ Run cmake-format.py on all CMakeFiles.txt """
-    logger.info("Running cmake-format linters")
-
-    if not fix:
-        logger.warn("run-cmake-format modifies files, regardless of --fix")
-
-    arrow_cmake_format = os.path.join(src.path, "run-cmake-format.py")
-    cmake_format = CMakeFormat(cmake_format_bin=arrow_cmake_format)
-    yield LintResult.from_cmd(cmake_format("--check"))
-
-
-def python_linter(src, fix=False):
-    """Run Python linters on python/pyarrow, python/examples, setup.py
-    and dev/. """
-    setup_py = os.path.join(src.python, "setup.py")
-    setup_cfg = os.path.join(src.python, "setup.cfg")
-
-    logger.info("Running Python formatter (autopep8)")
-
-    autopep8 = Autopep8()
-    if not autopep8.available:
-        logger.error(
-            "Python formatter requested but autopep8 binary not found. "
-            "Please run `pip install -r dev/archery/requirements-lint.txt`")
-        return
-
-    # Gather files for autopep8
-    patterns = ["python/pyarrow/**/*.py",
-                "python/pyarrow/**/*.pyx",
-                "python/pyarrow/**/*.pxd",
-                "python/pyarrow/**/*.pxi",
-                "python/examples/**/*.py",
-                "dev/archery/**/*.py",
-                ]
-    files = [setup_py]
-    for pattern in patterns:
-        files += list(map(str, Path(src.path).glob(pattern)))
-
-    args = ['--global-config', setup_cfg, '--ignore-local-config']
-    if fix:
-        args += ['-j0', '--in-place']
-        args += sorted(files)
-        yield LintResult.from_cmd(autopep8(*args))
-    else:
-        # XXX `-j0` doesn't work well with `--exit-code`, so instead
-        # we capture the diff and check whether it's empty
-        # (https://github.com/hhatto/autopep8/issues/543)
-        args += ['-j0', '--diff']
-        args += sorted(files)
-        diff = autopep8.run_captured(*args)
-        if diff:
-            print(diff.decode('utf8'))
-            yield LintResult(success=False)
-        else:
-            yield LintResult(success=True)
-
-    # Run flake8 after autopep8 (the latter may have modified some files)
-    logger.info("Running Python linter (flake8)")
-
-    flake8 = Flake8()
-    if not flake8.available:
-        logger.error(
-            "Python linter requested but flake8 binary not found. "
-            "Please run `pip install -r dev/archery/requirements-lint.txt`")
-        return
-
-    flake8_exclude = ['.venv*']
-
-    yield LintResult.from_cmd(
-        flake8("--extend-exclude=" + ','.join(flake8_exclude),
-               setup_py, src.pyarrow, os.path.join(src.python, "examples"),
-               src.dev, check=False))
-    config = os.path.join(src.python, ".flake8.cython")
-    yield LintResult.from_cmd(
-        flake8("--config=" + config, src.pyarrow, check=False))
-
-
-def python_numpydoc(symbols=None, allow_rules=None, disallow_rules=None):
-    """Run numpydoc linter on python.
-
-    Pyarrow must be available for import.
-    """
-    logger.info("Running Python docstring linters")
-    # by default try to run on all pyarrow package
-    symbols = symbols or {
-        'pyarrow',
-        'pyarrow.compute',
-        'pyarrow.csv',
-        'pyarrow.dataset',
-        'pyarrow.feather',
-        'pyarrow.flight',
-        'pyarrow.fs',
-        'pyarrow.gandiva',
-        'pyarrow.ipc',
-        'pyarrow.json',
-        'pyarrow.orc',
-        'pyarrow.parquet',
-        'pyarrow.plasma',
-        'pyarrow.types',
-    }
-    try:
-        numpydoc = NumpyDoc(symbols)
-    except RuntimeError as e:
-        logger.error(str(e))
-        yield LintResult(success=False)
-        return
-
-    results = numpydoc.validate(
-        # limit the validation scope to the pyarrow package
-        from_package='pyarrow',
-        allow_rules=allow_rules,
-        disallow_rules=disallow_rules
-    )
-
-    if len(results) == 0:
-        yield LintResult(success=True)
-        return
-
-    number_of_violations = 0
-    for obj, result in results:
-        errors = result['errors']
-
-        # inspect doesn't play nice with cython generated source code,
-        # to use a hacky way to represent a proper __qualname__
-        doc = getattr(obj, '__doc__', '')
-        name = getattr(obj, '__name__', '')
-        qualname = getattr(obj, '__qualname__', '')
-        module = getattr(obj, '__module__', '')
-        instance = getattr(obj, '__self__', '')
-        if instance:
-            klass = instance.__class__.__name__
-        else:
-            klass = ''
-
-        try:
-            cython_signature = doc.splitlines()[0]
-        except Exception:
-            cython_signature = ''
-
-        desc = '.'.join(filter(None, [module, klass, qualname or name]))
-
-        click.echo()
-        click.echo(click.style(desc, bold=True, fg='yellow'))
-        if cython_signature:
-            qualname_with_signature = '.'.join([module, cython_signature])
-            click.echo(
-                click.style(
-                    '-> {}'.format(qualname_with_signature),
-                    fg='yellow'
-                )
-            )
-
-        for error in errors:
-            number_of_violations += 1
-            click.echo('{}: {}'.format(*error))
-
-    msg = 'Total number of docstring violations: {}'.format(
-        number_of_violations
-    )
-    click.echo()
-    click.echo(click.style(msg, fg='red'))
-
-    yield LintResult(success=False)
-
-
-def rat_linter(src, root):
-    """Run apache-rat license linter."""
-    logger.info("Running apache-rat linter")
-
-    exclusion = exclusion_from_globs(
-        os.path.join(src.dev, "release", "rat_exclude_files.txt"))
-
-    # Creates a git-archive of ArrowSources, apache-rat expects a gzip
-    # compressed tar archive.
-    archive_path = os.path.join(root, "apache-arrow.tar.gz")
-    src.archive(archive_path, compressor=gzip.compress)
-    report = Rat().report(archive_path)
-
-    violations = list(report.validate(exclusion=exclusion))
-    for violation in violations:
-        print("apache-rat license violation: {}".format(violation))
-
-    yield LintResult(len(violations) == 0)
-
-
-def r_linter(src):
-    """Run R linter."""
-    logger.info("Running R linter")
-    r_lint_sh = os.path.join(src.r, "lint.sh")
-    yield LintResult.from_cmd(Bash().run(r_lint_sh, check=False))
-
-
-def rust_linter(src):
-    """Run Rust linter."""
-    logger.info("Running Rust linter")
-    cargo = Cargo()
-
-    if not cargo.available:
-        logger.error("Rust linter requested but cargo executable not found.")
-        return
-
-    yield LintResult.from_cmd(cargo.run("+stable", "fmt", "--all", "--",
-                                        "--check", cwd=src.rust,
-                                        check=False))
-
-
-class Hadolint(Command):
-    def __init__(self, hadolint_bin=None):
-        self.bin = default_bin(hadolint_bin, "hadolint")
-
-
-def is_docker_image(path):
-    dirname = os.path.dirname(path)
-    filename = os.path.basename(path)
-
-    excluded = dirname.startswith(
-        "dev") or dirname.startswith("python/manylinux")
-
-    return filename.startswith("Dockerfile") and not excluded
-
-
-def docker_linter(src):
-    """Run Hadolint docker linter."""
-    logger.info("Running Docker linter")
-
-    hadolint = Hadolint()
-
-    if not hadolint.available:
-        logger.error(
-            "hadolint linter requested but hadolint binary not found.")
-        return
-
-    for path in git.ls_files(git_dir=src.path):
-        if is_docker_image(path):
-            yield LintResult.from_cmd(hadolint.run(path, check=False,
-                                                   cwd=src.path))
-
-
-def linter(src, fix=False, *, clang_format=False, cpplint=False,
-           clang_tidy=False, iwyu=False, iwyu_all=False,
-           python=False, numpydoc=False, cmake_format=False, rat=False,
-           r=False, rust=False, docker=False):
-    """Run all linters."""
-    with tmpdir(prefix="arrow-lint-") as root:
-        build_dir = os.path.join(root, "cpp-build")
-
-        # Linters yield LintResult without raising exceptions on failure.
-        # This allows running all linters in one pass and exposing all
-        # errors to the user.
-        results = []
-
-        if clang_format or cpplint or clang_tidy or iwyu:
-            results.extend(cpp_linter(src, build_dir,
-                                      clang_format=clang_format,
-                                      cpplint=cpplint,
-                                      clang_tidy=clang_tidy,
-                                      iwyu=iwyu,
-                                      iwyu_all=iwyu_all,
-                                      fix=fix))
-
-        if python:
-            results.extend(python_linter(src, fix=fix))
-
-        if numpydoc:
-            results.extend(python_numpydoc())
-
-        if cmake_format:
-            results.extend(cmake_linter(src, fix=fix))
-
-        if rat:
-            results.extend(rat_linter(src, root))
-
-        if r:
-            results.extend(r_linter(src))
-
-        if rust:
-            results.extend(rust_linter(src))
-
-        if docker:
-            results.extend(docker_linter(src))
-
-        # Raise error if one linter failed, ensuring calling code can exit with
-        # non-zero.
-        for result in results:
-            result.ok()
diff --git a/dev/archery/archery/utils/logger.py b/dev/archery/archery/utils/logger.py
deleted file mode 100644
index 9d0feda..0000000
--- a/dev/archery/archery/utils/logger.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import logging
-
-""" Global logger. """
-logger = logging.getLogger("archery")
-
-
-class LoggingContext:
-    def __init__(self, quiet=False):
-        self.quiet = quiet
-
-
-ctx = LoggingContext()
diff --git a/dev/archery/archery/utils/rat.py b/dev/archery/archery/utils/rat.py
deleted file mode 100644
index e7fe19a..0000000
--- a/dev/archery/archery/utils/rat.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import fnmatch
-import re
-from xml.etree import ElementTree
-
-from ..lang.java import Jar
-from .cache import Cache
-from .command import capture_stdout
-
-RAT_VERSION = 0.13
-RAT_JAR_FILENAME = "apache-rat-{}.jar".format(RAT_VERSION)
-RAT_URL_ = "https://repo1.maven.org/maven2/org/apache/rat/apache-rat"
-RAT_URL = "/".join([RAT_URL_, str(RAT_VERSION), RAT_JAR_FILENAME])
-
-
-class Rat(Jar):
-    def __init__(self):
-        jar = Cache().get_or_insert_from_url(RAT_JAR_FILENAME, RAT_URL)
-        Jar.__init__(self, jar)
-
-    @capture_stdout(strip=False)
-    def run_report(self, archive_path, **kwargs):
-        return self.run("--xml", archive_path, **kwargs)
-
-    def report(self, archive_path, **kwargs):
-        return RatReport(self.run_report(archive_path, **kwargs))
-
-
-def exclusion_from_globs(exclusions_path):
-    with open(exclusions_path, 'r') as exclusions_fd:
-        exclusions = [e.strip() for e in exclusions_fd]
-        return lambda path: any([fnmatch.fnmatch(path, e) for e in exclusions])
-
-
-class RatReport:
-    def __init__(self, xml):
-        self.xml = xml
-        self.tree = ElementTree.fromstring(xml)
-
-    def __repr__(self):
-        return "RatReport({})".format(self.xml)
-
-    def validate(self, exclusion=None):
-        for r in self.tree.findall('resource'):
-            approvals = r.findall('license-approval')
-            if not approvals or approvals[0].attrib['name'] == 'true':
-                continue
-
-            clean_name = re.sub('^[^/]+/', '', r.attrib['name'])
-
-            if exclusion and exclusion(clean_name):
-                continue
-
-            yield clean_name
diff --git a/dev/archery/archery/utils/report.py b/dev/archery/archery/utils/report.py
deleted file mode 100644
index 6c7587d..0000000
--- a/dev/archery/archery/utils/report.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from abc import ABCMeta, abstractmethod
-import datetime
-
-import jinja2
-
-
-def markdown_escape(s):
-    for char in ('*', '#', '_', '~', '`', '>'):
-        s = s.replace(char, '\\' + char)
-    return s
-
-
-class Report(metaclass=ABCMeta):
-
-    def __init__(self, **kwargs):
-        for field in self.fields:
-            if field not in kwargs:
-                raise ValueError('Missing keyword argument {}'.format(field))
-        self._data = kwargs
-
-    def __getattr__(self, key):
-        return self._data[key]
-
-    @abstractmethod
-    def fields(self):
-        pass
-
-    @property
-    @abstractmethod
-    def templates(self):
-        pass
-
-
-class JinjaReport(Report):
-
-    def __init__(self, **kwargs):
-        self.env = jinja2.Environment(
-            loader=jinja2.PackageLoader('archery', 'templates')
-        )
-        self.env.filters['md'] = markdown_escape
-        self.env.globals['today'] = datetime.date.today
-        super().__init__(**kwargs)
-
-    def render(self, template_name):
-        template_path = self.templates[template_name]
-        template = self.env.get_template(template_path)
-        return template.render(**self._data)
diff --git a/dev/archery/archery/utils/source.py b/dev/archery/archery/utils/source.py
deleted file mode 100644
index 1ae0fe0..0000000
--- a/dev/archery/archery/utils/source.py
+++ /dev/null
@@ -1,205 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import os
-from pathlib import Path
-import subprocess
-
-from .git import git
-
-
-class InvalidArrowSource(Exception):
-    pass
-
-
-class ArrowSources:
-    """ ArrowSources is a companion class representing a directory containing
-    Apache Arrow's sources.
-    """
-    # Note that WORKSPACE is a reserved git revision name by this module to
-    # reference the current git workspace. In other words, this indicates to
-    # ArrowSources.at_revision that no cloning/checkout is required.
-    WORKSPACE = "WORKSPACE"
-
-    def __init__(self, path):
-        """ Initialize an ArrowSources
-
-        The caller must ensure that path is valid arrow source directory (can
-        be checked with ArrowSources.valid)
-
-        Parameters
-        ----------
-        path : src
-        """
-        self.path = Path(path)
-
-    @property
-    def archery(self):
-        """ Returns the archery directory of an Arrow sources. """
-        return self.dev / "archery"
-
-    @property
-    def cpp(self):
-        """ Returns the cpp directory of an Arrow sources. """
-        return self.path / "cpp"
-
-    @property
-    def dev(self):
-        """ Returns the dev directory of an Arrow sources. """
-        return self.path / "dev"
-
-    @property
-    def python(self):
-        """ Returns the python directory of an Arrow sources. """
-        return self.path / "python"
-
-    @property
-    def pyarrow(self):
-        """ Returns the python/pyarrow directory of an Arrow sources. """
-        return self.python / "pyarrow"
-
-    @property
-    def r(self):
-        """ Returns the r directory of an Arrow sources. """
-        return self.path / "r"
-
-    @property
-    def rust(self):
-        """ Returns the rust directory of an Arrow sources. """
-        return self.path / "rust"
-
-    @property
-    def git_backed(self):
-        """ Indicate if the sources are backed by git. """
-        return (self.path / ".git").exists()
-
-    @property
-    def git_dirty(self):
-        """ Indicate if the sources is a dirty git directory. """
-        return self.git_backed and git.dirty(git_dir=self.path)
-
-    def archive(self, path, dereference=False, compressor=None, revision=None):
-        """ Saves a git archive at path. """
-        if not self.git_backed:
-            raise ValueError("{} is not backed by git".format(self))
-
-        rev = revision if revision else "HEAD"
-        archive = git.archive("--prefix=apache-arrow/", rev,
-                              git_dir=self.path)
-
-        # TODO(fsaintjacques): fix dereference for
-
-        if compressor:
-            archive = compressor(archive)
-
-        with open(path, "wb") as archive_fd:
-            archive_fd.write(archive)
-
-    def at_revision(self, revision, clone_dir):
-        """ Return a copy of the current sources for a specified git revision.
-
-        This method may return the current object if no checkout is required.
-        The caller is responsible to remove the cloned repository directory.
-
-        The user can use the special WORKSPACE token to mean the current git
-        workspace (no checkout performed).
-
-        The second value of the returned tuple indicates if a clone was
-        performed.
-
-        Parameters
-        ----------
-        revision : str
-                   Revision to checkout sources at.
-        clone_dir : str
-                    Path to checkout the local clone.
-        """
-        if not self.git_backed:
-            raise ValueError("{} is not backed by git".format(self))
-
-        if revision == ArrowSources.WORKSPACE:
-            return self, False
-
-        # A local clone is required to leave the current sources intact such
-        # that builds depending on said sources are not invalidated (or worse
-        # slightly affected when re-invoking the generator).
-        # "--local" only works when dest dir is on same volume of source dir.
-        # "--shared" works even if dest dir is on different volume.
-        git.clone("--shared", self.path, clone_dir)
-
-        # Revision can reference "origin/" (or any remotes) that are not found
-        # in the local clone. Thus, revisions are dereferenced in the source
-        # repository.
-        original_revision = git.rev_parse(revision)
-
-        git.checkout(original_revision, git_dir=clone_dir)
-
-        return ArrowSources(clone_dir), True
-
-    @staticmethod
-    def find(path=None):
-        """ Infer Arrow sources directory from various method.
-
-        The following guesses are done in order until a valid match is found:
-
-        1. Checks the given optional parameter.
-
-        2. Checks if the environment variable `ARROW_SRC` is defined and use
-           this.
-
-        3. Checks if the current working directory (cwd) is an Arrow source
-           directory.
-
-        4. Checks if this file (cli.py) is still in the original source
-           repository. If so, returns the relative path to the source
-           directory.
-        """
-
-        # Explicit via environment
-        env = os.environ.get("ARROW_SRC")
-
-        # Implicit via cwd
-        cwd = Path.cwd()
-
-        # Implicit via current file
-        try:
-            this = Path(__file__).parents[4]
-        except IndexError:
-            this = None
-
-        # Implicit via git repository (if archery is installed system wide)
-        try:
-            repo = git.repository_root(git_dir=cwd)
-        except subprocess.CalledProcessError:
-            # We're not inside a git repository.
-            repo = None
-
-        paths = list(filter(None, [path, env, cwd, this, repo]))
-        for p in paths:
-            try:
-                return ArrowSources(p)
-            except InvalidArrowSource:
-                pass
-
-        searched_paths = "\n".join([" - {}".format(p) for p in paths])
-        raise InvalidArrowSource(
-            "Unable to locate Arrow's source directory. "
-            "Searched paths are:\n{}".format(searched_paths)
-        )
-
-    def __repr__(self):
-        return self.path
diff --git a/dev/archery/archery/utils/tmpdir.py b/dev/archery/archery/utils/tmpdir.py
deleted file mode 100644
index 07d7355..0000000
--- a/dev/archery/archery/utils/tmpdir.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-from contextlib import contextmanager
-from tempfile import mkdtemp, TemporaryDirectory
-
-
-@contextmanager
-def tmpdir(preserve=False, prefix="arrow-archery-"):
-    if preserve:
-        yield mkdtemp(prefix=prefix)
-    else:
-        with TemporaryDirectory(prefix=prefix) as tmp:
-            yield tmp
diff --git a/dev/archery/conftest.py b/dev/archery/conftest.py
deleted file mode 100644
index 06a643b..0000000
--- a/dev/archery/conftest.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import pathlib
-
-import pytest
-
-
-def pytest_addoption(parser):
-    parser.addoption(
-        "--enable-integration",
-        action="store_true",
-        default=False,
-        help="run slow tests"
-    )
-
-
-def pytest_configure(config):
-    config.addinivalue_line(
-        "markers",
-        (
-            "integration: mark test as integration tests involving more "
-            "extensive setup (only used for crossbow at the moment)"
-        )
-    )
-
-
-def pytest_collection_modifyitems(config, items):
-    if config.getoption("--enable-integration"):
-        return
-    marker = pytest.mark.skip(reason="need --enable-integration option to run")
-    for item in items:
-        if "integration" in item.keywords:
-            item.add_marker(marker)
-
-
-@pytest.fixture
-def load_fixture(request):
-    current_test_directory = pathlib.Path(request.node.fspath).parent
-
-    def decoder(path):
-        with path.open('r') as fp:
-            if path.suffix == '.json':
-                import json
-                return json.load(fp)
-            elif path.suffix == '.yaml':
-                import yaml
-                return yaml.load(fp)
-            else:
-                return fp.read()
-
-    def loader(name, decoder=decoder):
-        path = current_test_directory / 'fixtures' / name
-        return decoder(path)
-
-    return loader
diff --git a/dev/archery/generate_files_for_endian_test.sh b/dev/archery/generate_files_for_endian_test.sh
deleted file mode 100755
index 54019ea..0000000
--- a/dev/archery/generate_files_for_endian_test.sh
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/bin/bash
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-# This script generates json and arrow files of each type (e.g. primitive) for integration endian test
-# Usage: generate_files_for_endian_test.sh
-#        ARROW_CPP_EXE_PATH : where Arrow C++ binaries can be found
-#        TMP_DIR            : where files will be generated
-
-set -e
-
-: ${ARROW_CPP_EXE_PATH:=/arrow/cpp/build/debug/}
-: ${TMP_DIR:=/tmp/arrow}
-
-json_dir=$TMP_DIR/arrow.$$
-mkdir -p $json_dir
-
-archery integration --stop-on-error --with-cpp=1 --tempdir=$json_dir
-
-for f in $json_dir/*.json ; do
-    $ARROW_CPP_EXE_PATH/arrow-json-integration-test -mode JSON_TO_ARROW -json $f -arrow ${f%.*}.arrow_file -integration true ;
-done
-for f in $json_dir/*.arrow_file ; do
-    $ARROW_CPP_EXE_PATH/arrow-file-to-stream $f > ${f%.*}.stream;
-done
-for f in $json_dir/*.json ; do
-    gzip $f ;
-done
-echo "The files are under $json_dir"
diff --git a/dev/archery/requirements-lint.txt b/dev/archery/requirements-lint.txt
deleted file mode 100644
index fc7f339..0000000
--- a/dev/archery/requirements-lint.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-autopep8
-flake8
-cmake_format==0.5.2
diff --git a/dev/archery/requirements.txt b/dev/archery/requirements.txt
deleted file mode 100644
index 0e1258a..0000000
--- a/dev/archery/requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-click
-pygithub
-python-dotenv
-ruamel.yaml
diff --git a/dev/archery/setup.py b/dev/archery/setup.py
deleted file mode 100755
index 0537e8b..0000000
--- a/dev/archery/setup.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/usr/bin/env python
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-import functools
-import operator
-import sys
-from setuptools import setup
-
-if sys.version_info < (3, 6):
-    sys.exit('Python < 3.6 is not supported')
-
-# For pathlib.Path compatibility
-jinja_req = 'jinja2>=2.11'
-
-extras = {
-    'benchmark': ['pandas'],
-    'docker': ['ruamel.yaml', 'python-dotenv'],
-    'release': [jinja_req, 'jira', 'semver', 'gitpython'],
-    'crossbow': ['github3.py', jinja_req, 'pygit2', 'ruamel.yaml',
-                 'setuptools_scm'],
-}
-extras['bot'] = extras['crossbow'] + ['pygithub', 'jira']
-extras['all'] = list(set(functools.reduce(operator.add, extras.values())))
-
-setup(
-    name='archery',
-    version="0.1.0",
-    description='Apache Arrow Developers Tools',
-    url='http://github.com/apache/arrow',
-    maintainer='Arrow Developers',
-    maintainer_email='dev@arrow.apache.org',
-    packages=[
-        'archery',
-        'archery.benchmark',
-        'archery.integration',
-        'archery.lang',
-        'archery.utils'
-    ],
-    include_package_data=True,
-    install_requires=['click>=7'],
-    tests_require=['pytest', 'responses'],
-    extras_require=extras,
-    entry_points='''
-        [console_scripts]
-        archery=archery.cli:archery
-    '''
-)
diff --git a/dev/release/rat_exclude_files.txt b/dev/release/rat_exclude_files.txt
index f3eb273..d2b5aad 100644
--- a/dev/release/rat_exclude_files.txt
+++ b/dev/release/rat_exclude_files.txt
@@ -1,94 +1,14 @@
 *.npmrc
 *.gitignore
 .gitmodules
-*_generated.h
-*_generated.js
-*_generated.ts
 *.csv
 *.json
 *.snap
 .github/ISSUE_TEMPLATE/*.md
 .github/pull_request_template.md
-ci/etc/rprofile
-ci/etc/*.patch
-ci/vcpkg/*.patch
 CHANGELOG.md
-dev/requirements*.txt
-dev/archery/MANIFEST.in
-dev/archery/requirements*.txt
-dev/archery/archery/tests/fixtures/*
-dev/archery/archery/crossbow/tests/fixtures/*
-dev/release/rat_exclude_files.txt
-dev/tasks/homebrew-formulae/apache-arrow.rb
-dev/tasks/linux-packages/apache-arrow-apt-source/debian/apache-arrow-apt-source.install
-dev/tasks/linux-packages/apache-arrow-apt-source/debian/compat
-dev/tasks/linux-packages/apache-arrow-apt-source/debian/control
-dev/tasks/linux-packages/apache-arrow-apt-source/debian/rules
-dev/tasks/linux-packages/apache-arrow-apt-source/debian/source/format
-dev/tasks/linux-packages/apache-arrow/debian/compat
-dev/tasks/linux-packages/apache-arrow/debian/control.in
-dev/tasks/linux-packages/apache-arrow/debian/gir1.2-arrow-1.0.install
-dev/tasks/linux-packages/apache-arrow/debian/gir1.2-arrow-cuda-1.0.install
-dev/tasks/linux-packages/apache-arrow/debian/gir1.2-arrow-dataset-1.0.install
-dev/tasks/linux-packages/apache-arrow/debian/gir1.2-gandiva-1.0.install
-dev/tasks/linux-packages/apache-arrow/debian/gir1.2-parquet-1.0.install
-dev/tasks/linux-packages/apache-arrow/debian/gir1.2-plasma-1.0.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-glib-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-glib-doc.doc-base
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-glib-doc.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-glib-doc.links
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-glib400.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-cuda-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-cuda-glib-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-cuda-glib400.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-cuda400.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-dataset-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-dataset-glib-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-dataset-glib-doc.doc-base
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-dataset-glib-doc.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-dataset-glib-doc.links
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-dataset-glib400.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-dataset400.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-flight-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-flight400.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-python-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-python-flight-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-python-flight400.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow-python400.install
-dev/tasks/linux-packages/apache-arrow/debian/libarrow400.install
-dev/tasks/linux-packages/apache-arrow/debian/libgandiva-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libgandiva-glib-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libgandiva-glib-doc.doc-base
-dev/tasks/linux-packages/apache-arrow/debian/libgandiva-glib-doc.install
-dev/tasks/linux-packages/apache-arrow/debian/libgandiva-glib-doc.links
-dev/tasks/linux-packages/apache-arrow/debian/libgandiva-glib400.install
-dev/tasks/linux-packages/apache-arrow/debian/libgandiva400.install
-dev/tasks/linux-packages/apache-arrow/debian/libparquet-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libparquet-glib-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libparquet-glib-doc.doc-base
-dev/tasks/linux-packages/apache-arrow/debian/libparquet-glib-doc.install
-dev/tasks/linux-packages/apache-arrow/debian/libparquet-glib-doc.links
-dev/tasks/linux-packages/apache-arrow/debian/libparquet-glib400.install
-dev/tasks/linux-packages/apache-arrow/debian/libparquet400.install
-dev/tasks/linux-packages/apache-arrow/debian/libplasma-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libplasma-glib-dev.install
-dev/tasks/linux-packages/apache-arrow/debian/libplasma-glib-doc.doc-base
-dev/tasks/linux-packages/apache-arrow/debian/libplasma-glib-doc.install
-dev/tasks/linux-packages/apache-arrow/debian/libplasma-glib-doc.links
-dev/tasks/linux-packages/apache-arrow/debian/libplasma-glib400.install
-dev/tasks/linux-packages/apache-arrow/debian/libplasma400.install
-dev/tasks/linux-packages/apache-arrow/debian/patches/series
-dev/tasks/linux-packages/apache-arrow/debian/plasma-store-server.install
-dev/tasks/linux-packages/apache-arrow/debian/rules
-dev/tasks/linux-packages/apache-arrow/debian/source/format
-dev/tasks/linux-packages/apache-arrow/debian/watch
-dev/tasks/requirements*.txt
-dev/tasks/conda-recipes/*
 pax_global_header
 MANIFEST.in
-__init__.pxd
-__init__.py
 requirements.txt
 *.html
 *.sgml