upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/sub_commands/pull.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-02-22 12:18:01 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2024-02-22 12:18:01 +0000
commit312786fbdacd61fc9f3ed59612d9a6add9112b7f (patch)
tree660300df36b970c46c69d59893856bb6f03142b0 /src/sub_commands/pull.rs
parent79896914357ee322b08b302c3dde08c7a24a0a09 (diff)
feat(pull): support `--in-reply-to` revisions
added tests to cover one of these rebase scenarios
Diffstat (limited to 'src/sub_commands/pull.rs')
-rw-r--r--src/sub_commands/pull.rs141
1 files changed, 130 insertions, 11 deletions
diff --git a/src/sub_commands/pull.rs b/src/sub_commands/pull.rs
index de078e3..d832f6e 100644
--- a/src/sub_commands/pull.rs
+++ b/src/sub_commands/pull.rs
@@ -1,12 +1,13 @@
1use anyhow::{bail, Context, Result}; 1use anyhow::{bail, Context, Result};
2 2
3use super::list::{get_commit_id_from_patch, tag_value};
3#[cfg(not(test))] 4#[cfg(not(test))]
4use crate::client::Client; 5use crate::client::Client;
5#[cfg(test)] 6#[cfg(test)]
6use crate::client::MockConnect; 7use crate::client::MockConnect;
7use crate::{ 8use crate::{
8 client::Connect, 9 client::Connect,
9 git::{Repo, RepoActions}, 10 git::{str_to_sha1, Repo, RepoActions},
10 repo_ref, 11 repo_ref,
11 sub_commands::{ 12 sub_commands::{
12 list::get_most_recent_patch_with_ancestors, 13 list::get_most_recent_patch_with_ancestors,
@@ -14,6 +15,7 @@ use crate::{
14 }, 15 },
15}; 16};
16 17
18#[allow(clippy::too_many_lines)]
17pub async fn launch() -> Result<()> { 19pub async fn launch() -> Result<()> {
18 let git_repo = Repo::discover().context("cannot find a git repository")?; 20 let git_repo = Repo::discover().context("cannot find a git repository")?;
19 21
@@ -53,22 +55,139 @@ pub async fn launch() -> Result<()> {
53 ) 55 )
54 .await?; 56 .await?;
55 57
56 if git_repo.has_outstanding_changes()? { 58 let most_recent_proposal_patch_chain =
57 bail!("cannot pull changes when repository is not clean. discard changes and try again."); 59 get_most_recent_patch_with_ancestors(commit_events.clone())
58 } 60 .context("cannot get most recent patch for proposal")?;
61
62 let local_branch_tip = git_repo.get_tip_of_local_branch(&branch_name)?;
63
64 let (main_branch_name, master_tip) = git_repo.get_main_or_master_branch()?;
65
66 let (local_ahead_of_main, local_beind_main) =
67 git_repo.get_commits_ahead_behind(&master_tip, &local_branch_tip)?;
59 68
60 let most_recent_proposal_patch_chain = get_most_recent_patch_with_ancestors(commit_events) 69 let proposal_base_commit = str_to_sha1(&tag_value(
61 .context("cannot get most recent patch for proposal")?; 70 most_recent_proposal_patch_chain
71 .last()
72 .context("there should be at least one patch as we have already checked for this")?,
73 "parent-commit",
74 )?)
75 .context("cannot get valid parent commit id from patch")?;
62 76
63 let applied = git_repo 77 let (_, proposal_behind_main) =
64 .apply_patch_chain(&branch_name, most_recent_proposal_patch_chain) 78 git_repo.get_commits_ahead_behind(&master_tip, &proposal_base_commit)?;
65 .context("cannot apply patch chain")?;
66 79
67 if applied.is_empty() { 80 let proposal_tip =
81 str_to_sha1(
82 &get_commit_id_from_patch(most_recent_proposal_patch_chain.first().context(
83 "there should be at least one patch as we have already checked for this",
84 )?)
85 .context("cannot get valid commit_id from patch")?,
86 )
87 .context("cannot get valid commit_id from patch")?;
88
89 // if uptodate
90 if proposal_tip.eq(&local_branch_tip) {
68 println!("branch already up-to-date"); 91 println!("branch already up-to-date");
69 } else { 92 }
93 // if new appendments
94 else if most_recent_proposal_patch_chain.iter().any(|patch| {
95 get_commit_id_from_patch(patch)
96 .unwrap_or_default()
97 .eq(&local_branch_tip.to_string())
98 }) {
99 check_clean(&git_repo)?;
100 let applied = git_repo
101 .apply_patch_chain(&branch_name, most_recent_proposal_patch_chain)
102 .context("cannot apply patch chain")?;
70 println!("applied {} new commits", applied.len(),); 103 println!("applied {} new commits", applied.len(),);
71 } 104 }
105 // if parent commit doesnt exist
106 else if !git_repo.does_commit_exist(&proposal_base_commit.to_string())? {
107 println!(
108 "a new version of the proposal has a prant commit that doesnt exist in your local repository."
109 );
110 println!("your '{main_branch_name}' branch may not be up-to-date.");
111 println!("manually run `git pull` on '{main_branch_name}' and try again");
112 }
113 // if tip of local in proposal history (new, ammended or rebased version but no
114 // local changes)
115 else if commit_events.iter().any(|patch| {
116 get_commit_id_from_patch(patch)
117 .unwrap_or_default()
118 .eq(&local_branch_tip.to_string())
119 }) {
120 check_clean(&git_repo)?;
121
122 git_repo.create_branch_at_commit(&branch_name, &proposal_base_commit.to_string())?;
123 let applied = git_repo
124 .apply_patch_chain(&branch_name, most_recent_proposal_patch_chain)
125 .context("cannot apply patch chain")?;
126
127 println!(
128 "pulled new version of proposal ({} ahead {} behind '{main_branch_name}'), replacing old version ({} ahead {} behind '{main_branch_name}')",
129 applied.len(),
130 proposal_behind_main.len(),
131 local_ahead_of_main.len(),
132 local_beind_main.len(),
133 );
134 }
135 // if tip of proposal in branch in history (local appendments made to up-to-date
136 // proposal)
137 else if let Ok((local_ahead_of_proposal, _)) =
138 git_repo.get_commits_ahead_behind(&proposal_tip, &local_branch_tip)
139 {
140 println!(
141 "local proposal branch exists with {} unpublished commits on top of the most up-to-date version of the proposal",
142 local_ahead_of_proposal.len()
143 );
144 }
145 // user has probably has an unpublished rebase of the latest proposal version
146 // if tip of proposal commits exist (were once part of branch but have been
147 // ammended and git clean up job hasn't removed them)
148 else if git_repo.does_commit_exist(&proposal_tip.to_string())? {
149 println!(
150 "you have previously applied the latest version of the proposal ({} ahead {} behind '{main_branch_name}') but your local proposal branch has other unpublished changes ({} ahead {} behind '{main_branch_name}')",
151 most_recent_proposal_patch_chain.len(),
152 proposal_behind_main.len(),
153 local_ahead_of_main.len(),
154 local_beind_main.len(),
155 );
156 println!(
157 "if this sounds right then consider publishing your rebase `ngit push --force` or discarding your local branch"
158 );
159 }
160 // user has probaly has an unpublished rebase of an older version of the
161 // proposal
162 else {
163 println!(
164 "your local proposal branch ({} ahead {} behind '{main_branch_name}') has conflicting changes with the latest published proposal ({} ahead {} behind '{main_branch_name}')",
165 local_ahead_of_main.len(),
166 local_beind_main.len(),
167 most_recent_proposal_patch_chain.len(),
168 proposal_behind_main.len(),
169 );
170 println!(
171 "its likely that you are working off an old proposal version because git has no record of the latest proposal commit."
172 );
173 println!(
174 "it is possible that you have ammended the latest version and git has delete this commit as part of a clean up"
175 );
176
177 println!("to view the latest proposal but retain your changes:");
178 println!(" 1) create a new branch off the tip commit of this one to store your changes");
179 println!(" 2) run `ngit list` and checkout the latest published version of this proposal");
180
181 println!("if you are confident in your changes consider running `ngit push --force`");
182 }
183 Ok(())
184}
72 185
186fn check_clean(git_repo: &Repo) -> Result<()> {
187 if git_repo.has_outstanding_changes()? {
188 bail!(
189 "cannot pull proposal branch when repository is not clean. discard or stash (un)staged changes and try again."
190 );
191 }
73 Ok(()) 192 Ok(())
74} 193}