Skip to content

Commit 88a9817

Browse files
authored
Merge pull request #926 from karin0/push_negotiation
Add bindings for push_negotiation callback
2 parents 76897b8 + a638e23 commit 88a9817

File tree

4 files changed

+192
-0
lines changed

4 files changed

+192
-0
lines changed

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ pub use crate::patch::Patch;
116116
pub use crate::pathspec::{Pathspec, PathspecFailedEntries, PathspecMatchList};
117117
pub use crate::pathspec::{PathspecDiffEntries, PathspecEntries};
118118
pub use crate::proxy_options::ProxyOptions;
119+
pub use crate::push_update::PushUpdate;
119120
pub use crate::rebase::{Rebase, RebaseOperation, RebaseOperationType, RebaseOptions};
120121
pub use crate::reference::{Reference, ReferenceNames, References};
121122
pub use crate::reflog::{Reflog, ReflogEntry, ReflogIter};
@@ -694,6 +695,7 @@ mod packbuilder;
694695
mod patch;
695696
mod pathspec;
696697
mod proxy_options;
698+
mod push_update;
697699
mod rebase;
698700
mod reference;
699701
mod reflog;

src/push_update.rs

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use crate::util::Binding;
2+
use crate::{raw, Oid};
3+
use std::marker;
4+
use std::str;
5+
6+
/// Represents an update which will be performed on the remote during push.
7+
pub struct PushUpdate<'a> {
8+
raw: *const raw::git_push_update,
9+
_marker: marker::PhantomData<&'a raw::git_push_update>,
10+
}
11+
12+
impl<'a> Binding for PushUpdate<'a> {
13+
type Raw = *const raw::git_push_update;
14+
unsafe fn from_raw(raw: *const raw::git_push_update) -> PushUpdate<'a> {
15+
PushUpdate {
16+
raw,
17+
_marker: marker::PhantomData,
18+
}
19+
}
20+
fn raw(&self) -> Self::Raw {
21+
self.raw
22+
}
23+
}
24+
25+
impl PushUpdate<'_> {
26+
/// Returns the source name of the reference as a byte slice.
27+
pub fn src_refname_bytes(&self) -> &[u8] {
28+
unsafe { crate::opt_bytes(self, (*self.raw).src_refname).unwrap() }
29+
}
30+
31+
/// Returns the source name of the reference.
32+
pub fn src_refname(&self) -> Option<&str> {
33+
str::from_utf8(self.src_refname_bytes()).ok()
34+
}
35+
36+
/// Returns the destination name of the reference as a byte slice.
37+
pub fn dst_refname_bytes(&self) -> &[u8] {
38+
unsafe { crate::opt_bytes(self, (*self.raw).dst_refname).unwrap() }
39+
}
40+
41+
/// Returns the destination name of the reference.
42+
pub fn dst_refname(&self) -> Option<&str> {
43+
str::from_utf8(self.dst_refname_bytes()).ok()
44+
}
45+
46+
/// Returns the current target of the reference.
47+
pub fn src(&self) -> Oid {
48+
unsafe { Binding::from_raw(&(*self.raw).src as *const _) }
49+
}
50+
51+
/// Returns the new target for the reference.
52+
pub fn dst(&self) -> Oid {
53+
unsafe { Binding::from_raw(&(*self.raw).dst as *const _) }
54+
}
55+
}

src/remote.rs

+90
Original file line numberDiff line numberDiff line change
@@ -1014,4 +1014,94 @@ mod tests {
10141014
remote.prune(Some(callbacks)).unwrap();
10151015
assert_branch_count(&repo, 0);
10161016
}
1017+
1018+
#[test]
1019+
fn push_negotiation() {
1020+
let (_td, repo) = crate::test::repo_init();
1021+
let oid = repo.head().unwrap().target().unwrap();
1022+
1023+
let td2 = TempDir::new().unwrap();
1024+
let url = crate::test::path2url(td2.path());
1025+
let mut opts = crate::RepositoryInitOptions::new();
1026+
opts.bare(true);
1027+
opts.initial_head("main");
1028+
let remote_repo = Repository::init_opts(td2.path(), &opts).unwrap();
1029+
1030+
// reject pushing a branch
1031+
let mut remote = repo.remote("origin", &url).unwrap();
1032+
let mut updated = false;
1033+
{
1034+
let mut callbacks = RemoteCallbacks::new();
1035+
callbacks.push_negotiation(|updates| {
1036+
assert!(!updated);
1037+
updated = true;
1038+
assert_eq!(updates.len(), 1);
1039+
let u = &updates[0];
1040+
assert_eq!(u.src_refname().unwrap(), "refs/heads/main");
1041+
assert!(u.src().is_zero());
1042+
assert_eq!(u.dst_refname().unwrap(), "refs/heads/main");
1043+
assert_eq!(u.dst(), oid);
1044+
Err(crate::Error::from_str("rejected"))
1045+
});
1046+
let mut options = PushOptions::new();
1047+
options.remote_callbacks(callbacks);
1048+
assert!(remote
1049+
.push(&["refs/heads/main"], Some(&mut options))
1050+
.is_err());
1051+
}
1052+
assert!(updated);
1053+
assert_eq!(remote_repo.branches(None).unwrap().count(), 0);
1054+
1055+
// push 3 branches
1056+
let commit = repo.find_commit(oid).unwrap();
1057+
repo.branch("new1", &commit, true).unwrap();
1058+
repo.branch("new2", &commit, true).unwrap();
1059+
let mut flag = 0;
1060+
updated = false;
1061+
{
1062+
let mut callbacks = RemoteCallbacks::new();
1063+
callbacks.push_negotiation(|updates| {
1064+
assert!(!updated);
1065+
updated = true;
1066+
assert_eq!(updates.len(), 3);
1067+
for u in updates {
1068+
assert!(u.src().is_zero());
1069+
assert_eq!(u.dst(), oid);
1070+
let src_name = u.src_refname().unwrap();
1071+
let dst_name = u.dst_refname().unwrap();
1072+
match src_name {
1073+
"refs/heads/main" => {
1074+
assert_eq!(dst_name, src_name);
1075+
flag |= 1;
1076+
}
1077+
"refs/heads/new1" => {
1078+
assert_eq!(dst_name, "refs/heads/dev1");
1079+
flag |= 2;
1080+
}
1081+
"refs/heads/new2" => {
1082+
assert_eq!(dst_name, "refs/heads/dev2");
1083+
flag |= 4;
1084+
}
1085+
_ => panic!("unexpected refname: {}", src_name),
1086+
}
1087+
}
1088+
Ok(())
1089+
});
1090+
let mut options = PushOptions::new();
1091+
options.remote_callbacks(callbacks);
1092+
remote
1093+
.push(
1094+
&[
1095+
"refs/heads/main",
1096+
"refs/heads/new1:refs/heads/dev1",
1097+
"refs/heads/new2:refs/heads/dev2",
1098+
],
1099+
Some(&mut options),
1100+
)
1101+
.unwrap();
1102+
}
1103+
assert!(updated);
1104+
assert_eq!(flag, 7);
1105+
assert_eq!(remote_repo.branches(None).unwrap().count(), 3);
1106+
}
10171107
}

src/remote_callbacks.rs

+45
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::cert::Cert;
99
use crate::util::Binding;
1010
use crate::{
1111
panic, raw, Cred, CredentialType, Error, IndexerProgress, Oid, PackBuilderStage, Progress,
12+
PushUpdate,
1213
};
1314

1415
/// A structure to contain the callbacks which are invoked when a repository is
@@ -25,6 +26,7 @@ pub struct RemoteCallbacks<'a> {
2526
update_tips: Option<Box<UpdateTips<'a>>>,
2627
certificate_check: Option<Box<CertificateCheck<'a>>>,
2728
push_update_reference: Option<Box<PushUpdateReference<'a>>>,
29+
push_negotiation: Option<Box<PushNegotiation<'a>>>,
2830
}
2931

3032
/// Callback used to acquire credentials for when a remote is fetched.
@@ -87,6 +89,14 @@ pub type PushTransferProgress<'a> = dyn FnMut(usize, usize, usize) + 'a;
8789
/// * total
8890
pub type PackProgress<'a> = dyn FnMut(PackBuilderStage, usize, usize) + 'a;
8991

92+
/// Callback used to inform of upcoming updates.
93+
///
94+
/// The argument is a slice containing the updates which will be sent as
95+
/// commands to the destination.
96+
///
97+
/// The push is cancelled if an error is returned.
98+
pub type PushNegotiation<'a> = dyn FnMut(&[PushUpdate<'_>]) -> Result<(), Error> + 'a;
99+
90100
impl<'a> Default for RemoteCallbacks<'a> {
91101
fn default() -> Self {
92102
Self::new()
@@ -105,6 +115,7 @@ impl<'a> RemoteCallbacks<'a> {
105115
certificate_check: None,
106116
push_update_reference: None,
107117
push_progress: None,
118+
push_negotiation: None,
108119
}
109120
}
110121

@@ -211,6 +222,16 @@ impl<'a> RemoteCallbacks<'a> {
211222
self.pack_progress = Some(Box::new(cb) as Box<PackProgress<'a>>);
212223
self
213224
}
225+
226+
/// The callback is called once between the negotiation step and the upload.
227+
/// It provides information about what updates will be performed.
228+
pub fn push_negotiation<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
229+
where
230+
F: FnMut(&[PushUpdate<'_>]) -> Result<(), Error> + 'a,
231+
{
232+
self.push_negotiation = Some(Box::new(cb) as Box<PushNegotiation<'a>>);
233+
self
234+
}
214235
}
215236

216237
impl<'a> Binding for RemoteCallbacks<'a> {
@@ -256,6 +277,9 @@ impl<'a> Binding for RemoteCallbacks<'a> {
256277
) -> c_int = update_tips_cb;
257278
callbacks.update_tips = Some(f);
258279
}
280+
if self.push_negotiation.is_some() {
281+
callbacks.push_negotiation = Some(push_negotiation_cb);
282+
}
259283
callbacks.payload = self as *const _ as *mut _;
260284
callbacks
261285
}
@@ -471,3 +495,24 @@ extern "C" fn pack_progress_cb(
471495
})
472496
.unwrap_or(-1)
473497
}
498+
499+
extern "C" fn push_negotiation_cb(
500+
updates: *mut *const raw::git_push_update,
501+
len: size_t,
502+
payload: *mut c_void,
503+
) -> c_int {
504+
panic::wrap(|| unsafe {
505+
let payload = &mut *(payload as *mut RemoteCallbacks<'_>);
506+
let callback = match payload.push_negotiation {
507+
Some(ref mut c) => c,
508+
None => return 0,
509+
};
510+
511+
let updates = slice::from_raw_parts(updates as *mut PushUpdate<'_>, len);
512+
match callback(updates) {
513+
Ok(()) => 0,
514+
Err(e) => e.raw_code(),
515+
}
516+
})
517+
.unwrap_or(-1)
518+
}

0 commit comments

Comments
 (0)