Last active
March 8, 2026 16:23
-
-
Save lboulard/ba11adf2c65b9a34a206f664b8609347 to your computer and use it in GitHub Desktop.
Parse output of git clone and show nicer progress output
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 | |
| # frozen_string_literal: true | |
| require 'open3' | |
| require 'fileutils' | |
| require 'uri' | |
| repo = ARGV[0] | |
| raise 'missing repo argument' unless repo | |
| name = ARGV[1] || File.basename(URI(repo).path).delete_suffix('.git') | |
| FileUtils.rm_rf name | |
| # Requires '--progress', when not in terminal '--quiet' is enforced | |
| args = ['git', 'clone', '--progress', repo, name] | |
| def stream_cmd(*args, &block) | |
| Open3.popen3(*args) do |_stdin, stdout, stderr, thread| | |
| readers = [stdout, stderr] | |
| until readers.empty? | |
| begin | |
| # Block until at least one pipe is readable (timeout optional) | |
| ready, = IO.select(readers, nil, nil, 5) | |
| next unless ready # timeout hit, loop again | |
| ready.each do |io| | |
| line = io.gets # read one line from whichever pipe is ready | |
| if line | |
| block.call(io == stdout ? :stdout : :stderr, line) | |
| else | |
| readers.delete(io) # EOF on this pipe, stop watching it | |
| end | |
| end | |
| rescue StandardError | |
| begin | |
| Process.kill('TERM', thread.pid) | |
| rescue StandardError => e | |
| warn "failed to kill process #{thread.pid}:\n#{e}" | |
| end | |
| raise | |
| end | |
| end | |
| thread.value.exitstatus | |
| end | |
| end | |
| EIGHTHS = [' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'].freeze | |
| def smooth_bar(current, total, width: 20) | |
| pct = current.to_f / total | |
| full_cells = pct * width | |
| filled = full_cells.floor | |
| return '█' * filled if filled == width | |
| remainder = full_cells - filled # fractional part 0..1 | |
| tip = EIGHTHS[(remainder * 8).floor] | |
| "\e[1;93m\e[1;44m#{'█' * filled}#{tip}#{' ' * (width - filled - 1)}\e[0m" | |
| end | |
| newline = false | |
| begin | |
| progress = nil | |
| percent = 0 | |
| exit_code = stream_cmd(*args) do |stream, line| | |
| case stream | |
| when :stdout then puts "OUT | #{line.chomp}" | |
| when :stderr | |
| case line | |
| in %r{^(?<prefix>(?<remote>remote: )?[\w\s]+):\s+(?<progress>\d+)%\s\((?<current>\d+)/(?<total>\d+)\)(?<suffix>.*)?} | |
| current = Regexp.last_match(:current).to_i | |
| total = Regexp.last_match(:total).to_i | |
| bar = smooth_bar(current, total) | |
| percent = Regexp.last_match(:progress).to_i | |
| prefix = Regexp.last_match(:prefix) | |
| suffix = Regexp.last_match(:suffix).chomp | |
| progress = "▕#{bar}▏#{percent.to_s.rjust 5}% | #{prefix} (#{current}/#{total}) #{suffix}\e[K" | |
| in /^remote:\s(.*)/ | |
| event = 'REMOTE' | |
| else | |
| event = 'INFO' | |
| end | |
| end | |
| $stdout.write " #{event.ljust 8}▕ #{line.chomp}\e[K\n" if event | |
| if progress | |
| event = 'PROGRESS' | |
| $stdout.write " #{event.ljust 8}▕#{progress}\e[K\r" | |
| progress = nil if percent >= 100 | |
| newline = true | |
| end | |
| ensure | |
| $stdout.flush | |
| end | |
| ensure | |
| $stdout.write "\n" if newline | |
| $stdout.write "--\n" | |
| $stdout.flush | |
| end | |
| puts "Exited with: #{exit_code}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment