Skip to content

Commit 33ac72c

Browse files
author
Stephan Dilly
authored
fetch/prune branches (#1000)
1 parent ee7ec69 commit 33ac72c

File tree

13 files changed

+412
-20
lines changed

13 files changed

+412
-20
lines changed

asyncgit/src/fetch_job.rs

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//!
2+
3+
use crate::{
4+
asyncjob::{AsyncJob, RunParams},
5+
error::Result,
6+
sync::cred::BasicAuthCredential,
7+
sync::remotes::fetch_all,
8+
AsyncGitNotification, ProgressPercent, CWD,
9+
};
10+
11+
use std::sync::{Arc, Mutex};
12+
13+
enum JobState {
14+
Request(Option<BasicAuthCredential>),
15+
Response(Result<()>),
16+
}
17+
18+
///
19+
#[derive(Clone, Default)]
20+
pub struct AsyncFetchJob {
21+
state: Arc<Mutex<Option<JobState>>>,
22+
}
23+
24+
///
25+
impl AsyncFetchJob {
26+
///
27+
pub fn new(
28+
basic_credential: Option<BasicAuthCredential>,
29+
) -> Self {
30+
Self {
31+
state: Arc::new(Mutex::new(Some(JobState::Request(
32+
basic_credential,
33+
)))),
34+
}
35+
}
36+
37+
///
38+
pub fn result(&self) -> Option<Result<()>> {
39+
if let Ok(mut state) = self.state.lock() {
40+
if let Some(state) = state.take() {
41+
return match state {
42+
JobState::Request(_) => None,
43+
JobState::Response(result) => Some(result),
44+
};
45+
}
46+
}
47+
48+
None
49+
}
50+
}
51+
52+
impl AsyncJob for AsyncFetchJob {
53+
type Notification = AsyncGitNotification;
54+
type Progress = ProgressPercent;
55+
56+
fn run(
57+
&mut self,
58+
_params: RunParams<Self::Notification, Self::Progress>,
59+
) -> Result<Self::Notification> {
60+
if let Ok(mut state) = self.state.lock() {
61+
*state = state.take().map(|state| match state {
62+
JobState::Request(basic_credentials) => {
63+
//TODO: support progress
64+
let result =
65+
fetch_all(CWD, &basic_credentials, &None);
66+
67+
JobState::Response(result)
68+
}
69+
JobState::Response(result) => {
70+
JobState::Response(result)
71+
}
72+
});
73+
}
74+
75+
Ok(AsyncGitNotification::Fetch)
76+
}
77+
}

asyncgit/src/lib.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ pub mod cached;
2828
mod commit_files;
2929
mod diff;
3030
mod error;
31-
mod fetch;
31+
mod fetch_job;
3232
mod progress;
33+
mod pull;
3334
mod push;
3435
mod push_tags;
3536
pub mod remote_progress;
@@ -44,8 +45,9 @@ pub use crate::{
4445
commit_files::{AsyncCommitFiles, CommitFilesParams},
4546
diff::{AsyncDiff, DiffParams, DiffType},
4647
error::{Error, Result},
47-
fetch::{AsyncFetch, FetchRequest},
48+
fetch_job::AsyncFetchJob,
4849
progress::ProgressPercent,
50+
pull::{AsyncPull, FetchRequest},
4951
push::{AsyncPush, PushRequest},
5052
push_tags::{AsyncPushTags, PushTagsRequest},
5153
remote_progress::{RemoteProgress, RemoteProgressState},
@@ -83,11 +85,13 @@ pub enum AsyncGitNotification {
8385
///
8486
PushTags,
8587
///
86-
Fetch,
88+
Pull,
8789
///
8890
Blame,
8991
///
9092
RemoteTags,
93+
///
94+
Fetch,
9195
}
9296

9397
/// current working directory `./`

asyncgit/src/fetch.rs renamed to asyncgit/src/pull.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ pub struct FetchRequest {
2828
struct FetchState {}
2929

3030
///
31-
pub struct AsyncFetch {
31+
pub struct AsyncPull {
3232
state: Arc<Mutex<Option<FetchState>>>,
3333
last_result: Arc<Mutex<Option<(usize, String)>>>,
3434
progress: Arc<Mutex<Option<ProgressNotification>>>,
3535
sender: Sender<AsyncGitNotification>,
3636
}
3737

38-
impl AsyncFetch {
38+
impl AsyncPull {
3939
///
4040
pub fn new(sender: &Sender<AsyncGitNotification>) -> Self {
4141
Self {
@@ -84,7 +84,7 @@ impl AsyncFetch {
8484
let (progress_sender, receiver) = unbounded();
8585

8686
let handle = RemoteProgress::spawn_receiver_thread(
87-
AsyncGitNotification::Fetch,
87+
AsyncGitNotification::Pull,
8888
sender.clone(),
8989
receiver,
9090
arc_progress,
@@ -108,7 +108,7 @@ impl AsyncFetch {
108108
Self::clear_request(&arc_state).expect("clear error");
109109

110110
sender
111-
.send(AsyncGitNotification::Fetch)
111+
.send(AsyncGitNotification::Pull)
112112
.expect("AsyncNotification error");
113113
});
114114

asyncgit/src/sync/remotes/mod.rs

+57-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{
1010
cred::BasicAuthCredential,
1111
remotes::push::ProgressNotification, utils,
1212
},
13+
ProgressPercent,
1314
};
1415
use crossbeam_channel::Sender;
1516
use git2::{BranchType, FetchOptions, Repository};
@@ -75,14 +76,68 @@ pub(crate) fn get_default_remote_in_repo(
7576
Err(Error::NoDefaultRemoteFound)
7677
}
7778

78-
/// fetches from upstream/remote for `branch`
79+
///
80+
fn fetch_from_remote(
81+
repo_path: &str,
82+
remote: &str,
83+
basic_credential: Option<BasicAuthCredential>,
84+
progress_sender: Option<Sender<ProgressNotification>>,
85+
) -> Result<()> {
86+
let repo = utils::repo(repo_path)?;
87+
88+
let mut remote = repo.find_remote(remote)?;
89+
90+
let mut options = FetchOptions::new();
91+
let callbacks = Callbacks::new(progress_sender, basic_credential);
92+
options.prune(git2::FetchPrune::On);
93+
options.remote_callbacks(callbacks.callbacks());
94+
remote.fetch(&[] as &[&str], Some(&mut options), None)?;
95+
96+
Ok(())
97+
}
98+
99+
/// updates/prunes all branches from all remotes
100+
pub fn fetch_all(
101+
repo_path: &str,
102+
basic_credential: &Option<BasicAuthCredential>,
103+
progress_sender: &Option<Sender<ProgressPercent>>,
104+
) -> Result<()> {
105+
scope_time!("fetch_all");
106+
107+
let repo = utils::repo(repo_path)?;
108+
let remotes = repo
109+
.remotes()?
110+
.iter()
111+
.flatten()
112+
.map(String::from)
113+
.collect::<Vec<_>>();
114+
let remotes_count = remotes.len();
115+
116+
for (idx, remote) in remotes.into_iter().enumerate() {
117+
fetch_from_remote(
118+
repo_path,
119+
&remote,
120+
basic_credential.clone(),
121+
None,
122+
)?;
123+
124+
if let Some(sender) = progress_sender {
125+
let progress = ProgressPercent::new(idx, remotes_count);
126+
sender.send(progress)?;
127+
}
128+
}
129+
130+
Ok(())
131+
}
132+
133+
/// fetches from upstream/remote for local `branch`
79134
pub(crate) fn fetch(
80135
repo_path: &str,
81136
branch: &str,
82137
basic_credential: Option<BasicAuthCredential>,
83138
progress_sender: Option<Sender<ProgressNotification>>,
84139
) -> Result<usize> {
85-
scope_time!("fetch_origin");
140+
scope_time!("fetch");
86141

87142
let repo = utils::repo(repo_path)?;
88143
let branch_ref = repo

src/app.rs

+25-5
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ use crate::{
66
BranchListComponent, CommandBlocking, CommandInfo,
77
CommitComponent, CompareCommitsComponent, Component,
88
ConfirmComponent, CreateBranchComponent, DrawableComponent,
9-
ExternalEditorComponent, FileFindPopup, HelpComponent,
10-
InspectCommitComponent, MsgComponent, OptionsPopupComponent,
11-
PullComponent, PushComponent, PushTagsComponent,
12-
RenameBranchComponent, RevisionFilesPopup, SharedOptions,
13-
StashMsgComponent, TagCommitComponent, TagListComponent,
9+
ExternalEditorComponent, FetchComponent, FileFindPopup,
10+
HelpComponent, InspectCommitComponent, MsgComponent,
11+
OptionsPopupComponent, PullComponent, PushComponent,
12+
PushTagsComponent, RenameBranchComponent, RevisionFilesPopup,
13+
SharedOptions, StashMsgComponent, TagCommitComponent,
14+
TagListComponent,
1415
},
1516
input::{Input, InputEvent, InputState},
1617
keys::{KeyConfig, SharedKeyConfig},
@@ -55,6 +56,7 @@ pub struct App {
5556
push_popup: PushComponent,
5657
push_tags_popup: PushTagsComponent,
5758
pull_popup: PullComponent,
59+
fetch_popup: FetchComponent,
5860
tag_commit_popup: TagCommitComponent,
5961
create_branch_popup: CreateBranchComponent,
6062
rename_branch_popup: RenameBranchComponent,
@@ -158,6 +160,12 @@ impl App {
158160
theme.clone(),
159161
key_config.clone(),
160162
),
163+
fetch_popup: FetchComponent::new(
164+
&queue,
165+
sender,
166+
theme.clone(),
167+
key_config.clone(),
168+
),
161169
tag_commit_popup: TagCommitComponent::new(
162170
queue.clone(),
163171
theme.clone(),
@@ -389,6 +397,7 @@ impl App {
389397
self.push_popup.update_git(ev)?;
390398
self.push_tags_popup.update_git(ev)?;
391399
self.pull_popup.update_git(ev);
400+
self.fetch_popup.update_git(ev);
392401
self.select_branch_popup.update_git(ev)?;
393402
}
394403

@@ -421,6 +430,7 @@ impl App {
421430
|| self.push_popup.any_work_pending()
422431
|| self.push_tags_popup.any_work_pending()
423432
|| self.pull_popup.any_work_pending()
433+
|| self.fetch_popup.any_work_pending()
424434
|| self.revision_files_popup.any_work_pending()
425435
|| self.tags_popup.any_work_pending()
426436
}
@@ -453,6 +463,7 @@ impl App {
453463
push_popup,
454464
push_tags_popup,
455465
pull_popup,
466+
fetch_popup,
456467
tag_commit_popup,
457468
create_branch_popup,
458469
rename_branch_popup,
@@ -489,6 +500,7 @@ impl App {
489500
push_popup,
490501
push_tags_popup,
491502
pull_popup,
503+
fetch_popup,
492504
options_popup,
493505
reset,
494506
msg
@@ -696,6 +708,14 @@ impl App {
696708
}
697709
flags.insert(NeedsUpdate::ALL);
698710
}
711+
InternalEvent::FetchRemotes => {
712+
if let Err(error) = self.fetch_popup.fetch() {
713+
self.queue.push(InternalEvent::ShowErrorMsg(
714+
error.to_string(),
715+
));
716+
}
717+
flags.insert(NeedsUpdate::ALL);
718+
}
699719
InternalEvent::PushTags => {
700720
self.push_tags_popup.push_tags()?;
701721
flags.insert(NeedsUpdate::ALL);

src/components/branchlist.rs

+8
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,12 @@ impl Component for BranchListComponent {
196196
true,
197197
self.local,
198198
));
199+
200+
out.push(CommandInfo::new(
201+
strings::commands::fetch_remotes(&self.key_config),
202+
true,
203+
!self.local,
204+
));
199205
}
200206
visibility_blocking(self)
201207
}
@@ -290,6 +296,8 @@ impl Component for BranchListComponent {
290296
self.queue
291297
.push(InternalEvent::CompareCommits(b, None));
292298
}
299+
} else if e == self.key_config.keys.pull && !self.local {
300+
self.queue.push(InternalEvent::FetchRemotes);
293301
} else if e == self.key_config.keys.cmd_bar_toggle {
294302
//do not consume if its the more key
295303
return Ok(EventState::NotConsumed);

0 commit comments

Comments
 (0)