diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2024-08-08 17:34:40 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-08-08 17:36:44 +0100 |
| commit | 9971f23a184d57600ea9b1962910d32bf6aec185 (patch) | |
| tree | e4a82036c4785ddb8080be9e99e116cd3a7d10fa | |
| parent | fa2ab4b0dfc31e315d53bd9e36ada41ca95ec2d6 (diff) | |
feat(remote): `push` force push proposal
will issue a proposal revision
| -rw-r--r-- | src/git_remote_helper.rs | 102 | ||||
| -rw-r--r-- | test_utils/src/git.rs | 5 | ||||
| -rw-r--r-- | tests/git_remote_helper.rs | 153 |
3 files changed, 227 insertions, 33 deletions
diff --git a/src/git_remote_helper.rs b/src/git_remote_helper.rs index f36b23b..be230b1 100644 --- a/src/git_remote_helper.rs +++ b/src/git_remote_helper.rs | |||
| @@ -33,7 +33,10 @@ use sub_commands::{ | |||
| 33 | get_most_recent_patch_with_ancestors, get_proposals_and_revisions_from_cache, status_kinds, | 33 | get_most_recent_patch_with_ancestors, get_proposals_and_revisions_from_cache, status_kinds, |
| 34 | tag_value, | 34 | tag_value, |
| 35 | }, | 35 | }, |
| 36 | send::{event_is_revision_root, event_to_cover_letter, generate_patch_event, send_events}, | 36 | send::{ |
| 37 | event_is_revision_root, event_to_cover_letter, generate_cover_letter_and_patch_events, | ||
| 38 | generate_patch_event, send_events, | ||
| 39 | }, | ||
| 37 | }; | 40 | }; |
| 38 | 41 | ||
| 39 | #[cfg(not(test))] | 42 | #[cfg(not(test))] |
| @@ -656,21 +659,42 @@ async fn push( | |||
| 656 | if let Some((_, (proposal, patches))) = | 659 | if let Some((_, (proposal, patches))) = |
| 657 | find_proposal_and_patches_by_branch_name(to, &open_proposals) | 660 | find_proposal_and_patches_by_branch_name(to, &open_proposals) |
| 658 | { | 661 | { |
| 659 | if to.starts_with('+') { | 662 | if [repo_ref.maintainers.clone(), vec![proposal.author()]] |
| 660 | // TODO do force push - issue as revision | 663 | .concat() |
| 661 | } else { | 664 | .contains(&user_ref.public_key) |
| 662 | let tip_patch = patches.first().unwrap(); | 665 | { |
| 663 | let tip_of_proposal = get_commit_id_from_patch(tip_patch)?; | ||
| 664 | let tip_of_proposal_commit = | ||
| 665 | git_repo.get_commit_or_tip_of_reference(&tip_of_proposal)?; | ||
| 666 | let tip_of_pushed_branch = git_repo.get_commit_or_tip_of_reference(from)?; | 666 | let tip_of_pushed_branch = git_repo.get_commit_or_tip_of_reference(from)?; |
| 667 | let (mut ahead, behind) = git_repo | 667 | if refspec.starts_with('+') { |
| 668 | .get_commits_ahead_behind(&tip_of_proposal_commit, &tip_of_pushed_branch)?; | 668 | // force push |
| 669 | if behind.is_empty() { | 669 | let (_, main_tip) = git_repo.get_main_or_master_branch()?; |
| 670 | if [repo_ref.maintainers.clone(), vec![proposal.author()]] | 670 | let (mut ahead, _) = |
| 671 | .concat() | 671 | git_repo.get_commits_ahead_behind(&main_tip, &tip_of_pushed_branch)?; |
| 672 | .contains(&user_ref.public_key) | 672 | ahead.reverse(); |
| 673 | for patch in generate_cover_letter_and_patch_events( | ||
| 674 | None, | ||
| 675 | git_repo, | ||
| 676 | &ahead, | ||
| 677 | &signer, | ||
| 678 | repo_ref, | ||
| 679 | &Some(proposal.id().to_string()), | ||
| 680 | &[], | ||
| 681 | ) | ||
| 682 | .await? | ||
| 673 | { | 683 | { |
| 684 | events.push(patch); | ||
| 685 | } | ||
| 686 | } else { | ||
| 687 | // fast forward push | ||
| 688 | let tip_patch = patches.first().unwrap(); | ||
| 689 | let tip_of_proposal = get_commit_id_from_patch(tip_patch)?; | ||
| 690 | let tip_of_proposal_commit = | ||
| 691 | git_repo.get_commit_or_tip_of_reference(&tip_of_proposal)?; | ||
| 692 | |||
| 693 | let (mut ahead, behind) = git_repo.get_commits_ahead_behind( | ||
| 694 | &tip_of_proposal_commit, | ||
| 695 | &tip_of_pushed_branch, | ||
| 696 | )?; | ||
| 697 | if behind.is_empty() { | ||
| 674 | let thread_id = if let Ok(root_event_id) = get_event_root(tip_patch) { | 698 | let thread_id = if let Ok(root_event_id) = get_event_root(tip_patch) { |
| 675 | root_event_id | 699 | root_event_id |
| 676 | } else { | 700 | } else { |
| @@ -702,25 +726,25 @@ async fn push( | |||
| 702 | parent_patch = new_patch; | 726 | parent_patch = new_patch; |
| 703 | } | 727 | } |
| 704 | } else { | 728 | } else { |
| 729 | // we shouldn't get here | ||
| 730 | term.write_line( | ||
| 731 | format!( | ||
| 732 | "WARNING: failed to push {from} as nostr proposal. Try and force push ", | ||
| 733 | ) | ||
| 734 | .as_str(), | ||
| 735 | ) | ||
| 736 | .unwrap(); | ||
| 705 | println!( | 737 | println!( |
| 706 | "error {to} permission denied. you are not the proposal author or a repo maintainer" | 738 | "error {to} cannot fastforward as newer patches found on proposal" |
| 707 | ); | 739 | ); |
| 708 | rejected_proposal_refspecs.push(refspec.to_string()); | 740 | rejected_proposal_refspecs.push(refspec.to_string()); |
| 709 | } | 741 | } |
| 710 | } else { | ||
| 711 | // we shouldn't get here | ||
| 712 | term.write_line( | ||
| 713 | format!( | ||
| 714 | "WARNING: failed to push {from} as nostr proposal. Try and force push ", | ||
| 715 | ) | ||
| 716 | .as_str(), | ||
| 717 | ) | ||
| 718 | .unwrap(); | ||
| 719 | println!( | ||
| 720 | "error {to} cannot fastforward as newer patches found on proposal" | ||
| 721 | ); | ||
| 722 | rejected_proposal_refspecs.push(refspec.to_string()); | ||
| 723 | } | 742 | } |
| 743 | } else { | ||
| 744 | println!( | ||
| 745 | "error {to} permission denied. you are not the proposal author or a repo maintainer" | ||
| 746 | ); | ||
| 747 | rejected_proposal_refspecs.push(refspec.to_string()); | ||
| 724 | } | 748 | } |
| 725 | } else { | 749 | } else { |
| 726 | // TODO new proposal / proposal no longer open | 750 | // TODO new proposal / proposal no longer open |
| @@ -1046,7 +1070,14 @@ fn refspec_to_from_to(refspec: &str) -> Result<(&str, &str)> { | |||
| 1046 | ); | 1070 | ); |
| 1047 | } | 1071 | } |
| 1048 | let parts = refspec.split(':').collect::<Vec<&str>>(); | 1072 | let parts = refspec.split(':').collect::<Vec<&str>>(); |
| 1049 | Ok((parts.first().unwrap(), parts.get(1).unwrap())) | 1073 | Ok(( |
| 1074 | if parts.first().unwrap().starts_with('+') { | ||
| 1075 | &parts.first().unwrap()[1..] | ||
| 1076 | } else { | ||
| 1077 | parts.first().unwrap() | ||
| 1078 | }, | ||
| 1079 | parts.get(1).unwrap(), | ||
| 1080 | )) | ||
| 1050 | } | 1081 | } |
| 1051 | 1082 | ||
| 1052 | fn refspec_remote_ref_name( | 1083 | fn refspec_remote_ref_name( |
| @@ -1061,7 +1092,8 @@ fn refspec_remote_ref_name( | |||
| 1061 | Ok(format!( | 1092 | Ok(format!( |
| 1062 | "refs/remotes/{}/{}", | 1093 | "refs/remotes/{}/{}", |
| 1063 | nostr_remote.name().context("remote should have a name")?, | 1094 | nostr_remote.name().context("remote should have a name")?, |
| 1064 | to.replace("refs/heads/", ""), // TODO only replace if it begins with this | 1095 | to.replace("refs/heads/", ""), /* TODO only replace if it begins with this |
| 1096 | * TODO what about tags? */ | ||
| 1065 | )) | 1097 | )) |
| 1066 | } | 1098 | } |
| 1067 | 1099 | ||
| @@ -1289,4 +1321,14 @@ mod tests { | |||
| 1289 | } | 1321 | } |
| 1290 | } | 1322 | } |
| 1291 | } | 1323 | } |
| 1324 | |||
| 1325 | mod refspec_to_from_to { | ||
| 1326 | use super::*; | ||
| 1327 | |||
| 1328 | #[test] | ||
| 1329 | fn trailing_plus_stripped() { | ||
| 1330 | let (from, _) = refspec_to_from_to("+testing:testingb").unwrap(); | ||
| 1331 | assert_eq!(from, "testing"); | ||
| 1332 | } | ||
| 1333 | } | ||
| 1292 | } | 1334 | } |
diff --git a/test_utils/src/git.rs b/test_utils/src/git.rs index 5ae7f39..2a3d566 100644 --- a/test_utils/src/git.rs +++ b/test_utils/src/git.rs | |||
| @@ -274,12 +274,11 @@ impl GitTestRepo { | |||
| 274 | Ok(()) | 274 | Ok(()) |
| 275 | } | 275 | } |
| 276 | 276 | ||
| 277 | pub fn checkout_remote_branch(&self, branch_name: &str) -> Result<()> { | 277 | pub fn checkout_remote_branch(&self, branch_name: &str) -> Result<Oid> { |
| 278 | self.checkout(&format!("remotes/origin/{branch_name}"))?; | 278 | self.checkout(&format!("remotes/origin/{branch_name}"))?; |
| 279 | let mut branch = self.create_branch(branch_name)?; | 279 | let mut branch = self.create_branch(branch_name)?; |
| 280 | branch.set_upstream(Some(&format!("origin/{branch_name}")))?; | 280 | branch.set_upstream(Some(&format!("origin/{branch_name}")))?; |
| 281 | self.checkout(branch_name)?; | 281 | self.checkout(branch_name) |
| 282 | Ok(()) | ||
| 283 | } | 282 | } |
| 284 | } | 283 | } |
| 285 | 284 | ||
diff --git a/tests/git_remote_helper.rs b/tests/git_remote_helper.rs index dc6d931..379954d 100644 --- a/tests/git_remote_helper.rs +++ b/tests/git_remote_helper.rs | |||
| @@ -1856,4 +1856,157 @@ mod push { | |||
| 1856 | 1856 | ||
| 1857 | Ok(()) | 1857 | Ok(()) |
| 1858 | } | 1858 | } |
| 1859 | |||
| 1860 | #[tokio::test] | ||
| 1861 | #[serial] | ||
| 1862 | async fn force_push_creates_proposal_revision() -> Result<()> { | ||
| 1863 | let (events, source_git_repo) = prep_source_repo_and_events_including_proposals().await?; | ||
| 1864 | let source_path = source_git_repo.dir.to_str().unwrap().to_string(); | ||
| 1865 | |||
| 1866 | let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = ( | ||
| 1867 | Relay::new(8051, None, None), | ||
| 1868 | Relay::new(8052, None, None), | ||
| 1869 | Relay::new(8053, None, None), | ||
| 1870 | Relay::new(8055, None, None), | ||
| 1871 | Relay::new(8056, None, None), | ||
| 1872 | Relay::new(8057, None, None), | ||
| 1873 | ); | ||
| 1874 | r51.events = events.clone(); | ||
| 1875 | r55.events = events.clone(); | ||
| 1876 | |||
| 1877 | let before = r55.events.iter().cloned().collect::<HashSet<Event>>(); | ||
| 1878 | |||
| 1879 | let cli_tester_handle = std::thread::spawn(move || -> Result<(String, String)> { | ||
| 1880 | let branch_name = get_proposal_branch_name_from_events(&events, FEATURE_BRANCH_NAME_1)?; | ||
| 1881 | |||
| 1882 | let git_repo = clone_git_repo_with_nostr_url()?; | ||
| 1883 | let oid = git_repo.checkout_remote_branch(&branch_name)?; | ||
| 1884 | // remove last commit | ||
| 1885 | git_repo.checkout("main")?; | ||
| 1886 | git_repo.git_repo.branch( | ||
| 1887 | &branch_name, | ||
| 1888 | &git_repo.git_repo.find_commit(oid)?.parent(0)?, | ||
| 1889 | true, | ||
| 1890 | )?; | ||
| 1891 | git_repo.checkout(&branch_name)?; | ||
| 1892 | |||
| 1893 | std::fs::write(git_repo.dir.join("new.md"), "some content")?; | ||
| 1894 | git_repo.stage_and_commit("new.md")?; | ||
| 1895 | |||
| 1896 | std::fs::write(git_repo.dir.join("new2.md"), "some content")?; | ||
| 1897 | git_repo.stage_and_commit("new2.md")?; | ||
| 1898 | |||
| 1899 | let mut p = | ||
| 1900 | CliTester::new_git_with_remote_helper_from_dir(&git_repo.dir, ["push", "--force"]); | ||
| 1901 | cli_expect_nostr_fetch(&mut p)?; | ||
| 1902 | p.expect(format!("fetching refs list: {}...\r\n\r", source_path).as_str())?; | ||
| 1903 | p.expect(format!("To {}\r\n", get_nostr_remote_url()?).as_str())?; | ||
| 1904 | let output = p.expect_end_eventually()?; | ||
| 1905 | |||
| 1906 | for p in [51, 52, 53, 55, 56, 57] { | ||
| 1907 | relay::shutdown_relay(8000 + p)?; | ||
| 1908 | } | ||
| 1909 | |||
| 1910 | Ok((output, branch_name)) | ||
| 1911 | }); | ||
| 1912 | // launch relays | ||
| 1913 | let _ = join!( | ||
| 1914 | r51.listen_until_close(), | ||
| 1915 | r52.listen_until_close(), | ||
| 1916 | r53.listen_until_close(), | ||
| 1917 | r55.listen_until_close(), | ||
| 1918 | r56.listen_until_close(), | ||
| 1919 | r57.listen_until_close(), | ||
| 1920 | ); | ||
| 1921 | |||
| 1922 | let (output, branch_name) = cli_tester_handle.join().unwrap()?; | ||
| 1923 | |||
| 1924 | assert_eq!( | ||
| 1925 | output, | ||
| 1926 | format!(" + eb5d678...8a296c8 {branch_name} -> {branch_name} (forced update)\r\n") | ||
| 1927 | .as_str(), | ||
| 1928 | ); | ||
| 1929 | |||
| 1930 | let new_events = r55 | ||
| 1931 | .events | ||
| 1932 | .iter() | ||
| 1933 | .cloned() | ||
| 1934 | .collect::<HashSet<Event>>() | ||
| 1935 | .difference(&before) | ||
| 1936 | .cloned() | ||
| 1937 | .collect::<Vec<Event>>(); | ||
| 1938 | assert_eq!(new_events.len(), 3); | ||
| 1939 | |||
| 1940 | let proposal = r55 | ||
| 1941 | .events | ||
| 1942 | .iter() | ||
| 1943 | .find(|e| { | ||
| 1944 | e.iter_tags() | ||
| 1945 | .find(|t| t.as_vec()[0].eq("branch-name")) | ||
| 1946 | .is_some_and(|t| t.as_vec()[1].eq(FEATURE_BRANCH_NAME_1)) | ||
| 1947 | }) | ||
| 1948 | .unwrap(); | ||
| 1949 | |||
| 1950 | let revision_root_patch = new_events | ||
| 1951 | .iter() | ||
| 1952 | .find(|e| e.iter_tags().any(|t| t.as_vec()[1].eq("revision-root"))) | ||
| 1953 | .unwrap(); | ||
| 1954 | |||
| 1955 | assert_eq!( | ||
| 1956 | proposal.id().to_string(), | ||
| 1957 | revision_root_patch | ||
| 1958 | .tags | ||
| 1959 | .iter() | ||
| 1960 | .find(|t| t.is_reply()) | ||
| 1961 | .unwrap() | ||
| 1962 | .as_vec()[1], | ||
| 1963 | "revision root patch replies to original proposal" | ||
| 1964 | ); | ||
| 1965 | |||
| 1966 | assert!( | ||
| 1967 | revision_root_patch.content.contains("[PATCH 1/3]"), | ||
| 1968 | "revision root labeled with [PATCH 1/3]" | ||
| 1969 | ); | ||
| 1970 | |||
| 1971 | let second_patch = new_events | ||
| 1972 | .iter() | ||
| 1973 | .find(|e| e.content.contains("new.md")) | ||
| 1974 | .unwrap(); | ||
| 1975 | let third_patch = new_events | ||
| 1976 | .iter() | ||
| 1977 | .find(|e| e.content.contains("new2.md")) | ||
| 1978 | .unwrap(); | ||
| 1979 | assert!( | ||
| 1980 | second_patch.content.contains("[PATCH 2/3]"), | ||
| 1981 | "second patch labeled with [PATCH 2/3]" | ||
| 1982 | ); | ||
| 1983 | assert!( | ||
| 1984 | third_patch.content.contains("[PATCH 3/3]"), | ||
| 1985 | "third patch labeled with [PATCH 3/3]" | ||
| 1986 | ); | ||
| 1987 | |||
| 1988 | assert_eq!( | ||
| 1989 | revision_root_patch.id().to_string(), | ||
| 1990 | second_patch | ||
| 1991 | .tags | ||
| 1992 | .iter() | ||
| 1993 | .find(|t| t.is_root()) | ||
| 1994 | .unwrap() | ||
| 1995 | .as_vec()[1], | ||
| 1996 | "second patch sets revision id as root" | ||
| 1997 | ); | ||
| 1998 | |||
| 1999 | assert_eq!( | ||
| 2000 | second_patch.id().to_string(), | ||
| 2001 | third_patch | ||
| 2002 | .tags | ||
| 2003 | .iter() | ||
| 2004 | .find(|t| t.is_reply()) | ||
| 2005 | .unwrap() | ||
| 2006 | .as_vec()[1], | ||
| 2007 | "third patch replies to the second new patch" | ||
| 2008 | ); | ||
| 2009 | |||
| 2010 | Ok(()) | ||
| 2011 | } | ||
| 1859 | } | 2012 | } |