@@ -4478,6 +4478,39 @@ fn check_graft_refs() {
4478
4478
}
4479
4479
}
4480
4480
4481
+ #[ derive( PartialEq , Eq ) ]
4482
+ enum RecordMetadata {
4483
+ Never ,
4484
+ Phase ,
4485
+ Always ,
4486
+ // Like Always, but also applies to dry-run
4487
+ Force ,
4488
+ }
4489
+
4490
+ impl TryFrom < & OsStr > for RecordMetadata {
4491
+ type Error = String ;
4492
+
4493
+ fn try_from ( value : & OsStr ) -> Result < RecordMetadata , String > {
4494
+ value
4495
+ . to_str ( )
4496
+ . and_then ( |s| {
4497
+ Some ( match s {
4498
+ "never" => RecordMetadata :: Never ,
4499
+ "phase" => RecordMetadata :: Phase ,
4500
+ "always" => RecordMetadata :: Always ,
4501
+ "force" => RecordMetadata :: Force ,
4502
+ _ => return None ,
4503
+ } )
4504
+ } )
4505
+ . ok_or_else ( || {
4506
+ format ! (
4507
+ "`{}` is not one of `never`, `phase`, `always` or `force`" ,
4508
+ value. as_bytes( ) . as_bstr( )
4509
+ )
4510
+ } )
4511
+ }
4512
+ }
4513
+
4481
4514
fn remote_helper_push (
4482
4515
store : & mut Store ,
4483
4516
conn : & mut dyn HgRepo ,
@@ -4530,6 +4563,11 @@ fn remote_helper_push(
4530
4563
}
4531
4564
} ) ;
4532
4565
4566
+ let data = get_config_remote ( "data" , remote)
4567
+ . filter ( |d| !d. is_empty ( ) )
4568
+ . as_deref ( )
4569
+ . map_or ( Ok ( RecordMetadata :: Phase ) , RecordMetadata :: try_from) ?;
4570
+
4533
4571
let mut pushed = ChangesetHeads :: new ( ) ;
4534
4572
let result = ( || {
4535
4573
let push_commits = push_refs. iter ( ) . filter_map ( |( c, _, _) | * c) . collect_vec ( ) ;
@@ -4692,7 +4730,7 @@ fn remote_helper_push(
4692
4730
}
4693
4731
4694
4732
let mut result = None ;
4695
- if !push_commits. is_empty ( ) && !dry_run {
4733
+ if !push_commits. is_empty ( ) && ( !dry_run || data == RecordMetadata :: Force ) {
4696
4734
conn. require_capability ( b"unbundle" ) ;
4697
4735
4698
4736
let b2caps = conn
@@ -4718,6 +4756,7 @@ fn remote_helper_push(
4718
4756
. unwrap_or ( 1 ) ;
4719
4757
( BundleSpec :: V2None , version)
4720
4758
} ;
4759
+ // TODO: Ideally, for dry-run, we wouldn't even create a temporary file.
4721
4760
let tempfile = tempfile:: Builder :: new ( )
4722
4761
. prefix ( "hg-bundle-" )
4723
4762
. suffix ( ".hg" )
@@ -4734,40 +4773,43 @@ fn remote_helper_push(
4734
4773
version == 2 ,
4735
4774
) ?;
4736
4775
drop ( file) ;
4737
- let file = File :: open ( path) . unwrap ( ) ;
4738
- let empty_heads = [ HgChangesetId :: NULL ] ;
4739
- let heads = if force {
4740
- None
4741
- } else if no_topological_heads {
4742
- Some ( & empty_heads[ ..] )
4743
- } else {
4744
- Some ( & info. topological_heads [ ..] )
4745
- } ;
4746
- let response = conn. unbundle ( heads, file) ;
4747
- match response {
4748
- UnbundleResponse :: Bundlev2 ( data) => {
4749
- let mut bundle = BundleReader :: new ( data) . unwrap ( ) ;
4750
- while let Some ( part) = bundle. next_part ( ) . unwrap ( ) {
4751
- match part. part_type . as_bytes ( ) {
4752
- b"reply:changegroup" => {
4753
- // TODO: should check in-reply-to param.
4754
- let response = part. get_param ( "return" ) . unwrap ( ) ;
4755
- result = u32:: from_str ( response) . ok ( ) ;
4756
- }
4757
- b"error:abort" => {
4758
- let mut message = part. get_param ( "message" ) . unwrap ( ) . to_string ( ) ;
4759
- if let Some ( hint) = part. get_param ( "hint" ) {
4760
- message. push_str ( "\n \n " ) ;
4761
- message. push_str ( hint) ;
4776
+ if !dry_run {
4777
+ let file = File :: open ( path) . unwrap ( ) ;
4778
+ let empty_heads = [ HgChangesetId :: NULL ] ;
4779
+ let heads = if force {
4780
+ None
4781
+ } else if no_topological_heads {
4782
+ Some ( & empty_heads[ ..] )
4783
+ } else {
4784
+ Some ( & info. topological_heads [ ..] )
4785
+ } ;
4786
+ let response = conn. unbundle ( heads, file) ;
4787
+ match response {
4788
+ UnbundleResponse :: Bundlev2 ( data) => {
4789
+ let mut bundle = BundleReader :: new ( data) . unwrap ( ) ;
4790
+ while let Some ( part) = bundle. next_part ( ) . unwrap ( ) {
4791
+ match part. part_type . as_bytes ( ) {
4792
+ b"reply:changegroup" => {
4793
+ // TODO: should check in-reply-to param.
4794
+ let response = part. get_param ( "return" ) . unwrap ( ) ;
4795
+ result = u32:: from_str ( response) . ok ( ) ;
4796
+ }
4797
+ b"error:abort" => {
4798
+ let mut message =
4799
+ part. get_param ( "message" ) . unwrap ( ) . to_string ( ) ;
4800
+ if let Some ( hint) = part. get_param ( "hint" ) {
4801
+ message. push_str ( "\n \n " ) ;
4802
+ message. push_str ( hint) ;
4803
+ }
4804
+ error ! ( target: "root" , "{}" , message) ;
4762
4805
}
4763
- error ! ( target : "root" , "{}" , message ) ;
4806
+ _ => { }
4764
4807
}
4765
- _ => { }
4766
4808
}
4767
4809
}
4768
- }
4769
- UnbundleResponse :: Raw ( response) => {
4770
- result = u32 :: from_bytes ( & response ) . ok ( ) ;
4810
+ UnbundleResponse :: Raw ( response ) => {
4811
+ result = u32 :: from_bytes ( & response) . ok ( ) ;
4812
+ }
4771
4813
}
4772
4814
}
4773
4815
}
@@ -4851,76 +4893,60 @@ fn remote_helper_push(
4851
4893
stdout. flush ( ) . unwrap ( ) ;
4852
4894
}
4853
4895
4854
- let data = get_config_remote ( "data" , remote) ;
4855
- let data = data
4856
- . as_deref ( )
4857
- . and_then ( |d| ( !d. is_empty ( ) ) . then_some ( d) )
4858
- . unwrap_or_else ( || OsStr :: new ( "phase" ) ) ;
4859
- let valid = [
4860
- OsStr :: new ( "never" ) ,
4861
- OsStr :: new ( "phase" ) ,
4862
- OsStr :: new ( "always" ) ,
4863
- ] ;
4864
- if !valid. contains ( & data) {
4865
- die ! (
4866
- "`{}` is not one of `never`, `phase` or `always`" ,
4867
- data. as_bytes( ) . as_bstr( )
4868
- ) ;
4869
- }
4870
- let rollback = if status. is_empty ( ) || pushed. is_empty ( ) || dry_run {
4871
- true
4872
- } else {
4873
- match data. to_str ( ) . unwrap ( ) {
4874
- "always" => false ,
4875
- "never" => true ,
4876
- "phase" => {
4877
- let phases = conn. phases ( ) ;
4878
- let phases = ByteSlice :: lines ( & * phases)
4879
- . filter_map ( |l| {
4880
- l. splitn_exact ( b'\t' )
4881
- . map ( |[ k, v] | ( k. as_bstr ( ) , v. as_bstr ( ) ) )
4882
- } )
4883
- . collect :: < HashMap < _ , _ > > ( ) ;
4884
- let drafts = ( !phases. contains_key ( b"publishing" . as_bstr ( ) ) )
4885
- . then ( || {
4886
- phases
4887
- . into_iter ( )
4888
- . filter_map ( |( phase, is_draft) | {
4889
- u32:: from_bytes ( is_draft) . ok ( ) . and_then ( |is_draft| {
4890
- ( is_draft > 0 ) . then ( || HgChangesetId :: from_bytes ( phase) )
4896
+ let rollback =
4897
+ if status. is_empty ( ) || pushed. is_empty ( ) || ( dry_run && data != RecordMetadata :: Force ) {
4898
+ true
4899
+ } else {
4900
+ match data {
4901
+ RecordMetadata :: Force | RecordMetadata :: Always => false ,
4902
+ RecordMetadata :: Never => true ,
4903
+ RecordMetadata :: Phase => {
4904
+ let phases = conn. phases ( ) ;
4905
+ let phases = ByteSlice :: lines ( & * phases)
4906
+ . filter_map ( |l| {
4907
+ l. splitn_exact ( b'\t' )
4908
+ . map ( |[ k, v] | ( k. as_bstr ( ) , v. as_bstr ( ) ) )
4909
+ } )
4910
+ . collect :: < HashMap < _ , _ > > ( ) ;
4911
+ let drafts = ( !phases. contains_key ( b"publishing" . as_bstr ( ) ) )
4912
+ . then ( || {
4913
+ phases
4914
+ . into_iter ( )
4915
+ . filter_map ( |( phase, is_draft) | {
4916
+ u32:: from_bytes ( is_draft) . ok ( ) . and_then ( |is_draft| {
4917
+ ( is_draft > 0 ) . then ( || HgChangesetId :: from_bytes ( phase) )
4918
+ } )
4891
4919
} )
4892
- } )
4893
- . collect :: < Result < Vec < _ > , _ > > ( )
4894
- } )
4895
- . transpose ( )
4896
- . unwrap ( )
4897
- . unwrap_or_default ( ) ;
4898
- if drafts . is_empty ( ) {
4899
- false
4900
- } else {
4901
- // Theoretically, we could have commits with no
4902
- // metadata that the remote declares are public, while
4903
- // the rest of our push is in a draft state. That is
4904
- // however so unlikely that it's not worth the effort
4905
- // to support partial metadata storage.
4906
- ! reachable_subset (
4907
- pushed
4908
- . heads ( )
4909
- . copied ( )
4910
- . filter_map ( |h| h . to_git ( store ) )
4911
- . map ( Into :: into ) ,
4912
- drafts
4913
- . iter ( )
4914
- . copied ( )
4915
- . filter_map ( |h| h . to_git ( store ) )
4916
- . map ( Into :: into ) ,
4917
- )
4918
- . is_empty ( )
4920
+ . collect :: < Result < Vec < _ > , _ > > ( )
4921
+ } )
4922
+ . transpose ( )
4923
+ . unwrap ( )
4924
+ . unwrap_or_default ( ) ;
4925
+ if drafts . is_empty ( ) {
4926
+ false
4927
+ } else {
4928
+ // Theoretically, we could have commits with no
4929
+ // metadata that the remote declares are public, while
4930
+ // the rest of our push is in a draft state. That is
4931
+ // however so unlikely that it's not worth the effort
4932
+ // to support partial metadata storage.
4933
+ ! reachable_subset (
4934
+ pushed
4935
+ . heads ( )
4936
+ . copied ( )
4937
+ . filter_map ( |h| h . to_git ( store ) )
4938
+ . map ( Into :: into ) ,
4939
+ drafts
4940
+ . iter ( )
4941
+ . copied ( )
4942
+ . filter_map ( |h| h . to_git ( store ) )
4943
+ . map ( Into :: into ) ,
4944
+ )
4945
+ . is_empty ( )
4946
+ }
4919
4947
}
4920
4948
}
4921
- _ => unreachable ! ( ) ,
4922
- }
4923
- } ;
4949
+ } ;
4924
4950
if rollback {
4925
4951
unsafe {
4926
4952
do_cleanup ( 1 ) ;
0 commit comments