just playing with tangled
1// Copyright 2020 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.
14
15#![allow(missing_docs)]
16
17use std::collections::{BTreeMap, HashMap, HashSet};
18use std::fmt;
19
20use itertools::Itertools;
21
22use crate::backend::CommitId;
23use crate::index::Index;
24use crate::op_store;
25use crate::op_store::{BranchTarget, RefTarget, RefTargetOptionExt as _, WorkspaceId};
26use crate::refs::merge_ref_targets;
27
28#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug)]
29pub enum RefName {
30 LocalBranch(String),
31 RemoteBranch { branch: String, remote: String },
32 Tag(String),
33 GitRef(String),
34}
35
36impl fmt::Display for RefName {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 match self {
39 RefName::LocalBranch(name) => write!(f, "{name}"),
40 RefName::RemoteBranch { branch, remote } => write!(f, "{branch}@{remote}"),
41 RefName::Tag(name) => write!(f, "{name}"),
42 RefName::GitRef(name) => write!(f, "{name}"),
43 }
44 }
45}
46
47#[derive(PartialEq, Eq, Debug, Clone)]
48pub struct View {
49 data: op_store::View,
50}
51
52impl View {
53 pub fn new(op_store_view: op_store::View) -> Self {
54 View {
55 data: op_store_view,
56 }
57 }
58
59 pub fn wc_commit_ids(&self) -> &HashMap<WorkspaceId, CommitId> {
60 &self.data.wc_commit_ids
61 }
62
63 pub fn get_wc_commit_id(&self, workspace_id: &WorkspaceId) -> Option<&CommitId> {
64 self.data.wc_commit_ids.get(workspace_id)
65 }
66
67 pub fn workspaces_for_wc_commit_id(&self, commit_id: &CommitId) -> Vec<WorkspaceId> {
68 let mut workspaces_ids = vec![];
69 for (workspace_id, wc_commit_id) in &self.data.wc_commit_ids {
70 if wc_commit_id == commit_id {
71 workspaces_ids.push(workspace_id.clone());
72 }
73 }
74 workspaces_ids
75 }
76
77 pub fn is_wc_commit_id(&self, commit_id: &CommitId) -> bool {
78 self.data.wc_commit_ids.values().contains(commit_id)
79 }
80
81 pub fn heads(&self) -> &HashSet<CommitId> {
82 &self.data.head_ids
83 }
84
85 pub fn public_heads(&self) -> &HashSet<CommitId> {
86 &self.data.public_head_ids
87 }
88
89 pub fn branches(&self) -> &BTreeMap<String, BranchTarget> {
90 &self.data.branches
91 }
92
93 pub fn tags(&self) -> &BTreeMap<String, RefTarget> {
94 &self.data.tags
95 }
96
97 pub fn git_refs(&self) -> &BTreeMap<String, RefTarget> {
98 &self.data.git_refs
99 }
100
101 pub fn git_head(&self) -> &RefTarget {
102 &self.data.git_head
103 }
104
105 pub fn set_wc_commit(&mut self, workspace_id: WorkspaceId, commit_id: CommitId) {
106 self.data.wc_commit_ids.insert(workspace_id, commit_id);
107 }
108
109 pub fn remove_wc_commit(&mut self, workspace_id: &WorkspaceId) {
110 self.data.wc_commit_ids.remove(workspace_id);
111 }
112
113 pub fn add_head(&mut self, head_id: &CommitId) {
114 self.data.head_ids.insert(head_id.clone());
115 }
116
117 pub fn remove_head(&mut self, head_id: &CommitId) {
118 self.data.head_ids.remove(head_id);
119 }
120
121 pub fn add_public_head(&mut self, head_id: &CommitId) {
122 self.data.public_head_ids.insert(head_id.clone());
123 }
124
125 pub fn remove_public_head(&mut self, head_id: &CommitId) {
126 self.data.public_head_ids.remove(head_id);
127 }
128
129 pub fn get_ref(&self, name: &RefName) -> &RefTarget {
130 match &name {
131 RefName::LocalBranch(name) => self.get_local_branch(name),
132 RefName::RemoteBranch { branch, remote } => self.get_remote_branch(branch, remote),
133 RefName::Tag(name) => self.get_tag(name),
134 RefName::GitRef(name) => self.get_git_ref(name),
135 }
136 }
137
138 /// Sets reference of the specified kind to point to the given target. If
139 /// the target is absent, the reference will be removed.
140 pub fn set_ref_target(&mut self, name: &RefName, target: RefTarget) {
141 match name {
142 RefName::LocalBranch(name) => self.set_local_branch_target(name, target),
143 RefName::RemoteBranch { branch, remote } => {
144 self.set_remote_branch_target(branch, remote, target)
145 }
146 RefName::Tag(name) => self.set_tag_target(name, target),
147 RefName::GitRef(name) => self.set_git_ref_target(name, target),
148 }
149 }
150
151 pub fn get_branch(&self, name: &str) -> Option<&BranchTarget> {
152 self.data.branches.get(name)
153 }
154
155 pub fn set_branch(&mut self, name: String, target: BranchTarget) {
156 self.data.branches.insert(name, target);
157 }
158
159 pub fn remove_branch(&mut self, name: &str) {
160 self.data.branches.remove(name);
161 }
162
163 pub fn get_local_branch(&self, name: &str) -> &RefTarget {
164 if let Some(branch_target) = self.data.branches.get(name) {
165 &branch_target.local_target
166 } else {
167 RefTarget::absent_ref()
168 }
169 }
170
171 /// Sets local branch to point to the given target. If the target is absent,
172 /// and if no associated remote branches exist, the branch will be removed.
173 pub fn set_local_branch_target(&mut self, name: &str, target: RefTarget) {
174 if target.is_present() {
175 self.insert_local_branch(name.to_owned(), target);
176 } else {
177 self.remove_local_branch(name);
178 }
179 }
180
181 fn insert_local_branch(&mut self, name: String, target: RefTarget) {
182 assert!(target.is_present());
183 self.data.branches.entry(name).or_default().local_target = target;
184 }
185
186 fn remove_local_branch(&mut self, name: &str) {
187 if let Some(branch) = self.data.branches.get_mut(name) {
188 branch.local_target = RefTarget::absent();
189 if branch.remote_targets.is_empty() {
190 self.remove_branch(name);
191 }
192 }
193 }
194
195 pub fn get_remote_branch(&self, name: &str, remote_name: &str) -> &RefTarget {
196 if let Some(branch_target) = self.data.branches.get(name) {
197 let maybe_target = branch_target.remote_targets.get(remote_name);
198 maybe_target.flatten()
199 } else {
200 RefTarget::absent_ref()
201 }
202 }
203
204 /// Sets remote-tracking branch to point to the given target. If the target
205 /// is absent, the branch will be removed.
206 pub fn set_remote_branch_target(&mut self, name: &str, remote_name: &str, target: RefTarget) {
207 if target.is_present() {
208 self.insert_remote_branch(name.to_owned(), remote_name.to_owned(), target);
209 } else {
210 self.remove_remote_branch(name, remote_name);
211 }
212 }
213
214 fn insert_remote_branch(&mut self, name: String, remote_name: String, target: RefTarget) {
215 assert!(target.is_present());
216 self.data
217 .branches
218 .entry(name)
219 .or_default()
220 .remote_targets
221 .insert(remote_name, target);
222 }
223
224 fn remove_remote_branch(&mut self, name: &str, remote_name: &str) {
225 if let Some(branch) = self.data.branches.get_mut(name) {
226 branch.remote_targets.remove(remote_name);
227 if branch.remote_targets.is_empty() && branch.local_target.is_absent() {
228 self.remove_branch(name);
229 }
230 }
231 }
232
233 pub fn rename_remote(&mut self, old: &str, new: &str) {
234 for branch in self.data.branches.values_mut() {
235 let target = branch.remote_targets.remove(old).flatten();
236 if target.is_present() {
237 branch.remote_targets.insert(new.to_owned(), target);
238 }
239 }
240 }
241
242 pub fn get_tag(&self, name: &str) -> &RefTarget {
243 self.data.tags.get(name).flatten()
244 }
245
246 /// Sets tag to point to the given target. If the target is absent, the tag
247 /// will be removed.
248 pub fn set_tag_target(&mut self, name: &str, target: RefTarget) {
249 if target.is_present() {
250 self.data.tags.insert(name.to_owned(), target);
251 } else {
252 self.data.tags.remove(name);
253 }
254 }
255
256 pub fn get_git_ref(&self, name: &str) -> &RefTarget {
257 self.data.git_refs.get(name).flatten()
258 }
259
260 /// Sets the last imported Git ref to point to the given target. If the
261 /// target is absent, the reference will be removed.
262 pub fn set_git_ref_target(&mut self, name: &str, target: RefTarget) {
263 if target.is_present() {
264 self.data.git_refs.insert(name.to_owned(), target);
265 } else {
266 self.data.git_refs.remove(name);
267 }
268 }
269
270 /// Sets `HEAD@git` to point to the given target. If the target is absent,
271 /// the reference will be cleared.
272 pub fn set_git_head_target(&mut self, target: RefTarget) {
273 self.data.git_head = target;
274 }
275
276 pub fn set_view(&mut self, data: op_store::View) {
277 self.data = data;
278 }
279
280 pub fn store_view(&self) -> &op_store::View {
281 &self.data
282 }
283
284 pub fn store_view_mut(&mut self) -> &mut op_store::View {
285 &mut self.data
286 }
287
288 pub fn merge_single_ref(
289 &mut self,
290 index: &dyn Index,
291 ref_name: &RefName,
292 base_target: &RefTarget,
293 other_target: &RefTarget,
294 ) {
295 if base_target != other_target {
296 let self_target = self.get_ref(ref_name);
297 let new_target = merge_ref_targets(index, self_target, base_target, other_target);
298 if new_target != *self_target {
299 self.set_ref_target(ref_name, new_target);
300 }
301 }
302 }
303}