forked from mirrors/jj
git-push: Display messages from remote
The implementation of sideband progress message printing is aligned with
Git's implementation. See
43072b4ca1/sideband.c (L178)
.
Closes #3236.
This commit is contained in:
parent
2831459a95
commit
3034dbba3f
4 changed files with 113 additions and 9 deletions
|
@ -38,6 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
* Timestamps are now shown in local timezone and without milliseconds and
|
* Timestamps are now shown in local timezone and without milliseconds and
|
||||||
timezone offset by default.
|
timezone offset by default.
|
||||||
|
|
||||||
|
* `jj git push` now prints messages from the remote.
|
||||||
|
|
||||||
### Fixed bugs
|
### Fixed bugs
|
||||||
|
|
||||||
## [0.15.1] - 2024-03-06
|
## [0.15.1] - 2024-03-06
|
||||||
|
|
|
@ -50,7 +50,7 @@ use crate::command_error::{
|
||||||
};
|
};
|
||||||
use crate::git_util::{
|
use crate::git_util::{
|
||||||
get_git_repo, is_colocated_git_workspace, print_failed_git_export, print_git_import_stats,
|
get_git_repo, is_colocated_git_workspace, print_failed_git_export, print_git_import_stats,
|
||||||
with_remote_git_callbacks,
|
with_remote_git_callbacks, GitSidebandProgressMessageWriter,
|
||||||
};
|
};
|
||||||
use crate::ui::Ui;
|
use crate::ui::Ui;
|
||||||
|
|
||||||
|
@ -535,7 +535,7 @@ fn cmd_git_fetch(
|
||||||
};
|
};
|
||||||
let mut tx = workspace_command.start_transaction();
|
let mut tx = workspace_command.start_transaction();
|
||||||
for remote in &remotes {
|
for remote in &remotes {
|
||||||
let stats = with_remote_git_callbacks(ui, |cb| {
|
let stats = with_remote_git_callbacks(ui, None, |cb| {
|
||||||
git::fetch(
|
git::fetch(
|
||||||
tx.mut_repo(),
|
tx.mut_repo(),
|
||||||
&git_repo,
|
&git_repo,
|
||||||
|
@ -758,7 +758,7 @@ fn do_git_clone(
|
||||||
git_repo.remote(remote_name, source).unwrap();
|
git_repo.remote(remote_name, source).unwrap();
|
||||||
let mut fetch_tx = workspace_command.start_transaction();
|
let mut fetch_tx = workspace_command.start_transaction();
|
||||||
|
|
||||||
let stats = with_remote_git_callbacks(ui, |cb| {
|
let stats = with_remote_git_callbacks(ui, None, |cb| {
|
||||||
git::fetch(
|
git::fetch(
|
||||||
fetch_tx.mut_repo(),
|
fetch_tx.mut_repo(),
|
||||||
&git_repo,
|
&git_repo,
|
||||||
|
@ -1049,7 +1049,11 @@ fn cmd_git_push(
|
||||||
branch_updates,
|
branch_updates,
|
||||||
force_pushed_branches,
|
force_pushed_branches,
|
||||||
};
|
};
|
||||||
with_remote_git_callbacks(ui, |cb| {
|
let mut writer = GitSidebandProgressMessageWriter::new(ui);
|
||||||
|
let mut sideband_progress_callback = |progress_message: &[u8]| {
|
||||||
|
_ = writer.write(ui, progress_message);
|
||||||
|
};
|
||||||
|
with_remote_git_callbacks(ui, Some(&mut sideband_progress_callback), |cb| {
|
||||||
git::push_branches(tx.mut_repo(), &git_repo, &remote, &targets, cb)
|
git::push_branches(tx.mut_repo(), &git_repo, &remote, &targets, cb)
|
||||||
})
|
})
|
||||||
.map_err(|err| match err {
|
.map_err(|err| match err {
|
||||||
|
@ -1061,6 +1065,7 @@ fn cmd_git_push(
|
||||||
),
|
),
|
||||||
_ => user_error(err),
|
_ => user_error(err),
|
||||||
})?;
|
})?;
|
||||||
|
writer.flush(ui)?;
|
||||||
tx.finish(ui, tx_description)?;
|
tx.finish(ui, tx_description)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,18 +137,108 @@ fn get_ssh_keys(_username: &str) -> Vec<PathBuf> {
|
||||||
paths
|
paths
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_remote_git_callbacks<T>(ui: &Ui, f: impl FnOnce(git::RemoteCallbacks<'_>) -> T) -> T {
|
// Based on Git's implementation: https://github.com/git/git/blob/43072b4ca132437f21975ac6acc6b72dc22fd398/sideband.c#L178
|
||||||
let mut callback = None;
|
pub struct GitSidebandProgressMessageWriter {
|
||||||
|
display_prefix: &'static [u8],
|
||||||
|
suffix: &'static [u8],
|
||||||
|
scratch: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GitSidebandProgressMessageWriter {
|
||||||
|
pub fn new(ui: &Ui) -> Self {
|
||||||
|
let is_terminal = ui.use_progress_indicator();
|
||||||
|
|
||||||
|
GitSidebandProgressMessageWriter {
|
||||||
|
display_prefix: "remote: ".as_bytes(),
|
||||||
|
suffix: if is_terminal { "\x1B[K" } else { " " }.as_bytes(),
|
||||||
|
scratch: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, ui: &Ui, progress_message: &[u8]) -> std::io::Result<()> {
|
||||||
|
let mut index = 0;
|
||||||
|
// Append a suffix to each nonempty line to clear the end of the screen line.
|
||||||
|
loop {
|
||||||
|
let Some(i) = progress_message[index..]
|
||||||
|
.iter()
|
||||||
|
.position(|&c| c == b'\r' || c == b'\n')
|
||||||
|
.map(|i| index + i)
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
let line_length = i - index;
|
||||||
|
|
||||||
|
// For messages sent across the packet boundary, there would be a nonempty
|
||||||
|
// "scratch" buffer from last call of this function, and there may be a leading
|
||||||
|
// CR/LF in this message. For this case we should add a clear-to-eol suffix to
|
||||||
|
// clean leftover letters we previously have written on the same line.
|
||||||
|
if !self.scratch.is_empty() && line_length == 0 {
|
||||||
|
self.scratch.extend_from_slice(self.suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.scratch.is_empty() {
|
||||||
|
self.scratch.extend_from_slice(self.display_prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not add the clear-to-eol suffix to empty lines:
|
||||||
|
// For progress reporting we may receive a bunch of percentage updates
|
||||||
|
// followed by '\r' to remain on the same line, and at the end receive a single
|
||||||
|
// '\n' to move to the next line. We should preserve the final
|
||||||
|
// status report line by not appending clear-to-eol suffix to this single line
|
||||||
|
// break.
|
||||||
|
if line_length > 0 {
|
||||||
|
self.scratch.extend_from_slice(&progress_message[index..i]);
|
||||||
|
self.scratch.extend_from_slice(self.suffix);
|
||||||
|
}
|
||||||
|
self.scratch.extend_from_slice(&progress_message[i..i + 1]);
|
||||||
|
|
||||||
|
ui.stderr().write_all(&self.scratch)?;
|
||||||
|
self.scratch.clear();
|
||||||
|
|
||||||
|
index = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add leftover message to "scratch" buffer to be printed in next call.
|
||||||
|
if index < progress_message.len() && progress_message[index] != 0 {
|
||||||
|
if self.scratch.is_empty() {
|
||||||
|
self.scratch.extend_from_slice(self.display_prefix);
|
||||||
|
}
|
||||||
|
self.scratch.extend_from_slice(&progress_message[index..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flush(&mut self, ui: &Ui) -> std::io::Result<()> {
|
||||||
|
if !self.scratch.is_empty() {
|
||||||
|
self.scratch.push(b'\n');
|
||||||
|
ui.stderr().write_all(&self.scratch)?;
|
||||||
|
self.scratch.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SidebandProgressCallback<'a> = &'a mut dyn FnMut(&[u8]);
|
||||||
|
|
||||||
|
pub fn with_remote_git_callbacks<T>(
|
||||||
|
ui: &Ui,
|
||||||
|
sideband_progress_callback: Option<SidebandProgressCallback<'_>>,
|
||||||
|
f: impl FnOnce(git::RemoteCallbacks<'_>) -> T,
|
||||||
|
) -> T {
|
||||||
|
let mut callbacks = git::RemoteCallbacks::default();
|
||||||
|
let mut progress_callback = None;
|
||||||
if let Some(mut output) = ui.progress_output() {
|
if let Some(mut output) = ui.progress_output() {
|
||||||
let mut progress = Progress::new(Instant::now());
|
let mut progress = Progress::new(Instant::now());
|
||||||
callback = Some(move |x: &git::Progress| {
|
progress_callback = Some(move |x: &git::Progress| {
|
||||||
_ = progress.update(Instant::now(), x, &mut output);
|
_ = progress.update(Instant::now(), x, &mut output);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let mut callbacks = git::RemoteCallbacks::default();
|
callbacks.progress = progress_callback
|
||||||
callbacks.progress = callback
|
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.map(|x| x as &mut dyn FnMut(&git::Progress));
|
.map(|x| x as &mut dyn FnMut(&git::Progress));
|
||||||
|
callbacks.sideband_progress = sideband_progress_callback.map(|x| x as &mut dyn FnMut(&[u8]));
|
||||||
let mut get_ssh_keys = get_ssh_keys; // Coerce to unit fn type
|
let mut get_ssh_keys = get_ssh_keys; // Coerce to unit fn type
|
||||||
callbacks.get_ssh_keys = Some(&mut get_ssh_keys);
|
callbacks.get_ssh_keys = Some(&mut get_ssh_keys);
|
||||||
let mut get_pw =
|
let mut get_pw =
|
||||||
|
|
|
@ -1362,6 +1362,7 @@ fn push_refs(
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub struct RemoteCallbacks<'a> {
|
pub struct RemoteCallbacks<'a> {
|
||||||
pub progress: Option<&'a mut dyn FnMut(&Progress)>,
|
pub progress: Option<&'a mut dyn FnMut(&Progress)>,
|
||||||
|
pub sideband_progress: Option<&'a mut dyn FnMut(&[u8])>,
|
||||||
pub get_ssh_keys: Option<&'a mut dyn FnMut(&str) -> Vec<PathBuf>>,
|
pub get_ssh_keys: Option<&'a mut dyn FnMut(&str) -> Vec<PathBuf>>,
|
||||||
pub get_password: Option<&'a mut dyn FnMut(&str, &str) -> Option<String>>,
|
pub get_password: Option<&'a mut dyn FnMut(&str, &str) -> Option<String>>,
|
||||||
pub get_username_password: Option<&'a mut dyn FnMut(&str) -> Option<(String, String)>>,
|
pub get_username_password: Option<&'a mut dyn FnMut(&str) -> Option<(String, String)>>,
|
||||||
|
@ -1381,6 +1382,12 @@ impl<'a> RemoteCallbacks<'a> {
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if let Some(sideband_progress_cb) = self.sideband_progress {
|
||||||
|
callbacks.sideband_progress(move |data| {
|
||||||
|
sideband_progress_cb(data);
|
||||||
|
true
|
||||||
|
});
|
||||||
|
}
|
||||||
// TODO: We should expose the callbacks to the caller instead -- the library
|
// TODO: We should expose the callbacks to the caller instead -- the library
|
||||||
// crate shouldn't read environment variables.
|
// crate shouldn't read environment variables.
|
||||||
let mut tried_ssh_agent = false;
|
let mut tried_ssh_agent = false;
|
||||||
|
|
Loading…
Reference in a new issue