reactive_graph_net_git_model/component/
repository.rs

1use git2::AutotagOption;
2use git2::FetchOptions;
3use git2::RemoteCallbacks;
4use git2::Repository;
5use git2::build::CheckoutBuilder;
6use git2::build::RepoBuilder;
7
8use crate::NAMESPACE_GIT;
9use crate::TransferProgress;
10use log::trace;
11use reactive_graph_behaviour_model_api::behaviour_ty;
12use reactive_graph_behaviour_model_api::component_behaviour_ty;
13use reactive_graph_graph::component_model;
14use reactive_graph_graph::component_ty;
15use reactive_graph_graph::properties;
16use reactive_graph_net_http_model::Url;
17use reactive_graph_sys_file_model::FilePath;
18
19properties!(
20    RepositoryProperties,
21    (BRANCH, "branch", ""),
22    (REMOTE_NAME, "remote_name", ""),
23    (REMOTE_BRANCH, "remote_branch", ""),
24    (FETCH, "fetch", ""),
25    (FAST_FORWARD, "fast_forward", ""),
26    (PUSH, "push", "")
27);
28
29component_ty!(COMPONENT_REPOSITORY, NAMESPACE_GIT, COMPONENT_NAME_REPOSITORY, "repository");
30behaviour_ty!(BEHAVIOUR_REPOSITORY, NAMESPACE_GIT, BEHAVIOUR_NAME_REPOSITORY, "repository");
31component_behaviour_ty!(COMPONENT_BEHAVIOUR_REPOSITORY, COMPONENT_REPOSITORY, BEHAVIOUR_REPOSITORY);
32
33component_model!(
34    ComponentRepository,
35    data branch string,
36    data remote_name string,
37    data remote_branch string,
38);
39
40pub trait GitRepository: ComponentRepository + TransferProgress + FilePath + Url {
41    fn get_reference_name(&self) -> Option<String> {
42        self.get_remote_branch().map(|remote_branch| format!("refs/heads/{}", remote_branch))
43    }
44
45    fn exists(&self) -> bool {
46        self.get_path().and_then(|path| Repository::open(path).ok()).is_some()
47    }
48
49    fn open(&self) -> Option<Repository> {
50        self.get_path().and_then(|path| Repository::open(path).ok())
51    }
52
53    fn git_fetch_and_fast_forward(&self) {
54        self.git_fetch();
55        self.git_fast_forward();
56    }
57
58    fn git_fetch(&self) {
59        let Some(remote_name) = self.get_remote_name() else {
60            return;
61        };
62        let Some(remote_branch) = self.get_remote_branch() else {
63            return;
64        };
65        let Some(repository) = self.open() else {
66            return;
67        };
68        let Ok(mut remote) = repository.find_remote(&remote_name) else {
69            return;
70        };
71        let mut remote_callbacks = RemoteCallbacks::new();
72        remote_callbacks.transfer_progress(|stats| {
73            self.total_objects(stats.total_objects() as u64);
74            self.received_objects(stats.received_objects() as u64);
75            self.received_bytes(stats.received_bytes() as u64);
76            self.local_objects(stats.local_objects() as u64);
77            self.total_deltas(stats.total_deltas() as u64);
78            self.indexed_deltas(stats.indexed_deltas() as u64);
79            self.indexed_objects(stats.indexed_objects() as u64);
80            true
81        });
82        let mut fetch_options = FetchOptions::new();
83        fetch_options.remote_callbacks(remote_callbacks).download_tags(AutotagOption::All);
84        let Ok(_result) = remote.fetch(&[remote_branch], Some(&mut fetch_options), None) else {
85            return;
86        };
87    }
88
89    fn git_fast_forward(&self) {
90        let Some(repository) = self.open() else {
91            return;
92        };
93        let Ok(fetch_head) = repository.find_reference("FETCH_HEAD") else {
94            return;
95        };
96        let Ok(fetch_commit) = repository.reference_to_annotated_commit(&fetch_head) else {
97            return;
98        };
99        let Some(reference_name) = self.get_reference_name() else {
100            return;
101        };
102        let Ok(mut reference_head) = repository.find_reference(&reference_name) else {
103            return;
104        };
105        let name = match reference_head.name() {
106            Some(s) => s.to_string(),
107            None => String::from_utf8_lossy(reference_head.name_bytes()).to_string(),
108        };
109        let msg = format!("Fast-Forward: Setting {} to id: {}", name, fetch_commit.id());
110        if reference_head.set_target(fetch_commit.id(), &msg).is_err() {
111            return;
112        }
113        if repository.set_head(&name).is_err() {
114            return;
115        }
116        if repository.checkout_head(Some(git2::build::CheckoutBuilder::default().force())).is_err() {
117            trace!("ff");
118        }
119    }
120
121    fn git_clone(&self) {
122        let Some(local_path) = self.get_path() else {
123            return;
124        };
125        let Some(url) = self.get_url() else {
126            return;
127        };
128        let mut remote_callbacks = RemoteCallbacks::new();
129        remote_callbacks.transfer_progress(|stats| {
130            self.total_objects(stats.total_objects() as u64);
131            self.received_objects(stats.received_objects() as u64);
132            self.received_bytes(stats.received_bytes() as u64);
133            self.local_objects(stats.local_objects() as u64);
134            self.total_deltas(stats.total_deltas() as u64);
135            self.indexed_deltas(stats.indexed_deltas() as u64);
136            self.indexed_objects(stats.indexed_objects() as u64);
137            true
138        });
139        let mut fetch_options = FetchOptions::new();
140        fetch_options.remote_callbacks(remote_callbacks);
141
142        let mut checkout_builder = CheckoutBuilder::new();
143        checkout_builder.progress(|_path, _cur, _total| {});
144
145        let Ok(_repository) = RepoBuilder::new()
146            .fetch_options(fetch_options)
147            .with_checkout(checkout_builder)
148            .clone(url.as_str(), &local_path)
149        else {
150            return;
151        };
152    }
153
154    fn git_checkout(&self, branch_name: String) {
155        let Some(repository) = self.open() else {
156            return;
157        };
158        let Ok(head) = repository.head() else {
159            return;
160        };
161        let Some(oid) = head.target() else {
162            return;
163        };
164        let Ok(commit) = repository.find_commit(oid) else {
165            return;
166        };
167        let Ok(_branch) = repository.branch(&branch_name, &commit, false) else {
168            return;
169        };
170        let Ok(obj) = repository.revparse_single(&("refs/heads/".to_owned() + &branch_name)) else {
171            return;
172        };
173        let _ = repository.checkout_tree(&obj, None);
174        let _ = repository.set_head(&("refs/heads/".to_owned() + &branch_name));
175    }
176}