ZIm/crates/collab/src/db/queries/contributors.rs
Kirill Bulatov 0199eca289
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
2025-01-18 22:57:17 +02:00

100 lines
3.1 KiB
Rust

use super::*;
#[derive(Debug)]
pub enum ContributorSelector {
GitHubUserId { github_user_id: i32 },
GitHubLogin { github_login: String },
}
impl Database {
/// Retrieves the GitHub logins of all users who have signed the CLA.
pub async fn get_contributors(&self) -> Result<Vec<String>> {
self.transaction(|tx| async move {
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryGithubLogin {
GithubLogin,
}
Ok(contributor::Entity::find()
.inner_join(user::Entity)
.order_by_asc(contributor::Column::SignedAt)
.select_only()
.column(user::Column::GithubLogin)
.into_values::<_, QueryGithubLogin>()
.all(&*tx)
.await?)
})
.await
}
/// Records that a given user has signed the CLA.
pub async fn get_contributor_sign_timestamp(
&self,
selector: &ContributorSelector,
) -> Result<Option<DateTime>> {
self.transaction(|tx| async move {
let condition = match selector {
ContributorSelector::GitHubUserId { github_user_id } => {
user::Column::GithubUserId.eq(*github_user_id)
}
ContributorSelector::GitHubLogin { github_login } => {
user::Column::GithubLogin.eq(github_login)
}
};
if let Some(user) = user::Entity::find().filter(condition).one(&*tx).await? {
if user.admin {
return Ok(Some(user.created_at));
}
if let Some(contributor) =
contributor::Entity::find_by_id(user.id).one(&*tx).await?
{
return Ok(Some(contributor.signed_at));
}
}
Ok(None)
})
.await
}
/// Records that a given user has signed the CLA.
pub async fn add_contributor(
&self,
github_login: &str,
github_user_id: i32,
github_email: Option<&str>,
github_name: Option<&str>,
github_user_created_at: DateTimeUtc,
initial_channel_id: Option<ChannelId>,
) -> Result<()> {
self.transaction(|tx| async move {
let user = self
.get_or_create_user_by_github_account_tx(
github_login,
github_user_id,
github_email,
github_name,
github_user_created_at.naive_utc(),
initial_channel_id,
&tx,
)
.await?;
contributor::Entity::insert(contributor::ActiveModel {
user_id: ActiveValue::Set(user.id),
signed_at: ActiveValue::NotSet,
})
.on_conflict(
OnConflict::column(contributor::Column::UserId)
.do_nothing()
.to_owned(),
)
.exec_without_returning(&*tx)
.await?;
Ok(())
})
.await
}
}