diff --git a/src/commands.rs b/src/commands.rs index 0b09e8870..e4c59fabc 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -4365,9 +4365,11 @@ fn cmd_git_push( } let mut ref_updates = vec![]; + let mut new_heads = vec![]; for (branch_name, update) in branch_updates { let qualified_name = format!("refs/heads/{}", branch_name); if let Some(new_target) = update.new_target { + new_heads.push(new_target.clone()); let force = match update.old_target { None => false, Some(old_target) => !repo.index().is_ancestor(&old_target, &new_target), @@ -4386,6 +4388,24 @@ fn cmd_git_push( } } + // Check if there are conflicts in any commits we're about to push that haven't + // already been pushed. + let mut old_heads = vec![]; + for branch_target in repo.view().branches().values() { + if let Some(old_head) = branch_target.remote_targets.get(remote_name) { + old_heads.extend(old_head.adds()); + } + } + for index_entry in repo.index().walk_revs(&new_heads, &old_heads) { + let commit = repo.store().get_commit(&index_entry.commit_id())?; + if commit.tree().has_conflict() { + return Err(UserError(format!( + "Won't push commit {} since it has conflicts", + short_commit_hash(commit.id()) + ))); + } + } + let git_repo = get_git_repo(repo.store())?; git::push_updates(&git_repo, remote_name, &ref_updates) .map_err(|err| CommandError::UserError(err.to_string()))?; diff --git a/tests/test_git_push.rs b/tests/test_git_push.rs new file mode 100644 index 000000000..6f8aed1d7 --- /dev/null +++ b/tests/test_git_push.rs @@ -0,0 +1,70 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use jujutsu::testutils::{get_stdout_string, TestEnvironment}; + +#[test] +fn test_git_push() { + let test_env = TestEnvironment::default(); + let git_repo_path = test_env.env_root().join("git-repo"); + git2::Repository::init(&git_repo_path).unwrap(); + + test_env + .jj_cmd( + test_env.env_root(), + &["git", "clone", "git-repo", "jj-repo"], + ) + .assert() + .success(); + let workspace_root = test_env.env_root().join("jj-repo"); + + // No branches to push yet + let assert = test_env + .jj_cmd(&workspace_root, &["git", "push"]) + .assert() + .success(); + insta::assert_snapshot!(get_stdout_string(&assert), @"Nothing changed. +"); + + // Try pushing a conflict + std::fs::write(workspace_root.join("file"), "first").unwrap(); + test_env + .jj_cmd(&workspace_root, &["close", "-m", "first"]) + .assert() + .success(); + std::fs::write(workspace_root.join("file"), "second").unwrap(); + test_env + .jj_cmd(&workspace_root, &["close", "-m", "second"]) + .assert() + .success(); + std::fs::write(workspace_root.join("file"), "third").unwrap(); + test_env + .jj_cmd(&workspace_root, &["rebase", "-d", "@--"]) + .assert() + .success(); + test_env + .jj_cmd(&workspace_root, &["branch", "my-branch"]) + .assert() + .success(); + test_env + .jj_cmd(&workspace_root, &["close", "-m", "third"]) + .assert() + .success(); + let assert = test_env + .jj_cmd(&workspace_root, &["git", "push"]) + .assert() + .failure(); + insta::assert_snapshot!(get_stdout_string(&assert), @"Error: Won't push commit 56e09a8ca383 since it has conflicts +"); +}