Skip to content

Commit 81ba65e

Browse files
committed
git: force push when not known to be a fast-forward
With this change, we no longer fail if the user moves a branch sideways or backwards and then push. The push should ideally only succeed if the remote branch is where we thought it was (like `git push --force-with-lease`), but that requires rust-lang/git2-rs#733 to be fixed first.
1 parent d555e0c commit 81ba65e

File tree

3 files changed

+66
-5
lines changed

3 files changed

+66
-5
lines changed

lib/src/git.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ pub fn push_commit(
173173
target: &Commit,
174174
remote_name: &str,
175175
remote_branch: &str,
176+
// TODO: We want this to be an Option<CommitId> for the expected current commit on the remote.
177+
// It's a blunt "force" option instead until git2-rs supports the "push negotiation" callback
178+
// (https://github.com/rust-lang/git2-rs/issues/733).
179+
force: bool,
176180
) -> Result<(), GitPushError> {
177181
// Create a temporary ref to work around https://github.com/libgit2/libgit2/issues/3178
178182
let temp_ref_name = format!("refs/jj/git-push/{}", target.id().hex());
@@ -184,7 +188,12 @@ pub fn push_commit(
184188
)?;
185189
// Need to add "refs/heads/" prefix due to https://github.com/libgit2/libgit2/issues/1125
186190
let qualified_remote_branch = format!("refs/heads/{}", remote_branch);
187-
let refspec = format!("{}:{}", temp_ref_name, qualified_remote_branch);
191+
let refspec = format!(
192+
"{}{}:{}",
193+
(if force { "+" } else { "" }),
194+
temp_ref_name,
195+
qualified_remote_branch
196+
);
188197
let result = push_ref(git_repo, remote_name, &qualified_remote_branch, &refspec);
189198
// TODO: Figure out how to do the equivalent of absl::Cleanup for
190199
// temp_ref.delete().
@@ -196,6 +205,8 @@ pub fn delete_remote_branch(
196205
git_repo: &git2::Repository,
197206
remote_name: &str,
198207
remote_branch: &str,
208+
/* TODO: Similar to push_commit(), we want an CommitId for the expected current commit on
209+
* the remote. */
199210
) -> Result<(), GitPushError> {
200211
// Need to add "refs/heads/" prefix due to https://github.com/libgit2/libgit2/issues/1125
201212
let qualified_remote_branch = format!("refs/heads/{}", remote_branch);

lib/tests/test_git.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ fn test_push_commit_success() {
353353
let temp_dir = tempfile::tempdir().unwrap();
354354
let setup = set_up_push_repos(&settings, &temp_dir);
355355
let clone_repo = setup.jj_repo.store().git_repo().unwrap();
356-
let result = git::push_commit(&clone_repo, &setup.new_commit, "origin", "main");
356+
let result = git::push_commit(&clone_repo, &setup.new_commit, "origin", "main", false);
357357
assert_eq!(result, Ok(()));
358358

359359
// Check that the ref got updated in the source repo
@@ -388,10 +388,38 @@ fn test_push_commit_not_fast_forward() {
388388
&new_commit,
389389
"origin",
390390
"main",
391+
false,
391392
);
392393
assert_eq!(result, Err(GitPushError::NotFastForward));
393394
}
394395

396+
#[test]
397+
fn test_push_commit_not_fast_forward_with_force() {
398+
let settings = testutils::user_settings();
399+
let temp_dir = tempfile::tempdir().unwrap();
400+
let mut setup = set_up_push_repos(&settings, &temp_dir);
401+
let new_commit = testutils::create_random_commit(&settings, &setup.jj_repo)
402+
.write_to_new_transaction(&setup.jj_repo, "test");
403+
setup.jj_repo = setup.jj_repo.reload();
404+
let result = git::push_commit(
405+
&setup.jj_repo.store().git_repo().unwrap(),
406+
&new_commit,
407+
"origin",
408+
"main",
409+
true,
410+
);
411+
assert_eq!(result, Ok(()));
412+
413+
// Check that the ref got updated in the source repo
414+
let source_repo = git2::Repository::open(&setup.source_repo_dir).unwrap();
415+
let new_target = source_repo
416+
.find_reference("refs/heads/main")
417+
.unwrap()
418+
.target();
419+
let new_oid = Oid::from_bytes(&new_commit.id().0).unwrap();
420+
assert_eq!(new_target, Some(new_oid));
421+
}
422+
395423
#[test]
396424
fn test_push_commit_no_such_remote() {
397425
let settings = testutils::user_settings();
@@ -402,6 +430,7 @@ fn test_push_commit_no_such_remote() {
402430
&setup.new_commit,
403431
"invalid-remote",
404432
"main",
433+
false,
405434
);
406435
assert!(matches!(result, Err(GitPushError::NoSuchRemote(_))));
407436
}
@@ -416,6 +445,7 @@ fn test_push_commit_invalid_remote() {
416445
&setup.new_commit,
417446
"http://invalid-remote",
418447
"main",
448+
false,
419449
);
420450
assert!(matches!(result, Err(GitPushError::NoSuchRemote(_))));
421451
}

src/commands.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2585,7 +2585,8 @@ fn cmd_git_push(
25852585
}
25862586

25872587
let branch_target = maybe_branch_target.unwrap();
2588-
if branch_target.local_target.as_ref() == branch_target.remote_targets.get(remote_name) {
2588+
let maybe_remote_target = branch_target.remote_targets.get(remote_name);
2589+
if branch_target.local_target.as_ref() == maybe_remote_target {
25892590
writeln!(
25902591
ui,
25912592
"Branch {}@{} already matches {}",
@@ -2610,8 +2611,27 @@ fn cmd_git_push(
26102611
"Won't push open commit".to_string(),
26112612
));
26122613
}
2613-
git::push_commit(&git_repo, &new_target_commit, remote_name, branch_name)
2614-
.map_err(|err| CommandError::UserError(err.to_string()))?;
2614+
let force = match maybe_remote_target {
2615+
None => false,
2616+
Some(RefTarget::Conflict { .. }) => {
2617+
return Err(CommandError::UserError(format!(
2618+
"Branch {}@{} is conflicted",
2619+
branch_name, remote_name
2620+
)));
2621+
}
2622+
Some(RefTarget::Normal(old_target_id)) => {
2623+
!repo.index().is_ancestor(old_target_id, new_target_id)
2624+
}
2625+
};
2626+
2627+
git::push_commit(
2628+
&git_repo,
2629+
&new_target_commit,
2630+
remote_name,
2631+
branch_name,
2632+
force,
2633+
)
2634+
.map_err(|err| CommandError::UserError(err.to_string()))?;
26152635
}
26162636
}
26172637
} else {

0 commit comments

Comments
 (0)