1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
|
use std::{path::PathBuf, str::FromStr};
use git2::Repository;
use nostr::{Event, Filter, EventId};
use nostr_sdk::blocking::Client;
use crate::{ngit_tag::{tag_is_patch_parent, tag_is_initial_commit, tag_extract_value, tag_is_patch, tag_is_branch, tag_is_commit_parent, tag_is_commit}, utils::{load_event}, funcs::find_latest_patch::find_latest_patch, patch::{patch_commit_id, patch_is_commit}, branch_refs::BranchRefs, repo_config::RepoConfig, kind::Kind};
/// ancessor patch events first
pub fn get_updates_of_patches (
client: &Client,
branch_refs: &mut BranchRefs,
git_repo: &Repository,
repo_dir_path: &PathBuf,
branch_id: &String,
branch_name: &Option<String>,
pull_new_branch: bool,
) -> Vec<Event> {
let repo_config = RepoConfig::open(repo_dir_path);
let last_patch_timestamp = repo_config.last_patch_update_time(branch_id.clone());
// create direct patches filter
let direct_patches_filter = Filter::new()
.event(
EventId::from_str(branch_id)
.expect("branch_id to render as EventId")
)
.kinds(vec![Kind::Patch.into_sdk_custom_kind()]);
let mut filters = vec![
match &last_patch_timestamp {
None => direct_patches_filter,
Some(timestamp) => {
direct_patches_filter.since(timestamp.clone())
}
}
];
// get maintainers group
if branch_refs.maintainers_group(Some(&branch_id)).is_none() {
// fetch branch mantainers group and check again
client.add_relays(
branch_refs.branch_as_repo(Some(branch_id))
.relays
.clone().iter().map(|url| (url, None)).collect()
)
.expect("branch relays to be added to client");
let mut group_events = client.get_events_of(
vec![
// use the opportunity to get all the remaining groups
Filter::new().ids(branch_refs.group_ids_for_branches_without_cached_groups()),
],
None,
)
.expect("get_events_of to not return an error");
group_events.sort();
group_events.dedup();
branch_refs.updates(group_events);
}
// create indirect pacthes filter
let merges_into_branch: Vec<Event> = branch_refs.merges.iter().filter(|event|
// merged into branch
event.tags.iter().any(|t|
tag_is_branch(t)
&& tag_extract_value(t) == branch_id.clone()
)
// merge timestamp is after last_patch_timestamp - we already have patches before this date
&& match &last_patch_timestamp {
None => true,
Some(timestamp) => timestamp < &event.created_at
}
// author is member of branch maintainers group
&& branch_refs.is_authorized(Some(branch_id), &event.pubkey)
.expect("found group event for branch after checking on speficied relays")
).map(|e|e.clone())
.collect();
if !merges_into_branch.is_empty() {
filters.push(
// ids for all patches referenced in merges
Filter::new()
.ids(
merges_into_branch.iter().flat_map(|event|
event.tags.iter()
.filter(|t| tag_is_patch(t))
.map(|t| tag_extract_value(t).clone())
.collect::<Vec<String>>()
)
.collect::<Vec<String>>()
)
// .kinds(vec![Kind::Patch.into_sdk_custom_kind()])
)
}
// find patch events
let mut patch_events: Vec<Event> = client.get_events_of(
filters,
None,
)
.expect("get_events_of to not return an error when looking for patches");
patch_events.sort();
patch_events.dedup();
// find patch tip on branch
let latest_patch_on_branch = match find_latest_patch(
&branch_id,
&patch_events,
&merges_into_branch,
&branch_refs,
&repo_dir_path,
) {
// no patches return empty vector
None => { return vec![] }, // for pull_new_branch do we set the branch to the latest commit referneced even if we have it?
Some(event) => event,
};
let mut new_patches_on_branch = vec![];
// for pull_new_branch - cycle through patch parents until we find any patch that exists in our commit history
if pull_new_branch {
let mut patch_event_id = latest_patch_on_branch.id.to_string();
let mut patch_commit_id = tag_extract_value(
latest_patch_on_branch.tags.iter().find(|t|tag_is_commit(t))
.expect("all patch events to have a commit tag")
);
loop {
let patch = match patch_events.iter().find(|p| p.id.to_string() == patch_event_id.clone()) {
// patch event found in patch_events
Some(patch) => patch,
None => {
// loop for parent locally
if repo_dir_path.join(format!(
".ngit/patches/{}.json",
patch_commit_id,
)).exists() {
// break out of loop when we identify the commit where the branch begins
break
}
else {
panic!("cannot find parent patch locally or in patch_events. This will fail if the branch does not share a commit with main / master")
}
}
};
// add patch to list of patches to apply to new branch
new_patches_on_branch.push(patch.clone());
// prepare loop for next patch - set patch_event_id to current patches parent
patch_event_id = tag_extract_value(
patch.tags.iter().find(|t|tag_is_patch_parent(t))
.expect("patch to always have a patch parent.")
);
patch_commit_id = tag_extract_value(
patch.tags.iter().find(|t|tag_is_commit_parent(t))
.expect("patch to always have a commit parent. This will fail if the branch does not share a commit with main / master")
);
};
}
// cycle through patch parents until we the latest commit in our local branch, or error if detects a rebase (it exists in our branch history)
else {
// revwalk through branch to identify forced push
let mut revwalk = git_repo.revwalk()
.expect("revwalk to not error on git_repo");
match &branch_name {
Some(name) => {
revwalk.push(
git_repo.find_branch(
name.as_str(),
git2::BranchType::Local
)
.expect("branch found from the branch_name")
.get()
.peel_to_commit()
.expect("branch reference to peel back to a commit")
.id()
)
.expect("revwalk push_glob(branch_name) not to error if branch name is not None");
}
None => (),
}
let commit_ids_in_branch: Vec<String> = if branch_name.is_none() { vec![] } else {
revwalk.map(|oid|
oid
.expect("revwalk to produce oids without error")
.to_string()
).collect()
};
let latest_commit: Option<&String> = match commit_ids_in_branch.get(0) {
None => None,
Some(latest_commit) => {
// return empty if latest patch is in current chain
if commit_ids_in_branch.iter().any(|id|
patch_commit_id(&latest_patch_on_branch) == id.to_string()
) { return vec![]; }
Some(latest_commit)
},
};
// work back thorugh commit chain until we reach a commit in our branch history (tip or ealier for rebase)
new_patches_on_branch = vec![latest_patch_on_branch.clone()];
loop {
let next_parent_patch = new_patches_on_branch.last()
.expect("chain to contain at least latest_patch_on_main")
.clone();
match next_parent_patch.tags.iter().find(|t|tag_is_patch_parent(t)) {
None => {
// found root patch or error
next_parent_patch.tags.iter().find(|t|tag_is_initial_commit(t))
// tag_is_initial_commit is false when it should be true. is it always false or just the oposite?
.expect(
&format!(
"reach a patch which doesn't contain a either a tag_is_patch_parent or tag_is_initial_commit{:#?}",
&next_parent_patch
)
);
break;
},
Some(t) => {
let next_patch = match patch_events.iter().find(|event|event.id.to_string() == tag_extract_value(t)) {
None => {
let patch_path = repo_dir_path.join(format!(
".ngit/patches/{}.json",
tag_extract_value(
next_parent_patch.tags.iter().find(|t|tag_is_commit_parent(t))
.expect("patch to always have a commit parent if it has a patch parent")
),
));
if patch_path.exists() {
load_event(patch_path)
.expect("patch json at location that exists loads into event")
}
else {
panic!("cannot find parent patch id {} from patch {:#?}",tag_extract_value(t), next_parent_patch);
}
},
Some(event) => event.clone(),
};
// if reached current tip - break
if latest_commit.is_some() && patch_is_commit(
&next_patch,
latest_commit.unwrap(),
) { break; }
// detect rebase
if commit_ids_in_branch.iter().any(|id|
patch_commit_id(&next_patch) == id.to_string()
) {
panic!("force push detected. This branch has been force pushed since you last pulled. ngit doesnt handle this yet");
}
// new patch
new_patches_on_branch.push(next_patch.clone());
},
}
}
}
// oldest first
new_patches_on_branch.reverse();
new_patches_on_branch
}
|