{"action":"created","comment":{"url":"https://api.github.com/repos/broadinstitute/gatk/pulls/comments/235150887","pull_request_review_id":176862778,"id":235150887,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzNTE1MDg4Nw==","diff_hunk":"@@ -0,0 +1,706 @@\n+package org.broadinstitute.hellbender.tools.spark;\n+\n+import com.google.common.annotations.VisibleForTesting;\n+import com.google.common.collect.LinkedListMultimap;\n+import com.google.common.collect.ListMultimap;\n+import com.google.common.collect.Lists;\n+import htsjdk.samtools.*;\n+import htsjdk.samtools.util.*;\n+import htsjdk.tribble.AbstractFeatureReader;\n+import htsjdk.tribble.FeatureReader;\n+import org.apache.spark.api.java.JavaPairRDD;\n+import org.apache.spark.api.java.JavaRDD;\n+import org.apache.spark.api.java.JavaSparkContext;\n+import org.apache.spark.broadcast.Broadcast;\n+import org.broadinstitute.barclay.argparser.Argument;\n+import org.broadinstitute.barclay.argparser.BetaFeature;\n+import org.broadinstitute.barclay.argparser.CommandLineParser;\n+import org.broadinstitute.barclay.argparser.CommandLineProgramProperties;\n+import org.broadinstitute.barclay.help.DocumentedFeature;\n+import org.broadinstitute.hellbender.cmdline.StandardArgumentDefinitions;\n+import org.broadinstitute.hellbender.engine.filters.ReadFilter;\n+import org.broadinstitute.hellbender.engine.filters.ReadFilterLibrary;\n+import org.broadinstitute.hellbender.engine.spark.GATKSparkTool;\n+import org.broadinstitute.hellbender.exceptions.GATKException;\n+import org.broadinstitute.hellbender.utils.Utils;\n+import org.broadinstitute.hellbender.utils.codecs.table.TableCodec;\n+import org.broadinstitute.hellbender.utils.codecs.table.TableFeature;\n+import org.broadinstitute.hellbender.utils.read.GATKRead;\n+import org.broadinstitute.hellbender.utils.read.ReadUtils;\n+import org.broadinstitute.hellbender.utils.spark.SparkUtils;\n+import picard.cmdline.programgroups.ReadDataManipulationProgramGroup;\n+import scala.Tuple2;\n+\n+import java.io.File;\n+import java.io.IOException;\n+import java.nio.file.Files;\n+import java.nio.file.Path;\n+import java.nio.file.Paths;\n+import java.util.*;\n+import java.util.stream.Collectors;\n+\n+/**\n+ * Reverts a SAM file by optionally restoring original quality scores and by removing\n+ * all alignment information.\n+ * <p>\n+ * <p>\n+ * This tool removes or restores certain properties of the SAM records, including alignment information.\n+ * It can be used to produce an unmapped BAM (uBAM) from a previously aligned BAM. It is also capable of\n+ * restoring the original quality scores of a BAM file that has already undergone base quality score recalibration\n+ * (BQSR) if the original qualities were retained during the calibration (in the OQ tag).\n+ * <p>\n+ * <h3>Usage Examples</h3>\n+ * <h4>Output to a single file</h4>\n+ * <pre>\n+ * java -jar picard.jar RevertSam \\\\\n+ *      -I input.bam \\\\\n+ *      -O reverted.bam\n+ * </pre>\n+ * <p>\n+ * <h4>Output by read group into multiple files with sample map</h4>\n+ * <pre>\n+ * java -jar picard.jar RevertSam \\\\\n+ *      -I input.bam \\\\\n+ *      --output-by-readgroup \\\\\n+ *      --output-map reverted_bam_paths.tsv\n+ * </pre>\n+ * <p>\n+ * <h4>Output by read group with no output map</h4>\n+ * <pre>\n+ * java -jar picard.jar RevertSam \\\\\n+ *      -I input.bam \\\\\n+ *      --output-by-readgroup \\\\\n+ *      -O /write/reverted/read/group/bams/in/this/dir\n+ * </pre>\n+ * This will output a BAM (Can be overridden with outputByReadgroupFileFormat option.)\n+ * <br/>\n+ * Note: If the program fails due to a SAM validation error, consider setting the VALIDATION_STRINGENCY option to\n+ * LENIENT or SILENT if the failures are expected to be obviated by the reversion process\n+ * (e.g. invalid alignment information will be obviated when the dontRemoveAlignmentInformation option is used).\n+ */\n+\n+@DocumentedFeature\n+@CommandLineProgramProperties(\n+        summary =RevertSamSpark.USAGE_DETAILS,\n+        oneLineSummary =RevertSamSpark.USAGE_SUMMARY,\n+        programGroup = ReadDataManipulationProgramGroup.class)\n+@BetaFeature\n+public class RevertSamSpark extends GATKSparkTool {\n+    static final String USAGE_SUMMARY = \"Reverts SAM or BAM files to a previous state.  \";\n+    static final String USAGE_DETAILS = \"This tool removes or restores certain properties of the SAM records, including alignment \" +\n+            \"information, which can be used to produce an unmapped BAM (uBAM) from a previously aligned BAM. It is also capable of \" +\n+            \"restoring the original quality scores of a BAM file that has already undergone base quality score recalibration (BQSR) if the\" +\n+            \"original qualities were retained.\\n\" +\n+            \"<h3>Examples</h3>\\n\" +\n+            \"<h4>Example with single output:</h4>\\n\" +\n+            \"java -jar picard.jar RevertSam \\\\\\n\" +\n+            \"     -I input.bam \\\\\\n\" +\n+            \"     -O reverted.bam\\n\" +\n+            \"\\n\" +\n+            \"<h4>Example outputting by read group with output map:</h4>\\n\" +\n+            \"java -jar picard.jar RevertSam \\\\\\n\" +\n+            \"     -I input.bam \\\\\\n\" +\n+            \"     --output-by-readgroup \\\\\\n\" +\n+            \"     --output-map reverted_bam_paths.tsv\\n\" +\n+            \"\\n\" +\n+            \"Will output a BAM/SAM file per read group.\\n\" +\n+            \"<h4>Example outputting by read group without output map:</h4>\\n\" +\n+            \"java -jar picard.jar RevertSam \\\\\\n\" +\n+            \"     I=input.bam \\\\\\n\" +\n+            \"     --output-by-readgroup \\\\\\n\" +\n+            \"     -O /write/reverted/read/group/bams/in/this/dir\\n\" +\n+            \"\\n\" +\n+            \"Will output a BAM file per read group.\" +\n+            \" Output format can be overridden with the outputByReadgroupFileFormat option.\\n\" +\n+            \"Note: If the program fails due to a SAM validation error, consider setting the VALIDATION_STRINGENCY option to \" +\n+            \"LENIENT or SILENT if the failures are expected to be obviated by the reversion process \" +\n+            \"(e.g. invalid alignment information will be obviated when the dontRemoveAlignmentInformation option is used).\\n\" +\n+            \"\";\n+    @Override\n+    public boolean requiresReads() { return true; }\n+\n+    @Argument(mutex = {OUTPUT_MAP_ARG}, shortName = StandardArgumentDefinitions.OUTPUT_SHORT_NAME, fullName = StandardArgumentDefinitions.OUTPUT_LONG_NAME, doc = \"The output SAM/BAM file to create, or an output directory if '--output-by-readgroup' is set.\")\n+    public File output;\n+\n+    public static final String OUTPUT_MAP_ARG = \"output-map\";\n+    @Argument(mutex = {StandardArgumentDefinitions.OUTPUT_LONG_NAME}, fullName = OUTPUT_MAP_ARG, doc = \"Tab separated file with two columns, READ_GROUP_ID and OUTPUT, providing file mapping only used if '--output-by-readgroup' is set.\")\n+    public File outputMap;\n+\n+    public static final String OUTPUT_BY_READGROUP_ARG = \"output-by-readgroup\";\n+    @Argument(fullName = OUTPUT_BY_READGROUP_ARG, doc = \"When true, outputs each read group in a separate file.\")\n+    public boolean outputByReadGroup = false;\n+\n+    public static enum FileType implements CommandLineParser.ClpEnum {\n+        sam(\"Generate SAM files.\"),\n+        bam(\"Generate BAM files.\"),\n+        cram(\"Generate CRAM files.\"),\n+        dynamic(\"Generate files based on the extention of input.\");\n+\n+        final String description;\n+\n+        FileType(String descrition) {\n+            this.description = descrition;\n+        }\n+\n+        @Override\n+        public String getHelpDoc() {\n+            return description;\n+        }\n+    }\n+\n+    @Argument(doc = \"WARNING: This option is potentially destructive. If enabled will discard reads in order to produce \" +\n+            \"a consistent output BAM. Reads discarded include (but are not limited to) paired reads with missing \" +\n+            \"mates, duplicated records, records with mismatches in length of bases and qualities. This option should \" +\n+            \"only be enabled if the output sort order is queryname and will always cause sorting to occur.\")\n+    public boolean sanitize = false;\n+\n+    @Argument(doc = \"If 'sanitize' only one record when we find more than one record with the same name for R1/R2/unpaired reads respectively. \" +\n+            \"For paired end reads, keeps only the first R1 and R2 found respectively, and discards all unpaired reads. \" +\n+            \"Duplicates do not refer to the duplicate flag in the FLAG field, but instead reads with the same name.\",\n+            fullName = \"keep-first-duplicate\")\n+    public boolean keepFirstDuplicate = false;\n+\n+    public static final String OUTPUT_BY_READGROUP_FILE_FORMAT_ARG = \"output-by-readgroup-file-format\";\n+    @Argument(fullName = OUTPUT_BY_READGROUP_FILE_FORMAT_ARG, doc = \"When using outputByReadGroup, the output file format can be set to a certain format.\")\n+    public FileType outputByReadgroupFileFormat = FileType.dynamic;\n+\n+    @Argument(shortName = StandardArgumentDefinitions.SORT_ORDER_SHORT_NAME, fullName = StandardArgumentDefinitions.SORT_ORDER_LONG_NAME, doc = \"The sort order to create the reverted output file with, defaults to whatever is specified in the current file\", optional = true)\n+    public SAMFileHeader.SortOrder sortOrder = SAMFileHeader.SortOrder.queryname;\n+\n+    public static final String DONT_RESTORE_ORIGINAL_QUALITIES_ARG = \"dont-restore-original-qualities\";\n+    @Argument( fullName = DONT_RESTORE_ORIGINAL_QUALITIES_ARG, doc = \"Set to prevent the tool from setting the OQ field to the QUAL where avalible.\", optional = true)\n+    public boolean dontRestoreOriginalQualities = false;\n+\n+    public static final String DONT_REMOVE_DUPLICATE_INFORMATION_ARG = \"remove-duplicate-information\";\n+    @Argument(fullName = DONT_REMOVE_DUPLICATE_INFORMATION_ARG, doc = \"By default we remove duplicate read flags from all reads.  Note that if this is true, \" +\n+            \" the output may have the unusual but sometimes desirable trait of having unmapped reads that are marked as duplicates.\")\n+    public boolean dontRemoveDuplicateInformation = false;  //TODO flip this value\n+\n+    public static final String DONT_REMOVE_ALIGNMENT_INFORMATION_ARG = \"remove-alignment-information\";\n+    @Argument(fullName = DONT_REMOVE_ALIGNMENT_INFORMATION_ARG, doc = \"Remove all alignment information from the file.\")\n+    public boolean dontRemoveAlignmentInformation = false;\n+\n+    public static final String ATTRIBUTE_TO_CLEAR_ARG = \"attributes-to-clear\";\n+    @Argument(fullName = ATTRIBUTE_TO_CLEAR_ARG,doc = \"When removing alignment information, the set of optional tags to remove.\", optional = true)\n+    public List<String> attributesToClear = new ArrayList<String>();\n+\n+    public static final String REMOVE_DEFAULT_ATTRIBUTE_TO_CLEAR_ARG = \"remove-default-attributes-to-clear\";\n+    @Argument(fullName = REMOVE_DEFAULT_ATTRIBUTE_TO_CLEAR_ARG,doc = \"When removing alignment information, the set of optional tags to remove.\", optional = true)\n+    public boolean removeDefaults = false;\n+\n+    public static List<String> DEFAULT_ATTRIBUTES_TO_CLEAR = new ArrayList<String>() {{\n+        add(SAMTag.NM.name());\n+        add(SAMTag.UQ.name());\n+        add(SAMTag.PG.name());\n+        add(SAMTag.MD.name());\n+        add(SAMTag.MQ.name());\n+        add(SAMTag.SA.name()); // Supplementary alignment metadata\n+        add(SAMTag.MC.name()); // Mate Cigar\n+        add(SAMTag.AS.name());\n+    }};\n+\n+    public static final String SAMPLE_ALIAS_ARG = \"sample-alias\";\n+    @Argument(fullName = SAMPLE_ALIAS_ARG, doc = \"The sample alias to use in the reverted output file.  This will override the existing \" +\n+            \"sample alias in the file and is used only if all the read groups in the input file have the \" +\n+            \"same sample alias.\", shortName = StandardArgumentDefinitions.SAMPLE_ALIAS_SHORT_NAME, optional = true)\n+    public String sampleAlias;\n+\n+    public static final String LIBRARY_NAME_ARG = \"library-name\";\n+    @Argument(fullName = LIBRARY_NAME_ARG, doc = \"The library name to use in the reverted output file.  This will override the existing \" +\n+            \"sample alias in the file and is used only if all the read groups in the input file have the \" +\n+            \"same library name.\", optional = true)\n+    public String libraryName;\n+\n+    @Override\n+    public List<ReadFilter> getDefaultReadFilters() {\n+        return Collections.singletonList(ReadFilterLibrary.ALLOW_ALL_READS);\n+    }\n+\n+    /**\n+     * Enforce that output ordering is queryname when sanitization is turned on since it requires a queryname sort.\n+     */\n+    @Override\n+    protected String[] customCommandLineValidation() {\n+        final List<String> errors = new ArrayList<>();\n+        RevertSamSpark.ValidationUtil.validateOutputParams(outputByReadGroup, output, outputMap, errors);\n+\n+        if (!sanitize && keepFirstDuplicate) errors.add(\"'keepFirstDuplicate' cannot be used without 'sanitize'\");\n+\n+        if (!errors.isEmpty()) {\n+            return errors.toArray(new String[errors.size()]);\n+        }\n+        return null;\n+    }\n+\n+    @Override\n+    protected void runTool(JavaSparkContext ctx) {\n+        Broadcast<SAMFileHeader> headerb = ctx.broadcast(getHeaderForReads());\n+        JavaRDD<GATKRead> reads = getReads();\n+\n+        ////////////////////////////////////////////////////////////////////////////\n+        // Grab the input header and remap values where appropriate\n+        ////////////////////////////////////////////////////////////////////////////\n+        SAMFileHeader inHeader = getHeaderForReads();\n+        ValidationUtil.validateHeaderOverrides(inHeader, sampleAlias, libraryName);\n+        if (sampleAlias != null) {\n+            inHeader.getReadGroups().forEach(rg -> rg.setSample(sampleAlias));\n+        }\n+        if (libraryName != null) {\n+            inHeader.getReadGroups().forEach(rg -> rg.setLibrary(libraryName));\n+        }\n+\n+        ////////////////////////////////////////////////////////////////////////////\n+        // Map the readgroups in the header to appropriate\n+        ////////////////////////////////////////////////////////////////////////////\n+        Map<String,File> writerMap = getOutputMap(outputMap,\n+                                                  output,\n+                                                  getDefaultExtension(readArguments.getReadFiles().get(0).toString(), outputByReadgroupFileFormat),\n+                                                  inHeader.getReadGroups(),\n+                                                  outputByReadGroup);\n+\n+        ////////////////////////////////////////////////////////////////////////////\n+        // Construct appropriate headers for the output files\n+        ////////////////////////////////////////////////////////////////////////////\n+        final Map<String, SAMFileHeader> headerMap = getReadgroupHeaderMap(inHeader, writerMap);\n+\n+        // Revert the reads based on the given attributes\n+        List<String> attributesToRevert = removeDefaults? DEFAULT_ATTRIBUTES_TO_CLEAR: new ArrayList<>();\n+        attributesToRevert.addAll(attributesToClear);\n+        JavaRDD<GATKRead> readsReverted = revertReads(reads, attributesToRevert);\n+\n+        ////////////////////////////////////////////////////////////////////////////\n+        // Sanitize the reads, sorting them into appropriate order if necessary\n+        ////////////////////////////////////////////////////////////////////////////\n+        if (sanitize) {\n+            Map<String, FastqQualityFormat> readGroupFormatMap = createReadGroupFormatMap(readsReverted, headerb, !dontRestoreOriginalQualities);\n+\n+            readsReverted = sanitize(readGroupFormatMap, readsReverted, inHeader, keepFirstDuplicate);\n+        }\n+\n+        // Write the one or many read output files\n+        for (Map.Entry<String, File> rmap: writerMap.entrySet()) {\n+            //TODO what to do if the readgroup isn't present\n+            final String key = rmap.getKey();\n+            JavaRDD<GATKRead> filteredreads = rmap.getKey()==null? readsReverted :\n+                                                                    readsReverted.filter(r -> r.getReadGroup().equals(key));\n+            writeReads(ctx, rmap.getValue().getPath(), filteredreads, headerMap.get(rmap.getKey())); //TODO proper header map\n+        }\n+    }\n+\n+    /**\n+     * Runs the QualityEncodingDetector over a sampling of each readGroup present in the file to detect what the encoding format\n+     * for base quality those reads are.\n+     *\n+     * @param reads Reads RDD over which to iterate and detect readgroups\n+     * @param inHeader Header describing the readgroups present in the bam\n+     * @param restoreOriginalQualities Whether to use the OQ tag for determining the map\n+     * @return\n+     */\n+    private Map<String, FastqQualityFormat> createReadGroupFormatMap( final JavaRDD<GATKRead> reads,\n+                                                                      final Broadcast<SAMFileHeader> inHeader,\n+                                                                      final boolean restoreOriginalQualities) {\n+        final Map<String, FastqQualityFormat> output = new HashMap<>();\n+\n+        inHeader.getValue().getReadGroups().stream().parallel().forEach(rg -> {\n+            // For each readgroup filter down to just the reads in that group\n+            final String key = rg.getId();\n+            JavaRDD<GATKRead> filtered = reads.filter(r -> r.getReadGroup().equals(key));\n+\n+            if (!filtered.isEmpty()) {\n+\n+                // take the number of reads required by QualityEncodingDetector to determine quality score map\n+                CloseableIterator<SAMRecord> iterator = new CloseableIterator<SAMRecord>() {\n+                    Iterator<GATKRead> delegateIterator = filtered.take((int) QualityEncodingDetector.DEFAULT_MAX_RECORDS_TO_ITERATE).iterator();\n+\n+                    @Override\n+                    public void close() {\n+                        delegateIterator = null;\n+                    }\n+\n+                    @Override\n+                    public boolean hasNext() {\n+                        return delegateIterator != null && delegateIterator.hasNext();\n+                    }\n+\n+                    @Override\n+                    public SAMRecord next() {\n+                        if (!hasNext()) {\n+                            throw new NoSuchElementException(\"hasNext should be called before next\");\n+                        }\n+                        return delegateIterator.next().convertToSAMRecord(inHeader.getValue());\n+                    }\n+                };\n+\n+                // Save what the quality format for each readgroup was.\n+                output.put(rg.getId(), QualityEncodingDetector.detect(\n+                        QualityEncodingDetector.DEFAULT_MAX_RECORDS_TO_ITERATE,\n+                        iterator,\n+                        restoreOriginalQualities));\n+            }\n+        });\n+        return output;\n+    }\n+\n+    /**\n+     * If this is run, we want to be careful to remove duplicated reads from the bam.\n+     *\n+     * In order to do this we group each read by its readname and randomly select one read labeled as first in pair\n+     * and one read labled as second in pair to treat as the representative reads, throwing away the rest.\n+     */\n+    private JavaRDD<GATKRead> sanitize(final Map<String, FastqQualityFormat> readGroupToFormat, final JavaRDD<GATKRead> reads, final SAMFileHeader header, final boolean keepFirstDuplicate) {\n+        JavaRDD<GATKRead> sortedReads = querynameSortReadsIfNecessary(reads.filter(r -> r.getBases().length == r.getBaseQualities().length), getRecommendedNumReducers(), header);\n+        JavaPairRDD<String, Iterable<GATKRead>> readsByGroup = spanReadsByKey(sortedReads);\n+\n+        return readsByGroup.flatMap(group -> {\n+            final List<GATKRead> out = Lists.newArrayList();\n+\n+            List<GATKRead> primaryReads = Utils.stream(group._2()).collect(Collectors.toList());\n+\n+            // Get the number of R1s, R2s, and unpaired reads respectively.\n+            int firsts = 0, seconds = 0, unpaired = 0;\n+            GATKRead firstRecord = null, secondRecord = null, unpairedRecord = null;\n+            for (final GATKRead rec : primaryReads) {\n+                if (!rec.isPaired()) {\n+                    if (unpairedRecord == null) {\n+                        unpairedRecord = rec;\n+                    }\n+                    ++unpaired;\n+                } else {\n+                    if (rec.isFirstOfPair()) {\n+                        if (firstRecord == null) {\n+                            firstRecord = rec;\n+                        }\n+                        ++firsts;\n+                    }\n+                    if (rec.isSecondOfPair()) {\n+                        if (secondRecord == null) {\n+                            secondRecord = rec;\n+                        }\n+                        ++seconds;\n+                    }\n+                }\n+            }\n+\n+            // If we have paired reads, then check that there is exactly one first of pair and one second of pair.\n+            // Otherwise, check that we have only one unpaired read.\n+            if (firsts > 0 || seconds > 0) { // if we have any paired reads\n+                if (firsts != 1 || seconds != 1) { // if we do not have exactly one R1 and one R2\n+                    if (keepFirstDuplicate && firsts >= 1 && seconds >= 1) { // if we have at least one R1 and one R2, we can discard all but an arbitrary one\n+                        primaryReads = Arrays.asList(firstRecord, secondRecord);\n+                    }\n+                    // Otherwise don't admit anything from this group\n+                    else {\n+                        return out.iterator();\n+                    }\n+                }\n+            } else if (unpaired > 1) { // only unpaired reads, and we have too many\n+                if (keepFirstDuplicate) {\n+                    primaryReads = Collections.singletonList(unpairedRecord);\n+                }\n+                // Otherwise remove these reads entirely\n+                else {\n+                    return out.iterator();\n+                }\n+            }\n+\n+            // If we've made it this far spit the records into the output!\n+            for (final GATKRead rec : primaryReads) {\n+                // The only valid quality score encoding scheme is standard; if it's not standard, change it.\n+                final FastqQualityFormat recordFormat = readGroupToFormat.get(rec.getReadGroup());\n+                if (recordFormat != null && !recordFormat.equals(FastqQualityFormat.Standard)) {\n+                    final byte[] quals = rec.getBaseQualities();\n+                    for (int i = 0; i < quals.length; i++) {\n+                        quals[i] -= SolexaQualityConverter.ILLUMINA_TO_PHRED_SUBTRAHEND;\n+                    }\n+                    rec.setBaseQualities(quals);\n+                }\n+                out.add(rec);\n+            }\n+            return out.iterator();\n+        });\n+\n+    }\n+\n+    /**\n+     * Method which takes an RDD of reads that is guaranteed to have every readname group placed together on the same\n+     * partition and maps those so a JavaPairRDD with the readname as the key.\n+     */\n+    private static JavaPairRDD<String, Iterable<GATKRead>> spanReadsByKey(final JavaRDD<GATKRead> reads) {\n+        JavaPairRDD<String, GATKRead> nameReadPairs = reads.mapToPair(read -> new Tuple2<>(read.getName(), read));\n+        return SparkUtils.spanByKey(nameReadPairs).flatMapToPair(namedRead -> {\n+            // for each name, separate reads by key (group name)\n+            List<Tuple2<String, Iterable<GATKRead>>> out = Lists.newArrayList();\n+            ListMultimap<String, GATKRead> multi = LinkedListMultimap.create();\n+            for (GATKRead read : namedRead._2()) {\n+                multi.put(read.getName(), read);\n+            }\n+            for (String key : multi.keySet()) {\n+                // list from Multimap is not serializable by Kryo, so put in a new array list\n+                out.add(new Tuple2<>(key, Lists.newArrayList(multi.get(key))));\n+            }\n+            return out.iterator();\n+        });\n+    }\n+\n+    private static JavaRDD<GATKRead> querynameSortReadsIfNecessary(JavaRDD<GATKRead> reads, int numReducers, SAMFileHeader headerForTool) {\n+        JavaRDD<GATKRead> sortedReadsForMarking;\n+        if (ReadUtils.isReadNameGroupedBam(headerForTool)) {\n+            sortedReadsForMarking = reads;\n+        } else {\n+            headerForTool.setSortOrder(SAMFileHeader.SortOrder.queryname);\n+            sortedReadsForMarking = SparkUtils.sortReadsAccordingToHeader(reads, headerForTool, numReducers);\n+        }\n+        return sortedReadsForMarking;\n+    }\n+\n+\n+    private Map<String, SAMFileHeader> getReadgroupHeaderMap(SAMFileHeader inHeader, Map<String, File> writerMap) {\n+        final Map<String, SAMFileHeader> headerMap;\n+        if (outputByReadGroup) {\n+            if (inHeader.getReadGroups().isEmpty()) {\n+                throw new GATKException(\"The header is missing its read group map\");\n+            }\n+\n+            ValidationUtil.assertAllReadGroupsMapped(writerMap, inHeader.getReadGroups());\n+            headerMap = new HashMap<>();\n+            for (final SAMReadGroupRecord readGroup : inHeader.getReadGroups()) {\n+                final SAMFileHeader header = createOutHeader(inHeader, sortOrder, !dontRemoveAlignmentInformation);\n+                header.addReadGroup(readGroup);\n+                headerMap.put(readGroup.getId(), header);\n+            }\n+        } else {\n+            final SAMFileHeader singleOutHeader = createOutHeader(inHeader, sortOrder, !dontRemoveAlignmentInformation);\n+            inHeader.getReadGroups().forEach(singleOutHeader::addReadGroup);\n+            headerMap = Collections.singletonMap(null,singleOutHeader);\n+        }\n+        return headerMap;\n+    }\n+\n+    private SAMFileHeader createOutHeader(\n+            final SAMFileHeader inHeader,\n+            final SAMFileHeader.SortOrder sortOrder,\n+            final boolean removeAlignmentInformation) {\n+        final SAMFileHeader outHeader = new SAMFileHeader();\n+        outHeader.setSortOrder(sortOrder);\n+        if (!removeAlignmentInformation) {\n+            outHeader.setSequenceDictionary(inHeader.getSequenceDictionary());\n+            outHeader.setProgramRecords(inHeader.getProgramRecords());\n+        }\n+        return outHeader;\n+    }\n+\n+    @VisibleForTesting\n+    static String getDefaultExtension(final String input, final FileType setting) {\n+        if (setting == FileType.dynamic) {\n+            if (input.endsWith(\".sam\")) {\n+                return \".sam\";\n+            }\n+            if (input.endsWith(\".cram\")) {\n+                throw new GATKException(\"Input file is a cram. This is currently unsupported for this tool\");//TODO unsupported feature\n+            }\n+            return \".bam\";\n+        } else {\n+            return \".\" + setting.toString();\n+        }\n+    }\n+\n+    /**\n+     * Takes an individual SAMRecord and applies the set of changes/reversions to it that\n+     * have been requested by program level options.\n+     */\n+    public JavaRDD<GATKRead> revertReads(JavaRDD<GATKRead> reads, List<String> attributesToClear) {\n+        Broadcast<List<String>> attrBroadcast = JavaSparkContext.fromSparkContext(reads.context()).broadcast(attributesToClear);\n+\n+        if (!dontRestoreOriginalQualities) {\n+            reads = reads.map(r -> {\n+                final byte[] oq = r.getOriginalBaseQualities();\n+                if (oq != null) {\n+                    r.setBaseQualities(oq);\n+                    r.setAttribute(\"OQ\", (String)null);\n+                }\n+                return r;\n+            });\n+        }\n+\n+        if (!dontRemoveDuplicateInformation) {\n+            reads = reads.map(r -> {r.setIsDuplicate(false); return r;});\n+        }\n+\n+        if (!dontRemoveAlignmentInformation) {\n+            reads = reads.map(rec -> {\n+                if (rec.isReverseStrand()) {\n+                    rec.reverseComplement();\n+                    rec.setIsReverseStrand(false);\n+                }\n+\n+                // Remove all alignment based information about the read itself\n+                rec.setIsUnplaced();\n+                rec.setCigar(SAMRecord.NO_ALIGNMENT_CIGAR);\n+\n+                rec.setInferredInsertSize(0);\n+                rec.setIsSecondaryAlignment(false);\n+                rec.setIsProperlyPaired(false);\n+\n+                // Then remove any mate flags and info related to alignment\n+                rec.setMateIsUnplaced();\n+\n+                // And then remove any tags that are calculated from the alignment\n+                attrBroadcast.getValue().forEach(tag -> rec.setAttribute(tag, (String) null));\n+                return rec;\n+            });\n+        }\n+\n+        return reads;\n+    }\n+\n+    @VisibleForTesting\n+    static Map<String, File> getOutputMap(\n+            final File outputMapFile,\n+            final File outputDir,\n+            final String defaultExtension,\n+            final List<SAMReadGroupRecord> readGroups,\n+            final boolean outputByReadgroup) {\n+\n+        if (outputByReadgroup) {\n+            final Map<String, File> outputMap;\n+            if (outputMapFile != null) {\n+                try {\n+                    outputMap = createOutputMapFromFile(outputMapFile);\n+                } catch (IOException e) {\n+                    throw new GATKException(\"Encountered an error reading output map file\", e);\n+                }\n+            } else {\n+                outputMap = createOutputMapFromHeader(readGroups, outputDir, defaultExtension);\n+            }\n+            return outputMap;\n+        } else {\n+            return Collections.singletonMap(null, outputDir);\n+        }\n+    }\n+\n+    // Names the files based on the locations laid out in the readgroup map\n+    private static Map<String, File> createOutputMapFromFile(final File outputMapFile) throws IOException {\n+        final Map<String, File> outputMap = new HashMap<>();\n+        final FeatureReader<TableFeature>  parser = AbstractFeatureReader.getFeatureReader(outputMapFile.getAbsolutePath(), new TableCodec(null), false);\n+        for (final TableFeature row : parser.iterator()) {\n+            final String id = row.get(\"READ_GROUP_ID\");\n+            final String output = row.get(\"OUTPUT\");\n+            final File outputPath = new File(output);\n+            outputMap.put(id, outputPath);\n+        }\n+        CloserUtil.close(parser);\n+        return outputMap;\n+    }\n+\n+    // Names the files based on the readgroups individually presented in the header\n+    private static Map<String, File> createOutputMapFromHeader(final List<SAMReadGroupRecord> readGroups, final File outputDir, final String extension) {\n+        final Map<String, File> outputMap = new HashMap<>();\n+        for (final SAMReadGroupRecord readGroup : readGroups) {\n+            final String id = readGroup.getId();\n+            final String fileName = id + extension;\n+            final Path outputPath = Paths.get(outputDir.toString(), fileName);\n+            outputMap.put(id, outputPath.toFile());\n+        }\n+        return outputMap;\n+    }\n+\n+// ________________________________________________________________________________________________________________________\n+// sum garbage\n+// ________________________________________________________________________________________________________________________\n+    /**\n+     * Methods used for validating parameters to RevertSam.\n+     */\n+    static class ValidationUtil {\n+\n+        static void validateOutputParams(final boolean outputByReadGroup, final File output, final File outputMap, final List<String> errors) {\n+            if (outputByReadGroup) {\n+                validateOutputParamsByReadGroup(output, outputMap, errors);\n+            } else {\n+                validateOutputParamsNotByReadGroup(output, outputMap, errors);\n+            }\n+        }\n+\n+        static void validateOutputParamsByReadGroup(final File output, final File outputMap, final List<String> errors) {\n+            if (output != null) {\n+                if (!Files.isDirectory(output.toPath())) {\n+                    errors.add(\"When '--output-by-readgroup' is set and output is provided, it must be a directory: \" + output);\n+                }\n+                return;\n+            }\n+            // output is null if we reached here\n+            if (outputMap == null) {\n+                errors.add(\"Must provide either output or outputMap when '--output-by-readgroup' is set.\");\n+                return;\n+            }\n+            if (!Files.isReadable(outputMap.toPath())) {\n+                errors.add(\"Cannot read outputMap \" + outputMap);\n+                return;\n+            }\n+            final FeatureReader<TableFeature>  parser = AbstractFeatureReader.getFeatureReader(outputMap.getAbsolutePath(), new TableCodec(null),false);\n+            if (!RevertSamSpark.ValidationUtil.isOutputMapHeaderValid((List<String>)parser.getHeader())) {","path":"src/main/java/org/broadinstitute/hellbender/tools/spark/RevertSamSpark.java","position":640,"original_position":640,"commit_id":"8bb761dd96038b34f8ce86c39d511134b58c3098","original_commit_id":"8bb761dd96038b34f8ce86c39d511134b58c3098","user":{"login":"lbergelson","id":4700332,"node_id":"MDQ6VXNlcjQ3MDAzMzI=","avatar_url":"https://avatars3.githubusercontent.com/u/4700332?v=4","gravatar_id":"","url":"https://api.github.com/users/lbergelson","html_url":"https://github.com/lbergelson","followers_url":"https://api.github.com/users/lbergelson/followers","following_url":"https://api.github.com/users/lbergelson/following{/other_user}","gists_url":"https://api.github.com/users/lbergelson/gists{/gist_id}","starred_url":"https://api.github.com/users/lbergelson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lbergelson/subscriptions","organizations_url":"https://api.github.com/users/lbergelson/orgs","repos_url":"https://api.github.com/users/lbergelson/repos","events_url":"https://api.github.com/users/lbergelson/events{/privacy}","received_events_url":"https://api.github.com/users/lbergelson/received_events","type":"User","site_admin":false},"body":"you need to add a `SuppressWarnings(\"unchecked\")` to this method or you'll get a warning.","created_at":"2018-11-20T20:04:12Z","updated_at":"2018-11-20T20:41:09Z","html_url":"https://github.com/broadinstitute/gatk/pull/5395#discussion_r235150887","pull_request_url":"https://api.github.com/repos/broadinstitute/gatk/pulls/5395","author_association":"COLLABORATOR","_links":{"self":{"href":"https://api.github.com/repos/broadinstitute/gatk/pulls/comments/235150887"},"html":{"href":"https://github.com/broadinstitute/gatk/pull/5395#discussion_r235150887"},"pull_request":{"href":"https://api.github.com/repos/broadinstitute/gatk/pulls/5395"}}},"pull_request":{"url":"https://api.github.com/repos/broadinstitute/gatk/pulls/5395","id":229144685,"node_id":"MDExOlB1bGxSZXF1ZXN0MjI5MTQ0Njg1","html_url":"https://github.com/broadinstitute/gatk/pull/5395","diff_url":"https://github.com/broadinstitute/gatk/pull/5395.diff","patch_url":"https://github.com/broadinstitute/gatk/pull/5395.patch","issue_url":"https://api.github.com/repos/broadinstitute/gatk/issues/5395","number":5395,"state":"open","locked":false,"title":"Port of RevertSam tool into Spark","user":{"login":"jamesemery","id":16102845,"node_id":"MDQ6VXNlcjE2MTAyODQ1","avatar_url":"https://avatars2.githubusercontent.com/u/16102845?v=4","gravatar_id":"","url":"https://api.github.com/users/jamesemery","html_url":"https://github.com/jamesemery","followers_url":"https://api.github.com/users/jamesemery/followers","following_url":"https://api.github.com/users/jamesemery/following{/other_user}","gists_url":"https://api.github.com/users/jamesemery/gists{/gist_id}","starred_url":"https://api.github.com/users/jamesemery/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jamesemery/subscriptions","organizations_url":"https://api.github.com/users/jamesemery/orgs","repos_url":"https://api.github.com/users/jamesemery/repos","events_url":"https://api.github.com/users/jamesemery/events{/privacy}","received_events_url":"https://api.github.com/users/jamesemery/received_events","type":"User","site_admin":false},"body":"Blocked by #5320 \r\n\r\nFixes #4461 ","created_at":"2018-11-07T19:37:22Z","updated_at":"2018-11-20T20:41:08Z","closed_at":null,"merged_at":null,"merge_commit_sha":"39cec1b20f47363368476f7e257048d930c54324","assignee":{"login":"lbergelson","id":4700332,"node_id":"MDQ6VXNlcjQ3MDAzMzI=","avatar_url":"https://avatars3.githubusercontent.com/u/4700332?v=4","gravatar_id":"","url":"https://api.github.com/users/lbergelson","html_url":"https://github.com/lbergelson","followers_url":"https://api.github.com/users/lbergelson/followers","following_url":"https://api.github.com/users/lbergelson/following{/other_user}","gists_url":"https://api.github.com/users/lbergelson/gists{/gist_id}","starred_url":"https://api.github.com/users/lbergelson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lbergelson/subscriptions","organizations_url":"https://api.github.com/users/lbergelson/orgs","repos_url":"https://api.github.com/users/lbergelson/repos","events_url":"https://api.github.com/users/lbergelson/events{/privacy}","received_events_url":"https://api.github.com/users/lbergelson/received_events","type":"User","site_admin":false},"assignees":[{"login":"lbergelson","id":4700332,"node_id":"MDQ6VXNlcjQ3MDAzMzI=","avatar_url":"https://avatars3.githubusercontent.com/u/4700332?v=4","gravatar_id":"","url":"https://api.github.com/users/lbergelson","html_url":"https://github.com/lbergelson","followers_url":"https://api.github.com/users/lbergelson/followers","following_url":"https://api.github.com/users/lbergelson/following{/other_user}","gists_url":"https://api.github.com/users/lbergelson/gists{/gist_id}","starred_url":"https://api.github.com/users/lbergelson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lbergelson/subscriptions","organizations_url":"https://api.github.com/users/lbergelson/orgs","repos_url":"https://api.github.com/users/lbergelson/repos","events_url":"https://api.github.com/users/lbergelson/events{/privacy}","received_events_url":"https://api.github.com/users/lbergelson/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/broadinstitute/gatk/pulls/5395/commits","review_comments_url":"https://api.github.com/repos/broadinstitute/gatk/pulls/5395/comments","review_comment_url":"https://api.github.com/repos/broadinstitute/gatk/pulls/comments{/number}","comments_url":"https://api.github.com/repos/broadinstitute/gatk/issues/5395/comments","statuses_url":"https://api.github.com/repos/broadinstitute/gatk/statuses/8bb761dd96038b34f8ce86c39d511134b58c3098","head":{"label":"broadinstitute:je_portRevertSam","ref":"je_portRevertSam","sha":"8bb761dd96038b34f8ce86c39d511134b58c3098","user":{"login":"broadinstitute","id":393552,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM5MzU1Mg==","avatar_url":"https://avatars0.githubusercontent.com/u/393552?v=4","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,"node_id":"MDEwOlJlcG9zaXRvcnkyNzQ1MjgwNw==","name":"gatk","full_name":"broadinstitute/gatk","private":false,"owner":{"login":"broadinstitute","id":393552,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM5MzU1Mg==","avatar_url":"https://avatars0.githubusercontent.com/u/393552?v=4","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},"html_url":"https://github.com/broadinstitute/gatk","description":"Official code repository for GATK versions 4 and up","fork":false,"url":"https://api.github.com/repos/broadinstitute/gatk","forks_url":"https://api.github.com/repos/broadinstitute/gatk/forks","keys_url":"https://api.github.com/repos/broadinstitute/gatk/keys{/key_id}","collaborators_url":"https://api.github.com/repos/broadinstitute/gatk/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/broadinstitute/gatk/teams","hooks_url":"https://api.github.com/repos/broadinstitute/gatk/hooks","issue_events_url":"https://api.github.com/repos/broadinstitute/gatk/issues/events{/number}","events_url":"https://api.github.com/repos/broadinstitute/gatk/events","assignees_url":"https://api.github.com/repos/broadinstitute/gatk/assignees{/user}","branches_url":"https://api.github.com/repos/broadinstitute/gatk/branches{/branch}","tags_url":"https://api.github.com/repos/broadinstitute/gatk/tags","blobs_url":"https://api.github.com/repos/broadinstitute/gatk/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/broadinstitute/gatk/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/broadinstitute/gatk/git/refs{/sha}","trees_url":"https://api.github.com/repos/broadinstitute/gatk/git/trees{/sha}","statuses_url":"https://api.github.com/repos/broadinstitute/gatk/statuses/{sha}","languages_url":"https://api.github.com/repos/broadinstitute/gatk/languages","stargazers_url":"https://api.github.com/repos/broadinstitute/gatk/stargazers","contributors_url":"https://api.github.com/repos/broadinstitute/gatk/contributors","subscribers_url":"https://api.github.com/repos/broadinstitute/gatk/subscribers","subscription_url":"https://api.github.com/repos/broadinstitute/gatk/subscription","commits_url":"https://api.github.com/repos/broadinstitute/gatk/commits{/sha}","git_commits_url":"https://api.github.com/repos/broadinstitute/gatk/git/commits{/sha}","comments_url":"https://api.github.com/repos/broadinstitute/gatk/comments{/number}","issue_comment_url":"https://api.github.com/repos/broadinstitute/gatk/issues/comments{/number}","contents_url":"https://api.github.com/repos/broadinstitute/gatk/contents/{+path}","compare_url":"https://api.github.com/repos/broadinstitute/gatk/compare/{base}...{head}","merges_url":"https://api.github.com/repos/broadinstitute/gatk/merges","archive_url":"https://api.github.com/repos/broadinstitute/gatk/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/broadinstitute/gatk/downloads","issues_url":"https://api.github.com/repos/broadinstitute/gatk/issues{/number}","pulls_url":"https://api.github.com/repos/broadinstitute/gatk/pulls{/number}","milestones_url":"https://api.github.com/repos/broadinstitute/gatk/milestones{/number}","notifications_url":"https://api.github.com/repos/broadinstitute/gatk/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/broadinstitute/gatk/labels{/name}","releases_url":"https://api.github.com/repos/broadinstitute/gatk/releases{/id}","deployments_url":"https://api.github.com/repos/broadinstitute/gatk/deployments","created_at":"2014-12-02T20:50:36Z","updated_at":"2018-11-20T16:33:31Z","pushed_at":"2018-11-20T19:43:21Z","git_url":"git://github.com/broadinstitute/gatk.git","ssh_url":"git@github.com:broadinstitute/gatk.git","clone_url":"https://github.com/broadinstitute/gatk.git","svn_url":"https://github.com/broadinstitute/gatk","homepage":"https://software.broadinstitute.org/gatk","size":236301,"stargazers_count":493,"watchers_count":493,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":202,"mirror_url":null,"archived":false,"open_issues_count":1003,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause","node_id":"MDc6TGljZW5zZTU="},"forks":202,"open_issues":1003,"watchers":493,"default_branch":"master"}},"base":{"label":"broadinstitute:master","ref":"master","sha":"2b48dcf6fac01dee137feea6b693d9688ef719da","user":{"login":"broadinstitute","id":393552,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM5MzU1Mg==","avatar_url":"https://avatars0.githubusercontent.com/u/393552?v=4","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,"node_id":"MDEwOlJlcG9zaXRvcnkyNzQ1MjgwNw==","name":"gatk","full_name":"broadinstitute/gatk","private":false,"owner":{"login":"broadinstitute","id":393552,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM5MzU1Mg==","avatar_url":"https://avatars0.githubusercontent.com/u/393552?v=4","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},"html_url":"https://github.com/broadinstitute/gatk","description":"Official code repository for GATK versions 4 and up","fork":false,"url":"https://api.github.com/repos/broadinstitute/gatk","forks_url":"https://api.github.com/repos/broadinstitute/gatk/forks","keys_url":"https://api.github.com/repos/broadinstitute/gatk/keys{/key_id}","collaborators_url":"https://api.github.com/repos/broadinstitute/gatk/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/broadinstitute/gatk/teams","hooks_url":"https://api.github.com/repos/broadinstitute/gatk/hooks","issue_events_url":"https://api.github.com/repos/broadinstitute/gatk/issues/events{/number}","events_url":"https://api.github.com/repos/broadinstitute/gatk/events","assignees_url":"https://api.github.com/repos/broadinstitute/gatk/assignees{/user}","branches_url":"https://api.github.com/repos/broadinstitute/gatk/branches{/branch}","tags_url":"https://api.github.com/repos/broadinstitute/gatk/tags","blobs_url":"https://api.github.com/repos/broadinstitute/gatk/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/broadinstitute/gatk/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/broadinstitute/gatk/git/refs{/sha}","trees_url":"https://api.github.com/repos/broadinstitute/gatk/git/trees{/sha}","statuses_url":"https://api.github.com/repos/broadinstitute/gatk/statuses/{sha}","languages_url":"https://api.github.com/repos/broadinstitute/gatk/languages","stargazers_url":"https://api.github.com/repos/broadinstitute/gatk/stargazers","contributors_url":"https://api.github.com/repos/broadinstitute/gatk/contributors","subscribers_url":"https://api.github.com/repos/broadinstitute/gatk/subscribers","subscription_url":"https://api.github.com/repos/broadinstitute/gatk/subscription","commits_url":"https://api.github.com/repos/broadinstitute/gatk/commits{/sha}","git_commits_url":"https://api.github.com/repos/broadinstitute/gatk/git/commits{/sha}","comments_url":"https://api.github.com/repos/broadinstitute/gatk/comments{/number}","issue_comment_url":"https://api.github.com/repos/broadinstitute/gatk/issues/comments{/number}","contents_url":"https://api.github.com/repos/broadinstitute/gatk/contents/{+path}","compare_url":"https://api.github.com/repos/broadinstitute/gatk/compare/{base}...{head}","merges_url":"https://api.github.com/repos/broadinstitute/gatk/merges","archive_url":"https://api.github.com/repos/broadinstitute/gatk/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/broadinstitute/gatk/downloads","issues_url":"https://api.github.com/repos/broadinstitute/gatk/issues{/number}","pulls_url":"https://api.github.com/repos/broadinstitute/gatk/pulls{/number}","milestones_url":"https://api.github.com/repos/broadinstitute/gatk/milestones{/number}","notifications_url":"https://api.github.com/repos/broadinstitute/gatk/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/broadinstitute/gatk/labels{/name}","releases_url":"https://api.github.com/repos/broadinstitute/gatk/releases{/id}","deployments_url":"https://api.github.com/repos/broadinstitute/gatk/deployments","created_at":"2014-12-02T20:50:36Z","updated_at":"2018-11-20T16:33:31Z","pushed_at":"2018-11-20T19:43:21Z","git_url":"git://github.com/broadinstitute/gatk.git","ssh_url":"git@github.com:broadinstitute/gatk.git","clone_url":"https://github.com/broadinstitute/gatk.git","svn_url":"https://github.com/broadinstitute/gatk","homepage":"https://software.broadinstitute.org/gatk","size":236301,"stargazers_count":493,"watchers_count":493,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":202,"mirror_url":null,"archived":false,"open_issues_count":1003,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause","node_id":"MDc6TGljZW5zZTU="},"forks":202,"open_issues":1003,"watchers":493,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/broadinstitute/gatk/pulls/5395"},"html":{"href":"https://github.com/broadinstitute/gatk/pull/5395"},"issue":{"href":"https://api.github.com/repos/broadinstitute/gatk/issues/5395"},"comments":{"href":"https://api.github.com/repos/broadinstitute/gatk/issues/5395/comments"},"review_comments":{"href":"https://api.github.com/repos/broadinstitute/gatk/pulls/5395/comments"},"review_comment":{"href":"https://api.github.com/repos/broadinstitute/gatk/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/broadinstitute/gatk/pulls/5395/commits"},"statuses":{"href":"https://api.github.com/repos/broadinstitute/gatk/statuses/8bb761dd96038b34f8ce86c39d511134b58c3098"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"lbergelson"}}