Skip to content

Commit f7c1aad

Browse files
committed
Show symbol if tag is not yet pushed
This partly addresses #742.
1 parent 3bdb1d3 commit f7c1aad

File tree

6 files changed

+153
-2
lines changed

6 files changed

+153
-2
lines changed

asyncgit/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ mod progress;
3333
mod push;
3434
mod push_tags;
3535
pub mod remote_progress;
36+
///
37+
pub mod remotes;
3638
mod revlog;
3739
mod status;
3840
pub mod sync;
@@ -85,6 +87,9 @@ pub enum AsyncGitNotification {
8587
///
8688
//TODO: this does not belong here
8789
SyntaxHighlighting,
90+
///
91+
//TODO: this does not belong here
92+
RemoteTags,
8893
}
8994

9095
/// current working directory `./`

asyncgit/src/remotes.rs

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

asyncgit/src/sync/remotes/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ use push::remote_callbacks;
1616
use scopetime::scope_time;
1717
use utils::bytes2string;
1818

19+
pub use tags::tags_missing_remote;
20+
1921
/// origin
2022
pub const DEFAULT_REMOTE_NAME: &str = "origin";
2123

asyncgit/src/sync/remotes/tags.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ fn remote_tag_refs(
7373
}
7474

7575
/// lists the remotes tags missing
76-
fn tags_missing_remote(
76+
pub fn tags_missing_remote(
7777
repo_path: &str,
7878
remote: &str,
7979
basic_credential: Option<BasicAuthCredential>,

src/app.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ impl App {
167167
),
168168
tags_popup: TagListComponent::new(
169169
&queue,
170+
sender,
170171
theme.clone(),
171172
key_config.clone(),
172173
),
@@ -357,6 +358,7 @@ impl App {
357358
self.push_tags_popup.update_git(ev)?;
358359
self.pull_popup.update_git(ev)?;
359360
self.revision_files_popup.update(ev);
361+
self.tags_popup.update(ev);
360362

361363
//TODO: better system for this
362364
// can we simply process the queue here and everyone just uses the queue to schedule a cmd update?
@@ -383,6 +385,7 @@ impl App {
383385
|| self.push_tags_popup.any_work_pending()
384386
|| self.pull_popup.any_work_pending()
385387
|| self.revision_files_popup.any_work_pending()
388+
|| self.tags_popup.any_work_pending()
386389
}
387390

388391
///

src/components/taglist.rs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@ use crate::{
1111
};
1212
use anyhow::Result;
1313
use asyncgit::{
14+
asyncjob::AsyncSingleJob,
15+
remotes::AsyncRemoteTagsJob,
16+
sync::cred::{extract_username_password, need_username_password},
1417
sync::{get_tags_with_metadata, TagWithMetadata},
15-
CWD,
18+
AsyncGitNotification, CWD,
1619
};
20+
use crossbeam_channel::Sender;
1721
use crossterm::event::Event;
1822
use std::convert::TryInto;
1923
use tui::{
@@ -36,6 +40,9 @@ pub struct TagListComponent {
3640
visible: bool,
3741
table_state: std::cell::Cell<TableState>,
3842
current_height: std::cell::Cell<usize>,
43+
missing_remote_tags: Option<Vec<String>>,
44+
async_remote_tags:
45+
AsyncSingleJob<AsyncRemoteTagsJob, AsyncGitNotification>,
3946
key_config: SharedKeyConfig,
4047
}
4148

@@ -65,6 +72,8 @@ impl DrawableComponent for TagListComponent {
6572
});
6673

6774
let constraints = [
75+
// symbol if tag is not yet on remote and can be pushed
76+
Constraint::Length(1),
6877
// tag name
6978
Constraint::Length(tag_name_width.try_into()?),
7079
// commit date
@@ -230,6 +239,7 @@ impl Component for TagListComponent {
230239
impl TagListComponent {
231240
pub fn new(
232241
queue: &Queue,
242+
sender: &Sender<AsyncGitNotification>,
233243
theme: SharedTheme,
234244
key_config: SharedKeyConfig,
235245
) -> Self {
@@ -240,6 +250,11 @@ impl TagListComponent {
240250
visible: false,
241251
table_state: std::cell::Cell::new(TableState::default()),
242252
current_height: std::cell::Cell::new(0),
253+
missing_remote_tags: None,
254+
async_remote_tags: AsyncSingleJob::new(
255+
sender.clone(),
256+
AsyncGitNotification::RemoteTags,
257+
),
243258
key_config,
244259
}
245260
}
@@ -254,12 +269,46 @@ impl TagListComponent {
254269
Ok(())
255270
}
256271

272+
///
273+
pub fn update(&mut self, event: AsyncGitNotification) {
274+
if event == AsyncGitNotification::RemoteTags {
275+
if let Some(job) = self.async_remote_tags.take_last() {
276+
if let Some(Ok(missing_remote_tags)) = job.result() {
277+
self.missing_remote_tags =
278+
Some(missing_remote_tags);
279+
}
280+
}
281+
}
282+
}
283+
284+
///
285+
pub fn any_work_pending(&self) -> bool {
286+
self.async_remote_tags.is_pending()
287+
}
288+
257289
/// fetch list of tags
258290
pub fn update_tags(&mut self) -> Result<()> {
259291
let tags = get_tags_with_metadata(CWD)?;
260292

261293
self.tags = Some(tags);
262294

295+
if self.missing_remote_tags.is_none() {
296+
let basic_credential = if need_username_password()? {
297+
let credential = extract_username_password()?;
298+
299+
if credential.is_complete() {
300+
Some(credential)
301+
} else {
302+
None
303+
}
304+
} else {
305+
None
306+
};
307+
308+
self.async_remote_tags
309+
.spawn(AsyncRemoteTagsJob::new(basic_credential));
310+
}
311+
263312
Ok(())
264313
}
265314

@@ -307,7 +356,27 @@ impl TagListComponent {
307356

308357
///
309358
fn get_row(&self, tag: &TagWithMetadata) -> Row {
359+
const UPSTREAM_SYMBOL: &str = "\u{2191}";
360+
const EMPTY_SYMBOL: &str = " ";
361+
362+
let is_tag_missing_on_remote = self
363+
.missing_remote_tags
364+
.as_ref()
365+
.map_or(false, |missing_remote_tags| {
366+
let remote_tag = format!("refs/tags/{}", tag.name);
367+
368+
missing_remote_tags.contains(&remote_tag)
369+
});
370+
371+
let has_remote_str = if is_tag_missing_on_remote {
372+
UPSTREAM_SYMBOL
373+
} else {
374+
EMPTY_SYMBOL
375+
};
376+
310377
let cells: Vec<Cell> = vec![
378+
Cell::from(has_remote_str)
379+
.style(self.theme.commit_author(false)),
311380
Cell::from(tag.name.clone())
312381
.style(self.theme.text(true, false)),
313382
Cell::from(utils::time_to_string(tag.time, true))

0 commit comments

Comments
 (0)