diff options
Diffstat (limited to 'src/sub_commands')
| -rw-r--r-- | src/sub_commands/pull.rs | 141 |
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 @@ | |||
| 1 | use anyhow::{bail, Context, Result}; | 1 | use anyhow::{bail, Context, Result}; |
| 2 | 2 | ||
| 3 | use super::list::{get_commit_id_from_patch, tag_value}; | ||
| 3 | #[cfg(not(test))] | 4 | #[cfg(not(test))] |
| 4 | use crate::client::Client; | 5 | use crate::client::Client; |
| 5 | #[cfg(test)] | 6 | #[cfg(test)] |
| 6 | use crate::client::MockConnect; | 7 | use crate::client::MockConnect; |
| 7 | use crate::{ | 8 | use 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)] | ||
| 17 | pub async fn launch() -> Result<()> { | 19 | pub 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 | ||
| 186 | fn 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 | } |