reactive_graph_net_git_model/component/
repository.rs1use 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}