Skip to content

Commit bf33892

Browse files
authored
feat: add git tracing features (#175)
1 parent 3d819b8 commit bf33892

File tree

5 files changed

+201
-0
lines changed

5 files changed

+201
-0
lines changed

index.d.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8028,6 +8028,46 @@ export type StatusShow = 'Index'|
80288028
'Workdir'|
80298029
'IndexAndWorkdir';
80308030

8031+
/**
8032+
* Clear the global subscriber
8033+
*
8034+
* @category Tracing
8035+
* @signature
8036+
* ```ts
8037+
* function traceClear(): void;
8038+
* ```
8039+
*/
8040+
export declare function traceClear(): void
8041+
8042+
/**
8043+
* Available tracing levels. When tracing is set to a particular level,
8044+
* callers will be provided tracing at the given level and all lower levels.
8045+
*/
8046+
export type TraceLevel = 'None'|
8047+
'Fatal'|
8048+
'Error'|
8049+
'Warn'|
8050+
'Info'|
8051+
'Debug'|
8052+
'Trace';
8053+
8054+
/**
8055+
* Set the global subscriber called when libgit2 produces a tracing message.
8056+
*
8057+
* @category Tracing
8058+
* @signature
8059+
* ```ts
8060+
* function traceSet(
8061+
* level: TraceLevel,
8062+
* callback: (level: TraceLevel, message: string) => void,
8063+
* ): void;
8064+
* ```
8065+
*
8066+
* @param {TraceLevel} level - Level to set tracing to
8067+
* @param {(level: TraceLevel, message: string) => void} callback - Callback to call with trace data
8068+
*/
8069+
export declare function traceSet(level: TraceLevel, callback: (level: TraceLevel, message: string) => void): void
8070+
80318071
/**
80328072
* - `PreOrder` : Runs the traversal in pre-order.
80338073
* - `PostOrder` : Runs the traversal in post-order.

index.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/js.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/// Source codes are from [rolldown](https://github.com/rolldown/rolldown)
2+
/// See: https://github.com/rolldown/rolldown/blob/fc5ec4dbb8cf7a9bc32f2cba6e0e82eba3ac888d/crates/rolldown_binding/src/types/js_callback.rs#L98
3+
use napi::bindgen_prelude::{FromNapiValue, JsValuesTupleIntoVec};
4+
use napi::threadsafe_function::{ThreadsafeFunction, UnknownReturnValue};
5+
use napi::{Either, Error, Status};
6+
use std::sync::{Arc, Condvar, Mutex};
7+
8+
pub type JsCallback<Args = (), Ret = ()> =
9+
Arc<ThreadsafeFunction<Args, Either<Ret, UnknownReturnValue>, Args, Status, false, true>>;
10+
11+
pub trait JsCallbackExt<Args, Ret> {
12+
fn invoke(&self, args: Args) -> Result<Ret, Error>;
13+
}
14+
15+
impl<Args, Ret> JsCallbackExt<Args, Ret> for JsCallback<Args, Ret>
16+
where
17+
Args: 'static + Send + JsValuesTupleIntoVec,
18+
Ret: 'static + Send + FromNapiValue,
19+
Either<Ret, UnknownReturnValue>: FromNapiValue,
20+
{
21+
fn invoke(&self, args: Args) -> Result<Ret, Error> {
22+
let init_value = Ok(Either::B(UnknownReturnValue));
23+
let pair = Arc::new((Mutex::new(init_value), Condvar::new()));
24+
let pair_clone = Arc::clone(&pair);
25+
26+
self.call_with_return_value(
27+
args,
28+
napi::threadsafe_function::ThreadsafeFunctionCallMode::NonBlocking,
29+
move |ret, _env| {
30+
let (lock, cvar) = &*pair;
31+
*lock.lock().unwrap() = ret;
32+
cvar.notify_one();
33+
Ok(())
34+
},
35+
);
36+
37+
let (lock, cvar) = &*pair_clone;
38+
let notified = lock.lock().unwrap();
39+
let mut res = cvar
40+
.wait(notified)
41+
.map_err(|err| Error::new(Status::GenericFailure, format!("PoisonError: {err:?}",)))?;
42+
let res = res
43+
.as_mut()
44+
.map_err(|err| Error::new(Status::GenericFailure, format!("{err:?}",)))?;
45+
46+
match std::mem::replace(res, Either::B(UnknownReturnValue)) {
47+
Either::A(ret) => Ok(ret),
48+
Either::B(_unknown) => unknown_return_err::<Ret>(),
49+
}
50+
}
51+
}
52+
53+
fn unknown_return_err<Ret>() -> Result<Ret, Error> {
54+
let js_type = "unknown";
55+
Err(Error::new(
56+
Status::InvalidArg,
57+
format!("UNKNOWN_RETURN_VALUE. Cannot convert {js_type}"),
58+
))
59+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub mod diff;
1414
mod error;
1515
pub mod ignore;
1616
pub mod index;
17+
pub(crate) mod js;
1718
pub mod mailmap;
1819
pub mod merge;
1920
pub mod note;
@@ -30,6 +31,7 @@ pub mod signature;
3031
pub mod stash;
3132
pub mod status;
3233
pub mod tag;
34+
pub mod tracing;
3335
pub mod tree;
3436
pub(crate) mod util;
3537

src/tracing.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use crate::js::{JsCallback, JsCallbackExt};
2+
use napi::bindgen_prelude::FnArgs;
3+
use napi_derive::napi;
4+
use std::sync::{Mutex, OnceLock};
5+
6+
/// Available tracing levels. When tracing is set to a particular level,
7+
/// callers will be provided tracing at the given level and all lower levels.
8+
#[napi(string_enum)]
9+
pub enum TraceLevel {
10+
None,
11+
Fatal,
12+
Error,
13+
Warn,
14+
Info,
15+
Debug,
16+
Trace,
17+
}
18+
19+
impl From<TraceLevel> for git2::TraceLevel {
20+
fn from(value: TraceLevel) -> Self {
21+
match value {
22+
TraceLevel::None => git2::TraceLevel::None,
23+
TraceLevel::Fatal => git2::TraceLevel::Fatal,
24+
TraceLevel::Error => git2::TraceLevel::Error,
25+
TraceLevel::Warn => git2::TraceLevel::Warn,
26+
TraceLevel::Info => git2::TraceLevel::Info,
27+
TraceLevel::Debug => git2::TraceLevel::Debug,
28+
TraceLevel::Trace => git2::TraceLevel::Trace,
29+
}
30+
}
31+
}
32+
33+
impl From<git2::TraceLevel> for TraceLevel {
34+
fn from(value: git2::TraceLevel) -> Self {
35+
match value {
36+
git2::TraceLevel::None => TraceLevel::None,
37+
git2::TraceLevel::Fatal => TraceLevel::Fatal,
38+
git2::TraceLevel::Error => TraceLevel::Error,
39+
git2::TraceLevel::Warn => TraceLevel::Warn,
40+
git2::TraceLevel::Info => TraceLevel::Info,
41+
git2::TraceLevel::Debug => TraceLevel::Debug,
42+
git2::TraceLevel::Trace => TraceLevel::Trace,
43+
}
44+
}
45+
}
46+
47+
pub type TracingCallback = JsCallback<FnArgs<(TraceLevel, String)>>;
48+
49+
#[allow(clippy::type_complexity)]
50+
static TRACING_CALLBACK: OnceLock<Mutex<Option<TracingCallback>>> = OnceLock::new();
51+
52+
fn tracing_callback(level: git2::TraceLevel, msg: &[u8]) {
53+
let Some(lock) = TRACING_CALLBACK.get() else { return };
54+
let Ok(guard) = lock.lock() else { return };
55+
let Some(callback) = guard.as_ref() else { return };
56+
if let Ok(message) = std::str::from_utf8(msg) {
57+
let _ = callback.invoke((level.into(), message.to_string()).into());
58+
}
59+
}
60+
61+
#[napi]
62+
/// Set the global subscriber called when libgit2 produces a tracing message.
63+
///
64+
/// @category Tracing
65+
/// @signature
66+
/// ```ts
67+
/// function traceSet(
68+
/// level: TraceLevel,
69+
/// callback: (level: TraceLevel, message: string) => void,
70+
/// ): void;
71+
/// ```
72+
///
73+
/// @param {TraceLevel} level - Level to set tracing to
74+
/// @param {(level: TraceLevel, message: string) => void} callback - Callback to call with trace data
75+
pub fn trace_set(
76+
level: TraceLevel,
77+
#[napi(ts_arg_type = "(level: TraceLevel, message: string) => void")] callback: TracingCallback,
78+
) -> crate::Result<()> {
79+
let global = TRACING_CALLBACK.get_or_init(|| Mutex::new(None));
80+
*global.lock().unwrap() = Some(callback);
81+
git2::trace_set(level.into(), tracing_callback)?;
82+
Ok(())
83+
}
84+
85+
#[napi]
86+
/// Clear the global subscriber
87+
///
88+
/// @category Tracing
89+
/// @signature
90+
/// ```ts
91+
/// function traceClear(): void;
92+
/// ```
93+
pub fn trace_clear() {
94+
if let Some(global) = TRACING_CALLBACK.get() {
95+
*global.lock().unwrap() = None;
96+
}
97+
}

0 commit comments

Comments
 (0)