Skip to content

Commit 96fca6b

Browse files
Implement symref (HEAD) forwarding (#543)
* Implement symref (HEAD) forwarding Previously it was simply assumed that HEAD always points to refs/heads/master but as it turns out that is far from true. Now HEAD is queried per repo from the upstream and forwarded. Unfortunately it seems that it is not avoidable to to an extra request to the upstream server for this. (calling ls-remote) * Silence output of merges in tests With the new git release sometimes the merge will be done by "ort" instead of "recursive". We don't really care about that as the results are the same. * Remove expect for broken refs
1 parent 1063041 commit 96fca6b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+289
-384
lines changed

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

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@ type FetchTimers = HashMap<String, std::time::Instant>;
3434
type Polls =
3535
Arc<std::sync::Mutex<std::collections::HashSet<(String, josh_proxy::auth::Handle, String)>>>;
3636

37+
type HeadsMap = Arc<std::sync::RwLock<std::collections::HashMap<String, String>>>;
38+
3739
#[derive(Clone)]
3840
struct JoshProxyService {
3941
port: String,
4042
repo_path: std::path::PathBuf,
4143
upstream_url: String,
4244
fetch_timers: Arc<RwLock<FetchTimers>>,
45+
heads_map: HeadsMap,
4346
fetch_permits: Arc<tokio::sync::Semaphore>,
4447
filter_permits: Arc<tokio::sync::Semaphore>,
4548
poll: Polls,
@@ -66,10 +69,10 @@ async fn fetch_upstream(
6669
let auth = auth.clone();
6770
let key = remote_url.clone();
6871

69-
let refs_to_fetch = if !headref.is_empty() && !headref.starts_with("refs/heads/") {
70-
vec!["refs/heads/*", "refs/tags/*", headref]
72+
let refs_to_fetch = if headref != "HEAD" && !headref.starts_with("refs/heads/") {
73+
vec!["HEAD*", "refs/heads/*", "refs/tags/*", headref]
7174
} else {
72-
vec!["refs/heads/*", "refs/tags/*"]
75+
vec!["HEAD*", "refs/heads/*", "refs/tags/*"]
7376
};
7477

7578
let refs_to_fetch: Vec<_> = refs_to_fetch.iter().map(|x| x.to_string()).collect();
@@ -113,6 +116,7 @@ async fn fetch_upstream(
113116
}
114117

115118
let fetch_timers = service.fetch_timers.clone();
119+
let heads_map = service.heads_map.clone();
116120
let br_path = service.repo_path.clone();
117121

118122
let s = tracing::span!(tracing::Level::TRACE, "fetch worker");
@@ -126,6 +130,21 @@ async fn fetch_upstream(
126130
})
127131
.await?;
128132

133+
let us = upstream_repo.clone();
134+
let s = tracing::span!(tracing::Level::TRACE, "get_head worker");
135+
let br_path = service.repo_path.clone();
136+
let ru = remote_url.clone();
137+
let a = auth.clone();
138+
let hres = tokio::task::spawn_blocking(move || {
139+
let _e = s.enter();
140+
josh_proxy::get_head(&br_path, &ru, &a)
141+
})
142+
.await?;
143+
144+
if let Ok(hres) = hres {
145+
heads_map.write()?.insert(us, hres);
146+
}
147+
129148
std::mem::drop(permit);
130149

131150
if let Ok(res) = res {
@@ -227,6 +246,7 @@ async fn do_filter(
227246
headref: String,
228247
) -> josh::JoshResult<()> {
229248
let permit = service.filter_permits.acquire().await;
249+
let heads_map = service.heads_map.clone();
230250

231251
let s = tracing::span!(tracing::Level::TRACE, "do_filter worker");
232252
let r = tokio::task::spawn_blocking(move || {
@@ -273,13 +293,25 @@ async fn do_filter(
273293
temp_ns.reference(&headref),
274294
));
275295

296+
let mut headref = headref;
297+
276298
josh::filter_refs(&transaction, filter, &from_to)?;
277-
transaction.repo().reference_symbolic(
278-
&temp_ns.reference("HEAD"),
279-
&temp_ns.reference(&headref),
280-
true,
281-
"",
282-
)?;
299+
if headref == "HEAD" {
300+
headref = heads_map
301+
.read()?
302+
.get(&upstream_repo)
303+
.unwrap_or(&"invalid".to_string())
304+
.clone();
305+
}
306+
transaction
307+
.repo()
308+
.reference_symbolic(
309+
&temp_ns.reference("HEAD"),
310+
&temp_ns.reference(&headref),
311+
true,
312+
"",
313+
)
314+
.ok();
283315
Ok(())
284316
})
285317
.await?;
@@ -368,17 +400,14 @@ async fn call_service(
368400
} else {
369401
return Ok(Response::builder()
370402
.status(302)
371-
.header(
372-
"Location",
373-
format!("/~/browse{}@refs/heads/master(:/)/()", path),
374-
)
403+
.header("Location", format!("/~/browse{}@HEAD(:/)/()", path))
375404
.body(hyper::Body::empty())?);
376405
}
377406
};
378407

379408
let mut headref = parsed_url.headref.trim_start_matches('@').to_owned();
380409
if headref.is_empty() {
381-
headref = "refs/heads/master".to_string();
410+
headref = "HEAD".to_string();
382411
}
383412

384413
let remote_url = [
@@ -611,6 +640,7 @@ async fn run_proxy() -> josh::JoshResult<i32> {
611640
repo_path: local.to_owned(),
612641
upstream_url: remote.to_owned(),
613642
fetch_timers: Arc::new(RwLock::new(FetchTimers::new())),
643+
heads_map: Arc::new(RwLock::new(std::collections::HashMap::new())),
614644
poll: Arc::new(std::sync::Mutex::new(std::collections::HashSet::new())),
615645
fetch_permits: Arc::new(tokio::sync::Semaphore::new(
616646
ARGS.value_of("n").unwrap_or("1").parse()?,

josh-proxy/src/lib.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,34 @@ fn url_with_auth(url: &str, username: &str) -> String {
340340
}
341341
}
342342

343+
pub fn get_head(
344+
path: &std::path::Path,
345+
url: &str,
346+
auth: &auth::Handle,
347+
) -> josh::JoshResult<String> {
348+
let shell = josh::shell::Shell {
349+
cwd: path.to_owned(),
350+
};
351+
let (username, password) = auth.parse()?;
352+
let nurl = url_with_auth(url, &username);
353+
354+
let cmd = format!("git ls-remote --symref {} {}", &nurl, "HEAD");
355+
tracing::info!("get_head {:?} {:?} {:?}", cmd, path, "");
356+
357+
let (stdout, _stderr, _) = shell.command_env(&cmd, &[], &[("GIT_PASSWORD", &password)]);
358+
359+
let head = stdout
360+
.lines()
361+
.next()
362+
.unwrap_or("refs/heads/master")
363+
.to_string();
364+
365+
let head = head.replacen("ref: ", "", 1);
366+
let head = head.replacen("\tHEAD", "", 1);
367+
368+
Ok(head)
369+
}
370+
343371
pub fn fetch_refs_from_url(
344372
path: &std::path::Path,
345373
upstream_repo: &str,

src/housekeeping.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub fn default_from_to(
3030
}
3131
refs.append(&mut memorize_from_to(
3232
repo,
33-
&crate::to_filtered_ref(upstream_repo, filter_spec),
33+
&to_filtered_ref(upstream_repo, filter_spec),
3434
upstream_repo,
3535
));
3636

@@ -43,13 +43,10 @@ pub fn memorize_from_to(
4343
upstream_repo: &str,
4444
) -> Vec<(String, String)> {
4545
let mut refs = vec![];
46-
let glob = format!(
47-
"refs/josh/upstream/{}/refs/heads/master",
48-
&to_ns(upstream_repo)
49-
);
46+
let glob = format!("refs/josh/upstream/{}/HEAD", &to_ns(upstream_repo));
5047
for refname in repo.references_glob(&glob).unwrap().names() {
5148
let refname = refname.unwrap();
52-
let to_ref = format!("refs/{}/heads/master", &namespace);
49+
let to_ref = format!("refs/{}/HEAD", &namespace);
5350

5451
refs.push((refname.to_owned(), to_ref.clone()));
5552
}

tests/filter/ambigous_merge.t

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,7 @@
7272
* 81a8353 add sub_/fileY
7373
* 7671c2a add sub1/file1
7474

75-
$ git merge hidden_branch1 --no-ff
76-
Merge made by the 'recursive' strategy.
77-
sub1/file3 | 1 +
78-
sub1/fileY | 1 +
79-
2 files changed, 2 insertions(+)
80-
create mode 100644 sub1/file3
81-
create mode 100644 sub1/fileY
75+
$ git merge -q hidden_branch1 --no-ff
8276
$ git log --graph --oneline
8377
* 2fcb6a4 Merge branch 'hidden_branch1' into hidden_master
8478
|\

tests/filter/empty_orphan.t

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,7 @@ Empty root commits from unrelated parts of the tree should not be included
5656

5757
$ git checkout master
5858
Switched to branch 'master'
59-
$ git merge other --no-ff --allow-unrelated
60-
Merge made by the 'recursive' strategy.
61-
some_file | 1 +
62-
some_other_file | 1 +
63-
2 files changed, 2 insertions(+)
64-
create mode 100644 some_file
65-
create mode 100644 some_other_file
59+
$ git merge -q other --no-ff --allow-unrelated
6660

6761
$ tree
6862
.

tests/filter/empty_reimport.t

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,7 @@
2626
$ git add .
2727
$ git commit -m "unrelated change on this" 1> /dev/null
2828

29-
$ git merge other_branch
30-
Merge made by the 'recursive' strategy.
31-
pre/testfile2 | 1 +
32-
pre/testfile4 | 1 +
33-
2 files changed, 2 insertions(+)
34-
create mode 100644 pre/testfile2
35-
create mode 100644 pre/testfile4
29+
$ git merge -q other_branch
3630
$ git log --graph --pretty=%s
3731
* Merge branch 'other_branch'
3832
|\
@@ -68,13 +62,7 @@
6862
$ echo content > testfile7
6963
$ git add .
7064
$ git commit -m "more unrelated change on this" 1> /dev/null
71-
$ git merge other_branch
72-
Merge made by the 'recursive' strategy.
73-
pre/blafile1 | 1 +
74-
pre/blefile2 | 1 +
75-
2 files changed, 2 insertions(+)
76-
create mode 100644 pre/blafile1
77-
create mode 100644 pre/blefile2
65+
$ git merge -q other_branch
7866

7967
$ git log --graph --pretty=%s
8068
* Merge branch 'other_branch'

tests/filter/reverse_merge.t

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,7 @@
6565
* add file3
6666
* add file1
6767

68-
$ git merge hidden_branch1 --no-ff
69-
Merge made by the 'recursive' strategy.
70-
sub1/file3 | 1 +
71-
1 file changed, 1 insertion(+)
72-
create mode 100644 sub1/file3
68+
$ git merge -q hidden_branch1 --no-ff
7369
$ git log --graph --pretty=%s
7470
* Merge branch 'hidden_branch1' into hidden_master
7571
|\

tests/proxy/amend_patchset.t

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,12 @@
125125
| |-- filtered
126126
| | `-- real_repo.git
127127
| | |-- %3A
128-
| | | `-- heads
129-
| | | `-- master
128+
| | | `-- HEAD
130129
| | `-- %3A%2Fsub3
131-
| | `-- heads
132-
| | `-- master
130+
| | `-- HEAD
133131
| `-- upstream
134132
| `-- real_repo.git
133+
| |-- HEAD
135134
| `-- refs
136135
| |-- changes
137136
| | `-- 1
@@ -143,4 +142,4 @@
143142
|-- namespaces
144143
`-- tags
145144

146-
17 directories, 5 files
145+
15 directories, 6 files

tests/proxy/authentication.t

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,17 +128,16 @@
128128
| |-- filtered
129129
| | `-- real_repo.git
130130
| | |-- %3A
131-
| | | `-- heads
132-
| | | `-- master
131+
| | | `-- HEAD
133132
| | `-- %3A%2Fsub1
134-
| | `-- heads
135-
| | `-- master
133+
| | `-- HEAD
136134
| `-- upstream
137135
| `-- real_repo.git
136+
| |-- HEAD
138137
| `-- refs
139138
| `-- heads
140139
| `-- master
141140
|-- namespaces
142141
`-- tags
143142

144-
14 directories, 3 files
143+
12 directories, 4 files

tests/proxy/clone_absent_head.t

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,9 @@
8888
refs
8989
|-- heads
9090
|-- josh
91-
| `-- filtered
92-
| `-- real_repo.git
93-
| `-- %3A
94-
| `-- heads
95-
| `-- master
9691
|-- namespaces
9792
`-- tags
9893

99-
8 directories, 1 file
94+
4 directories, 0 files
10095

96+
$ cat ${TESTTMP}/josh-proxy.out

0 commit comments

Comments
 (0)