From a4967cf53207c5a11a9bd4031b22e2bfe1e3a479 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 16 May 2026 23:11:21 +0300 Subject: [PATCH] clone: stream git clone progress to repo's progress file --- git/git.v | 52 +++++++++++++++++++++++++++++++ static/css/gitly.scss | 16 ++++++++++ templates/cloning_in_process.html | 6 +++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/git/git.v b/git/git.v index 99096a6..b54b6b5 100644 --- a/git/git.v +++ b/git/git.v @@ -1,6 +1,7 @@ module git import os +import time pub struct Git {} @@ -29,6 +30,57 @@ pub fn Git.clone(url string, path string) os.Result { return os.exec(['git', 'clone', '--bare', url, path]) } +// Git.clone_with_progress runs `git clone --bare --progress` and streams +// every byte of git's progress output (which goes to stderr) into +// `progress_path` while the clone is running, so a separate process can +// poll the file and show live progress to the user. +pub fn Git.clone_with_progress(url string, path string, progress_path string) os.Result { + println('new clone (progress) url="${url}" path="${path}" progress="${progress_path}"') + os.rm(progress_path) or {} + mut p := os.new_process('git') + p.set_args(['clone', '--bare', '--progress', url, path]) + p.set_redirect_stdio() + p.run() + mut log := os.open_append(progress_path) or { + eprintln('clone_with_progress: cannot open progress file "${progress_path}": ${err}') + // fall back to non-streaming behaviour + p.wait() + out := p.stdout_slurp() + p.stderr_slurp() + code := p.code + p.close() + return os.Result{ + exit_code: code + output: out + } + } + mut collected := '' + for p.is_alive() { + chunk := p.stderr_read() + if chunk.len > 0 { + log.write_string(chunk) or {} + log.flush() + collected += chunk + } + // drain stdout so the pipe buffer never blocks the child + _ := p.stdout_read() + time.sleep(100 * time.millisecond) + } + final := p.stderr_slurp() + if final.len > 0 { + log.write_string(final) or {} + log.flush() + collected += final + } + log.close() + p.wait() + exit_code := p.code + p.close() + return os.Result{ + exit_code: exit_code + output: collected + } +} + pub fn Git.show_file_blob(repo_dir string, branch string, file_path string) !string { result := Git.exec_in_dir(repo_dir, ['--no-pager', 'show', '${branch}:${file_path}']) if result.exit_code != 0 { diff --git a/static/css/gitly.scss b/static/css/gitly.scss index 1aedd46..b7e81f0 100644 --- a/static/css/gitly.scss +++ b/static/css/gitly.scss @@ -101,6 +101,22 @@ pre > code { } } +.clone-status-log { + margin: 20px auto 0; + max-width: 720px; + padding: 12px 16px; + background: #f6f8fa; + border: 1px solid #e1e4e8; + border-radius: $small-radius; + color: $gray-dark; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 12px; + line-height: 1.5; + text-align: left; + white-space: pre-wrap; + word-break: break-word; +} + #top_right_div { float:right; display: flex; diff --git a/templates/cloning_in_process.html b/templates/cloning_in_process.html index 5b587e5..dba39c6 100644 --- a/templates/cloning_in_process.html +++ b/templates/cloning_in_process.html @@ -15,7 +15,11 @@ %repo_being_cloned_clone_url @clone_url

@end - + @if clone_progress != '' +
@clone_progress
+ @else + + @end -- 2.39.5