rust-lang / git2-rs

libgit2 bindings for Rust
https://docs.rs/git2
Apache License 2.0
1.64k stars 380 forks source link

How to pull & rebase #994

Closed NYBACHOK closed 9 months ago

NYBACHOK commented 9 months ago

Hi, I need to write method similar for git pull upstream master --rebase and after that to push changes to remote

ehuss commented 9 months ago

I believe it would be roughly equivalent to do a fetch (https://github.com/rust-lang/git2-rs/blob/master/examples/fetch.rs roughly shows the API) and then a rebase using Repository::rebase (https://github.com/rust-lang/git2-rs/blob/dc63db55c5d605e6ac0516d48c3fff4e34b709f8/src/rebase.rs#L337-L441 shows some rough examples of that API).

NYBACHOK commented 9 months ago

@ehuss Thanks. Later I add my result with merging and rebase. I think it will be helpful for others.

NYBACHOK commented 9 months ago

My final code. In one moment, I was said to replace rebase with merge

pub fn repo_merge_upstream( &self )
  -> Result< (), GitErrors >
  {
    //---------------- ADD REMOTE AND FETCH -------------\\
    const UPSTREAM_NAME : &str = "upstream";

    let mut fetch_options = FetchOptions::new();

    fetch_options.remote_callbacks( Self::callback_with_credentials_get( &self.options.auth  ) );

    self.repository.remote( UPSTREAM_NAME, self.options.upstream_url.0.as_str() )
    .map_err( GitErrors::Git2Error )?
    .fetch( &[ &self.options.default_branch ], Some( &mut fetch_options ), None)
    .map_err( GitErrors::Git2Error )?;

    //--------------- MERGE ------------\\

    let merge_from_name = format!( "{UPSTREAM_NAME}/{}", self.options.default_branch );

    let merge_from = self.repository.find_branch( &merge_from_name, BranchType::Remote )
    .map_err( GitErrors::Git2Error )?;
    let merge_into = self.repository.find_branch(&self.options.default_branch , BranchType::Local )
    .map_err( GitErrors::Git2Error )?;

    let merge_from_commit = self.repository.reference_to_annotated_commit( merge_from.get() )
    .map_err( GitErrors::Git2Error )?;

    let (merge_analysis, _merge_preference) = self.repository.merge_analysis(&[ &merge_from_commit] )
    .map_err( GitErrors::Git2Error )?;

    if merge_analysis.is_up_to_date()
    {
      return Err( GitErrors::UpToDate )
    }

    let mut checkout_opt = CheckoutBuilder::default();
    checkout_opt.update_index( true );
    checkout_opt.use_theirs( true );
    checkout_opt.allow_conflicts( true );

    self.repository.merge( &[ &merge_from_commit ], None, Some( &mut checkout_opt ) )
    .map_err( GitErrors::Git2Error )?;

    let mut index = self.repository.index()
    .map_err( GitErrors::Git2Error )?;

    index.add_all( [ "*" ].iter(), IndexAddOption::DEFAULT, None )
    .map_err( GitErrors::Git2Error )?;

    index.write()
    .map_err( GitErrors::Git2Error )?;

    let tree_id = index.write_tree()
    .map_err( GitErrors::Git2Error )?;

    if index.has_conflicts()
    {
      return Err( GitErrors::MergeConflicts )
    }

    let signature = Signature::now( "example", "example@example.com" ).map_err( GitErrors::Git2Error )?;
    let commit_msg = format!( "Merge branch '{UPSTREAM_NAME}/{}'", self.options.default_branch );

    let _commit_oid = self.repository.commit
    (
      Some( "HEAD" ),       // Use HEAD to automatically point to the current branch
      &signature,             // Commit author
      &signature,          // Commit committer
      &commit_msg,         // Commit message
      &self.repository.find_tree( tree_id ).map_err( GitErrors::Git2Error )?, // The tree to commit
      &[ &merge_into.get().peel_to_commit().map_err( GitErrors::Git2Error )? ],
    ).map_err( GitErrors::Git2Error )?;

    dbg!( &_commit_oid );

    // --------------- REBASE ----------- \\

    // let upstream_master_oid = self.repository.refname_to_id(&format!("refs/remotes/{UPSTREAM_NAME}/{}", self.options.default_branch))
    // .map_err( GitErrors::Git2Error )?;
    // dbg!(&upstream_master_oid);
    // 
    // let upstream_master_commit = self.repository.find_annotated_commit(upstream_master_oid)
    // .map_err( GitErrors::Git2Error )?;
    // 
    // let self_master_oid = self.repository.refname_to_id(&format!("refs/remotes/origin/{}", self.options.default_branch))
    // .map_err( GitErrors::Git2Error )?;
    // dbg!(&self_master_oid);
    // 
    // let self_master_commit = self.repository.find_annotated_commit(upstream_master_oid)
    // .map_err( GitErrors::Git2Error )?;
    // 
    // let mut rebase_options = RebaseOptions::default();
    // rebase_options.inmemory( false );
    // 
    // let mut rebase = self.repository.rebase(
    //   None,
    //   Some( &upstream_master_commit ),
    //   Some(&self_master_commit),
    //   Some(&mut rebase_options)
    // )
    // .map_err( GitErrors::Git2Error )?;
    // 
    // for reb in rebase.by_ref()
    // {
    //   reb.map_err( GitErrors::Git2Error )?;
    // }
    // 
    // rebase.finish( None)
    // .map_err( GitErrors::Git2Error )?;

    //------------------ PUSH TO REMOTE ----------------\\

    let mut remote = self.repository.find_remote( "origin" )
    .map_err( GitErrors::Git2Error )?;

    let mut push_options = PushOptions::new();
    push_options.remote_callbacks(Self::callback_with_credentials_get( &self.options.auth ) );

    // "+refs/heads/master:refs/heads/master" for force push
    let refspec : &[&str] = &["refs/heads/master:refs/heads/master" ];

    remote.push(refspec, Some( &mut push_options ) )
    .map_err( GitErrors::Git2Error )?;

    Ok( () )
  }