Allow filling co-authors in the git panel's commit input (#23329)
https://github.com/user-attachments/assets/78db908e-cfe5-4803-b0dc-4f33bc457840 * starts to extract usernames out of `users/` GitHub API responses, and pass those along with e-mails in the collab sessions as part of the `User` data * adjusts various prefill and seed test methods so that the new data can be retrieved from GitHub properly * if there's an active call, where guests have write permissions and e-mails, allow to trigger `FillCoAuthors` action in the context of the git panel, that will fill in `co-authored-by:` lines, using e-mail and names (or GitHub handle names if name is absent) * the action tries to not duplicate such entries, if any are present already, and adds those below the rest of the commit input's text Concerns: * users with write permissions and no e-mails will be silently omitted — adding odd entries that try to indicate this or raising pop-ups is very intrusive (maybe, we can add `#`-prefixed comments?), logging seems pointless * it's not clear whether the data prefill will run properly on the existing users — seems tolerable now, as it seems that we get e-mails properly already, so we'll see GitHub handles instead of names in the worst case. This can be prefilled better later. * e-mails and names for a particular project may be not what the user wants. E.g. my `.gitconfig` has ``` [user] email = mail4score@gmail.com # .....snip [includeif "gitdir:**/work/zed/**/.git"] path = ~/.gitconfig.work ``` and that one has ``` [user] email = kirill@zed.dev ``` while my GitHub profile is configured so, that `mail4score@gmail.com` is the public, commit e-mail. So, when I'm a participant in a Zed session, wrong e-mail will be picked. The problem is, it's impossible for a host to get remote's collaborator git metadata for a particular project, as that might not even exist on disk for the client. Seems that we might want to add some "project git URL <-> user name and email" mapping in the settings(?). The design of this is not very clear, so the PR concentrates on the basics for now. When https://github.com/zed-industries/zed/pull/23308 lands, most of the issues can be solved by collaborators manually, before committing. Release Notes: - N/A
This commit is contained in:
parent
ac214c52c9
commit
0199eca289
32 changed files with 215 additions and 14 deletions
|
@ -40,6 +40,15 @@ pub struct LocalParticipant {
|
||||||
pub role: proto::ChannelRole,
|
pub role: proto::ChannelRole,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LocalParticipant {
|
||||||
|
pub fn can_write(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self.role,
|
||||||
|
proto::ChannelRole::Admin | proto::ChannelRole::Member
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RemoteParticipant {
|
pub struct RemoteParticipant {
|
||||||
pub user: Arc<User>,
|
pub user: Arc<User>,
|
||||||
pub peer_id: proto::PeerId,
|
pub peer_id: proto::PeerId,
|
||||||
|
@ -57,4 +66,11 @@ impl RemoteParticipant {
|
||||||
pub fn has_video_tracks(&self) -> bool {
|
pub fn has_video_tracks(&self) -> bool {
|
||||||
!self.video_tracks.is_empty()
|
!self.video_tracks.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn can_write(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self.role,
|
||||||
|
proto::ChannelRole::Admin | proto::ChannelRole::Member
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,15 @@ pub struct LocalParticipant {
|
||||||
pub role: proto::ChannelRole,
|
pub role: proto::ChannelRole,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LocalParticipant {
|
||||||
|
pub fn can_write(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self.role,
|
||||||
|
proto::ChannelRole::Admin | proto::ChannelRole::Member
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RemoteParticipant {
|
pub struct RemoteParticipant {
|
||||||
pub user: Arc<User>,
|
pub user: Arc<User>,
|
||||||
|
@ -57,4 +66,11 @@ impl RemoteParticipant {
|
||||||
pub fn has_video_tracks(&self) -> bool {
|
pub fn has_video_tracks(&self) -> bool {
|
||||||
!self.video_tracks.is_empty()
|
!self.video_tracks.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn can_write(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self.role,
|
||||||
|
proto::ChannelRole::Admin | proto::ChannelRole::Member
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,6 +164,8 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||||
id: 5,
|
id: 5,
|
||||||
github_login: "nathansobo".into(),
|
github_login: "nathansobo".into(),
|
||||||
avatar_url: "http://avatar.com/nathansobo".into(),
|
avatar_url: "http://avatar.com/nathansobo".into(),
|
||||||
|
name: None,
|
||||||
|
email: None,
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -216,6 +218,8 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||||
id: 6,
|
id: 6,
|
||||||
github_login: "maxbrunsfeld".into(),
|
github_login: "maxbrunsfeld".into(),
|
||||||
avatar_url: "http://avatar.com/maxbrunsfeld".into(),
|
avatar_url: "http://avatar.com/maxbrunsfeld".into(),
|
||||||
|
name: None,
|
||||||
|
email: None,
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -259,6 +263,8 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
||||||
id: 7,
|
id: 7,
|
||||||
github_login: "as-cii".into(),
|
github_login: "as-cii".into(),
|
||||||
avatar_url: "http://avatar.com/as-cii".into(),
|
avatar_url: "http://avatar.com/as-cii".into(),
|
||||||
|
name: None,
|
||||||
|
email: None,
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -43,6 +43,8 @@ pub struct User {
|
||||||
pub id: UserId,
|
pub id: UserId,
|
||||||
pub github_login: String,
|
pub github_login: String,
|
||||||
pub avatar_uri: SharedUri,
|
pub avatar_uri: SharedUri,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub email: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -798,6 +800,8 @@ impl User {
|
||||||
id: message.id,
|
id: message.id,
|
||||||
github_login: message.github_login,
|
github_login: message.github_login,
|
||||||
avatar_uri: message.avatar_url.into(),
|
avatar_uri: message.avatar_url.into(),
|
||||||
|
name: message.name,
|
||||||
|
email: message.email,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ CREATE TABLE "users" (
|
||||||
"github_login" VARCHAR,
|
"github_login" VARCHAR,
|
||||||
"admin" BOOLEAN,
|
"admin" BOOLEAN,
|
||||||
"email_address" VARCHAR(255) DEFAULT NULL,
|
"email_address" VARCHAR(255) DEFAULT NULL,
|
||||||
|
"name" TEXT,
|
||||||
"invite_code" VARCHAR(64),
|
"invite_code" VARCHAR(64),
|
||||||
"invite_count" INTEGER NOT NULL DEFAULT 0,
|
"invite_count" INTEGER NOT NULL DEFAULT 0,
|
||||||
"inviter_id" INTEGER REFERENCES users (id),
|
"inviter_id" INTEGER REFERENCES users (id),
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE users ADD COLUMN name TEXT;
|
|
@ -144,6 +144,7 @@ struct AuthenticatedUserParams {
|
||||||
github_user_id: i32,
|
github_user_id: i32,
|
||||||
github_login: String,
|
github_login: String,
|
||||||
github_email: Option<String>,
|
github_email: Option<String>,
|
||||||
|
github_name: Option<String>,
|
||||||
github_user_created_at: chrono::DateTime<chrono::Utc>,
|
github_user_created_at: chrono::DateTime<chrono::Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,6 +166,7 @@ async fn get_authenticated_user(
|
||||||
¶ms.github_login,
|
¶ms.github_login,
|
||||||
params.github_user_id,
|
params.github_user_id,
|
||||||
params.github_email.as_deref(),
|
params.github_email.as_deref(),
|
||||||
|
params.github_name.as_deref(),
|
||||||
params.github_user_created_at,
|
params.github_user_created_at,
|
||||||
initial_channel_id,
|
initial_channel_id,
|
||||||
)
|
)
|
||||||
|
|
|
@ -115,6 +115,7 @@ async fn add_contributor(
|
||||||
¶ms.github_login,
|
¶ms.github_login,
|
||||||
params.github_user_id,
|
params.github_user_id,
|
||||||
params.github_email.as_deref(),
|
params.github_email.as_deref(),
|
||||||
|
params.github_name.as_deref(),
|
||||||
params.github_user_created_at,
|
params.github_user_created_at,
|
||||||
initial_channel_id,
|
initial_channel_id,
|
||||||
)
|
)
|
||||||
|
|
|
@ -248,6 +248,7 @@ mod test {
|
||||||
let user = db
|
let user = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"example@example.com",
|
"example@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "example".into(),
|
github_login: "example".into(),
|
||||||
|
|
|
@ -726,6 +726,8 @@ impl Database {
|
||||||
user.github_login
|
user.github_login
|
||||||
),
|
),
|
||||||
github_login: user.github_login,
|
github_login: user.github_login,
|
||||||
|
name: user.name,
|
||||||
|
email: user.email_address,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
proto::ChannelMember {
|
proto::ChannelMember {
|
||||||
|
|
|
@ -65,6 +65,7 @@ impl Database {
|
||||||
github_login: &str,
|
github_login: &str,
|
||||||
github_user_id: i32,
|
github_user_id: i32,
|
||||||
github_email: Option<&str>,
|
github_email: Option<&str>,
|
||||||
|
github_name: Option<&str>,
|
||||||
github_user_created_at: DateTimeUtc,
|
github_user_created_at: DateTimeUtc,
|
||||||
initial_channel_id: Option<ChannelId>,
|
initial_channel_id: Option<ChannelId>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
@ -74,6 +75,7 @@ impl Database {
|
||||||
github_login,
|
github_login,
|
||||||
github_user_id,
|
github_user_id,
|
||||||
github_email,
|
github_email,
|
||||||
|
github_name,
|
||||||
github_user_created_at.naive_utc(),
|
github_user_created_at.naive_utc(),
|
||||||
initial_channel_id,
|
initial_channel_id,
|
||||||
&tx,
|
&tx,
|
||||||
|
|
|
@ -7,6 +7,7 @@ impl Database {
|
||||||
pub async fn create_user(
|
pub async fn create_user(
|
||||||
&self,
|
&self,
|
||||||
email_address: &str,
|
email_address: &str,
|
||||||
|
name: Option<&str>,
|
||||||
admin: bool,
|
admin: bool,
|
||||||
params: NewUserParams,
|
params: NewUserParams,
|
||||||
) -> Result<NewUserResult> {
|
) -> Result<NewUserResult> {
|
||||||
|
@ -14,6 +15,7 @@ impl Database {
|
||||||
let tx = tx;
|
let tx = tx;
|
||||||
let user = user::Entity::insert(user::ActiveModel {
|
let user = user::Entity::insert(user::ActiveModel {
|
||||||
email_address: ActiveValue::set(Some(email_address.into())),
|
email_address: ActiveValue::set(Some(email_address.into())),
|
||||||
|
name: ActiveValue::set(name.map(|s| s.into())),
|
||||||
github_login: ActiveValue::set(params.github_login.clone()),
|
github_login: ActiveValue::set(params.github_login.clone()),
|
||||||
github_user_id: ActiveValue::set(params.github_user_id),
|
github_user_id: ActiveValue::set(params.github_user_id),
|
||||||
admin: ActiveValue::set(admin),
|
admin: ActiveValue::set(admin),
|
||||||
|
@ -101,6 +103,7 @@ impl Database {
|
||||||
github_login: &str,
|
github_login: &str,
|
||||||
github_user_id: i32,
|
github_user_id: i32,
|
||||||
github_email: Option<&str>,
|
github_email: Option<&str>,
|
||||||
|
github_name: Option<&str>,
|
||||||
github_user_created_at: DateTimeUtc,
|
github_user_created_at: DateTimeUtc,
|
||||||
initial_channel_id: Option<ChannelId>,
|
initial_channel_id: Option<ChannelId>,
|
||||||
) -> Result<User> {
|
) -> Result<User> {
|
||||||
|
@ -109,6 +112,7 @@ impl Database {
|
||||||
github_login,
|
github_login,
|
||||||
github_user_id,
|
github_user_id,
|
||||||
github_email,
|
github_email,
|
||||||
|
github_name,
|
||||||
github_user_created_at.naive_utc(),
|
github_user_created_at.naive_utc(),
|
||||||
initial_channel_id,
|
initial_channel_id,
|
||||||
&tx,
|
&tx,
|
||||||
|
@ -118,11 +122,13 @@ impl Database {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn get_or_create_user_by_github_account_tx(
|
pub async fn get_or_create_user_by_github_account_tx(
|
||||||
&self,
|
&self,
|
||||||
github_login: &str,
|
github_login: &str,
|
||||||
github_user_id: i32,
|
github_user_id: i32,
|
||||||
github_email: Option<&str>,
|
github_email: Option<&str>,
|
||||||
|
github_name: Option<&str>,
|
||||||
github_user_created_at: NaiveDateTime,
|
github_user_created_at: NaiveDateTime,
|
||||||
initial_channel_id: Option<ChannelId>,
|
initial_channel_id: Option<ChannelId>,
|
||||||
tx: &DatabaseTransaction,
|
tx: &DatabaseTransaction,
|
||||||
|
@ -150,6 +156,7 @@ impl Database {
|
||||||
} else {
|
} else {
|
||||||
let user = user::Entity::insert(user::ActiveModel {
|
let user = user::Entity::insert(user::ActiveModel {
|
||||||
email_address: ActiveValue::set(github_email.map(|email| email.into())),
|
email_address: ActiveValue::set(github_email.map(|email| email.into())),
|
||||||
|
name: ActiveValue::set(github_name.map(|name| name.into())),
|
||||||
github_login: ActiveValue::set(github_login.into()),
|
github_login: ActiveValue::set(github_login.into()),
|
||||||
github_user_id: ActiveValue::set(github_user_id),
|
github_user_id: ActiveValue::set(github_user_id),
|
||||||
github_user_created_at: ActiveValue::set(Some(github_user_created_at)),
|
github_user_created_at: ActiveValue::set(Some(github_user_created_at)),
|
||||||
|
|
|
@ -13,6 +13,7 @@ pub struct Model {
|
||||||
pub github_user_id: i32,
|
pub github_user_id: i32,
|
||||||
pub github_user_created_at: Option<NaiveDateTime>,
|
pub github_user_created_at: Option<NaiveDateTime>,
|
||||||
pub email_address: Option<String>,
|
pub email_address: Option<String>,
|
||||||
|
pub name: Option<String>,
|
||||||
pub admin: bool,
|
pub admin: bool,
|
||||||
pub invite_code: Option<String>,
|
pub invite_code: Option<String>,
|
||||||
pub invite_count: i32,
|
pub invite_count: i32,
|
||||||
|
|
|
@ -177,6 +177,7 @@ static GITHUB_USER_ID: AtomicI32 = AtomicI32::new(5);
|
||||||
async fn new_test_user(db: &Arc<Database>, email: &str) -> UserId {
|
async fn new_test_user(db: &Arc<Database>, email: &str) -> UserId {
|
||||||
db.create_user(
|
db.create_user(
|
||||||
email,
|
email,
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: email[0..email.find('@').unwrap()].to_string(),
|
github_login: email[0..email.find('@').unwrap()].to_string(),
|
||||||
|
|
|
@ -13,6 +13,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||||
let a_id = db
|
let a_id = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user_a@example.com",
|
"user_a@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user_a".into(),
|
github_login: "user_a".into(),
|
||||||
|
@ -25,6 +26,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||||
let b_id = db
|
let b_id = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user_b@example.com",
|
"user_b@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user_b".into(),
|
github_login: "user_b".into(),
|
||||||
|
@ -39,6 +41,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||||
let c_id = db
|
let c_id = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user_c@example.com",
|
"user_c@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user_c".into(),
|
github_login: "user_c".into(),
|
||||||
|
@ -176,6 +179,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
||||||
let user_id = db
|
let user_id = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user_a@example.com",
|
"user_a@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user_a".into(),
|
github_login: "user_a".into(),
|
||||||
|
@ -188,6 +192,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
||||||
let observer_id = db
|
let observer_id = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user_b@example.com",
|
"user_b@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user_b".into(),
|
github_login: "user_b".into(),
|
||||||
|
|
|
@ -269,6 +269,7 @@ async fn test_channel_renames(db: &Arc<Database>) {
|
||||||
let user_1 = db
|
let user_1 = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user1@example.com",
|
"user1@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user1".into(),
|
github_login: "user1".into(),
|
||||||
|
@ -282,6 +283,7 @@ async fn test_channel_renames(db: &Arc<Database>) {
|
||||||
let user_2 = db
|
let user_2 = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user2@example.com",
|
"user2@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user2".into(),
|
github_login: "user2".into(),
|
||||||
|
@ -318,6 +320,7 @@ async fn test_db_channel_moving(db: &Arc<Database>) {
|
||||||
let a_id = db
|
let a_id = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user1@example.com",
|
"user1@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user1".into(),
|
github_login: "user1".into(),
|
||||||
|
@ -372,6 +375,7 @@ async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
|
||||||
let user_id = db
|
let user_id = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user1@example.com",
|
"user1@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user1".into(),
|
github_login: "user1".into(),
|
||||||
|
|
|
@ -13,6 +13,7 @@ test_both_dbs!(
|
||||||
async fn test_contributors(db: &Arc<Database>) {
|
async fn test_contributors(db: &Arc<Database>) {
|
||||||
db.create_user(
|
db.create_user(
|
||||||
"user1@example.com",
|
"user1@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user1".to_string(),
|
github_login: "user1".to_string(),
|
||||||
|
@ -25,7 +26,7 @@ async fn test_contributors(db: &Arc<Database>) {
|
||||||
assert_eq!(db.get_contributors().await.unwrap(), Vec::<String>::new());
|
assert_eq!(db.get_contributors().await.unwrap(), Vec::<String>::new());
|
||||||
|
|
||||||
let user1_created_at = Utc::now();
|
let user1_created_at = Utc::now();
|
||||||
db.add_contributor("user1", 1, None, user1_created_at, None)
|
db.add_contributor("user1", 1, None, None, user1_created_at, None)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -34,7 +35,7 @@ async fn test_contributors(db: &Arc<Database>) {
|
||||||
);
|
);
|
||||||
|
|
||||||
let user2_created_at = Utc::now();
|
let user2_created_at = Utc::now();
|
||||||
db.add_contributor("user2", 2, None, user2_created_at, None)
|
db.add_contributor("user2", 2, None, None, user2_created_at, None)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -17,6 +17,7 @@ async fn test_get_users(db: &Arc<Database>) {
|
||||||
let user = db
|
let user = db
|
||||||
.create_user(
|
.create_user(
|
||||||
&format!("user{i}@example.com"),
|
&format!("user{i}@example.com"),
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: format!("user{i}"),
|
github_login: format!("user{i}"),
|
||||||
|
@ -79,6 +80,7 @@ test_both_dbs!(
|
||||||
async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
|
async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
|
||||||
db.create_user(
|
db.create_user(
|
||||||
"user1@example.com",
|
"user1@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "login1".into(),
|
github_login: "login1".into(),
|
||||||
|
@ -90,6 +92,7 @@ async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
|
||||||
let user_id2 = db
|
let user_id2 = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user2@example.com",
|
"user2@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "login2".into(),
|
github_login: "login2".into(),
|
||||||
|
@ -101,7 +104,7 @@ async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
|
||||||
.user_id;
|
.user_id;
|
||||||
|
|
||||||
let user = db
|
let user = db
|
||||||
.get_or_create_user_by_github_account("the-new-login2", 102, None, Utc::now(), None)
|
.get_or_create_user_by_github_account("the-new-login2", 102, None, None, Utc::now(), None)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(user.id, user_id2);
|
assert_eq!(user.id, user_id2);
|
||||||
|
@ -113,6 +116,7 @@ async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
|
||||||
"login3",
|
"login3",
|
||||||
103,
|
103,
|
||||||
Some("user3@example.com"),
|
Some("user3@example.com"),
|
||||||
|
None,
|
||||||
Utc::now(),
|
Utc::now(),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -133,6 +137,7 @@ async fn test_create_access_tokens(db: &Arc<Database>) {
|
||||||
let user_1 = db
|
let user_1 = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"u1@example.com",
|
"u1@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "u1".into(),
|
github_login: "u1".into(),
|
||||||
|
@ -145,6 +150,7 @@ async fn test_create_access_tokens(db: &Arc<Database>) {
|
||||||
let user_2 = db
|
let user_2 = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"u2@example.com",
|
"u2@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "u2".into(),
|
github_login: "u2".into(),
|
||||||
|
@ -296,6 +302,7 @@ async fn test_add_contacts(db: &Arc<Database>) {
|
||||||
user_ids.push(
|
user_ids.push(
|
||||||
db.create_user(
|
db.create_user(
|
||||||
&format!("user{i}@example.com"),
|
&format!("user{i}@example.com"),
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: format!("user{i}"),
|
github_login: format!("user{i}"),
|
||||||
|
@ -457,6 +464,7 @@ async fn test_metrics_id(db: &Arc<Database>) {
|
||||||
} = db
|
} = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"person1@example.com",
|
"person1@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "person1".into(),
|
github_login: "person1".into(),
|
||||||
|
@ -472,6 +480,7 @@ async fn test_metrics_id(db: &Arc<Database>) {
|
||||||
} = db
|
} = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"person2@example.com",
|
"person2@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "person2".into(),
|
github_login: "person2".into(),
|
||||||
|
@ -500,6 +509,7 @@ async fn test_project_count(db: &Arc<Database>) {
|
||||||
let user1 = db
|
let user1 = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"admin@example.com",
|
"admin@example.com",
|
||||||
|
None,
|
||||||
true,
|
true,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "admin".into(),
|
github_login: "admin".into(),
|
||||||
|
@ -511,6 +521,7 @@ async fn test_project_count(db: &Arc<Database>) {
|
||||||
let user2 = db
|
let user2 = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user@example.com",
|
"user@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user".into(),
|
github_login: "user".into(),
|
||||||
|
@ -588,6 +599,7 @@ async fn test_fuzzy_search_users(cx: &mut gpui::TestAppContext) {
|
||||||
{
|
{
|
||||||
db.create_user(
|
db.create_user(
|
||||||
&format!("{github_login}@example.com"),
|
&format!("{github_login}@example.com"),
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: github_login.into(),
|
github_login: github_login.into(),
|
||||||
|
|
|
@ -15,6 +15,7 @@ async fn test_get_user_flags(db: &Arc<Database>) {
|
||||||
let user_1 = db
|
let user_1 = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user1@example.com",
|
"user1@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user1".to_string(),
|
github_login: "user1".to_string(),
|
||||||
|
@ -28,6 +29,7 @@ async fn test_get_user_flags(db: &Arc<Database>) {
|
||||||
let user_2 = db
|
let user_2 = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user2@example.com",
|
"user2@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user2".to_string(),
|
github_login: "user2".to_string(),
|
||||||
|
|
|
@ -16,6 +16,7 @@ async fn test_accepted_tos(db: &Arc<Database>) {
|
||||||
let user_id = db
|
let user_id = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user1@example.com",
|
"user1@example.com",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user1".to_string(),
|
github_login: "user1".to_string(),
|
||||||
|
|
|
@ -189,6 +189,7 @@ mod tests {
|
||||||
let user_1 = db
|
let user_1 = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user-1@zed.dev",
|
"user-1@zed.dev",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user-1".into(),
|
github_login: "user-1".into(),
|
||||||
|
@ -201,6 +202,7 @@ mod tests {
|
||||||
let user_2 = db
|
let user_2 = db
|
||||||
.create_user(
|
.create_user(
|
||||||
"user-2@zed.dev",
|
"user-2@zed.dev",
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: "user-2".into(),
|
github_login: "user-2".into(),
|
||||||
|
|
|
@ -2418,6 +2418,8 @@ async fn get_users(
|
||||||
id: user.id.to_proto(),
|
id: user.id.to_proto(),
|
||||||
avatar_url: format!("https://github.com/{}.png?size=128", user.github_login),
|
avatar_url: format!("https://github.com/{}.png?size=128", user.github_login),
|
||||||
github_login: user.github_login,
|
github_login: user.github_login,
|
||||||
|
email: user.email_address,
|
||||||
|
name: user.name,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
response.send(proto::UsersResponse { users })?;
|
response.send(proto::UsersResponse { users })?;
|
||||||
|
@ -2449,6 +2451,8 @@ async fn fuzzy_search_users(
|
||||||
id: user.id.to_proto(),
|
id: user.id.to_proto(),
|
||||||
avatar_url: format!("https://github.com/{}.png?size=128", user.github_login),
|
avatar_url: format!("https://github.com/{}.png?size=128", user.github_login),
|
||||||
github_login: user.github_login,
|
github_login: user.github_login,
|
||||||
|
name: user.name,
|
||||||
|
email: user.email_address,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
response.send(proto::UsersResponse { users })?;
|
response.send(proto::UsersResponse { users })?;
|
||||||
|
|
|
@ -16,6 +16,7 @@ struct GithubUser {
|
||||||
id: i32,
|
id: i32,
|
||||||
login: String,
|
login: String,
|
||||||
email: Option<String>,
|
email: Option<String>,
|
||||||
|
name: Option<String>,
|
||||||
created_at: DateTime<Utc>,
|
created_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +76,7 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result
|
||||||
let user = db
|
let user = db
|
||||||
.create_user(
|
.create_user(
|
||||||
&user.email.unwrap_or(format!("{admin_login}@example.com")),
|
&user.email.unwrap_or(format!("{admin_login}@example.com")),
|
||||||
|
user.name.as_deref(),
|
||||||
true,
|
true,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: user.login,
|
github_login: user.login,
|
||||||
|
@ -129,6 +131,7 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result
|
||||||
&github_user.login,
|
&github_user.login,
|
||||||
github_user.id,
|
github_user.id,
|
||||||
github_user.email.as_deref(),
|
github_user.email.as_deref(),
|
||||||
|
github_user.name.as_deref(),
|
||||||
github_user.created_at,
|
github_user.created_at,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -152,14 +155,20 @@ fn load_admins(path: impl AsRef<Path>) -> anyhow::Result<SeedConfig> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_github<T: DeserializeOwned>(client: &reqwest::Client, url: &str) -> T {
|
async fn fetch_github<T: DeserializeOwned>(client: &reqwest::Client, url: &str) -> T {
|
||||||
let response = client
|
let mut request_builder = client.get(url);
|
||||||
.get(url)
|
if let Ok(github_token) = std::env::var("GITHUB_TOKEN") {
|
||||||
|
request_builder =
|
||||||
|
request_builder.header("Authorization", format!("Bearer {}", github_token));
|
||||||
|
}
|
||||||
|
let response = request_builder
|
||||||
.header("user-agent", "zed")
|
.header("user-agent", "zed")
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|error| panic!("failed to fetch '{url}': {error}"));
|
.unwrap_or_else(|error| panic!("failed to fetch '{url}': {error}"));
|
||||||
response
|
let response_text = response.text().await.unwrap_or_else(|error| {
|
||||||
.json()
|
panic!("failed to fetch '{url}': {error}");
|
||||||
.await
|
});
|
||||||
.unwrap_or_else(|error| panic!("failed to deserialize github user from '{url}': {error}"))
|
serde_json::from_str(&response_text).unwrap_or_else(|error| {
|
||||||
|
panic!("failed to deserialize github user from '{url}'. Error: '{error}', text: '{response_text}'");
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,7 +174,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||||
server
|
server
|
||||||
.app_state
|
.app_state
|
||||||
.db
|
.db
|
||||||
.get_or_create_user_by_github_account("user_b", 100, None, Utc::now(), None)
|
.get_or_create_user_by_github_account("user_b", 100, None, None, Utc::now(), None)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -278,7 +278,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes
|
||||||
server
|
server
|
||||||
.app_state
|
.app_state
|
||||||
.db
|
.db
|
||||||
.add_contributor("user_b", 100, None, Utc::now(), None)
|
.add_contributor("user_b", 100, None, None, Utc::now(), None)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1840,6 +1840,8 @@ async fn test_active_call_events(
|
||||||
id: client_a.user_id().unwrap(),
|
id: client_a.user_id().unwrap(),
|
||||||
github_login: "user_a".to_string(),
|
github_login: "user_a".to_string(),
|
||||||
avatar_uri: "avatar_a".into(),
|
avatar_uri: "avatar_a".into(),
|
||||||
|
name: None,
|
||||||
|
email: None,
|
||||||
}),
|
}),
|
||||||
project_id: project_a_id,
|
project_id: project_a_id,
|
||||||
worktree_root_names: vec!["a".to_string()],
|
worktree_root_names: vec!["a".to_string()],
|
||||||
|
@ -1858,6 +1860,8 @@ async fn test_active_call_events(
|
||||||
id: client_b.user_id().unwrap(),
|
id: client_b.user_id().unwrap(),
|
||||||
github_login: "user_b".to_string(),
|
github_login: "user_b".to_string(),
|
||||||
avatar_uri: "avatar_b".into(),
|
avatar_uri: "avatar_b".into(),
|
||||||
|
name: None,
|
||||||
|
email: None,
|
||||||
}),
|
}),
|
||||||
project_id: project_b_id,
|
project_id: project_b_id,
|
||||||
worktree_root_names: vec!["b".to_string()]
|
worktree_root_names: vec!["b".to_string()]
|
||||||
|
|
|
@ -220,6 +220,7 @@ impl<T: RandomizedTest> TestPlan<T> {
|
||||||
.db
|
.db
|
||||||
.create_user(
|
.create_user(
|
||||||
&format!("{username}@example.com"),
|
&format!("{username}@example.com"),
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: username.clone(),
|
github_login: username.clone(),
|
||||||
|
|
|
@ -186,6 +186,7 @@ impl TestServer {
|
||||||
.db
|
.db
|
||||||
.create_user(
|
.create_user(
|
||||||
&format!("{name}@example.com"),
|
&format!("{name}@example.com"),
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
NewUserParams {
|
NewUserParams {
|
||||||
github_login: name.into(),
|
github_login: name.into(),
|
||||||
|
|
|
@ -86,6 +86,7 @@ impl UserBackfiller {
|
||||||
&user.github_login,
|
&user.github_login,
|
||||||
github_user.id,
|
github_user.id,
|
||||||
user.email_address.as_deref(),
|
user.email_address.as_deref(),
|
||||||
|
user.name.as_deref(),
|
||||||
github_user.created_at,
|
github_user.created_at,
|
||||||
initial_channel_id,
|
initial_channel_id,
|
||||||
)
|
)
|
||||||
|
@ -159,4 +160,5 @@ impl UserBackfiller {
|
||||||
struct GithubUser {
|
struct GithubUser {
|
||||||
id: i32,
|
id: i32,
|
||||||
created_at: DateTime<Utc>,
|
created_at: DateTime<Utc>,
|
||||||
|
name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1194,6 +1194,8 @@ mod tests {
|
||||||
github_login: "fgh".into(),
|
github_login: "fgh".into(),
|
||||||
avatar_uri: "avatar_fgh".into(),
|
avatar_uri: "avatar_fgh".into(),
|
||||||
id: 103,
|
id: 103,
|
||||||
|
name: None,
|
||||||
|
email: None,
|
||||||
}),
|
}),
|
||||||
nonce: 5,
|
nonce: 5,
|
||||||
mentions: vec![(ranges[0].clone(), 101), (ranges[1].clone(), 102)],
|
mentions: vec![(ranges[0].clone(), 101), (ranges[1].clone(), 102)],
|
||||||
|
@ -1248,6 +1250,8 @@ mod tests {
|
||||||
github_login: "fgh".into(),
|
github_login: "fgh".into(),
|
||||||
avatar_uri: "avatar_fgh".into(),
|
avatar_uri: "avatar_fgh".into(),
|
||||||
id: 103,
|
id: 103,
|
||||||
|
name: None,
|
||||||
|
email: None,
|
||||||
}),
|
}),
|
||||||
nonce: 5,
|
nonce: 5,
|
||||||
mentions: Vec::new(),
|
mentions: Vec::new(),
|
||||||
|
@ -1295,6 +1299,8 @@ mod tests {
|
||||||
github_login: "fgh".into(),
|
github_login: "fgh".into(),
|
||||||
avatar_uri: "avatar_fgh".into(),
|
avatar_uri: "avatar_fgh".into(),
|
||||||
id: 103,
|
id: 103,
|
||||||
|
name: None,
|
||||||
|
email: None,
|
||||||
}),
|
}),
|
||||||
nonce: 5,
|
nonce: 5,
|
||||||
mentions: Vec::new(),
|
mentions: Vec::new(),
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::git_panel_settings::StatusStyle;
|
||||||
use crate::{git_panel_settings::GitPanelSettings, git_status_icon};
|
use crate::{git_panel_settings::GitPanelSettings, git_status_icon};
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
|
use editor::actions::MoveToEnd;
|
||||||
use editor::scroll::ScrollbarAutoHide;
|
use editor::scroll::ScrollbarAutoHide;
|
||||||
use editor::{Editor, EditorSettings, ShowScrollbar};
|
use editor::{Editor, EditorSettings, ShowScrollbar};
|
||||||
use futures::channel::mpsc;
|
use futures::channel::mpsc;
|
||||||
|
@ -39,7 +40,8 @@ actions!(
|
||||||
OpenMenu,
|
OpenMenu,
|
||||||
OpenSelected,
|
OpenSelected,
|
||||||
FocusEditor,
|
FocusEditor,
|
||||||
FocusChanges
|
FocusChanges,
|
||||||
|
FillCoAuthors,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -85,6 +87,7 @@ pub struct GitPanel {
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
hide_scrollbar_task: Option<Task<()>>,
|
hide_scrollbar_task: Option<Task<()>>,
|
||||||
pending_serialization: Task<Option<()>>,
|
pending_serialization: Task<Option<()>>,
|
||||||
|
workspace: WeakView<Workspace>,
|
||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
scroll_handle: UniformListScrollHandle,
|
scroll_handle: UniformListScrollHandle,
|
||||||
scrollbar_state: ScrollbarState,
|
scrollbar_state: ScrollbarState,
|
||||||
|
@ -154,8 +157,8 @@ impl GitPanel {
|
||||||
let current_commit_message = git_state
|
let current_commit_message = git_state
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|git_state| git_state.read(cx).commit_message.clone());
|
.map(|git_state| git_state.read(cx).commit_message.clone());
|
||||||
|
|
||||||
let (err_sender, mut err_receiver) = mpsc::channel(1);
|
let (err_sender, mut err_receiver) = mpsc::channel(1);
|
||||||
|
let workspace = cx.view().downgrade();
|
||||||
|
|
||||||
let git_panel = cx.new_view(|cx: &mut ViewContext<Self>| {
|
let git_panel = cx.new_view(|cx: &mut ViewContext<Self>| {
|
||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
|
@ -345,6 +348,7 @@ impl GitPanel {
|
||||||
project,
|
project,
|
||||||
reveal_in_editor: Task::ready(()),
|
reveal_in_editor: Task::ready(()),
|
||||||
err_sender,
|
err_sender,
|
||||||
|
workspace,
|
||||||
};
|
};
|
||||||
git_panel.schedule_update();
|
git_panel.schedule_update();
|
||||||
git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
|
git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
|
||||||
|
@ -732,6 +736,70 @@ impl GitPanel {
|
||||||
.update(cx, |editor, cx| editor.set_text("", cx));
|
.update(cx, |editor, cx| editor.set_text("", cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fill_co_authors(&mut self, _: &FillCoAuthors, cx: &mut ViewContext<Self>) {
|
||||||
|
const CO_AUTHOR_PREFIX: &str = "co-authored-by: ";
|
||||||
|
|
||||||
|
let Some(room) = self
|
||||||
|
.workspace
|
||||||
|
.upgrade()
|
||||||
|
.and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned())
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut existing_text = self.commit_editor.read(cx).text(cx);
|
||||||
|
existing_text.make_ascii_lowercase();
|
||||||
|
let mut ends_with_co_authors = false;
|
||||||
|
let existing_co_authors = existing_text
|
||||||
|
.lines()
|
||||||
|
.filter_map(|line| {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.starts_with(CO_AUTHOR_PREFIX) {
|
||||||
|
ends_with_co_authors = true;
|
||||||
|
Some(line)
|
||||||
|
} else {
|
||||||
|
ends_with_co_authors = false;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
|
let new_co_authors = room
|
||||||
|
.read(cx)
|
||||||
|
.remote_participants()
|
||||||
|
.values()
|
||||||
|
.filter(|participant| participant.can_write())
|
||||||
|
.map(|participant| participant.user.clone())
|
||||||
|
.filter_map(|user| {
|
||||||
|
let email = user.email.as_deref()?;
|
||||||
|
let name = user.name.as_deref().unwrap_or(&user.github_login);
|
||||||
|
Some(format!("{CO_AUTHOR_PREFIX}{name} <{email}>"))
|
||||||
|
})
|
||||||
|
.filter(|co_author| {
|
||||||
|
!existing_co_authors.contains(co_author.to_ascii_lowercase().as_str())
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if new_co_authors.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.commit_editor.update(cx, |editor, cx| {
|
||||||
|
let editor_end = editor.buffer().read(cx).read(cx).len();
|
||||||
|
let mut edit = String::new();
|
||||||
|
if !ends_with_co_authors {
|
||||||
|
edit.push('\n');
|
||||||
|
}
|
||||||
|
for co_author in new_co_authors {
|
||||||
|
edit.push('\n');
|
||||||
|
edit.push_str(&co_author);
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.edit(Some((editor_end..editor_end, edit)), cx);
|
||||||
|
editor.move_to_end(&MoveToEnd, cx);
|
||||||
|
editor.focus(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn no_entries(&self, cx: &mut ViewContext<Self>) -> bool {
|
fn no_entries(&self, cx: &mut ViewContext<Self>) -> bool {
|
||||||
self.git_state(cx)
|
self.git_state(cx)
|
||||||
.map_or(true, |git_state| git_state.read(cx).entry_count() == 0)
|
.map_or(true, |git_state| git_state.read(cx).entry_count() == 0)
|
||||||
|
@ -1332,6 +1400,19 @@ impl GitPanel {
|
||||||
impl Render for GitPanel {
|
impl Render for GitPanel {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let project = self.project.read(cx);
|
let project = self.project.read(cx);
|
||||||
|
let has_co_authors = self
|
||||||
|
.workspace
|
||||||
|
.upgrade()
|
||||||
|
.and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned())
|
||||||
|
.map(|room| {
|
||||||
|
let room = room.read(cx);
|
||||||
|
room.local_participant().can_write()
|
||||||
|
&& room
|
||||||
|
.remote_participants()
|
||||||
|
.values()
|
||||||
|
.any(|remote_participant| remote_participant.can_write())
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.id("git_panel")
|
.id("git_panel")
|
||||||
|
@ -1363,6 +1444,9 @@ impl Render for GitPanel {
|
||||||
.on_action(cx.listener(Self::focus_changes_list))
|
.on_action(cx.listener(Self::focus_changes_list))
|
||||||
.on_action(cx.listener(Self::focus_editor))
|
.on_action(cx.listener(Self::focus_editor))
|
||||||
.on_action(cx.listener(Self::toggle_staged_for_selected))
|
.on_action(cx.listener(Self::toggle_staged_for_selected))
|
||||||
|
.when(has_co_authors, |git_panel| {
|
||||||
|
git_panel.on_action(cx.listener(Self::fill_co_authors))
|
||||||
|
})
|
||||||
// .on_action(cx.listener(|this, &OpenSelected, cx| this.open_selected(&OpenSelected, cx)))
|
// .on_action(cx.listener(|this, &OpenSelected, cx| this.open_selected(&OpenSelected, cx)))
|
||||||
.on_hover(cx.listener(|this, hovered, cx| {
|
.on_hover(cx.listener(|this, hovered, cx| {
|
||||||
if *hovered {
|
if *hovered {
|
||||||
|
|
|
@ -1750,6 +1750,8 @@ message User {
|
||||||
uint64 id = 1;
|
uint64 id = 1;
|
||||||
string github_login = 2;
|
string github_login = 2;
|
||||||
string avatar_url = 3;
|
string avatar_url = 3;
|
||||||
|
optional string email = 4;
|
||||||
|
optional string name = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message File {
|
message File {
|
||||||
|
|
|
@ -4085,7 +4085,7 @@ impl Workspace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active_call(&self) -> Option<&Model<ActiveCall>> {
|
pub fn active_call(&self) -> Option<&Model<ActiveCall>> {
|
||||||
self.active_call.as_ref().map(|(call, _)| call)
|
self.active_call.as_ref().map(|(call, _)| call)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue