From 5fe4877177e0cf0afcfcfbe55f75ecac0b8f87ce Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Sun, 7 Jul 2024 09:23:08 +0900 Subject: [PATCH] Initial, experimental, support for pushing to named branches. --- src/main.rs | 91 +++++++++++++++++++++++++++++++++++++++++++++-- tests/push-refs.t | 44 +++++++++++++++++++---- 2 files changed, 127 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index a28622387..00a46bbe3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,7 +66,7 @@ pub(crate) mod hg_connect_stdio; pub(crate) mod hg_data; use std::borrow::{Borrow, Cow}; -use std::cell::{Cell, OnceCell}; +use std::cell::{Cell, OnceCell, RefCell}; use std::cmp::Ordering; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::ffi::{CStr, CString, OsStr, OsString}; @@ -4585,7 +4585,7 @@ fn remote_helper_push( let common = find_common(store, conn, local_bases, remote); - let push_commits = rev_list_with_parents( + let mut push_commits = rev_list_with_parents( ["--topo-order", "--full-history", "--reverse"] .iter() .map(ToString::to_string) @@ -4603,6 +4603,89 @@ fn remote_helper_push( .map(|(c, p)| (c, p, None)) .collect_vec(); + if experiment(Experiments::BRANCH) + && info + .refs_style + .intersects(RefsStyle::HEADS | RefsStyle::TIPS) + { + #[derive(Clone, Debug)] + enum BranchInfo { + Known(Option>), + Inferred(Option>), + Set(Option>), + } + + impl BranchInfo { + fn unwrap(self) -> Option> { + match self { + BranchInfo::Known(b) | BranchInfo::Inferred(b) | BranchInfo::Set(b) => b, + } + } + } + + let get_branch = |c| { + let cs_metadata = + RawGitChangesetMetadata::read(store, GitChangesetId::from_unchecked(c))?; + let cs_metadata = cs_metadata.parse().unwrap(); + Some(BranchInfo::Known(cs_metadata.extra().and_then(|e| { + e.get(b"branch").map(|b| b.as_bstr().to_boxed()) + }))) + }; + + let mut dag: Dag>> = Dag::new(); + for (c, p, _) in &push_commits { + dag.add( + *c, + p, + RefCell::new(get_branch(*c).or_else(|| { + p.first().and_then(|p| { + dag.get(*p) + .and_then(|(_, b)| b.clone().into_inner()) + .or_else(|| { + get_branch(*p).map(|b| BranchInfo::Inferred(b.unwrap())) + }) + }) + })), + ); + } + + let prefix = if info.refs_style.contains(RefsStyle::BOOKMARKS) { + &b"refs/heads/branches/"[..] + } else { + &b"refs/heads/"[..] + }; + for (c, branch) in push_refs.iter().filter_map(|(c, remote_ref, _)| { + c.and_then(|c| { + remote_ref.strip_prefix(prefix).and_then(|s| { + if info.refs_style.contains(RefsStyle::HEADS) { + s.splitn_exact::<2>(b'/').map(|[b, _]| (c, b)) + } else { + Some((c, s)) + } + }) + }) + }) { + for (_, info) in dag.traverse_parents(&[c], |_, info| { + matches!(*info.borrow(), Some(BranchInfo::Inferred(_)) | None) + }) { + if matches!(*info.borrow(), Some(BranchInfo::Inferred(_)) | None) { + info.replace(Some(BranchInfo::Set( + (branch != b"default").then(|| branch.as_bstr().to_boxed()), + ))); + } + } + } + for (c, _, b) in &mut push_commits { + if let Some((_, info)) = dag.get(*c) { + *b = info + .borrow() + .clone() + .expect("Branch name should have been set") + .unwrap(); + } + } + } + if !push_commits.is_empty() { let has_root = push_commits.iter().any(|(_, p, _)| p.is_empty()); if has_root && !no_topological_heads { @@ -5202,6 +5285,7 @@ bitflags! { // Add git commit as extra metadata in mercurial changesets. const GIT_COMMIT = 0x2; const TAG = 0x4; + const BRANCH = 0x8; } } pub struct AllExperiments { @@ -5227,6 +5311,9 @@ static EXPERIMENTS: Lazy = Lazy::new(|| { b"tag" => { flags |= Experiments::TAG; } + b"branch" => { + flags |= Experiments::BRANCH; + } s if s.starts_with(b"similarity") => { if let Some(value) = s[b"similarity".len()..].strip_prefix(b"=") { match u8::from_bytes(value) { diff --git a/tests/push-refs.t b/tests/push-refs.t index d08bebaf8..1e4248c9d 100755 --- a/tests/push-refs.t +++ b/tests/push-refs.t @@ -334,9 +334,13 @@ Force push `f`. To hg::.*/push-refs.t/repo-from-git (re) * [new branch] 23bcc26b9fea7e37426260465bed35eac54af5e1 -> branches/foo/tip -TODO: this should either fail because creating the branch is not supported, -or work and create the branch -# $ git -C repo-git2 push origin -f 23bcc26b9fea7e37426260465bed35eac54af5e1:refs/heads/branches/foo/tip + $ git -c cinnabar.experiments=branch -C repo-git2 push origin -f 23bcc26b9fea7e37426260465bed35eac54af5e1:refs/heads/branches/foo/tip + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 2 changesets with 2 changes to 2 files (+1 heads) + To hg::.*/push-refs.t/repo-from-git2 (re) + * [new branch] 23bcc26b9fea7e37426260465bed35eac54af5e1 -> branches/foo/tip $ git -c cinnabar.refs=heads -C repo-git ls-remote hg::$REPO-from-hg 5c5b259d3c128f3d7b50ce3bd5c9eaafd8d17611 HEAD @@ -353,6 +357,14 @@ or work and create the branch 0000000000000000000000000000000000000000 refs/heads/default/a08654acdc93834f96695eff2760efaa4e3562bc 23bcc26b9fea7e37426260465bed35eac54af5e1 refs/heads/foo/312a5a9c675e3ce302a33bd4605205a6be36d561 + $ git -c cinnabar.refs=heads -C repo-git2 ls-remote + From hg::.*/push-refs.t/repo-from-git2 (re) + 5c5b259d3c128f3d7b50ce3bd5c9eaafd8d17611 HEAD + d04f6df4abe2870ceb759263ee6aaa9241c4f93c refs/heads/default/636e60525868096cbdc961870493510558f41d2f + 5c5b259d3c128f3d7b50ce3bd5c9eaafd8d17611 refs/heads/default/7937e1a594596ae25c637d317503d775767671b5 + 0000000000000000000000000000000000000000 refs/heads/default/a08654acdc93834f96695eff2760efaa4e3562bc + 23bcc26b9fea7e37426260465bed35eac54af5e1 refs/heads/foo/312a5a9c675e3ce302a33bd4605205a6be36d561 + Removing heads or branches is not supported. $ git -C repo-git push origin :branches/default/636e60525868096cbdc961870493510558f41d2f @@ -407,9 +419,15 @@ Push everything at once. * [new branch] 5c5b259d3c128f3d7b50ce3bd5c9eaafd8d17611 -> branches/default/head2 * [new branch] 23bcc26b9fea7e37426260465bed35eac54af5e1 -> branches/foo/tip -TODO: this should either fail for the foo branch because creating the branch -is not supported, or work and create the branch -# $ git -C repo-git2 push origin 23bcc26b9fea7e37426260465bed35eac54af5e1:refs/heads/branches/foo/tip + $ git -c cinnabar.experiments=branch -C repo-git2 push origin d04f6df4abe2870ceb759263ee6aaa9241c4f93c:refs/heads/branches/default/head1 5c5b259d3c128f3d7b50ce3bd5c9eaafd8d17611:refs/heads/branches/default/head2 23bcc26b9fea7e37426260465bed35eac54af5e1:refs/heads/branches/foo/tip + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 6 changesets with 6 changes to 6 files (+2 heads) + To hg::.*/push-refs.t/repo-from-git2 (re) + * [new branch] d04f6df4abe2870ceb759263ee6aaa9241c4f93c -> branches/default/head1 + * [new branch] 5c5b259d3c128f3d7b50ce3bd5c9eaafd8d17611 -> branches/default/head2 + * [new branch] 23bcc26b9fea7e37426260465bed35eac54af5e1 -> branches/foo/tip $ git -C repo-git ls-remote hg::$REPO-from-hg 5c5b259d3c128f3d7b50ce3bd5c9eaafd8d17611 HEAD @@ -437,6 +455,20 @@ is not supported, or work and create the branch 5c5b259d3c128f3d7b50ce3bd5c9eaafd8d17611 refs/heads/default/7937e1a594596ae25c637d317503d775767671b5 23bcc26b9fea7e37426260465bed35eac54af5e1 refs/heads/foo/312a5a9c675e3ce302a33bd4605205a6be36d561 + $ git -C repo-git2 ls-remote + From hg::.*/push-refs.t/repo-from-git2 (re) + 5c5b259d3c128f3d7b50ce3bd5c9eaafd8d17611 HEAD + d04f6df4abe2870ceb759263ee6aaa9241c4f93c refs/heads/branches/default/636e60525868096cbdc961870493510558f41d2f + 5c5b259d3c128f3d7b50ce3bd5c9eaafd8d17611 refs/heads/branches/default/tip + 23bcc26b9fea7e37426260465bed35eac54af5e1 refs/heads/branches/foo/tip + + $ git -c cinnabar.refs=heads -C repo-git2 ls-remote + From hg::.*/push-refs.t/repo-from-git2 (re) + 5c5b259d3c128f3d7b50ce3bd5c9eaafd8d17611 HEAD + d04f6df4abe2870ceb759263ee6aaa9241c4f93c refs/heads/default/636e60525868096cbdc961870493510558f41d2f + 5c5b259d3c128f3d7b50ce3bd5c9eaafd8d17611 refs/heads/default/7937e1a594596ae25c637d317503d775767671b5 + 23bcc26b9fea7e37426260465bed35eac54af5e1 refs/heads/foo/312a5a9c675e3ce302a33bd4605205a6be36d561 + Reset target mercurial repositories again. $ rm -rf $REPO-from-hg $REPO-from-git $REPO-from-git2