ok/jj
1
0
Fork 0
forked from mirrors/jj

git: on push, update jj's view of remote branches without using import_refs()

This means that the commits previously pinned by remote branches are no longer
abandoned. I think that's more correct since "push" is the operation to
propagate local view to remote, and uninteresting commits should have been
locally abandoned.
This commit is contained in:
Yuya Nishihara 2023-10-15 04:27:44 +09:00
parent c8a848d260
commit e0965c4533
7 changed files with 131 additions and 18 deletions

View file

@ -38,6 +38,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
See [automatic local branch creation](docs/config.md#automatic-local-branch-creation)
for details.
* Pushing deleted/moved branches no longer abandons the local commits referenced
by the remote branches.
### New features
* `jj workspace add` now takes a `--revision` argument.

View file

@ -977,7 +977,7 @@ fn cmd_git_push(
force_pushed_branches,
};
with_remote_callbacks(ui, |cb| {
git::push_branches(&git_repo, &remote, &targets, cb)
git::push_branches(tx.mut_repo(), &git_repo, &remote, &targets, cb)
})
.map_err(|err| match err {
GitPushError::InternalGitError(err) => map_git_error(err),
@ -988,9 +988,6 @@ fn cmd_git_push(
),
_ => user_error(err.to_string()),
})?;
// TODO: mark pushed remote branches as tracking
let stats = git::import_refs(tx.mut_repo(), &git_repo, &command.settings().git_settings())?;
print_git_import_stats(ui, &stats)?;
tx.finish(ui)?;
Ok(())
}

View file

@ -300,13 +300,22 @@ fn test_git_push_multiple() {
Delete branch branch1 from 45a3aa29e907
Force branch branch2 from 8476341eb395 to 15dcdaa4f12f
Add branch my-branch to 15dcdaa4f12f
Abandoned 2 commits that are no longer reachable.
"###);
let stdout = test_env.jj_cmd_success(&workspace_root, &["branch", "list"]);
insta::assert_snapshot!(stdout, @r###"
branch2: yqosqzyt 15dcdaa4 (empty) foo
my-branch: yqosqzyt 15dcdaa4 (empty) foo
"###);
let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-rall()"]);
insta::assert_snapshot!(stdout, @r###"
@ yqosqzyt test.user@example.com 2001-02-03 04:05:17.000 +07:00 branch2 my-branch 15dcdaa4
(empty) foo
rlzusymt test.user@example.com 2001-02-03 04:05:10.000 +07:00 8476341e
(empty) description 2
lzmmnrxq test.user@example.com 2001-02-03 04:05:08.000 +07:00 45a3aa29
(empty) description 1
zzzzzzzz root() 00000000
"###);
}
#[test]
@ -588,7 +597,16 @@ fn test_git_push_deleted() {
insta::assert_snapshot!(stderr, @r###"
Branch changes to push to origin:
Delete branch branch1 from 45a3aa29e907
Abandoned 1 commits that are no longer reachable.
"###);
let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-rall()"]);
insta::assert_snapshot!(stdout, @r###"
rlzusymt test.user@example.com 2001-02-03 04:05:10.000 +07:00 branch2 8476341e
(empty) description 2
lzmmnrxq test.user@example.com 2001-02-03 04:05:08.000 +07:00 45a3aa29
(empty) description 1
@ yqosqzyt test.user@example.com 2001-02-03 04:05:13.000 +07:00 5b36783c
(empty) (no description set)
zzzzzzzz root() 00000000
"###);
let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "push", "--deleted"]);
insta::assert_snapshot!(stdout, @"");

View file

@ -197,8 +197,7 @@ In particular, a merge of local and remote targets is
* ~`tags`~ (not implemented, but should be the same as `branches`)
2. Pushes diff to the remote Git repo (as well as remote tracking branches
in the backing Git repo.)
3. Sets `remotes[remote].branches[name].state = tracking`
4. Import changes only for `remotes[remote].branches[glob]`
3. Updates `remotes[remote]` and `git_refs` reflecting the push.
* `jj git export`
1. Copies local `branches`/`tags` back to `remotes["git"]`.
@ -288,25 +287,22 @@ Note: desired behavior of `jj branch forget` is to
* Pushing new branch, remote doesn't exist
1. Pushes `[local, absent] - [absent]` -> `local`
2. Sets `remotes[remote].branches[name].state = tracking`
3. `import_refs()` merges `[local, local] - [absent]` -> `local` (noop)
2. Sets `remotes[remote].branches[name].target = local`, `.state = tracking`
* Pushing new branch, untracked remote exists
1. Pushes `[local, remote] - [absent]`
* Fails if `local` moved backwards or sideways
2. Sets `remotes[remote].branches[name].state = tracking`
3. `import_refs()` merges `[local, local] - [remote]` -> `local` (noop)
2. Sets `remotes[remote].branches[name].target = local`, `.state = tracking`
* Pushing existing branch to tracking remote
1. Pushes `[local, remote] - [remote]` -> `local`
* Fails if `local` moved backwards or sideways, and if `remote` is out of
sync
2. `import_refs()` merges `[local, local] - [remote]` -> `local` (noop)
2. Sets `remotes[remote].branches[name].target = local`
* Pushing existing branch to untracked remote
* Same as "new branch"
* Pushing deleted branch to tracking remote
1. Pushes `[absent, remote] - [remote]` -> `absent`
* TODO: Fails if `remote` is out of sync?
2. `import_refs()` merges `[absent, absent] - [remote]` -> `absent`
3. Removes `remotes[remote].branches[name]` (`target` becomes `absent`)
2. Removes `remotes[remote].branches[name]` (`target` becomes `absent`)
* Pushing deleted branch to untracked remote
* Noop since `[absent, remote] - [absent]` -> `remote`
* Perhaps, UI will report error

View file

@ -1094,7 +1094,9 @@ pub struct GitRefUpdate {
pub new_target: Option<CommitId>,
}
/// Pushes the specified branches and updates the repo view accordingly.
pub fn push_branches(
mut_repo: &mut MutableRepo,
git_repo: &git2::Repository,
remote_name: &str,
targets: &GitBranchPushTargets,
@ -1110,9 +1112,24 @@ pub fn push_branches(
})
.collect_vec();
push_updates(git_repo, remote_name, &ref_updates, callbacks)?;
// TODO: add support for partially pushed refs? we could update the view
// excluding rejected refs, but the transaction would be aborted anyway
// if we returned an Err.
for (branch_name, update) in &targets.branch_updates {
let git_ref_name = format!("refs/remotes/{remote_name}/{branch_name}");
let new_remote_ref = RemoteRef {
target: RefTarget::resolved(update.new_target.clone()),
state: RemoteRefState::Tracking,
};
mut_repo.set_git_ref_target(&git_ref_name, new_remote_ref.target.clone());
mut_repo.set_remote_branch(branch_name, remote_name, new_remote_ref);
}
Ok(())
}
/// Pushes the specified Git refs without updating the repo view.
pub fn push_updates(
git_repo: &git2::Repository,
remote_name: &str,

View file

@ -83,6 +83,11 @@ impl RefTarget {
&TARGET
}
/// Creates non-conflicting target that optionally points to a commit.
pub fn resolved(maybe_id: Option<CommitId>) -> Self {
Self::from_merge(Merge::resolved(maybe_id))
}
/// Creates non-conflicting target pointing to a commit.
pub fn normal(id: CommitId) -> Self {
Self::from_merge(Merge::normal(id))

View file

@ -2256,6 +2256,20 @@ fn set_up_push_repos(settings: &UserSettings, temp_dir: &TempDir) -> PushTestSet
.set_parents(vec![initial_commit.id().clone()])
.write()
.unwrap();
tx.mut_repo().set_git_ref_target(
"refs/remotes/origin/main",
RefTarget::normal(initial_commit.id().clone()),
);
tx.mut_repo().set_remote_branch(
"main",
"origin",
RemoteRef {
target: RefTarget::normal(initial_commit.id().clone()),
// Caller expects the main branch is tracked. The corresponding local branch will
// be created (or left as deleted) by caller.
state: RemoteRefState::Tracking,
},
);
let jj_repo = tx.commit();
PushTestSetup {
source_repo_dir,
@ -2269,8 +2283,9 @@ fn set_up_push_repos(settings: &UserSettings, temp_dir: &TempDir) -> PushTestSet
fn test_push_branches_success() {
let settings = testutils::user_settings();
let temp_dir = testutils::new_temp_dir();
let setup = set_up_push_repos(&settings, &temp_dir);
let mut setup = set_up_push_repos(&settings, &temp_dir);
let clone_repo = get_git_repo(&setup.jj_repo);
let mut tx = setup.jj_repo.start_transaction(&settings, "test");
let targets = GitBranchPushTargets {
branch_updates: vec![(
@ -2283,6 +2298,7 @@ fn test_push_branches_success() {
force_pushed_branches: hashset! {},
};
let result = git::push_branches(
tx.mut_repo(),
&clone_repo,
"origin",
&targets,
@ -2307,14 +2323,35 @@ fn test_push_branches_success() {
.unwrap()
.target();
assert_eq!(new_target, Some(new_oid));
// Check that the repo view got updated
let view = tx.mut_repo().view();
assert_eq!(
*view.get_git_ref("refs/remotes/origin/main"),
RefTarget::normal(setup.new_commit.id().clone()),
);
assert_eq!(
*view.get_remote_branch("main", "origin"),
RemoteRef {
target: RefTarget::normal(setup.new_commit.id().clone()),
state: RemoteRefState::Tracking,
},
);
// Check that the repo view reflects the changes in the Git repo
setup.jj_repo = tx.commit();
let mut tx = setup.jj_repo.start_transaction(&settings, "test");
git::import_refs(tx.mut_repo(), &clone_repo, &GitSettings::default()).unwrap();
assert!(!tx.mut_repo().has_changes());
}
#[test]
fn test_push_branches_deletion() {
let settings = testutils::user_settings();
let temp_dir = testutils::new_temp_dir();
let setup = set_up_push_repos(&settings, &temp_dir);
let mut setup = set_up_push_repos(&settings, &temp_dir);
let clone_repo = get_git_repo(&setup.jj_repo);
let mut tx = setup.jj_repo.start_transaction(&settings, "test");
let source_repo = git2::Repository::open(&setup.source_repo_dir).unwrap();
// Test the setup
@ -2331,6 +2368,7 @@ fn test_push_branches_deletion() {
force_pushed_branches: hashset! {},
};
let result = git::push_branches(
tx.mut_repo(),
&get_git_repo(&setup.jj_repo),
"origin",
&targets,
@ -2347,14 +2385,26 @@ fn test_push_branches_deletion() {
assert!(clone_repo
.find_reference("refs/remotes/origin/main")
.is_err());
// Check that the repo view got updated
let view = tx.mut_repo().view();
assert!(view.get_git_ref("refs/remotes/origin/main").is_absent());
assert!(view.get_remote_branch("main", "origin").is_absent());
// Check that the repo view reflects the changes in the Git repo
setup.jj_repo = tx.commit();
let mut tx = setup.jj_repo.start_transaction(&settings, "test");
git::import_refs(tx.mut_repo(), &clone_repo, &GitSettings::default()).unwrap();
assert!(!tx.mut_repo().has_changes());
}
#[test]
fn test_push_branches_mixed_deletion_and_addition() {
let settings = testutils::user_settings();
let temp_dir = testutils::new_temp_dir();
let setup = set_up_push_repos(&settings, &temp_dir);
let mut setup = set_up_push_repos(&settings, &temp_dir);
let clone_repo = get_git_repo(&setup.jj_repo);
let mut tx = setup.jj_repo.start_transaction(&settings, "test");
let targets = GitBranchPushTargets {
branch_updates: vec![
@ -2376,6 +2426,7 @@ fn test_push_branches_mixed_deletion_and_addition() {
force_pushed_branches: hashset! {},
};
let result = git::push_branches(
tx.mut_repo(),
&clone_repo,
"origin",
&targets,
@ -2393,6 +2444,28 @@ fn test_push_branches_mixed_deletion_and_addition() {
// Check that the main ref got deleted in the source repo
assert!(source_repo.find_reference("refs/heads/main").is_err());
// Check that the repo view got updated
let view = tx.mut_repo().view();
assert!(view.get_git_ref("refs/remotes/origin/main").is_absent());
assert!(view.get_remote_branch("main", "origin").is_absent());
assert_eq!(
*view.get_git_ref("refs/remotes/origin/topic"),
RefTarget::normal(setup.new_commit.id().clone()),
);
assert_eq!(
*view.get_remote_branch("topic", "origin"),
RemoteRef {
target: RefTarget::normal(setup.new_commit.id().clone()),
state: RemoteRefState::Tracking,
},
);
// Check that the repo view reflects the changes in the Git repo
setup.jj_repo = tx.commit();
let mut tx = setup.jj_repo.start_transaction(&settings, "test");
git::import_refs(tx.mut_repo(), &clone_repo, &GitSettings::default()).unwrap();
assert!(!tx.mut_repo().has_changes());
}
#[test]
@ -2403,6 +2476,7 @@ fn test_push_branches_not_fast_forward() {
let mut tx = setup.jj_repo.start_transaction(&settings, "test");
let new_commit = write_random_commit(tx.mut_repo(), &settings);
setup.jj_repo = tx.commit();
let mut tx = setup.jj_repo.start_transaction(&settings, "test");
let targets = GitBranchPushTargets {
branch_updates: vec![(
@ -2415,6 +2489,7 @@ fn test_push_branches_not_fast_forward() {
force_pushed_branches: hashset! {},
};
let result = git::push_branches(
tx.mut_repo(),
&get_git_repo(&setup.jj_repo),
"origin",
&targets,
@ -2431,6 +2506,7 @@ fn test_push_branches_not_fast_forward_with_force() {
let mut tx = setup.jj_repo.start_transaction(&settings, "test");
let new_commit = write_random_commit(tx.mut_repo(), &settings);
setup.jj_repo = tx.commit();
let mut tx = setup.jj_repo.start_transaction(&settings, "test");
let targets = GitBranchPushTargets {
branch_updates: vec![(
@ -2445,6 +2521,7 @@ fn test_push_branches_not_fast_forward_with_force() {
},
};
let result = git::push_branches(
tx.mut_repo(),
&get_git_repo(&setup.jj_repo),
"origin",
&targets,