Skip to main content

cli/bridge/
git_sync.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Sync threads/markers functionality for Git bridge.
3
4use 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
11/// Sync Heddle threads to Git branches.
12pub 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
29/// Sync Heddle markers to Git tags.
30pub 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
47/// Sync Git branches to Heddle threads.
48pub 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
82/// Sync Git tags to Heddle markers.
83pub 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
117/// Sync a Heddle thread to a Git branch.
118pub 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
149/// Sync a Heddle marker to a Git tag.
150pub 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}