Skip to content

Commit aff07c2

Browse files
committed
better error handling, fix upstream sha
1 parent 67c8241 commit aff07c2

File tree

3 files changed

+162
-9
lines changed

3 files changed

+162
-9
lines changed

git2-hooks/src/lib.rs

Lines changed: 138 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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.\nstdout: {}\nstderr: {}",
1151+
response.stdout,
1152+
response.stderr
1153+
);
1154+
}
10221155
}

src/popups/push.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,18 @@ impl PushPopup {
148148
let remote_url = asyncgit::sync::get_remote_url(
149149
&self.repo.borrow(),
150150
&remote,
151-
)?
152-
.unwrap_or_default();
151+
)?;
152+
153+
// If remote doesn't have a URL configured, we can't push
154+
let Some(remote_url) = remote_url else {
155+
log::error!("remote '{remote}' has no URL configured");
156+
self.queue.push(InternalEvent::ShowErrorMsg(format!(
157+
"Remote '{remote}' has no URL configured"
158+
)));
159+
self.pending = false;
160+
self.visible = false;
161+
return Ok(());
162+
};
153163

154164
// run pre push hook - can reject push
155165
if let HookResult::NotOk(e) = hooks_pre_push(

src/popups/push_tags.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,18 @@ impl PushTagsPopup {
9090
let remote_url = asyncgit::sync::get_remote_url(
9191
&self.repo.borrow(),
9292
&remote,
93-
)?
94-
.unwrap_or_default();
93+
)?;
94+
95+
// If remote doesn't have a URL configured, we can't push
96+
let Some(remote_url) = remote_url else {
97+
log::error!("remote '{remote}' has no URL configured");
98+
self.queue.push(InternalEvent::ShowErrorMsg(format!(
99+
"Remote '{remote}' has no URL configured"
100+
)));
101+
self.pending = false;
102+
self.visible = false;
103+
return Ok(());
104+
};
95105

96106
// run pre push hook - can reject push
97107
if let HookResult::NotOk(e) = hooks_pre_push(

0 commit comments

Comments
 (0)