just playing with tangled
at diffedit3 274 lines 12 kB view raw
1// Copyright 2022 The Jujutsu Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14use std::path::Path; 15 16use itertools::Itertools as _; 17use jj_lib::backend::CommitId; 18 19use crate::common::TestEnvironment; 20 21#[test] 22fn test_resolution_of_git_tracking_branches() { 23 let test_env = TestEnvironment::default(); 24 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 25 let repo_path = test_env.env_root().join("repo"); 26 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "main"]); 27 test_env.jj_cmd_ok(&repo_path, &["describe", "-r", "main", "-m", "old_message"]); 28 29 // Create local-git tracking branch 30 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "export"]); 31 insta::assert_snapshot!(stdout, @""); 32 insta::assert_snapshot!(stderr, @""); 33 // Move the local branch somewhere else 34 test_env.jj_cmd_ok(&repo_path, &["describe", "-r", "main", "-m", "new_message"]); 35 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###" 36 main: qpvuntsm 3af37026 (empty) new_message 37 @git (ahead by 1 commits, behind by 1 commits): qpvuntsm hidden 16d541ca (empty) old_message 38 "###); 39 40 // Test that we can address both revisions 41 let query = |expr| { 42 let template = r#"commit_id ++ " " ++ description"#; 43 test_env.jj_cmd_success( 44 &repo_path, 45 &["log", "-r", expr, "-T", template, "--no-graph"], 46 ) 47 }; 48 insta::assert_snapshot!(query("main"), @r###" 49 3af370264cdcbba791762f8ef6bc79b456dcbf3b new_message 50 "###); 51 insta::assert_snapshot!(query("main@git"), @r###" 52 16d541ca40f42baf2dea41aa61a0b5f1cbf1f91b old_message 53 "###); 54 // Can't be selected by remote_branches() 55 insta::assert_snapshot!(query(r#"remote_branches(exact:"main", exact:"git")"#), @""); 56} 57 58#[test] 59fn test_git_export_conflicting_git_refs() { 60 let test_env = TestEnvironment::default(); 61 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 62 let repo_path = test_env.env_root().join("repo"); 63 64 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "main"]); 65 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "main/sub"]); 66 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "export"]); 67 insta::assert_snapshot!(stdout, @""); 68 insta::with_settings!({filters => vec![(": The lock for resource.*", ": ...")]}, { 69 insta::assert_snapshot!(stderr, @r###" 70 Warning: Failed to export some branches: 71 main/sub: Failed to set: A lock could not be obtained for reference "refs/heads/main/sub": ... 72 Hint: Git doesn't allow a branch name that looks like a parent directory of 73 another (e.g. `foo` and `foo/bar`). Try to rename the branches that failed to 74 export or their "parent" branches. 75 "###); 76 }); 77} 78 79#[test] 80fn test_git_export_undo() { 81 let test_env = TestEnvironment::default(); 82 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 83 let repo_path = test_env.env_root().join("repo"); 84 let git_repo = git2::Repository::open(repo_path.join(".jj/repo/store/git")).unwrap(); 85 86 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "a"]); 87 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###" 88 a: qpvuntsm 230dd059 (empty) (no description set) 89 "###); 90 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "export"]); 91 insta::assert_snapshot!(stdout, @""); 92 insta::assert_snapshot!(stderr, @""); 93 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["log", "-ra@git"]), @r###" 94 @ qpvuntsm test.user@example.com 2001-02-03 08:05:07 a 230dd059 95 │ (empty) (no description set) 96 ~ 97 "###); 98 99 // Exported refs won't be removed by undoing the export, but the git-tracking 100 // branch is. This is the same as remote-tracking branches. 101 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["op", "undo"]); 102 insta::assert_snapshot!(stdout, @""); 103 insta::assert_snapshot!(stderr, @""); 104 insta::assert_debug_snapshot!(get_git_repo_refs(&git_repo), @r###" 105 [ 106 ( 107 "refs/heads/a", 108 CommitId( 109 "230dd059e1b059aefc0da06a2e5a7dbf22362f22", 110 ), 111 ), 112 ] 113 "###); 114 insta::assert_snapshot!(test_env.jj_cmd_failure(&repo_path, &["log", "-ra@git"]), @r###" 115 Error: Revision "a@git" doesn't exist 116 Hint: Did you mean "a"? 117 "###); 118 119 // This would re-export branch "a" and create git-tracking branch. 120 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "export"]); 121 insta::assert_snapshot!(stdout, @""); 122 insta::assert_snapshot!(stderr, @""); 123 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["log", "-ra@git"]), @r###" 124 @ qpvuntsm test.user@example.com 2001-02-03 08:05:07 a 230dd059 125 │ (empty) (no description set) 126 ~ 127 "###); 128} 129 130#[test] 131fn test_git_import_undo() { 132 let test_env = TestEnvironment::default(); 133 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 134 let repo_path = test_env.env_root().join("repo"); 135 let git_repo = git2::Repository::open(repo_path.join(".jj/repo/store/git")).unwrap(); 136 137 // Create branch "a" in git repo 138 let commit_id = 139 test_env.jj_cmd_success(&repo_path, &["log", "-Tcommit_id", "--no-graph", "-r@"]); 140 let commit = git_repo 141 .find_commit(git2::Oid::from_str(&commit_id).unwrap()) 142 .unwrap(); 143 git_repo.branch("a", &commit, true).unwrap(); 144 145 // Initial state we will return to after `undo`. There are no branches. 146 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @""); 147 let base_operation_id = test_env.current_operation_id(&repo_path); 148 149 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "import"]); 150 insta::assert_snapshot!(stdout, @""); 151 insta::assert_snapshot!(stderr, @r###" 152 branch: a [new] tracked 153 "###); 154 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###" 155 a: qpvuntsm 230dd059 (empty) (no description set) 156 @git: qpvuntsm 230dd059 (empty) (no description set) 157 "###); 158 159 // "git import" can be undone by default. 160 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["op", "restore", &base_operation_id]); 161 insta::assert_snapshot!(stdout, @""); 162 insta::assert_snapshot!(stderr, @""); 163 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @""); 164 // Try "git import" again, which should re-import the branch "a". 165 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "import"]); 166 insta::assert_snapshot!(stdout, @""); 167 insta::assert_snapshot!(stderr, @r###" 168 branch: a [new] tracked 169 "###); 170 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###" 171 a: qpvuntsm 230dd059 (empty) (no description set) 172 @git: qpvuntsm 230dd059 (empty) (no description set) 173 "###); 174} 175 176#[test] 177fn test_git_import_move_export_with_default_undo() { 178 let test_env = TestEnvironment::default(); 179 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]); 180 let repo_path = test_env.env_root().join("repo"); 181 let git_repo = git2::Repository::open(repo_path.join(".jj/repo/store/git")).unwrap(); 182 183 // Create branch "a" in git repo 184 let commit_id = 185 test_env.jj_cmd_success(&repo_path, &["log", "-Tcommit_id", "--no-graph", "-r@"]); 186 let commit = git_repo 187 .find_commit(git2::Oid::from_str(&commit_id).unwrap()) 188 .unwrap(); 189 git_repo.branch("a", &commit, true).unwrap(); 190 191 // Initial state we will try to return to after `op restore`. There are no 192 // branches. 193 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @""); 194 let base_operation_id = test_env.current_operation_id(&repo_path); 195 196 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "import"]); 197 insta::assert_snapshot!(stdout, @""); 198 insta::assert_snapshot!(stderr, @r###" 199 branch: a [new] tracked 200 "###); 201 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###" 202 a: qpvuntsm 230dd059 (empty) (no description set) 203 @git: qpvuntsm 230dd059 (empty) (no description set) 204 "###); 205 206 // Move branch "a" and export to git repo 207 test_env.jj_cmd_ok(&repo_path, &["new"]); 208 test_env.jj_cmd_ok(&repo_path, &["branch", "set", "a"]); 209 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###" 210 a: yqosqzyt 096dc80d (empty) (no description set) 211 @git (behind by 1 commits): qpvuntsm 230dd059 (empty) (no description set) 212 "###); 213 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "export"]); 214 insta::assert_snapshot!(stdout, @""); 215 insta::assert_snapshot!(stderr, @""); 216 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###" 217 a: yqosqzyt 096dc80d (empty) (no description set) 218 @git: yqosqzyt 096dc80d (empty) (no description set) 219 "###); 220 221 // "git import" can be undone with the default `restore` behavior, as shown in 222 // the previous test. However, "git export" can't: the branches in the git 223 // repo stay where they were. 224 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["op", "restore", &base_operation_id]); 225 insta::assert_snapshot!(stdout, @""); 226 insta::assert_snapshot!(stderr, @r###" 227 Working copy now at: qpvuntsm 230dd059 (empty) (no description set) 228 Parent commit : zzzzzzzz 00000000 (empty) (no description set) 229 "###); 230 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @""); 231 insta::assert_debug_snapshot!(get_git_repo_refs(&git_repo), @r###" 232 [ 233 ( 234 "refs/heads/a", 235 CommitId( 236 "096dc80da67094fbaa6683e2a205dddffa31f9a8", 237 ), 238 ), 239 ] 240 "###); 241 242 // The last branch "a" state is imported from git. No idea what's the most 243 // intuitive result here. 244 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "import"]); 245 insta::assert_snapshot!(stdout, @""); 246 insta::assert_snapshot!(stderr, @r###" 247 branch: a [new] tracked 248 "###); 249 insta::assert_snapshot!(get_branch_output(&test_env, &repo_path), @r###" 250 a: yqosqzyt 096dc80d (empty) (no description set) 251 @git: yqosqzyt 096dc80d (empty) (no description set) 252 "###); 253} 254 255fn get_branch_output(test_env: &TestEnvironment, repo_path: &Path) -> String { 256 test_env.jj_cmd_success(repo_path, &["branch", "list", "--all-remotes"]) 257} 258 259fn get_git_repo_refs(git_repo: &git2::Repository) -> Vec<(String, CommitId)> { 260 let mut refs: Vec<_> = git_repo 261 .references() 262 .unwrap() 263 .filter_ok(|git_ref| git_ref.is_tag() || git_ref.is_branch() || git_ref.is_remote()) 264 .filter_map_ok(|git_ref| { 265 let full_name = git_ref.name()?.to_owned(); 266 let git_commit = git_ref.peel_to_commit().ok()?; 267 let commit_id = CommitId::from_bytes(git_commit.id().as_bytes()); 268 Some((full_name, commit_id)) 269 }) 270 .try_collect() 271 .unwrap(); 272 refs.sort(); 273 refs 274}