{"action":"created","comment":{"url":"https://api.github.com/repos/broadinstitute/hellbender/pulls/comments/39894508","id":39894508,"diff_hunk":"@@ -0,0 +1,382 @@\n+package org.broadinstitute.hellbender.engine.dataflow.datasources;\n+\n+import com.google.api.services.storage.Storage;\n+import com.google.cloud.dataflow.sdk.Pipeline;\n+import com.google.cloud.dataflow.sdk.runners.DataflowPipeline;\n+import com.google.cloud.dataflow.sdk.transforms.Create;\n+import com.google.cloud.dataflow.sdk.transforms.ParDo;\n+import com.google.cloud.dataflow.sdk.values.KV;\n+import com.google.cloud.dataflow.sdk.values.PCollection;\n+import com.google.cloud.genomics.dataflow.readers.bam.BAMIO;\n+import com.google.cloud.genomics.dataflow.utils.GCSOptions;\n+import com.google.cloud.genomics.utils.GenomicsFactory;\n+import com.google.common.base.Stopwatch;\n+import htsjdk.samtools.SAMRecord;\n+import htsjdk.samtools.SAMRecordIterator;\n+import htsjdk.samtools.SamReader;\n+import htsjdk.samtools.ValidationStringency;\n+import htsjdk.samtools.util.Locatable;\n+import org.apache.logging.log4j.LogManager;\n+import org.apache.logging.log4j.Logger;\n+import org.broadinstitute.hellbender.engine.ReadsDataSource;\n+import org.broadinstitute.hellbender.engine.dataflow.DoFnWLog;\n+import org.broadinstitute.hellbender.utils.SimpleInterval;\n+import org.broadinstitute.hellbender.utils.dataflow.BucketUtils;\n+import org.broadinstitute.hellbender.utils.read.GATKRead;\n+import org.broadinstitute.hellbender.utils.read.SAMRecordToGATKReadAdapter;\n+\n+import java.io.File;\n+import java.time.Duration;\n+import java.time.Instant;\n+import java.util.ArrayList;\n+import java.util.Collections;\n+import java.util.Date;\n+import java.util.HashMap;\n+import java.util.Iterator;\n+import java.util.List;\n+import java.util.concurrent.TimeUnit;\n+import java.util.stream.Collectors;\n+\n+/**\n+ * see ShardedReadsDatasource.get\n+ */\n+public final class ShardedReadsDatasource {\n+    private final static Logger logger = LogManager.getLogger(ShardedReadsDatasource.class);\n+\n+    /**\n+     * Create a {@link PCollection< KV <String,Iterable<GATKRead>>>} containing all the reads starting in the given intervals and their mates,\n+     * grouped by the shard they start in.\n+     * The shards are strings as per SimpleInterval's ToString syntax.\n+     * Reads that are malformed or unmapped are ignored.\n+     *\n+     * Note: for performance reasons, this will give you SAMRecords that have no header. You have to put it back in.\n+     *\n+     * @param bam the file to open, on GCS or local disk.\n+     * @param intervals the subset of the file you want returned.\n+     * @param readShardSize shards of this size are assigned to individual machines (streamed in from GCS; larger is better).\n+     * @param  outputShardSize reads over this many bases are output at a time (held all together in RAM; too large and you'll go boom).\n+     */\n+    public static PCollection<KV<String,Iterable<GATKRead>>> get(String bam, List<SimpleInterval> intervals, int readShardSize, int outputShardSize, Pipeline pipeline, GenomicsFactory.OfflineAuth auth) {\n+        final boolean cloudStorageUrl = BucketUtils.isCloudStorageUrl(bam);\n+        final boolean hadoopUrl = BucketUtils.isHadoopUrl(bam);\n+\n+        List<SimpleInterval> shards = cutToShards(intervals, readShardSize);\n+        // so if there are empty ranges, they are spread evenly across the workers.\n+        Collections.shuffle(shards);\n+        logger.info(\"Input range sharded into \"+shards.size()+\" intervals.\");\n+        List<KV<String,Void>> intervalKeys;\n+        intervalKeys = shards.stream().map(s-> KV.<String,Void>of(s.toString(), null)).collect(Collectors.<KV<String,Void>>toList());\n+\n+        return pipeline\n+            // ship shards to workers\n+            .apply(Create.<SimpleInterval>of(shards)).setName(\"shards\")\n+                // for each shard, load the corresponding reads\n+            .apply(ParDo.named(\"getGroupedReadPCollection\").of(new DoFnWLog<SimpleInterval, KV<String, Iterable<GATKRead>>>(\"getGroupedReadPCollection\") {\n+                private static final long serialVersionUID = 1L;\n+                private transient Storage.Objects storageClient;\n+                private transient Stopwatch querying;\n+                private transient Stopwatch loading;\n+                private transient Stopwatch choosing;\n+                private transient Stopwatch shipping;\n+                private long readsEmitted = 0;\n+                Date started;\n+\n+                @Override\n+                public void startBundle(Context c) throws Exception {\n+                    super.startBundle(c);\n+                    if (cloudStorageUrl) {\n+                        storageClient = GCSOptions.Methods.createStorageClient(c.getPipelineOptions().as(GCSOptions.class), auth);\n+                    }\n+                    started = new Date();\n+                    readsEmitted = 0;\n+                    querying = Stopwatch.createUnstarted();\n+                    loading = Stopwatch.createUnstarted();\n+                    choosing = Stopwatch.createUnstarted();\n+                    shipping = Stopwatch.createUnstarted();\n+                }\n+\n+                @Override\n+                public void finishBundle(Context c) throws Exception {\n+                    super.finishBundle(c);\n+                    final Instant instant = new Date().toInstant();\n+                    long ms = Duration.between(started.toInstant(), instant).toMillis();\n+                    String q = querying.elapsed(TimeUnit.MILLISECONDS) + \" ms\";\n+                    String l = loading.elapsed(TimeUnit.MILLISECONDS) + \" ms\";\n+                    String ch = choosing.elapsed(TimeUnit.MILLISECONDS) + \" ms\";\n+                    String s = shipping.elapsed(TimeUnit.MILLISECONDS) + \" ms\";\n+                    bunny.stepEnd(\"Bundle sent \" + readsEmitted + \" reads in \" + ms + \" ms. Breakdown: querying \" + q + \" loading \" + l + \" choosing \" + ch + \" shipping \" + s);\n+                }\n+\n+                @Override\n+                public void processElement(ProcessContext c) throws Exception {\n+                    int rejected = 0;\n+                    int accepted = 0;\n+                    Stopwatch processing = Stopwatch.createStarted();\n+\n+                    Stopwatch kving = Stopwatch.createUnstarted();\n+                    Stopwatch outputting = Stopwatch.createUnstarted();\n+\n+                    Stopwatch readingInput = Stopwatch.createStarted();\n+                    SimpleInterval i = c.element();\n+                    readingInput.stop();\n+                    Stopwatch init = Stopwatch.createStarted();\n+                    ArrayList<SimpleInterval> is = new ArrayList<>();\n+                    is.add(i);\n+\n+                    Stopwatch cutShards = Stopwatch.createStarted();\n+                    List<SimpleInterval> shards = cutToShards(is, outputShardSize);\n+                    cutShards.stop();\n+\n+                    int currentShardIndex = 0;\n+                    SimpleInterval currentShard = shards.get(currentShardIndex);\n+                    List<GATKRead> ret = new ArrayList<GATKRead>();\n+                    init.stop();\n+\n+                    if (!cloudStorageUrl && !hadoopUrl) {\n+                        // local file, use setIntervalsForTraversal\n+                        querying.start();\n+                        ReadsDataSource rds = new ReadsDataSource(new File(bam));\n+                        rds.setIntervalsForTraversal(is);\n+                        querying.stop();\n+                        choosing.start();\n+                        for (GATKRead r : rds) {\n+                            if (!accept(r, i)) {\n+                                rejected++;\n+                                continue;\n+                            }\n+                            while (currentShard.getEnd() < r.getStart()) {\n+                                // move to the next shard.\n+                                if (!ret.isEmpty()) {\n+                                    accepted += ret.size();\n+                                    choosing.stop();\n+                                    shipping.start();\n+                                    kving.start();\n+                                    KV<String, Iterable<GATKRead>> out = KV.of(currentShard.toString(), ret);\n+                                    kving.stop();\n+                                    outputting.start();\n+                                    c.output(out);\n+                                    outputting.stop();\n+                                    //bunny.stepEnd(\"output subshard \" + currentShardIndex + \", \" + ret.size() + \" reads\");\n+                                    shipping.stop();\n+                                    choosing.start();\n+                                    ret.clear();\n+                                }\n+                                currentShardIndex++;\n+                                currentShard = shards.get(currentShardIndex);\n+                            }\n+                            ret.add(r);\n+                        }\n+                        choosing.stop();\n+                        // what remains in ret is sent after the if statement\n+                    } else if (cloudStorageUrl) {\n+                        // GCS file, load just the bit we need.\n+                        querying.start();\n+                        final SamReader reader = BAMIO.openBAM(storageClient, bam, ValidationStringency.DEFAULT_STRINGENCY);\n+                        SAMRecordIterator query = reader.query(i.getContig(), i.getStart(), i.getEnd(), false);\n+                        // we read multiple at once to estimate how long we spend waiting for disk.\n+                        BatchingIterator<SAMRecord> batcher = new BatchingIterator<SAMRecord>(query, outputShardSize);\n+                        querying.stop();\n+\n+                        loading.start();\n+                        while (batcher.hasNext()) {\n+                            ArrayList<SAMRecord> buf = batcher.next();\n+                            loading.stop();\n+                            choosing.start();\n+                            bunny.stepEnd(\"done loading a batch\");\n+                            for (SAMRecord r : buf) {\n+                                if (!accept(r, i)) {\n+                                    rejected++;\n+                                    continue;\n+                                }\n+\n+                                while (currentShard.getEnd() < r.getStart()) {\n+                                    // move to the next shard.\n+                                    if (!ret.isEmpty()) {\n+                                        accepted += ret.size();\n+                                        choosing.stop();\n+                                        shipping.start();\n+                                        kving.start();\n+                                        KV<String, Iterable<GATKRead>> out = KV.of(currentShard.toString(), ret);\n+                                        kving.stop();\n+                                        outputting.start();\n+                                        c.output(out);\n+                                        outputting.stop();\n+                                        bunny.stepEnd(\"output subshard \" + currentShardIndex + \", \" + ret.size() + \" reads\");\n+                                        shipping.stop();\n+                                        choosing.start();\n+                                        ret.clear();\n+                                    }\n+                                    currentShardIndex++;\n+                                    currentShard = shards.get(currentShardIndex);\n+                                }\n+\n+                                // work around some serialization madness\n+                                r.setHeader(null);\n+                                ret.add(new SAMRecordToGATKReadAdapter(r));\n+                            }\n+                            buf.clear();\n+                            batcher.reuse(buf);\n+                            choosing.stop();\n+                            loading.start();\n+                        }\n+                        bunny.stepEnd(\"that was the last batch\");\n+                        loading.stop();\n+                    } else {\n+                        // TODO: HDFS input\n+                        throw new RuntimeException(\"Sorry, Hadoop support not yet implemented\");\n+                    }\n+                    shipping.start();\n+                    if (!ret.isEmpty()) {\n+                        kving.start();\n+                        accepted += ret.size();\n+                        KV<String, Iterable<GATKRead>> out = KV.of(currentShard.toString(), ret);\n+                        kving.stop();\n+                        outputting.start();\n+                        c.output(out);\n+                        outputting.stop();\n+                        //bunny.stepEnd(\"output \" + ret.size() + \" reads\");\n+                    }\n+                    shipping.stop();\n+                    readsEmitted += accepted;\n+                    //bunny.stepEnd(\"loading \" + accepted + \" reads for \" + i.toString()\n+                    //    + \" in \" + processing.elapsed(TimeUnit.MILLISECONDS) + \" ms. \"\n+                    //    + \" readingInput: \" + readingInput.elapsed(TimeUnit.MILLISECONDS) + \", \"\n+                    //    + \" init: \" + init.elapsed(TimeUnit.MILLISECONDS) + \", \"\n+                    //    + \" cutShards: \" + cutShards.elapsed(TimeUnit.MILLISECONDS) + \", \"\n+                    //    + \" kv.of: \" + kving.elapsed(TimeUnit.MILLISECONDS) + \", \"\n+                    //    + \" output: \" + outputting.elapsed(TimeUnit.MILLISECONDS) + \", \"\n+                    //    + \" rejected \" + rejected);\n+                }\n+\n+                private boolean accept(GATKRead r, SimpleInterval region) {\n+                    if (r.isUnmapped()) {\n+                        // we don't want unmapped reads, *unless* their mate is in the region\n+                        return (!r.mateIsUnmapped() && r.getMateStart() >= region.getStart());\n+                    } else {\n+                        // the query gives us reads that overlap the region. We want only reads that *start* in those intervals.\n+                        return (r.getStart() >= region.getStart());\n+                    }\n+                }\n+\n+                private boolean accept(SAMRecord r, SimpleInterval region) {\n+                    if (r.getReadUnmappedFlag() ||\n+                        r.getReferenceName() == null || r.getReferenceName().equals(SAMRecord.NO_ALIGNMENT_REFERENCE_NAME) ||\n+                        r.getAlignmentStart() == SAMRecord.NO_ALIGNMENT_START) {\n+                        // unmapped read. We don't want it, *unless* their mate is in the region\n+                        return (!r.getMateUnmappedFlag() && r.getMateAlignmentStart() >= region.getStart());\n+                    } else {\n+                        // mapped read.\n+                        // the query gives us reads that overlap the region. We want only reads that *start* in those intervals.\n+                        return (r.getStart() >= region.getStart());\n+                    }\n+                }\n+\n+\n+            }));\n+    }\n+\n+\n+    // chr2:1-200 -> chr2:1-100,chr2:101-200\n+    static public List<SimpleInterval> cutToShards(Iterable<SimpleInterval> intervals, int shardSize) {\n+        ArrayList<SimpleInterval> ret = new ArrayList<SimpleInterval>();\n+        for (SimpleInterval i : intervals) {\n+            int beginShard = shardIndex(i.getStart(), shardSize);\n+            int endShard = shardIndex(i.getEnd(), shardSize);\n+            if (beginShard==endShard) {\n+                ret.add(i);\n+                continue;\n+            }\n+            // more than one shard: output begin to end-of-shard, then multiple full shards, then begin-of-shard to end.\n+            ret.add(new SimpleInterval(i.getContig(), i.getStart(), endOfShard(beginShard, shardSize)));\n+            for (int shard = beginShard+1; shard<endShard; shard++) {\n+                ret.add(new SimpleInterval(i.getContig(), beginOfShard(shard, shardSize), endOfShard(shard, shardSize)));\n+            }\n+            ret.add(new SimpleInterval(i.getContig(), beginOfShard(endShard, shardSize), i.getEnd()));\n+        }\n+        return ret;\n+    }\n+\n+    /**\n+     * Each key corresponds to a shard (1-based shards)\n+     * Each Locatable is placed in each shard that it overlaps (that means they may be placed more than once).\n+     */\n+    static public HashMap<String, ArrayList<Locatable>> groupByOverlappingShard(Iterable<? extends Locatable> intervals, int shardSize) {\n+        HashMap<String, ArrayList<Locatable>> ret = new HashMap<String, ArrayList<Locatable>>();\n+        for (Locatable i : intervals) {\n+            int startIndex = shardIndex(i.getStart(), shardSize);\n+            int endIndex = shardIndex(i.getEnd(), shardSize);\n+            for (int index = startIndex; index<=endIndex; index++) {\n+                SimpleInterval startInterval = new SimpleInterval(i.getContig(), beginOfShard(index, shardSize), endOfShard(index, shardSize));\n+                String key = startInterval.toString();\n+                if (!ret.containsKey(key)) ret.put(key, new ArrayList<>());","path":"src/main/java/org/broadinstitute/hellbender/engine/dataflow/datasources/ShardedReadsDatasource.java","position":311,"original_position":311,"commit_id":"5fc5ca9f3a5bbe76b147b82ade0b7e00968d812f","original_commit_id":"5fc5ca9f3a5bbe76b147b82ade0b7e00968d812f","user":{"login":"droazen","id":798637,"avatar_url":"https://avatars.githubusercontent.com/u/798637?v=3","gravatar_id":"","url":"https://api.github.com/users/droazen","html_url":"https://github.com/droazen","followers_url":"https://api.github.com/users/droazen/followers","following_url":"https://api.github.com/users/droazen/following{/other_user}","gists_url":"https://api.github.com/users/droazen/gists{/gist_id}","starred_url":"https://api.github.com/users/droazen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/droazen/subscriptions","organizations_url":"https://api.github.com/users/droazen/orgs","repos_url":"https://api.github.com/users/droazen/repos","events_url":"https://api.github.com/users/droazen/events{/privacy}","received_events_url":"https://api.github.com/users/droazen/received_events","type":"User","site_admin":false},"body":"Add braces around if body as security against future bugs.","created_at":"2015-09-18T19:57:01Z","updated_at":"2015-09-18T19:57:01Z","html_url":"https://github.com/broadinstitute/hellbender/pull/894#discussion_r39894508","pull_request_url":"https://api.github.com/repos/broadinstitute/hellbender/pulls/894","_links":{"self":{"href":"https://api.github.com/repos/broadinstitute/hellbender/pulls/comments/39894508"},"html":{"href":"https://github.com/broadinstitute/hellbender/pull/894#discussion_r39894508"},"pull_request":{"href":"https://api.github.com/repos/broadinstitute/hellbender/pulls/894"}}},"pull_request":{"url":"https://api.github.com/repos/broadinstitute/hellbender/pulls/894","id":45111944,"html_url":"https://github.com/broadinstitute/hellbender/pull/894","diff_url":"https://github.com/broadinstitute/hellbender/pull/894.diff","patch_url":"https://github.com/broadinstitute/hellbender/pull/894.patch","issue_url":"https://api.github.com/repos/broadinstitute/hellbender/issues/894","number":894,"state":"open","locked":false,"title":"BaseRecalibrator, optimized","user":{"login":"jean-philippe-martin","id":10458949,"avatar_url":"https://avatars.githubusercontent.com/u/10458949?v=3","gravatar_id":"","url":"https://api.github.com/users/jean-philippe-martin","html_url":"https://github.com/jean-philippe-martin","followers_url":"https://api.github.com/users/jean-philippe-martin/followers","following_url":"https://api.github.com/users/jean-philippe-martin/following{/other_user}","gists_url":"https://api.github.com/users/jean-philippe-martin/gists{/gist_id}","starred_url":"https://api.github.com/users/jean-philippe-martin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jean-philippe-martin/subscriptions","organizations_url":"https://api.github.com/users/jean-philippe-martin/orgs","repos_url":"https://api.github.com/users/jean-philippe-martin/repos","events_url":"https://api.github.com/users/jean-philippe-martin/events{/privacy}","received_events_url":"https://api.github.com/users/jean-philippe-martin/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-09-15T22:44:39Z","updated_at":"2015-09-18T19:57:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":"101dfda298f1110f561bd527fd2656cbe42bf384","assignee":{"login":"droazen","id":798637,"avatar_url":"https://avatars.githubusercontent.com/u/798637?v=3","gravatar_id":"","url":"https://api.github.com/users/droazen","html_url":"https://github.com/droazen","followers_url":"https://api.github.com/users/droazen/followers","following_url":"https://api.github.com/users/droazen/following{/other_user}","gists_url":"https://api.github.com/users/droazen/gists{/gist_id}","starred_url":"https://api.github.com/users/droazen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/droazen/subscriptions","organizations_url":"https://api.github.com/users/droazen/orgs","repos_url":"https://api.github.com/users/droazen/repos","events_url":"https://api.github.com/users/droazen/events{/privacy}","received_events_url":"https://api.github.com/users/droazen/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/broadinstitute/hellbender/pulls/894/commits","review_comments_url":"https://api.github.com/repos/broadinstitute/hellbender/pulls/894/comments","review_comment_url":"https://api.github.com/repos/broadinstitute/hellbender/pulls/comments{/number}","comments_url":"https://api.github.com/repos/broadinstitute/hellbender/issues/894/comments","statuses_url":"https://api.github.com/repos/broadinstitute/hellbender/statuses/5fc5ca9f3a5bbe76b147b82ade0b7e00968d812f","head":{"label":"broadinstitute:jp_perf_br","ref":"jp_perf_br","sha":"5fc5ca9f3a5bbe76b147b82ade0b7e00968d812f","user":{"login":"broadinstitute","id":393552,"avatar_url":"https://avatars.githubusercontent.com/u/393552?v=3","gravatar_id":"","url":"https://api.github.com/users/broadinstitute","html_url":"https://github.com/broadinstitute","followers_url":"https://api.github.com/users/broadinstitute/followers","following_url":"https://api.github.com/users/broadinstitute/following{/other_user}","gists_url":"https://api.github.com/users/broadinstitute/gists{/gist_id}","starred_url":"https://api.github.com/users/broadinstitute/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/broadinstitute/subscriptions","organizations_url":"https://api.github.com/users/broadinstitute/orgs","repos_url":"https://api.github.com/users/broadinstitute/repos","events_url":"https://api.github.com/users/broadinstitute/events{/privacy}","received_events_url":"https://api.github.com/users/broadinstitute/received_events","type":"Organization","site_admin":false},"repo":{"id":27452807,"name":"hellbender","full_name":"broadinstitute/hellbender","owner":{"login":"broadinstitute","id":393552,"avatar_url":"https://avatars.githubusercontent.com/u/393552?v=3","gravatar_id":"","url":"https://api.github.com/users/broadinstitute","html_url":"https://github.com/broadinstitute","followers_url":"https://api.github.com/users/broadinstitute/followers","following_url":"https://api.github.com/users/broadinstitute/following{/other_user}","gists_url":"https://api.github.com/users/broadinstitute/gists{/gist_id}","starred_url":"https://api.github.com/users/broadinstitute/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/broadinstitute/subscriptions","organizations_url":"https://api.github.com/users/broadinstitute/orgs","repos_url":"https://api.github.com/users/broadinstitute/repos","events_url":"https://api.github.com/users/broadinstitute/events{/privacy}","received_events_url":"https://api.github.com/users/broadinstitute/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/broadinstitute/hellbender","description":"GATK4 development","fork":false,"url":"https://api.github.com/repos/broadinstitute/hellbender","forks_url":"https://api.github.com/repos/broadinstitute/hellbender/forks","keys_url":"https://api.github.com/repos/broadinstitute/hellbender/keys{/key_id}","collaborators_url":"https://api.github.com/repos/broadinstitute/hellbender/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/broadinstitute/hellbender/teams","hooks_url":"https://api.github.com/repos/broadinstitute/hellbender/hooks","issue_events_url":"https://api.github.com/repos/broadinstitute/hellbender/issues/events{/number}","events_url":"https://api.github.com/repos/broadinstitute/hellbender/events","assignees_url":"https://api.github.com/repos/broadinstitute/hellbender/assignees{/user}","branches_url":"https://api.github.com/repos/broadinstitute/hellbender/branches{/branch}","tags_url":"https://api.github.com/repos/broadinstitute/hellbender/tags","blobs_url":"https://api.github.com/repos/broadinstitute/hellbender/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/broadinstitute/hellbender/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/broadinstitute/hellbender/git/refs{/sha}","trees_url":"https://api.github.com/repos/broadinstitute/hellbender/git/trees{/sha}","statuses_url":"https://api.github.com/repos/broadinstitute/hellbender/statuses/{sha}","languages_url":"https://api.github.com/repos/broadinstitute/hellbender/languages","stargazers_url":"https://api.github.com/repos/broadinstitute/hellbender/stargazers","contributors_url":"https://api.github.com/repos/broadinstitute/hellbender/contributors","subscribers_url":"https://api.github.com/repos/broadinstitute/hellbender/subscribers","subscription_url":"https://api.github.com/repos/broadinstitute/hellbender/subscription","commits_url":"https://api.github.com/repos/broadinstitute/hellbender/commits{/sha}","git_commits_url":"https://api.github.com/repos/broadinstitute/hellbender/git/commits{/sha}","comments_url":"https://api.github.com/repos/broadinstitute/hellbender/comments{/number}","issue_comment_url":"https://api.github.com/repos/broadinstitute/hellbender/issues/comments{/number}","contents_url":"https://api.github.com/repos/broadinstitute/hellbender/contents/{+path}","compare_url":"https://api.github.com/repos/broadinstitute/hellbender/compare/{base}...{head}","merges_url":"https://api.github.com/repos/broadinstitute/hellbender/merges","archive_url":"https://api.github.com/repos/broadinstitute/hellbender/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/broadinstitute/hellbender/downloads","issues_url":"https://api.github.com/repos/broadinstitute/hellbender/issues{/number}","pulls_url":"https://api.github.com/repos/broadinstitute/hellbender/pulls{/number}","milestones_url":"https://api.github.com/repos/broadinstitute/hellbender/milestones{/number}","notifications_url":"https://api.github.com/repos/broadinstitute/hellbender/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/broadinstitute/hellbender/labels{/name}","releases_url":"https://api.github.com/repos/broadinstitute/hellbender/releases{/id}","created_at":"2014-12-02T20:50:36Z","updated_at":"2015-09-18T17:13:00Z","pushed_at":"2015-09-18T19:34:12Z","git_url":"git://github.com/broadinstitute/hellbender.git","ssh_url":"git@github.com:broadinstitute/hellbender.git","clone_url":"https://github.com/broadinstitute/hellbender.git","svn_url":"https://github.com/broadinstitute/hellbender","homepage":"","size":46943,"stargazers_count":30,"watchers_count":30,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":8,"mirror_url":null,"open_issues_count":251,"forks":8,"open_issues":251,"watchers":30,"default_branch":"master"}},"base":{"label":"broadinstitute:master","ref":"master","sha":"960d353d9bd03e0a4c8f99bad9b84bb17d501822","user":{"login":"broadinstitute","id":393552,"avatar_url":"https://avatars.githubusercontent.com/u/393552?v=3","gravatar_id":"","url":"https://api.github.com/users/broadinstitute","html_url":"https://github.com/broadinstitute","followers_url":"https://api.github.com/users/broadinstitute/followers","following_url":"https://api.github.com/users/broadinstitute/following{/other_user}","gists_url":"https://api.github.com/users/broadinstitute/gists{/gist_id}","starred_url":"https://api.github.com/users/broadinstitute/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/broadinstitute/subscriptions","organizations_url":"https://api.github.com/users/broadinstitute/orgs","repos_url":"https://api.github.com/users/broadinstitute/repos","events_url":"https://api.github.com/users/broadinstitute/events{/privacy}","received_events_url":"https://api.github.com/users/broadinstitute/received_events","type":"Organization","site_admin":false},"repo":{"id":27452807,"name":"hellbender","full_name":"broadinstitute/hellbender","owner":{"login":"broadinstitute","id":393552,"avatar_url":"https://avatars.githubusercontent.com/u/393552?v=3","gravatar_id":"","url":"https://api.github.com/users/broadinstitute","html_url":"https://github.com/broadinstitute","followers_url":"https://api.github.com/users/broadinstitute/followers","following_url":"https://api.github.com/users/broadinstitute/following{/other_user}","gists_url":"https://api.github.com/users/broadinstitute/gists{/gist_id}","starred_url":"https://api.github.com/users/broadinstitute/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/broadinstitute/subscriptions","organizations_url":"https://api.github.com/users/broadinstitute/orgs","repos_url":"https://api.github.com/users/broadinstitute/repos","events_url":"https://api.github.com/users/broadinstitute/events{/privacy}","received_events_url":"https://api.github.com/users/broadinstitute/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/broadinstitute/hellbender","description":"GATK4 development","fork":false,"url":"https://api.github.com/repos/broadinstitute/hellbender","forks_url":"https://api.github.com/repos/broadinstitute/hellbender/forks","keys_url":"https://api.github.com/repos/broadinstitute/hellbender/keys{/key_id}","collaborators_url":"https://api.github.com/repos/broadinstitute/hellbender/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/broadinstitute/hellbender/teams","hooks_url":"https://api.github.com/repos/broadinstitute/hellbender/hooks","issue_events_url":"https://api.github.com/repos/broadinstitute/hellbender/issues/events{/number}","events_url":"https://api.github.com/repos/broadinstitute/hellbender/events","assignees_url":"https://api.github.com/repos/broadinstitute/hellbender/assignees{/user}","branches_url":"https://api.github.com/repos/broadinstitute/hellbender/branches{/branch}","tags_url":"https://api.github.com/repos/broadinstitute/hellbender/tags","blobs_url":"https://api.github.com/repos/broadinstitute/hellbender/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/broadinstitute/hellbender/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/broadinstitute/hellbender/git/refs{/sha}","trees_url":"https://api.github.com/repos/broadinstitute/hellbender/git/trees{/sha}","statuses_url":"https://api.github.com/repos/broadinstitute/hellbender/statuses/{sha}","languages_url":"https://api.github.com/repos/broadinstitute/hellbender/languages","stargazers_url":"https://api.github.com/repos/broadinstitute/hellbender/stargazers","contributors_url":"https://api.github.com/repos/broadinstitute/hellbender/contributors","subscribers_url":"https://api.github.com/repos/broadinstitute/hellbender/subscribers","subscription_url":"https://api.github.com/repos/broadinstitute/hellbender/subscription","commits_url":"https://api.github.com/repos/broadinstitute/hellbender/commits{/sha}","git_commits_url":"https://api.github.com/repos/broadinstitute/hellbender/git/commits{/sha}","comments_url":"https://api.github.com/repos/broadinstitute/hellbender/comments{/number}","issue_comment_url":"https://api.github.com/repos/broadinstitute/hellbender/issues/comments{/number}","contents_url":"https://api.github.com/repos/broadinstitute/hellbender/contents/{+path}","compare_url":"https://api.github.com/repos/broadinstitute/hellbender/compare/{base}...{head}","merges_url":"https://api.github.com/repos/broadinstitute/hellbender/merges","archive_url":"https://api.github.com/repos/broadinstitute/hellbender/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/broadinstitute/hellbender/downloads","issues_url":"https://api.github.com/repos/broadinstitute/hellbender/issues{/number}","pulls_url":"https://api.github.com/repos/broadinstitute/hellbender/pulls{/number}","milestones_url":"https://api.github.com/repos/broadinstitute/hellbender/milestones{/number}","notifications_url":"https://api.github.com/repos/broadinstitute/hellbender/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/broadinstitute/hellbender/labels{/name}","releases_url":"https://api.github.com/repos/broadinstitute/hellbender/releases{/id}","created_at":"2014-12-02T20:50:36Z","updated_at":"2015-09-18T17:13:00Z","pushed_at":"2015-09-18T19:34:12Z","git_url":"git://github.com/broadinstitute/hellbender.git","ssh_url":"git@github.com:broadinstitute/hellbender.git","clone_url":"https://github.com/broadinstitute/hellbender.git","svn_url":"https://github.com/broadinstitute/hellbender","homepage":"","size":46943,"stargazers_count":30,"watchers_count":30,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":8,"mirror_url":null,"open_issues_count":251,"forks":8,"open_issues":251,"watchers":30,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/broadinstitute/hellbender/pulls/894"},"html":{"href":"https://github.com/broadinstitute/hellbender/pull/894"},"issue":{"href":"https://api.github.com/repos/broadinstitute/hellbender/issues/894"},"comments":{"href":"https://api.github.com/repos/broadinstitute/hellbender/issues/894/comments"},"review_comments":{"href":"https://api.github.com/repos/broadinstitute/hellbender/pulls/894/comments"},"review_comment":{"href":"https://api.github.com/repos/broadinstitute/hellbender/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/broadinstitute/hellbender/pulls/894/commits"},"statuses":{"href":"https://api.github.com/repos/broadinstitute/hellbender/statuses/5fc5ca9f3a5bbe76b147b82ade0b7e00968d812f"}}}}