1- use crate :: domain:: { RepositoryInfo , RepoStatus , StatusItem , BranchInfo , CommitInfo , LocalBranch , TagInfo } ;
1+ use crate :: domain:: { RepositoryInfo , RepoStatus , StatusItem , BranchInfo , CommitInfo , LocalBranch , TagInfo , RemoteInfo } ;
22use crate :: error:: { AppError , Result } ;
33use git2:: { Repository , StatusOptions } ;
44use ignore:: WalkBuilder ;
@@ -488,9 +488,11 @@ fn publish_branch_impl(
488488 // Get repository config for credential helper
489489 let config = repo. config ( ) ?;
490490
491- // Clone the username/password for the callback
492- let auth_username = username. clone ( ) ;
493- let auth_password = password. clone ( ) ;
491+ // Clone credentials for both push and fetch callbacks
492+ let auth_username_push = username. clone ( ) ;
493+ let auth_password_push = password. clone ( ) ;
494+ let auth_username_fetch = username. clone ( ) ;
495+ let auth_password_fetch = password. clone ( ) ;
494496
495497 // Set up remote callbacks for authentication
496498 let mut callbacks = git2:: RemoteCallbacks :: new ( ) ;
@@ -501,7 +503,7 @@ fn publish_branch_impl(
501503 let default_username = username_from_url. unwrap_or ( "git" ) ;
502504
503505 // If username and password are provided, use them
504- if let ( Some ( user) , Some ( pass) ) = ( & auth_username , & auth_password ) {
506+ if let ( Some ( user) , Some ( pass) ) = ( & auth_username_push , & auth_password_push ) {
505507 return git2:: Cred :: userpass_plaintext ( user, pass) ;
506508 }
507509
@@ -531,8 +533,35 @@ fn publish_branch_impl(
531533 let mut branch = repo. find_branch ( branch_name, git2:: BranchType :: Local ) ?;
532534 branch. set_upstream ( Some ( & format ! ( "{}/{}" , remote, branch_name) ) ) ?;
533535
534- // Manually update remote-tracking reference
535- update_tracking_branch ( repo, remote, branch_name) ?;
536+ // Fetch to update remote-tracking branch instead of manually updating it
537+ // This ensures the remote-tracking branch reflects the actual remote state
538+ let config_for_fetch = repo. config ( ) ?;
539+ let mut fetch_callbacks = git2:: RemoteCallbacks :: new ( ) ;
540+ fetch_callbacks. credentials ( move |url, username_from_url, allowed_types| {
541+ let default_username = username_from_url. unwrap_or ( "git" ) ;
542+ if let ( Some ( user) , Some ( pass) ) = ( & auth_username_fetch, & auth_password_fetch) {
543+ return git2:: Cred :: userpass_plaintext ( user, pass) ;
544+ }
545+ if allowed_types. contains ( git2:: CredentialType :: SSH_KEY ) {
546+ git2:: Cred :: ssh_key_from_agent ( default_username)
547+ } else if allowed_types. contains ( git2:: CredentialType :: USER_PASS_PLAINTEXT ) {
548+ git2:: Cred :: credential_helper ( & config_for_fetch, url, Some ( default_username) )
549+ } else if allowed_types. contains ( git2:: CredentialType :: DEFAULT ) {
550+ git2:: Cred :: credential_helper ( & config_for_fetch, url, Some ( default_username) )
551+ } else {
552+ Err ( git2:: Error :: from_str ( "no authentication method available" ) )
553+ }
554+ } ) ;
555+
556+ let mut fetch_options = git2:: FetchOptions :: new ( ) ;
557+ fetch_options. remote_callbacks ( fetch_callbacks) ;
558+
559+ // Fetch the specific branch to update the remote-tracking reference
560+ let fetch_refspec = format ! ( "{}:{}" , branch_name, branch_name) ;
561+ if let Err ( e) = remote_obj. fetch ( & [ & fetch_refspec] , Some ( & mut fetch_options) , None ) {
562+ // If fetch fails (e.g., remote doesn't allow anonymous fetch), log but don't fail the push
563+ eprintln ! ( "Warning: Failed to fetch after push: {}" , e) ;
564+ }
536565
537566 Ok ( ( ) )
538567}
@@ -567,9 +596,11 @@ fn push_branch_impl(
567596 // Get repository config for credential helper
568597 let config = repo. config ( ) ?;
569598
570- // Clone the username/password for the callback
571- let auth_username = username. clone ( ) ;
572- let auth_password = password. clone ( ) ;
599+ // Clone credentials for both push and fetch callbacks
600+ let auth_username_push = username. clone ( ) ;
601+ let auth_password_push = password. clone ( ) ;
602+ let auth_username_fetch = username. clone ( ) ;
603+ let auth_password_fetch = password. clone ( ) ;
573604
574605 // Set up remote callbacks for authentication
575606 let mut callbacks = git2:: RemoteCallbacks :: new ( ) ;
@@ -580,7 +611,7 @@ fn push_branch_impl(
580611 let default_username = username_from_url. unwrap_or ( "git" ) ;
581612
582613 // If username and password are provided, use them
583- if let ( Some ( user) , Some ( pass) ) = ( & auth_username , & auth_password ) {
614+ if let ( Some ( user) , Some ( pass) ) = ( & auth_username_push , & auth_password_push ) {
584615 return git2:: Cred :: userpass_plaintext ( user, pass) ;
585616 }
586617
@@ -606,21 +637,36 @@ fn push_branch_impl(
606637 // Perform the push
607638 remote_obj. push ( & [ & refspec] , Some ( & mut push_options) ) ?;
608639
609- // Manually update remote-tracking reference
610- update_tracking_branch ( repo, remote, branch_name) ?;
640+ // Fetch to update remote-tracking branch instead of manually updating it
641+ // This ensures the remote-tracking branch reflects the actual remote state
642+ let config_for_fetch = repo. config ( ) ?;
643+ let mut fetch_callbacks = git2:: RemoteCallbacks :: new ( ) ;
644+ fetch_callbacks. credentials ( move |url, username_from_url, allowed_types| {
645+ let default_username = username_from_url. unwrap_or ( "git" ) ;
646+ if let ( Some ( user) , Some ( pass) ) = ( & auth_username_fetch, & auth_password_fetch) {
647+ return git2:: Cred :: userpass_plaintext ( user, pass) ;
648+ }
649+ if allowed_types. contains ( git2:: CredentialType :: SSH_KEY ) {
650+ git2:: Cred :: ssh_key_from_agent ( default_username)
651+ } else if allowed_types. contains ( git2:: CredentialType :: USER_PASS_PLAINTEXT ) {
652+ git2:: Cred :: credential_helper ( & config_for_fetch, url, Some ( default_username) )
653+ } else if allowed_types. contains ( git2:: CredentialType :: DEFAULT ) {
654+ git2:: Cred :: credential_helper ( & config_for_fetch, url, Some ( default_username) )
655+ } else {
656+ Err ( git2:: Error :: from_str ( "no authentication method available" ) )
657+ }
658+ } ) ;
611659
612- Ok ( ( ) )
613- }
660+ let mut fetch_options = git2 :: FetchOptions :: new ( ) ;
661+ fetch_options . remote_callbacks ( fetch_callbacks ) ;
614662
615- fn update_tracking_branch ( repo : & Repository , remote : & str , branch_name : & str ) -> Result < ( ) > {
616- // Get the OID of the local branch
617- let local_branch = repo. find_branch ( branch_name, git2:: BranchType :: Local ) ?;
618- let oid = local_branch. get ( ) . peel_to_commit ( ) ?. id ( ) ;
663+ // Fetch the specific branch to update the remote-tracking reference
664+ let fetch_refspec = format ! ( "{}:{}" , branch_name, branch_name) ;
665+ if let Err ( e) = remote_obj. fetch ( & [ & fetch_refspec] , Some ( & mut fetch_options) , None ) {
666+ // If fetch fails (e.g., remote doesn't allow anonymous fetch), log but don't fail the push
667+ eprintln ! ( "Warning: Failed to fetch after push: {}" , e) ;
668+ }
619669
620- // Update the remote-tracking branch reference
621- let remote_ref_name = format ! ( "refs/remotes/{}/{}" , remote, branch_name) ;
622- repo. reference ( & remote_ref_name, oid, true , "Update remote-tracking branch after push" ) ?;
623-
624670 Ok ( ( ) )
625671}
626672
@@ -1120,3 +1166,61 @@ fn delete_remote_tag_impl(
11201166
11211167 Ok ( ( ) )
11221168}
1169+
1170+ /// Get list of remotes for a repository
1171+ #[ tauri:: command]
1172+ pub async fn get_remotes ( path : String ) -> std:: result:: Result < Vec < RemoteInfo > , String > {
1173+ let repo = Repository :: open ( & path) . map_err ( |e| e. to_string ( ) ) ?;
1174+ get_remotes_impl ( & repo) . map_err ( |e| e. to_string ( ) )
1175+ }
1176+
1177+ fn get_remotes_impl ( repo : & Repository ) -> Result < Vec < RemoteInfo > > {
1178+ let remotes = repo. remotes ( ) ?;
1179+ let mut remote_infos = Vec :: new ( ) ;
1180+
1181+ for remote_name in remotes. iter ( ) {
1182+ if let Some ( name) = remote_name {
1183+ if let Ok ( remote) = repo. find_remote ( name) {
1184+ remote_infos. push ( RemoteInfo {
1185+ name : name. to_string ( ) ,
1186+ url : remote. url ( ) . map ( |s| s. to_string ( ) ) ,
1187+ push_url : remote. pushurl ( ) . map ( |s| s. to_string ( ) ) ,
1188+ } ) ;
1189+ }
1190+ }
1191+ }
1192+
1193+ Ok ( remote_infos)
1194+ }
1195+
1196+ /// Add a new remote
1197+ #[ tauri:: command]
1198+ pub async fn add_remote ( path : String , name : String , url : String ) -> std:: result:: Result < ( ) , String > {
1199+ let repo = Repository :: open ( & path) . map_err ( |e| e. to_string ( ) ) ?;
1200+ repo. remote ( & name, & url) . map_err ( |e| e. to_string ( ) ) ?;
1201+ Ok ( ( ) )
1202+ }
1203+
1204+ /// Remove a remote
1205+ #[ tauri:: command]
1206+ pub async fn remove_remote ( path : String , name : String ) -> std:: result:: Result < ( ) , String > {
1207+ let repo = Repository :: open ( & path) . map_err ( |e| e. to_string ( ) ) ?;
1208+ repo. remote_delete ( & name) . map_err ( |e| e. to_string ( ) ) ?;
1209+ Ok ( ( ) )
1210+ }
1211+
1212+ /// Rename a remote
1213+ #[ tauri:: command]
1214+ pub async fn rename_remote ( path : String , old_name : String , new_name : String ) -> std:: result:: Result < ( ) , String > {
1215+ let repo = Repository :: open ( & path) . map_err ( |e| e. to_string ( ) ) ?;
1216+ repo. remote_rename ( & old_name, & new_name) . map_err ( |e| e. to_string ( ) ) ?;
1217+ Ok ( ( ) )
1218+ }
1219+
1220+ /// Set remote URL
1221+ #[ tauri:: command]
1222+ pub async fn set_remote_url ( path : String , name : String , url : String ) -> std:: result:: Result < ( ) , String > {
1223+ let repo = Repository :: open ( & path) . map_err ( |e| e. to_string ( ) ) ?;
1224+ repo. remote_set_url ( & name, & url) . map_err ( |e| e. to_string ( ) ) ?;
1225+ Ok ( ( ) )
1226+ }
0 commit comments