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
|
use anyhow::{bail, Context, Result};
use nostr_sdk::PublicKey;
use crate::{
client::{
fetching_with_report, get_all_proposal_patch_events_from_cache,
get_proposals_and_revisions_from_cache, get_repo_ref_from_cache, Client, Connect,
},
git::{str_to_sha1, Repo, RepoActions},
git_events::{
event_is_revision_root, event_to_cover_letter, get_commit_id_from_patch,
get_most_recent_patch_with_ancestors, tag_value,
},
repo_ref::get_repo_coordinates,
};
#[allow(clippy::too_many_lines)]
pub async fn launch() -> Result<()> {
let git_repo = Repo::discover().context("cannot find a git repository")?;
let git_repo_path = git_repo.get_path()?;
let (main_or_master_branch_name, _) = git_repo
.get_main_or_master_branch()
.context("no main or master branch")?;
let branch_name = git_repo
.get_checked_out_branch_name()
.context("cannot get checked out branch name")?;
if branch_name == main_or_master_branch_name {
bail!("checkout a branch associated with a proposal first")
}
let client = Client::default();
let repo_coordinates = get_repo_coordinates(&git_repo, &client).await?;
fetching_with_report(git_repo_path, &client, &repo_coordinates).await?;
let repo_ref = get_repo_ref_from_cache(git_repo_path, &repo_coordinates).await?;
let public_key_if_known =
if let Ok(Some(npub)) = git_repo.get_git_config_item("nostr.npub", None) {
PublicKey::parse(npub).ok()
} else {
None
};
let proposal_root_event =
get_proposals_and_revisions_from_cache(git_repo_path, repo_ref.coordinates())
.await?
.iter()
.find(|e| {
event_to_cover_letter(e).is_ok_and(|cl| {
cl.branch_name.eq(&branch_name)
|| (public_key_if_known
.is_some_and(|public_key| e.author().eq(&public_key))
&& branch_name.eq(&format!("pr/{}", cl.branch_name)))
|| cl.get_branch_name().is_ok_and(|s| s.eq(&branch_name))
}) && !event_is_revision_root(e)
})
.context("cannot find proposal that matches the current branch name")?
.clone();
let commit_events = get_all_proposal_patch_events_from_cache(
git_repo_path,
&repo_ref,
&proposal_root_event.id(),
)
.await?;
let most_recent_proposal_patch_chain =
get_most_recent_patch_with_ancestors(commit_events.clone())
.context("cannot get most recent patch for proposal")?;
let local_branch_tip = git_repo.get_tip_of_branch(&branch_name)?;
let (main_branch_name, master_tip) = git_repo.get_main_or_master_branch()?;
let (local_ahead_of_main, local_beind_main) =
git_repo.get_commits_ahead_behind(&master_tip, &local_branch_tip)?;
let proposal_base_commit = str_to_sha1(&tag_value(
most_recent_proposal_patch_chain
.last()
.context("there should be at least one patch as we have already checked for this")?,
"parent-commit",
)?)
.context("cannot get valid parent commit id from patch")?;
let (_, proposal_behind_main) =
git_repo.get_commits_ahead_behind(&master_tip, &proposal_base_commit)?;
let proposal_tip =
str_to_sha1(
&get_commit_id_from_patch(most_recent_proposal_patch_chain.first().context(
"there should be at least one patch as we have already checked for this",
)?)
.context("cannot get valid commit_id from patch")?,
)
.context("cannot get valid commit_id from patch")?;
// if uptodate
if proposal_tip.eq(&local_branch_tip) {
println!("branch already up-to-date");
}
// if new appendments
else if most_recent_proposal_patch_chain.iter().any(|patch| {
get_commit_id_from_patch(patch)
.unwrap_or_default()
.eq(&local_branch_tip.to_string())
}) {
check_clean(&git_repo)?;
let applied = git_repo
.apply_patch_chain(&branch_name, most_recent_proposal_patch_chain)
.context("cannot apply patch chain")?;
println!("applied {} new commits", applied.len(),);
}
// if parent commit doesnt exist
else if !git_repo.does_commit_exist(&proposal_base_commit.to_string())? {
println!(
"a new version of the proposal has a prant commit that doesnt exist in your local repository."
);
println!("your '{main_branch_name}' branch may not be up-to-date.");
println!("manually run `git pull` on '{main_branch_name}' and try again");
}
// if new revision and no local changes (tip of local in proposal history)
else if commit_events.iter().any(|patch| {
get_commit_id_from_patch(patch)
.unwrap_or_default()
.eq(&local_branch_tip.to_string())
}) {
check_clean(&git_repo)?;
git_repo.create_branch_at_commit(&branch_name, &proposal_base_commit.to_string())?;
let applied = git_repo
.apply_patch_chain(&branch_name, most_recent_proposal_patch_chain)
.context("cannot apply patch chain")?;
println!(
"pulled new version of proposal ({} ahead {} behind '{main_branch_name}'), replacing old version ({} ahead {} behind '{main_branch_name}')",
applied.len(),
proposal_behind_main.len(),
local_ahead_of_main.len(),
local_beind_main.len(),
);
}
// if tip of proposal in branch in history (local appendments made to up-to-date
// proposal)
else if git_repo.ancestor_of(&local_branch_tip, &proposal_tip)? {
let (local_ahead_of_proposal, _) = git_repo
.get_commits_ahead_behind(&proposal_tip, &local_branch_tip)
.context("cannot get commits ahead behind for propsal_top and local_branch_tip")?;
println!(
"local proposal branch exists with {} unpublished commits on top of the most up-to-date version of the proposal",
local_ahead_of_proposal.len()
);
} else {
println!("you have an amended/rebase version the proposal that is unpublished");
// user probably has a unpublished amended or rebase version of the latest
// proposal version
// if tip of proposal commits exist (were once part of branch but have been
// amended and git clean up job hasn't removed them)
if git_repo.does_commit_exist(&proposal_tip.to_string())? {
println!(
"you have previously applied the latest version of the proposal ({} ahead {} behind '{main_branch_name}') but your local proposal branch has amended or rebased it ({} ahead {} behind '{main_branch_name}')",
most_recent_proposal_patch_chain.len(),
proposal_behind_main.len(),
local_ahead_of_main.len(),
local_beind_main.len(),
);
}
// user probably has a unpublished amended or rebase version of an older
// proposal version
else {
println!(
"your local proposal branch ({} ahead {} behind '{main_branch_name}') has conflicting changes with the latest published proposal ({} ahead {} behind '{main_branch_name}')",
local_ahead_of_main.len(),
local_beind_main.len(),
most_recent_proposal_patch_chain.len(),
proposal_behind_main.len(),
);
println!(
"its likely that you have rebased / amended an old proposal version because git has no record of the latest proposal commit."
);
println!(
"it is possible that you have been working off the latest version and git has delete this commit as part of a clean up"
);
}
println!("to view the latest proposal but retain your changes:");
println!(" 1) create a new branch off the tip commit of this one to store your changes");
println!(" 2) run `ngit list` and checkout the latest published version of this proposal");
println!("if you are confident in your changes consider running `ngit push --force`");
// TODO: this copy could be refined further based on this:
// - amended commits in the proposal
// - if local_base eq proposal base
// - amended an older version of proposal
// - if local_base is behind proposal_base
// - rebased the proposal
// - if local_base is ahead of proposal_base
}
Ok(())
}
fn check_clean(git_repo: &Repo) -> Result<()> {
if git_repo.has_outstanding_changes()? {
bail!(
"cannot pull proposal branch when repository is not clean. discard or stash (un)staged changes and try again."
);
}
Ok(())
}
|