1use gix::refs::transaction::PreviousValue;
5
6use crate::bridge::{
7 git_core::{GitBridge, GitBridgeError, GitResult, git_err, set_reference},
8 git_import::thread_can_adopt_change,
9};
10
11pub fn sync_threads(bridge: &mut GitBridge) -> GitResult<usize> {
13 let repo = bridge.open_git_repo()?;
14 let mut stats = 0;
15
16 let threads = bridge.heddle_repo.refs().list_threads()?;
17 for track_name in threads {
18 if let Some(state_id) = bridge.heddle_repo.refs().get_thread(&track_name)?
19 && let Some(git_oid) = bridge.mapping.get_git(&state_id)
20 {
21 sync_track_to_branch(&repo, &track_name, git_oid)?;
22 stats += 1;
23 }
24 }
25
26 Ok(stats)
27}
28
29pub fn sync_markers(bridge: &mut GitBridge) -> GitResult<usize> {
31 let repo = bridge.open_git_repo()?;
32 let mut stats = 0;
33
34 let markers = bridge.heddle_repo.refs().list_markers()?;
35 for marker_name in markers {
36 if let Some(state_id) = bridge.heddle_repo.refs().get_marker(&marker_name)?
37 && let Some(git_oid) = bridge.mapping.get_git(&state_id)
38 {
39 sync_marker_to_tag(&repo, &marker_name, git_oid)?;
40 stats += 1;
41 }
42 }
43
44 Ok(stats)
45}
46
47pub fn sync_branches(bridge: &mut GitBridge) -> GitResult<usize> {
49 let repo = bridge.open_git_repo()?;
50 let mut stats = 0;
51
52 for branch in repo
53 .references()
54 .map_err(git_err)?
55 .local_branches()
56 .map_err(git_err)?
57 {
58 let mut branch = branch.map_err(git_err)?;
59 let name = branch.name().shorten().to_string();
60 let target = branch.peel_to_id().map_err(git_err)?.detach();
61 if let Some(change_id) = bridge.mapping.get_heddle(target) {
62 if let Some(existing) = bridge.heddle_repo.refs().get_thread(&name)?
63 && !thread_can_adopt_change(bridge.heddle_repo, &existing, &change_id)?
64 {
65 return Err(GitBridgeError::Conflict(format!(
66 "thread {} at {} differs from branch {} at {}. \
67 To recover, switch to '{}' and run `heddle sync` after \
68 resolving the divergent history, or explicitly reset the \
69 Heddle thread if the Git branch should replace it.",
70 name, existing, name, change_id, name
71 )));
72 }
73
74 bridge.heddle_repo.refs().set_thread(&name, &change_id)?;
75 stats += 1;
76 }
77 }
78
79 Ok(stats)
80}
81
82pub fn sync_tags(bridge: &mut GitBridge) -> GitResult<usize> {
84 let repo = bridge.open_git_repo()?;
85 let mut stats = 0;
86
87 for tag in repo
88 .references()
89 .map_err(git_err)?
90 .tags()
91 .map_err(git_err)?
92 {
93 let mut tag = tag.map_err(git_err)?;
94 let name = tag.name().shorten().to_string();
95 let oid = tag.peel_to_id().map_err(git_err)?.detach();
96
97 if let Some(change_id) = bridge.mapping.get_heddle(oid) {
98 match bridge.heddle_repo.refs().get_marker(&name) {
99 Ok(Some(existing)) if existing != change_id => {
100 return Err(GitBridgeError::Conflict(format!(
101 "marker {} at {} differs from tag {} at {}",
102 name, existing, name, change_id
103 )));
104 }
105 Ok(_) => {}
106 Err(err) => return Err(err.into()),
107 }
108
109 bridge.heddle_repo.refs().create_marker(&name, &change_id)?;
110 stats += 1;
111 }
112 }
113
114 Ok(stats)
115}
116
117pub fn sync_track_to_branch(
119 repo: &gix::Repository,
120 track_name: &str,
121 git_oid: gix::hash::ObjectId,
122) -> GitResult<()> {
123 let branch_ref = format!("refs/heads/{}", track_name);
124
125 if let Ok(mut branch) = repo.find_reference(&branch_ref) {
126 let existing = branch.peel_to_id().map_err(git_err)?.detach();
127 if existing != git_oid {
128 set_reference(
129 repo,
130 &branch_ref,
131 git_oid,
132 PreviousValue::Any,
133 "heddle: sync thread",
134 )?;
135 }
136 return Ok(());
137 }
138
139 repo.reference(
140 branch_ref,
141 git_oid,
142 PreviousValue::MustNotExist,
143 "heddle: sync thread",
144 )
145 .map_err(git_err)?;
146 Ok(())
147}
148
149pub fn sync_marker_to_tag(
151 repo: &gix::Repository,
152 marker_name: &str,
153 git_oid: gix::hash::ObjectId,
154) -> GitResult<()> {
155 let tag_ref = format!("refs/tags/{}", marker_name);
156 if let Ok(mut reference) = repo.find_reference(&tag_ref) {
157 let existing = reference.peel_to_id().map_err(git_err)?.detach();
158 if existing != git_oid {
159 return Err(GitBridgeError::Conflict(format!(
160 "tag {} at {} differs from marker {} at {}",
161 marker_name, existing, marker_name, git_oid
162 )));
163 }
164 return Ok(());
165 }
166
167 repo.tag_reference(marker_name, git_oid, PreviousValue::MustNotExist)
168 .map_err(git_err)?;
169 Ok(())
170}