Skip to content

Commit

Permalink
Initial, experimental, support for pushing to named branches.
Browse files Browse the repository at this point in the history
  • Loading branch information
glandium committed Jul 30, 2024
1 parent bf028a9 commit 5fe4877
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 8 deletions.
91 changes: 89 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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)
Expand All @@ -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<Box<BStr>>),
Inferred(Option<Box<BStr>>),
Set(Option<Box<BStr>>),
}

impl BranchInfo {
fn unwrap(self) -> Option<Box<BStr>> {
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<CommitId, RefCell<Option<BranchInfo>>> = 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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -5227,6 +5311,9 @@ static EXPERIMENTS: Lazy<AllExperiments> = 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) {
Expand Down
44 changes: 38 additions & 6 deletions tests/push-refs.t
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 5fe4877

Please sign in to comment.