thoughts_tool/git/
pull.rs1use anyhow::{Context, Result};
2use git2::{AnnotatedCommit, Repository};
3use std::path::Path;
4
5use crate::git::shell_fetch;
6use crate::git::utils::is_worktree_dirty;
7
8pub fn pull_ff_only(repo_path: &Path, remote_name: &str, branch: Option<&str>) -> Result<()> {
11 {
13 let repo = Repository::open(repo_path)
14 .with_context(|| format!("Failed to open repository at {}", repo_path.display()))?;
15 if repo.find_remote(remote_name).is_err() {
16 return Ok(());
18 }
19 }
20
21 let branch = branch.unwrap_or("main");
22
23 shell_fetch::fetch(repo_path, remote_name).with_context(|| {
25 format!(
26 "Fetch failed for remote '{}' in '{}'",
27 remote_name,
28 repo_path.display()
29 )
30 })?;
31
32 let repo = Repository::open(repo_path)
34 .with_context(|| format!("Failed to re-open repository at {}", repo_path.display()))?;
35
36 let remote_ref = format!("refs/remotes/{}/{}", remote_name, branch);
38 let fetch_head = match repo.find_reference(&remote_ref) {
39 Ok(r) => r,
40 Err(_) => {
41 return Ok(());
43 }
44 };
45 let fetch_commit = repo.reference_to_annotated_commit(&fetch_head)?;
46
47 try_fast_forward(&repo, &format!("refs/heads/{}", branch), &fetch_commit)?;
48 Ok(())
49}
50
51fn try_fast_forward(
52 repo: &Repository,
53 local_ref: &str,
54 fetch_commit: &AnnotatedCommit,
55) -> Result<()> {
56 let analysis = repo.merge_analysis(&[fetch_commit])?;
57 if analysis.0.is_up_to_date() {
58 return Ok(());
59 }
60 if analysis.0.is_fast_forward() {
61 if is_worktree_dirty(repo)? {
63 anyhow::bail!(
64 "Cannot fast-forward: working tree has uncommitted changes. Please commit or stash before pulling."
65 );
66 }
67 repo.set_head(local_ref)?;
71 let obj = repo.find_object(fetch_commit.id(), None)?;
73 repo.reset(
74 &obj,
75 git2::ResetType::Hard,
76 Some(git2::build::CheckoutBuilder::default().force()),
77 )?;
78 return Ok(());
79 }
80 anyhow::bail!(
81 "Non fast-forward update required (local and remote have diverged; rebase or merge needed)."
82 )
83}