Skip to content

Commit 279ff6d

Browse files
Support push + force push over SSH
* Support receive-pack over SSH * Improve tracing in push routines * Improve error propagation between hooks and proxy * Improve push option handling: use predefined struct * Add `force` push option commit-id:2dbf5e93
1 parent cd9a4bb commit 279ff6d

34 files changed

+654
-402
lines changed

josh-proxy/src/bin/josh-proxy.rs

Lines changed: 172 additions & 105 deletions
Large diffs are not rendered by default.

josh-proxy/src/lib.rs

Lines changed: 101 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod auth;
22
pub mod cli;
33
pub mod juniper_hyper;
4+
pub mod trace;
45

56
#[macro_use]
67
extern crate lazy_static;
@@ -108,19 +109,44 @@ pub struct RepoUpdate {
108109
pub context_propagator: std::collections::HashMap<String, String>,
109110
}
110111

112+
#[derive(serde::Serialize, serde::Deserialize, Debug)]
113+
#[serde(default)]
114+
pub struct PushOptions {
115+
pub merge: bool,
116+
pub create: bool,
117+
pub force: bool,
118+
pub base: Option<String>,
119+
pub author: Option<String>,
120+
}
121+
122+
impl Default for PushOptions {
123+
fn default() -> Self {
124+
PushOptions {
125+
merge: false,
126+
create: false,
127+
force: false,
128+
base: None,
129+
author: None,
130+
}
131+
}
132+
}
133+
111134
pub fn process_repo_update(repo_update: RepoUpdate) -> josh::JoshResult<String> {
112-
let p = std::path::PathBuf::from(&repo_update.git_dir)
135+
let push_options_path = std::path::PathBuf::from(&repo_update.git_dir)
113136
.join("refs/namespaces")
114137
.join(&repo_update.git_ns)
115138
.join("push_options");
116139

117-
let push_options_string = std::fs::read_to_string(p)?;
118-
let push_options: std::collections::HashMap<String, String> =
119-
serde_json::from_str(&push_options_string)?;
140+
let push_options = std::fs::read_to_string(push_options_path)?;
141+
let push_options: PushOptions = serde_json::from_str(&push_options)
142+
.map_err(|e| josh_error(&format!("Failed to parse push options: {}", e)))?;
120143

121-
for (refname, (old, new)) in repo_update.refs.iter() {
122-
tracing::debug!("REPO_UPDATE env ok");
144+
tracing::debug!(
145+
push_options = ?push_options,
146+
"process_repo_update"
147+
);
123148

149+
for (refname, (old, new)) in repo_update.refs.iter() {
124150
let transaction = josh::cache::Transaction::open(
125151
std::path::Path::new(&repo_update.git_dir),
126152
Some(&format!("refs/josh/upstream/{}/", repo_update.base_ns)),
@@ -141,28 +167,34 @@ pub fn process_repo_update(repo_update: RepoUpdate) -> josh::JoshResult<String>
141167
)?;
142168

143169
let old = git2::Oid::from_str(old)?;
144-
145170
let (baseref, push_to, options, push_mode) = baseref_and_options(refname)?;
146-
let josh_merge = push_options.contains_key("merge");
147-
148-
tracing::debug!("push options: {:?}", push_options);
149-
tracing::debug!("josh-merge: {:?}", josh_merge);
150171

151172
let old = if old == git2::Oid::zero() {
152173
let rev = format!("refs/namespaces/{}/{}", repo_update.git_ns, &baseref);
153-
let oid = if let Ok(x) = transaction.repo().revparse_single(&rev) {
154-
x.id()
174+
let oid = if let Ok(resolved) = transaction.repo().revparse_single(&rev) {
175+
resolved.id()
155176
} else {
156177
old
157178
};
158-
tracing::debug!("push: old oid: {:?}, rev: {:?}", oid, rev);
179+
180+
tracing::debug!(
181+
old_oid = ?oid,
182+
rev = %rev,
183+
"resolve_old"
184+
);
185+
159186
oid
160187
} else {
161-
tracing::debug!("push: old oid: {:?}, refname: {:?}", old, refname);
188+
tracing::debug!(
189+
old_oid = ?old,
190+
refname = %refname,
191+
"resolve_old"
192+
);
193+
162194
old
163195
};
164196

165-
let original_target_ref = if let Some(base) = push_options.get("base") {
197+
let original_target_ref = if let Some(base) = &push_options.base {
166198
// Allow user to use just the branchname as the base:
167199
let full_path_base_refname =
168200
transaction_mirror.refname(&format!("refs/heads/{}", base));
@@ -173,7 +205,7 @@ pub fn process_repo_update(repo_update: RepoUpdate) -> josh::JoshResult<String>
173205
{
174206
full_path_base_refname
175207
} else {
176-
transaction_mirror.refname(base)
208+
transaction_mirror.refname(&base)
177209
}
178210
} else {
179211
transaction_mirror.refname(&baseref)
@@ -184,12 +216,18 @@ pub fn process_repo_update(repo_update: RepoUpdate) -> josh::JoshResult<String>
184216
.refname_to_id(&original_target_ref)
185217
{
186218
tracing::debug!(
187-
"push: original_target oid: {:?}, original_target_ref: {:?}",
188-
oid,
189-
original_target_ref
219+
original_target_oid = ?oid,
220+
original_target_ref = %original_target_ref,
221+
"resolve_original_target"
190222
);
223+
191224
oid
192225
} else {
226+
tracing::debug!(
227+
original_target_ref = %original_target_ref,
228+
"resolve_original_target"
229+
);
230+
193231
return Err(josh::josh_error(&unindent::unindent(&format!(
194232
r###"
195233
Reference {:?} does not exist on remote.
@@ -200,14 +238,14 @@ pub fn process_repo_update(repo_update: RepoUpdate) -> josh::JoshResult<String>
200238
))));
201239
};
202240

203-
let reparent_orphans = if push_options.contains_key("create") {
241+
let reparent_orphans = if push_options.create {
204242
Some(original_target)
205243
} else {
206244
None
207245
};
208246

209-
let author = if let Some(p) = push_options.get("author") {
210-
p.to_string()
247+
let author = if let Some(author) = &push_options.author {
248+
author.to_string()
211249
} else {
212250
"".to_string()
213251
};
@@ -219,26 +257,30 @@ pub fn process_repo_update(repo_update: RepoUpdate) -> josh::JoshResult<String>
219257
None
220258
};
221259

222-
let filterobj = josh::filter::parse(&repo_update.filter_spec)?;
260+
let filter = josh::filter::parse(&repo_update.filter_spec)?;
223261
let new_oid = git2::Oid::from_str(new)?;
224262
let backward_new_oid = {
225-
tracing::debug!("=== MORE");
226-
227-
tracing::debug!("=== processed_old {:?}", old);
228-
229-
josh::history::unapply_filter(
263+
let unapply_result = josh::history::unapply_filter(
230264
&transaction,
231-
filterobj,
265+
filter,
232266
original_target,
233267
old,
234268
new_oid,
235-
josh_merge,
269+
push_options.merge,
236270
reparent_orphans,
237271
&mut changes,
238-
)?
272+
)?;
273+
274+
tracing::debug!(
275+
processed_old = ?old,
276+
unapply_result = ?unapply_result,
277+
"unapply_filter"
278+
);
279+
280+
unapply_result
239281
};
240282

241-
let oid_to_push = if josh_merge {
283+
let oid_to_push = if push_options.merge {
242284
let backward_commit = transaction.repo().find_commit(backward_new_oid)?;
243285
if let Ok(base_commit_id) = transaction_mirror
244286
.repo()
@@ -301,6 +343,8 @@ pub fn process_repo_update(repo_update: RepoUpdate) -> josh::JoshResult<String>
301343
let mut resp = vec![];
302344

303345
for (reference, oid, display_name) in to_push {
346+
let force_push = push_mode != PushMode::Normal || push_options.force;
347+
304348
let (text, status) = push_head_url(
305349
transaction.repo(),
306350
&format!("{}/objects", repo_update.mirror_git_dir),
@@ -310,17 +354,17 @@ pub fn process_repo_update(repo_update: RepoUpdate) -> josh::JoshResult<String>
310354
&repo_update.remote_auth,
311355
&repo_update.git_ns,
312356
&display_name,
313-
push_mode != PushMode::Normal,
357+
force_push,
314358
)?;
359+
315360
if status != 0 {
316361
return Err(josh::josh_error(&text));
317362
}
318363

319364
resp.push(text.to_string());
320-
321365
let mut warnings = josh::filter::compute_warnings(
322366
&transaction,
323-
filterobj,
367+
filter,
324368
transaction.repo().find_commit(oid)?.tree()?,
325369
);
326370

@@ -331,7 +375,7 @@ pub fn process_repo_update(repo_update: RepoUpdate) -> josh::JoshResult<String>
331375
}
332376

333377
let reapply = josh::filter::apply_to_commit(
334-
filterobj,
378+
filter,
335379
&transaction.repo().find_commit(oid_to_push)?,
336380
&transaction,
337381
)?;
@@ -342,16 +386,22 @@ pub fn process_repo_update(repo_update: RepoUpdate) -> josh::JoshResult<String>
342386
&format!(
343387
"refs/josh/rewrites/{}/{:?}/r_{}",
344388
repo_update.base_ns,
345-
filterobj.id(),
389+
filter.id(),
346390
reapply
347391
),
348392
reapply,
349393
true,
350394
"reapply",
351395
)?;
352396
}
397+
398+
tracing::debug!(
399+
new_oid = ?new_oid,
400+
reapply = ?reapply,
401+
"rewrite"
402+
);
403+
353404
let text = format!("REWRITE({} -> {})", new_oid, reapply);
354-
tracing::debug!("{}", text);
355405
resp.push(text);
356406
}
357407

@@ -437,27 +487,29 @@ pub fn push_head_url(
437487
display_name: &str,
438488
force: bool,
439489
) -> josh::JoshResult<(String, i32)> {
440-
let rn = format!("refs/{}", &namespace);
441-
442-
let spec = format!("{}:{}", &rn, &refname);
490+
let push_temp_ref = format!("refs/{}", &namespace);
491+
let push_refspec = format!("{}:{}", &push_temp_ref, &refname);
443492

444493
let mut cmd = vec!["git", "push"];
445494
if force {
446495
cmd.push("--force")
447496
}
448497
cmd.push(url);
449-
cmd.push(&spec);
498+
cmd.push(&push_refspec);
450499

451-
let mut fakehead = repo.reference(&rn, oid, true, "push_head_url")?;
500+
let mut fake_head = repo.reference(&push_temp_ref, oid, true, "push_head_url")?;
452501
let (stdout, stderr, status) =
453502
run_git_with_auth(repo.path(), &cmd, remote_auth, Some(alternate.to_owned()))?;
454-
fakehead.delete()?;
455-
456-
tracing::debug!("{}", &stderr);
457-
tracing::debug!("{}", &stdout);
503+
fake_head.delete()?;
458504

459-
let stderr = stderr.replace(&rn, display_name);
505+
tracing::debug!(
506+
stdout = %stdout,
507+
stderr = %stderr,
508+
status = %status,
509+
"push_head_url: run_git"
510+
);
460511

512+
let stderr = stderr.replace(&push_temp_ref, display_name);
461513
Ok((stderr, status))
462514
}
463515

josh-proxy/src/trace.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use opentelemetry::global;
2+
use std::collections::HashMap;
3+
use tracing::Span;
4+
use tracing_opentelemetry::OpenTelemetrySpanExt;
5+
6+
pub fn make_context_propagator() -> HashMap<String, String> {
7+
let span = Span::current();
8+
9+
let mut context_propagator = HashMap::<String, String>::default();
10+
let context = span.context();
11+
global::get_text_map_propagator(|propagator| {
12+
propagator.inject_context(&context, &mut context_propagator);
13+
});
14+
15+
tracing::debug!("context propagator: {:?}", context_propagator);
16+
context_propagator
17+
}

run-josh.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
#!/bin/bash
2-
cd /josh/
2+
3+
cd /josh/ || exit 1
4+
5+
# shellcheck disable=SC2086
6+
# intended to pass along the arguments
37
RUST_BACKTRACE=1 josh-proxy --gc --local=/data/git/ --remote="${JOSH_REMOTE}" ${JOSH_EXTRA_OPTS}

run-tests.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ CONFIG_FILE=$(mktemp)
2222
trap 'rm ${CONFIG_FILE}' EXIT
2323

2424
export GIT_CONFIG_GLOBAL=${CONFIG_FILE}
25-
#git config --global init.defaultBranch master
2625

2726
cargo fmt
2827
python3 -m cram "$@"

tests/proxy/amend_patchset.t

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@
6060
> EOF
6161

6262
$ git push origin HEAD:refs/for/master 2>&1 >/dev/null | sed -e 's/[ ]*$//g'
63-
remote: josh-proxy
64-
remote: response from upstream:
63+
remote: josh-proxy: pre-receive hook
64+
remote: upstream: response status: 200 OK
65+
remote: upstream: response body:
66+
remote:
6567
remote: To http://localhost:8001/real_repo.git
6668
remote: * [new reference] JOSH_PUSH -> refs/for/master
67-
remote:
68-
remote:
6969
To http://localhost:8002/real_repo.git
7070
* [new reference] HEAD -> refs/for/master
7171

@@ -92,12 +92,12 @@
9292
$ git add .
9393
$ git commit --amend --no-edit -q
9494
$ git push origin HEAD:refs/for/master 2>&1 >/dev/null | sed -e 's/[ ]*$//g'
95-
remote: josh-proxy
96-
remote: response from upstream:
95+
remote: josh-proxy: pre-receive hook
96+
remote: upstream: response status: 200 OK
97+
remote: upstream: response body:
98+
remote:
9799
remote: To http://localhost:8001/real_repo.git
98100
remote: * [new reference] JOSH_PUSH -> refs/for/master
99-
remote:
100-
remote:
101101
To http://localhost:8002/real_repo.git:/sub3.git
102102
* [new reference] HEAD -> refs/for/master
103103

tests/proxy/authentication.t

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,12 @@
8787
1 file changed, 1 insertion(+)
8888
create mode 100644 file2
8989
$ git push
90-
remote: josh-proxy
91-
remote: response from upstream:
90+
remote: josh-proxy: pre-receive hook
91+
remote: upstream: response status: 200 OK
92+
remote: upstream: response body:
93+
remote:
9294
remote: To http://localhost:8001/real_repo.git
9395
remote: bb282e9..f23daa6 JOSH_PUSH -> master
94-
remote:
95-
remote:
9696
To http://localhost:8002/real_repo.git
9797
bb282e9..f23daa6 master -> master
9898

0 commit comments

Comments
 (0)