@@ -231,12 +231,23 @@ pub fn hooks_pre_push(
231231 let remote_branch = remote_branch_name. unwrap_or ( branch) ;
232232 let remote_ref = format ! ( "refs/heads/{remote_branch}" ) ;
233233
234- // Try to get remote commit SHA from upstream
235- // If there's no upstream (new branch), use 40 zeros as per Git spec
236- let remote_sha = if let Ok ( upstream) = local_branch. upstream ( )
237- {
238- upstream. get ( ) . peel_to_commit ( ) ?. id ( ) . to_string ( )
234+ // Try to get remote commit SHA from the actual push target remote
235+ // We need to look up refs/remotes/<remote>/<branch>, not the upstream
236+ let remote_sha = if let Some ( remote_name) = remote {
237+ // Try to find the remote tracking branch for the push target
238+ let remote_ref_name =
239+ format ! ( "refs/remotes/{remote_name}/{remote_branch}" ) ;
240+ if let Ok ( remote_ref) =
241+ repo. find_reference ( & remote_ref_name)
242+ {
243+ // Remote branch exists, get its SHA
244+ remote_ref. peel_to_commit ( ) ?. id ( ) . to_string ( )
245+ } else {
246+ // Remote branch doesn't exist yet (new branch on remote)
247+ "0" . repeat ( 40 )
248+ }
239249 } else {
250+ // No remote specified (shouldn't happen in practice)
240251 "0" . repeat ( 40 )
241252 } ;
242253
@@ -1019,4 +1030,126 @@ exit 0
10191030 response. stderr
10201031 ) ;
10211032 }
1033+
1034+ #[ test]
1035+ fn test_pre_push_uses_push_target_remote_not_upstream ( ) {
1036+ let ( _td, repo) = repo_init ( ) ;
1037+
1038+ // repo_init() already creates an initial commit on master
1039+ // Get the current HEAD commit
1040+ let head = repo. head ( ) . unwrap ( ) ;
1041+ let local_commit = head. target ( ) . unwrap ( ) ;
1042+
1043+ // Set up scenario:
1044+ // - Local master is at local_commit (latest)
1045+ // - origin/master exists at local_commit (fully synced - upstream)
1046+ // - backup/master exists at old_commit (behind/different)
1047+ // - Branch tracks origin/master as upstream
1048+ // - We push to "backup" remote
1049+ // - Expected: remote SHA should be old_commit
1050+ // - Bug (before fix): remote SHA was from upstream origin/master
1051+
1052+ // Create origin/master tracking branch (at same commit as local)
1053+ repo. reference (
1054+ "refs/remotes/origin/master" ,
1055+ local_commit,
1056+ true ,
1057+ "create origin/master" ,
1058+ )
1059+ . unwrap ( ) ;
1060+
1061+ // Create backup/master at a different commit (simulating it's behind)
1062+ // We can't create a reference to a non-existent commit, so we'll
1063+ // create an actual old commit first
1064+ let sig = repo. signature ( ) . unwrap ( ) ;
1065+ let tree_id = {
1066+ let mut index = repo. index ( ) . unwrap ( ) ;
1067+ index. write_tree ( ) . unwrap ( )
1068+ } ;
1069+ let tree = repo. find_tree ( tree_id) . unwrap ( ) ;
1070+ let old_commit = repo
1071+ . commit (
1072+ None , // Don't update any refs
1073+ & sig,
1074+ & sig,
1075+ "old backup commit" ,
1076+ & tree,
1077+ & [ ] , // No parents
1078+ )
1079+ . unwrap ( ) ;
1080+
1081+ // Now create backup/master pointing to this old commit
1082+ repo. reference (
1083+ "refs/remotes/backup/master" ,
1084+ old_commit,
1085+ true ,
1086+ "create backup/master at old commit" ,
1087+ )
1088+ . unwrap ( ) ;
1089+
1090+ // Configure branch.master.remote and branch.master.merge to set upstream
1091+ {
1092+ let mut config = repo. config ( ) . unwrap ( ) ;
1093+ config. set_str ( "branch.master.remote" , "origin" ) . unwrap ( ) ;
1094+ config
1095+ . set_str ( "branch.master.merge" , "refs/heads/master" )
1096+ . unwrap ( ) ;
1097+ }
1098+
1099+ // Hook that extracts and prints the remote SHA
1100+ let hook = format ! (
1101+ r#"#!/bin/sh
1102+ stdin_content=$(cat)
1103+ echo "stdin: $stdin_content"
1104+
1105+ # Extract the 4th field (remote SHA)
1106+ remote_sha=$(echo "$stdin_content" | awk '{{print $4}}')
1107+ echo "remote_sha=$remote_sha"
1108+
1109+ # When pushing to backup, we should get backup/master's old SHA
1110+ # NOT the SHA from origin/master upstream
1111+ if [ "$remote_sha" = "{}" ]; then
1112+ echo "BUG: Got origin/master SHA (upstream) instead of backup/master SHA" >&2
1113+ exit 1
1114+ fi
1115+
1116+ if [ "$remote_sha" = "{}" ]; then
1117+ echo "SUCCESS: Got correct backup/master SHA"
1118+ exit 0
1119+ fi
1120+
1121+ echo "ERROR: Got unexpected SHA: $remote_sha" >&2
1122+ echo "Expected backup/master: {}" >&2
1123+ echo "Or origin/master (bug): {}" >&2
1124+ exit 1
1125+ "# ,
1126+ local_commit, old_commit, old_commit, local_commit
1127+ ) ;
1128+
1129+ create_hook ( & repo, HOOK_PRE_PUSH , hook. as_bytes ( ) ) ;
1130+
1131+ // Push to "backup" remote (which doesn't have master yet)
1132+ // This is different from the upstream "origin"
1133+ let res = hooks_pre_push (
1134+ & repo,
1135+ None ,
1136+ Some ( "backup" ) ,
1137+ "https://github.com/user/backup-repo.git" ,
1138+ Some ( "master" ) ,
1139+ None ,
1140+ )
1141+ . unwrap ( ) ;
1142+
1143+ let HookResult :: Run ( response) = res else {
1144+ panic ! ( "Expected Run result, got: {res:?}" )
1145+ } ;
1146+
1147+ // This test now passes after fix
1148+ assert ! (
1149+ response. is_successful( ) ,
1150+ "Hook should receive backup/master SHA, not upstream origin/master SHA.\n stdout: {}\n stderr: {}" ,
1151+ response. stdout,
1152+ response. stderr
1153+ ) ;
1154+ }
10221155}
0 commit comments