support deleting tag on remote (#1079)

This commit is contained in:
Stephan Dilly 2022-01-16 22:56:39 +01:00 committed by GitHub
parent 060380fdd6
commit 13b6b2fdc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 221 additions and 61 deletions

View File

@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- allow reverting a commit from the commit log ([#927](https://github.com/extrawurst/gitui/issues/927))
- disable pull cmd on local-only branches ([#1047](https://github.com/extrawurst/gitui/issues/1047))
- support adding annotations to tags ([#747](https://github.com/extrawurst/gitui/issues/747))
- support deleting tag on remote ([#1074](https://github.com/extrawurst/gitui/issues/1074))
### Fixed
- Keep commit message when pre-commit hook fails ([#1035](https://github.com/extrawurst/gitui/issues/1035))

View File

@ -55,6 +55,7 @@ pub use crate::{
status::{AsyncStatus, StatusParams},
sync::{
diff::{DiffLine, DiffLineType, FileDiff},
remotes::push::PushType,
status::{StatusItem, StatusItemType},
},
tags::AsyncTags,

View File

@ -1,8 +1,10 @@
use crate::{
error::{Error, Result},
sync::{
cred::BasicAuthCredential, remotes::push::push,
remotes::push::ProgressNotification, RepoPath,
cred::BasicAuthCredential,
remotes::push::push_raw,
remotes::push::{ProgressNotification, PushType},
RepoPath,
},
AsyncGitNotification, RemoteProgress,
};
@ -20,6 +22,8 @@ pub struct PushRequest {
///
pub branch: String,
///
pub push_type: PushType,
///
pub force: bool,
///
pub delete: bool,
@ -100,10 +104,11 @@ impl AsyncPush {
arc_progress,
);
let res = push(
let res = push_raw(
&repo,
params.remote.as_str(),
params.branch.as_str(),
params.push_type,
params.force,
params.delete,
params.basic_credential.clone(),

View File

@ -99,7 +99,7 @@ mod test {
use super::*;
use crate::sync::{
branch_compare_upstream,
remotes::{fetch, push::push},
remotes::{fetch, push::push_branch},
tests::{
debug_cmd_print, get_commit_ids, repo_clone,
repo_init_bare, write_commit_file, write_commit_file_at,
@ -129,7 +129,7 @@ mod test {
Time::new(1, 0),
);
push(
push_branch(
&clone1_dir.path().to_str().unwrap().into(),
"origin",
"master",
@ -151,7 +151,7 @@ mod test {
);
//push should fail since origin diverged
assert!(push(
assert!(push_branch(
&clone2_dir.into(),
"origin",
"master",
@ -228,7 +228,7 @@ mod test {
"git status",
);
push(
push_branch(
&clone1_dir.path().to_str().unwrap().into(),
"origin",
"master",

View File

@ -53,7 +53,7 @@ pub fn branch_merge_upstream_fastforward(
pub mod test {
use super::*;
use crate::sync::{
remotes::{fetch, push::push},
remotes::{fetch, push::push_branch},
tests::{
debug_cmd_print, get_commit_ids, repo_clone,
repo_init_bare, write_commit_file,
@ -75,7 +75,7 @@ pub mod test {
let commit1 =
write_commit_file(&clone1, "test.txt", "test", "commit1");
push(
push_branch(
&clone1_dir.path().to_str().unwrap().into(),
"origin",
"master",
@ -99,7 +99,7 @@ pub mod test {
"commit2",
);
push(
push_branch(
&clone2_dir.path().to_str().unwrap().into(),
"origin",
"master",

View File

@ -38,7 +38,7 @@ mod test {
use super::*;
use crate::sync::{
branch_compare_upstream, get_commits_info,
remotes::{fetch, push::push},
remotes::{fetch, push::push_branch},
tests::{
debug_cmd_print, get_commit_ids, repo_clone,
repo_init_bare, write_commit_file, write_commit_file_at,
@ -81,7 +81,7 @@ mod test {
assert_eq!(clone1.head_detached().unwrap(), false);
push(
push_branch(
&clone1_dir.into(),
"origin",
"master",
@ -111,7 +111,7 @@ mod test {
assert_eq!(clone2.head_detached().unwrap(), false);
push(
push_branch(
&clone2_dir.into(),
"origin",
"master",
@ -193,7 +193,7 @@ mod test {
Time::new(0, 0),
);
push(
push_branch(
&clone1_dir.into(),
"origin",
"master",
@ -219,7 +219,7 @@ mod test {
Time::new(1, 0),
);
push(
push_branch(
&clone2_dir.into(),
"origin",
"master",
@ -287,7 +287,7 @@ mod test {
let _commit1 =
write_commit_file(&clone1, "test.txt", "test", "commit1");
push(
push_branch(
&clone1_dir.into(),
"origin",
"master",
@ -312,7 +312,7 @@ mod test {
"commit2",
);
push(
push_branch(
&clone2_dir.into(),
"origin",
"master",

View File

@ -458,7 +458,7 @@ mod tests_branch_compare {
mod tests_branches {
use super::*;
use crate::sync::{
remotes::{get_remotes, push::push},
remotes::{get_remotes, push::push_branch},
rename_branch,
tests::{
debug_cmd_print, repo_clone, repo_init, repo_init_bare,
@ -509,7 +509,7 @@ mod tests_branches {
write_commit_file(&repo, "f1.txt", "foo", "c1");
rename_branch(&dir.into(), "refs/heads/master", branch_name)
.unwrap();
push(
push_branch(
&dir.into(),
"origin",
branch_name,
@ -715,7 +715,7 @@ mod test_delete_branch {
#[cfg(test)]
mod test_remote_branches {
use super::*;
use crate::sync::remotes::push::push;
use crate::sync::remotes::push::push_branch;
use crate::sync::tests::{
repo_clone, repo_init_bare, write_commit_file,
};
@ -744,7 +744,7 @@ mod test_remote_branches {
write_commit_file(&clone1, "test.txt", "test", "commit1");
push(
push_branch(
&clone1_dir.into(),
"origin",
"master",
@ -759,7 +759,7 @@ mod test_remote_branches {
write_commit_file(&clone1, "test.txt", "test2", "commit2");
push(
push_branch(
&clone1_dir.into(),
"origin",
"foo",
@ -801,7 +801,7 @@ mod test_remote_branches {
// clone1
write_commit_file(&clone1, "test.txt", "test", "commit1");
push(
push_branch(
&clone1_dir.into(),
"origin",
"master",
@ -813,7 +813,7 @@ mod test_remote_branches {
.unwrap();
create_branch(&clone1_dir.into(), "foo").unwrap();
write_commit_file(&clone1, "test.txt", "test2", "commit2");
push(
push_branch(
&clone1_dir.into(),
"origin",
"foo",
@ -869,7 +869,7 @@ mod test_remote_branches {
let branch_name = "bar/foo";
write_commit_file(&clone1, "test.txt", "test", "commit1");
push(
push_branch(
&clone1_dir.into(),
"origin",
"master",
@ -881,7 +881,7 @@ mod test_remote_branches {
.unwrap();
create_branch(&clone1_dir.into(), branch_name).unwrap();
write_commit_file(&clone1, "test.txt", "test2", "commit2");
push(
push_branch(
&clone1_dir.into(),
"origin",
branch_name,
@ -921,7 +921,7 @@ mod test_remote_branches {
// clone1
write_commit_file(&clone1, "test.txt", "test", "commit1");
push(
push_branch(
&clone1_dir.into(),
"origin",
"master",
@ -933,7 +933,7 @@ mod test_remote_branches {
.unwrap();
create_branch(&clone1_dir.into(), "foo").unwrap();
write_commit_file(&clone1, "test.txt", "test2", "commit2");
push(
push_branch(
&clone1_dir.into(),
"origin",
"foo",

View File

@ -95,6 +95,12 @@ fn fetch_from_remote(
options.download_tags(git2::AutotagOption::All);
options.remote_callbacks(callbacks.callbacks());
remote.fetch(&[] as &[&str], Some(&mut options), None)?;
// fetch tags (also removing remotely deleted ones)
remote.fetch(
&["refs/tags/*:refs/tags/*"],
Some(&mut options),
None,
)?;
Ok(())
}

View File

@ -88,8 +88,23 @@ impl AsyncProgress for ProgressNotification {
}
}
#[allow(clippy::redundant_pub_crate)]
pub(crate) fn push(
///
#[derive(Copy, Clone, Debug)]
pub enum PushType {
///
Branch,
///
Tag,
}
impl Default for PushType {
fn default() -> Self {
Self::Branch
}
}
#[cfg(test)]
pub fn push_branch(
repo_path: &RepoPath,
remote: &str,
branch: &str,
@ -97,6 +112,30 @@ pub(crate) fn push(
delete: bool,
basic_credential: Option<BasicAuthCredential>,
progress_sender: Option<Sender<ProgressNotification>>,
) -> Result<()> {
push_raw(
repo_path,
remote,
branch,
PushType::Branch,
force,
delete,
basic_credential,
progress_sender,
)
}
//TODO: clenaup
#[allow(clippy::too_many_arguments)]
pub fn push_raw(
repo_path: &RepoPath,
remote: &str,
branch: &str,
ref_type: PushType,
force: bool,
delete: bool,
basic_credential: Option<BasicAuthCredential>,
progress_sender: Option<Sender<ProgressNotification>>,
) -> Result<()> {
scope_time!("push");
@ -115,8 +154,13 @@ pub(crate) fn push(
(true, false) => "+",
(false, false) => "",
};
let ref_type = match ref_type {
PushType::Branch => "heads",
PushType::Tag => "tags",
};
let branch_name =
format!("{}refs/heads/{}", branch_modifier, branch);
format!("{}refs/{}/{}", branch_modifier, ref_type, branch);
remote.push(&[branch_name.as_str()], Some(&mut options))?;
if let Some((reference, msg)) =
@ -182,7 +226,7 @@ mod tests {
)
.unwrap();
push(
push_branch(
&tmp_repo_dir.path().to_str().unwrap().into(),
"origin",
"master",
@ -208,7 +252,7 @@ mod tests {
// Attempt a normal push,
// should fail as branches diverged
assert_eq!(
push(
push_branch(
&tmp_other_repo_dir.path().to_str().unwrap().into(),
"origin",
"master",
@ -224,7 +268,7 @@ mod tests {
// Attempt force push,
// should work as it forces the push through
assert_eq!(
push(
push_branch(
&tmp_other_repo_dir.path().to_str().unwrap().into(),
"origin",
"master",
@ -294,7 +338,7 @@ mod tests {
let commits = get_commit_ids(&repo, 1);
assert!(commits.contains(&repo_1_commit));
push(
push_branch(
&tmp_repo_dir.path().to_str().unwrap().into(),
"origin",
"master",
@ -337,7 +381,7 @@ mod tests {
// Attempt a normal push,
// should fail as branches diverged
assert_eq!(
push(
push_branch(
&tmp_other_repo_dir.path().to_str().unwrap().into(),
"origin",
"master",
@ -358,7 +402,7 @@ mod tests {
// Attempt force push,
// should work as it forces the push through
push(
push_branch(
&tmp_other_repo_dir.path().to_str().unwrap().into(),
"origin",
"master",
@ -405,7 +449,7 @@ mod tests {
let commits = get_commit_ids(&repo, 1);
assert!(commits.contains(&commit_1));
push(
push_branch(
&tmp_repo_dir.path().to_str().unwrap().into(),
"origin",
"master",
@ -424,7 +468,7 @@ mod tests {
.unwrap();
// Push the local branch
push(
push_branch(
&tmp_repo_dir.path().to_str().unwrap().into(),
"origin",
"test_branch",
@ -450,7 +494,7 @@ mod tests {
// Delete the remote branch
assert_eq!(
push(
push_branch(
&tmp_repo_dir.path().to_str().unwrap().into(),
"origin",
"test_branch",

View File

@ -153,10 +153,16 @@ pub fn push_tags(
#[cfg(test)]
mod tests {
use super::*;
use crate::sync::{
self,
remotes::{fetch, fetch_all, push::push},
tests::{repo_clone, repo_init_bare},
use crate::{
sync::{
self, delete_tag,
remotes::{
fetch, fetch_all,
push::{push_branch, push_raw},
},
tests::{repo_clone, repo_init_bare},
},
PushType,
};
use pretty_assertions::assert_eq;
use sync::tests::write_commit_file;
@ -183,7 +189,7 @@ mod tests {
sync::tag_commit(clone1_dir, &commit1, "tag1", None).unwrap();
push(
push_branch(
clone1_dir, "origin", "master", false, false, None, None,
)
.unwrap();
@ -231,7 +237,7 @@ mod tests {
sync::tag_commit(clone1_dir, &commit1, "tag1", None).unwrap();
push(
push_branch(
clone1_dir, "origin", "master", false, false, None, None,
)
.unwrap();
@ -265,7 +271,7 @@ mod tests {
sync::tag_commit(clone1_dir, &commit1, "tag1", None).unwrap();
push(
push_branch(
clone1_dir, "origin", "master", false, false, None, None,
)
.unwrap();
@ -294,7 +300,7 @@ mod tests {
let commit1 =
write_commit_file(&clone1, "test.txt", "test", "commit1");
push(
push_branch(
clone1_dir, "origin", "master", false, false, None, None,
)
.unwrap();
@ -334,7 +340,7 @@ mod tests {
let commit1 =
write_commit_file(&clone1, "test.txt", "test", "commit1");
push(
push_branch(
clone1_dir, "origin", "master", false, false, None, None,
)
.unwrap();
@ -362,4 +368,58 @@ mod tests {
assert_eq!(tags1, tags2);
}
#[test]
fn test_tags_delete_remote() {
let (r1_dir, _repo) = repo_init_bare().unwrap();
let r1_dir = r1_dir.path().to_str().unwrap();
let (clone1_dir, clone1) = repo_clone(r1_dir).unwrap();
let clone1_dir: &RepoPath =
&clone1_dir.path().to_str().unwrap().into();
let commit1 =
write_commit_file(&clone1, "test.txt", "test", "commit1");
push_branch(
clone1_dir, "origin", "master", false, false, None, None,
)
.unwrap();
let (clone2_dir, _clone2) = repo_clone(r1_dir).unwrap();
let clone2_dir: &RepoPath =
&clone2_dir.path().to_str().unwrap().into();
// clone1 - creates tag
sync::tag_commit(clone1_dir, &commit1, "tag1", None).unwrap();
push_tags(clone1_dir, "origin", None, None).unwrap();
// clone 2 - pull
fetch_all(clone2_dir, &None, &None).unwrap();
assert_eq!(sync::get_tags(clone2_dir).unwrap().len(), 1);
// delete on clone 1
delete_tag(clone1_dir, "tag1").unwrap();
push_raw(
clone1_dir,
"origin",
"tag1",
PushType::Tag,
false,
true,
None,
None,
)
.unwrap();
push_tags(clone1_dir, "origin", None, None).unwrap();
// clone 2
fetch_all(clone2_dir, &None, &None).unwrap();
assert_eq!(sync::get_tags(clone2_dir).unwrap().len(), 0);
}
}

View File

@ -25,7 +25,7 @@ use crate::{
use anyhow::{bail, Result};
use asyncgit::{
sync::{self, RepoPathRef},
AsyncGitNotification,
AsyncGitNotification, PushType,
};
use crossbeam_channel::Sender;
use crossterm::event::{Event, KeyEvent};
@ -723,8 +723,9 @@ impl App {
self.file_to_open = path;
flags.insert(NeedsUpdate::COMMANDS);
}
InternalEvent::Push(branch, force, delete) => {
self.push_popup.push(branch, force, delete)?;
InternalEvent::Push(branch, push_type, force, delete) => {
self.push_popup
.push(branch, push_type, force, delete)?;
flags.insert(NeedsUpdate::ALL);
}
InternalEvent::Pull(branch) => {
@ -790,6 +791,7 @@ impl App {
Ok(flags)
}
#[allow(clippy::too_many_lines)]
fn process_confirmed_action(
&mut self,
action: Action,
@ -850,6 +852,7 @@ impl App {
|name| {
InternalEvent::Push(
name.to_string(),
PushType::Branch,
false,
true,
)
@ -867,13 +870,33 @@ impl App {
error.to_string(),
));
} else {
let remote = sync::get_default_remote(
&self.repo.borrow(),
)?;
self.queue.push(InternalEvent::ConfirmAction(
Action::DeleteRemoteTag(tag_name, remote),
));
flags.insert(NeedsUpdate::ALL);
self.tags_popup.update_tags()?;
}
}
Action::DeleteRemoteTag(tag_name, _remote) => {
self.queue.push(InternalEvent::Push(
tag_name,
PushType::Tag,
false,
true,
));
}
Action::ForcePush(branch, force) => {
self.queue
.push(InternalEvent::Push(branch, force, false));
self.queue.push(InternalEvent::Push(
branch,
PushType::Branch,
force,
false,
));
}
Action::PullMerge { rebase, .. } => {
self.pull_popup.try_conflict_free_merge(rebase);

View File

@ -17,8 +17,8 @@ use asyncgit::{
},
get_branch_remote, get_default_remote, RepoPathRef,
},
AsyncGitNotification, AsyncPush, PushRequest, RemoteProgress,
RemoteProgressState,
AsyncGitNotification, AsyncPush, PushRequest, PushType,
RemoteProgress, RemoteProgressState,
};
use crossbeam_channel::Sender;
use crossterm::event::Event;
@ -57,6 +57,7 @@ pub struct PushComponent {
progress: Option<RemoteProgress>,
pending: bool,
branch: String,
push_type: PushType,
queue: Queue,
theme: SharedTheme,
key_config: SharedKeyConfig,
@ -79,6 +80,7 @@ impl PushComponent {
pending: false,
visible: false,
branch: String::new(),
push_type: PushType::Branch,
git_push: AsyncPush::new(repo.borrow().clone(), sender),
progress: None,
input_cred: CredComponent::new(
@ -94,10 +96,12 @@ impl PushComponent {
pub fn push(
&mut self,
branch: String,
push_type: PushType,
force: bool,
delete: bool,
) -> Result<()> {
self.branch = branch;
self.push_type = push_type;
self.modifier = match (force, delete) {
(true, true) => PushComponentModifier::ForceDelete,
(false, true) => PushComponentModifier::Delete,
@ -149,6 +153,7 @@ impl PushComponent {
self.git_push.request(PushRequest {
remote,
branch: self.branch.clone(),
push_type: self.push_type,
force,
delete: self.modifier.delete(),
basic_credential: cred,

View File

@ -183,6 +183,10 @@ impl ConfirmComponent {
&self.key_config,
tag_name,
),
),
Action::DeleteRemoteTag(_tag_name,remote) => (
strings::confirm_title_delete_tag_remote(),
strings::confirm_msg_delete_tag_remote(remote),
),
Action::ForcePush(branch, _force) => (
strings::confirm_title_force_push(

View File

@ -1,6 +1,7 @@
use crate::{components::AppOption, tabs::StashingOptions};
use asyncgit::sync::{
diff::DiffLinePosition, CommitId, CommitTags, TreeFile,
use asyncgit::{
sync::{diff::DiffLinePosition, CommitId, CommitTags, TreeFile},
PushType,
};
use bitflags::bitflags;
use std::{
@ -39,6 +40,7 @@ pub enum Action {
DeleteLocalBranch(String),
DeleteRemoteBranch(String),
DeleteTag(String),
DeleteRemoteTag(String, String),
ForcePush(String, bool),
PullMerge { incoming: usize, rebase: bool },
AbortMerge,
@ -85,7 +87,7 @@ pub enum InternalEvent {
///
OpenExternalEditor(Option<String>),
///
Push(String, bool, bool),
Push(String, PushType, bool, bool),
///
Pull(String),
///

View File

@ -243,6 +243,12 @@ pub fn confirm_msg_delete_tag(
) -> String {
format!("Confirm deleting Tag: '{}' ?", tag_name)
}
pub fn confirm_title_delete_tag_remote() -> String {
"Delete Tag (remote)".to_string()
}
pub fn confirm_msg_delete_tag_remote(remote_name: &str) -> String {
format!("Confirm deleting tag on remote '{}'?", remote_name)
}
pub fn confirm_title_force_push(
_key_config: &SharedKeyConfig,
) -> String {

View File

@ -20,7 +20,7 @@ use asyncgit::{
},
sync::{BranchCompare, CommitId},
AsyncDiff, AsyncGitNotification, AsyncStatus, DiffParams,
DiffType, StatusParams,
DiffType, PushType, StatusParams,
};
use crossbeam_channel::Sender;
use crossterm::event::Event;
@ -558,7 +558,10 @@ impl Status {
));
} else {
self.queue.push(InternalEvent::Push(
branch, force, false,
branch,
PushType::Branch,
force,
false,
));
}
}