Created
September 24, 2021 02:28
-
-
Save brasic/63fa7eabac02b6f1b8157034b2b3c46a to your computer and use it in GitHub Desktop.
delete_refs.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env ruby | |
| require "net/http" | |
| require "json" | |
| require "optparse" | |
| # example usage: | |
| # ./delete_refs.rb --uri https://api.github.com/graphql --file <reflist.txt> --nwo some/nwo | |
| # Delete a list of refs using the `UpdateRefs` GraphQL API. | |
| class DeleteRefs | |
| def initialize(refs:, endpoint:, nwo:, token:) | |
| @refs = refs | |
| @uri = URI(endpoint) | |
| @owner, @name = nwo.split("/", 2) | |
| @token = token | |
| if !@owner || !@name | |
| raise ArgumentError, "nwo #{nwo.inspect} is invalid, needs slash" | |
| end | |
| end | |
| MUTATION = <<-'GRAPHQL' | |
| mutation($input: UpdateRefsInput!) { | |
| updateRefs(input: $input) { | |
| clientMutationId | |
| } | |
| } | |
| GRAPHQL | |
| GET_ID = <<-'GRAPHQL' | |
| query($owner: String!, $name: String!) { | |
| repository(owner: $owner, name: $name) { | |
| id | |
| } | |
| } | |
| GRAPHQL | |
| NULL_OID = "0"*40 | |
| BATCH_SIZE = 200 | |
| def generate_deletion_payload(ref_chunk) | |
| JSON.generate( | |
| query: MUTATION, | |
| variables: { | |
| input: { | |
| repositoryId: @repo_id, | |
| refUpdates: ref_chunk.map { |ref_name| | |
| { name: ref_name, afterOid: NULL_OID } | |
| } | |
| } | |
| } | |
| ) | |
| end | |
| def generate_get_id_payload | |
| JSON.generate( | |
| query: GET_ID, | |
| variables: { | |
| owner: @owner, | |
| name: @name | |
| } | |
| ) | |
| end | |
| def parse_response(response) | |
| if response.code != "200" | |
| raise "request failed with status #{response.code}:\n#{response.body}" | |
| end | |
| parsed = JSON.parse(response.body) | |
| if parsed.key?("errors") | |
| raise "error: #{parsed["errors"][0].values_at("type", "message")}" | |
| end | |
| parsed | |
| end | |
| def get_repo_id(http) | |
| request = build_request(generate_get_id_payload) | |
| response = parse_response(http.request(request)) | |
| response.dig("data", "repository", "id") or fail "bad data:#{parsed.inspect}" | |
| end | |
| def delete_refs(ref_chunk, http) | |
| request = build_request(generate_deletion_payload(ref_chunk)) | |
| response = parse_response(http.request(request)) | |
| end | |
| def call | |
| count = 0 | |
| Net::HTTP.start(@uri.host, @uri.port, use_ssl: (@uri.scheme == 'https')) do |http| | |
| @repo_id = get_repo_id(http) | |
| @refs.each_slice(BATCH_SIZE) do |slice| | |
| delete_refs(slice, http) | |
| count += slice.size | |
| print "refs deleted so far: #{count}\r" | |
| end | |
| end | |
| puts "\ndone! total refs deleted: #{count}" | |
| end | |
| def build_request(payload) | |
| request = Net::HTTP::Post.new @uri | |
| request["Authorization"] = "bearer #{@token}" | |
| request["Accept"] = "application/vnd.github.update-refs-preview" | |
| request.body = payload | |
| request | |
| end | |
| end | |
| if $PROGRAM_NAME == __FILE__ | |
| def get_ref_list(path) | |
| lines = File.readlines(path).map(&:chomp).uniq | |
| if lines.any?{ |l| l.start_with?("refs/") } | |
| puts "all refs must be qualified (start with `refs/`)" | |
| exit 1 | |
| end | |
| lines | |
| end | |
| options = {} | |
| ARGV << "-h" if ARGV.empty? | |
| parser = OptionParser.new do |parser| | |
| parser.banner = "Usage: TOKEN=<token> #{$PROGRAM_NAME} [options]" | |
| parser.on("--uri URI", | |
| "Full URI of graphql endpoint, e.g. 'https://api.github.com/graphql'") | |
| parser.on("-f", "--file FILE", | |
| "Path to the file containing the list of refs to delete") | |
| parser.on("--nwo NWO", | |
| "Name With Owner of the repo to target, e.g. 'golang/go'") | |
| parser.on("-h", "--help", "Show this message") do | |
| puts parser | |
| exit | |
| end | |
| end.parse!(into: options) | |
| unless ENV.key?("TOKEN") | |
| puts "Please set the TOKEN env var to a PAT with sufficient rights" | |
| exit 1 | |
| end | |
| DeleteRefs.new( | |
| nwo: options.fetch(:nwo), | |
| endpoint: options.fetch(:uri), | |
| token: ENV.fetch("TOKEN"), | |
| refs: get_ref_list(options.fetch(:file)) | |
| ).call | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment