upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/git.rs34
-rw-r--r--src/sub_commands/pull.rs141
2 files changed, 164 insertions, 11 deletions
diff --git a/src/git.rs b/src/git.rs
index 0a06ab5..6edca0f 100644
--- a/src/git.rs
+++ b/src/git.rs
@@ -344,6 +344,12 @@ impl RepoActions for Repo {
344 } 344 }
345 345
346 fn create_branch_at_commit(&self, branch_name: &str, commit: &str) -> Result<()> { 346 fn create_branch_at_commit(&self, branch_name: &str, commit: &str) -> Result<()> {
347 let branch_checkedout = self.get_checked_out_branch_name()?.eq(branch_name);
348 if branch_checkedout {
349 let (name, _) = self.get_main_or_master_branch()?;
350 self.checkout(name)?;
351 }
352
347 self.git_repo 353 self.git_repo
348 .branch( 354 .branch(
349 branch_name, 355 branch_name,
@@ -351,6 +357,10 @@ impl RepoActions for Repo {
351 true, 357 true,
352 ) 358 )
353 .context("branch could not be created")?; 359 .context("branch could not be created")?;
360
361 if branch_checkedout {
362 self.checkout(branch_name)?;
363 }
354 Ok(()) 364 Ok(())
355 } 365 }
356 /* returns patches applied */ 366 /* returns patches applied */
@@ -1292,6 +1302,30 @@ mod tests {
1292 assert_eq!(test_repo.checkout(branch_name)?, ahead_1_oid); 1302 assert_eq!(test_repo.checkout(branch_name)?, ahead_1_oid);
1293 Ok(()) 1303 Ok(())
1294 } 1304 }
1305
1306 #[test]
1307 fn when_branch_is_checkedout_new_tip_specified_it_is_updated() -> Result<()> {
1308 let test_repo = GitTestRepo::default();
1309 test_repo.populate()?;
1310 // create feature branch and add 2 commits
1311 test_repo.create_branch("feature")?;
1312 test_repo.checkout("feature")?;
1313 std::fs::write(test_repo.dir.join("t3.md"), "some content")?;
1314 let ahead_1_oid = test_repo.stage_and_commit("add t3.md")?;
1315 std::fs::write(test_repo.dir.join("t4.md"), "some content")?;
1316 let ahead_2_oid = test_repo.stage_and_commit("add t4.md")?;
1317
1318 let git_repo = Repo::from_path(&test_repo.dir)?;
1319
1320 let branch_name = "test-name-1";
1321 git_repo.create_branch_at_commit(branch_name, &ahead_1_oid.to_string())?;
1322 test_repo.checkout(branch_name)?;
1323 git_repo.create_branch_at_commit(branch_name, &ahead_2_oid.to_string())?;
1324 test_repo.checkout("main")?;
1325
1326 assert_eq!(test_repo.checkout(branch_name)?, ahead_2_oid);
1327 Ok(())
1328 }
1295 } 1329 }
1296 } 1330 }
1297 1331
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}