Fix permalink-to-line when Git repo root and worktree dir don't coincide (#22003)
Closes #21505. This should work if the git dir is an ancestor of the worktree dir or vice versa. Release Notes: - Fixed GitHub permalink-to-line actions when worktree dir and Git dir aren't the same
This commit is contained in:
parent
1e2fa3b022
commit
2094d50514
4 changed files with 91 additions and 8 deletions
|
@ -68,6 +68,7 @@ snippet.workspace = true
|
|||
snippet_provider.workspace = true
|
||||
terminal.workspace = true
|
||||
text.workspace = true
|
||||
toml.workspace = true
|
||||
util.workspace = true
|
||||
url.workspace = true
|
||||
which.workspace = true
|
||||
|
|
|
@ -5,12 +5,12 @@ use crate::{
|
|||
ProjectItem as _, ProjectPath,
|
||||
};
|
||||
use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use client::Client;
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use fs::Fs;
|
||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt as _, StreamExt};
|
||||
use git::{blame::Blame, diff::BufferDiff};
|
||||
use git::{blame::Blame, diff::BufferDiff, repository::RepoPath};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, Subscription,
|
||||
Task, WeakModel,
|
||||
|
@ -24,8 +24,16 @@ use language::{
|
|||
Buffer, BufferEvent, Capability, DiskState, File as _, Language, Operation,
|
||||
};
|
||||
use rpc::{proto, AnyProtoClient, ErrorExt as _, TypedEnvelope};
|
||||
use serde::Deserialize;
|
||||
use smol::channel::Receiver;
|
||||
use std::{io, ops::Range, path::Path, str::FromStr as _, sync::Arc, time::Instant};
|
||||
use std::{
|
||||
io,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr as _,
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
use text::{BufferId, LineEnding, Rope};
|
||||
use util::{debug_panic, maybe, ResultExt as _, TryFutureExt};
|
||||
use worktree::{File, PathChange, ProjectEntryId, UpdatedGitRepositoriesSet, Worktree, WorktreeId};
|
||||
|
@ -1213,11 +1221,36 @@ impl BufferStore {
|
|||
|
||||
match file.worktree.read(cx) {
|
||||
Worktree::Local(worktree) => {
|
||||
let Some(repo) = worktree.local_git_repo(file.path()) else {
|
||||
return Task::ready(Err(anyhow!("no repository for buffer found")));
|
||||
let worktree_path = worktree.abs_path().clone();
|
||||
let Some((repo_entry, repo)) =
|
||||
worktree.repository_for_path(file.path()).and_then(|entry| {
|
||||
let repo = worktree.get_local_repo(&entry)?.repo().clone();
|
||||
Some((entry, repo))
|
||||
})
|
||||
else {
|
||||
// If we're not in a Git repo, check whether this is a Rust source
|
||||
// file in the Cargo registry (presumably opened with go-to-definition
|
||||
// from a normal Rust file). If so, we can put together a permalink
|
||||
// using crate metadata.
|
||||
if !buffer
|
||||
.language()
|
||||
.is_some_and(|lang| lang.name() == "Rust".into())
|
||||
{
|
||||
return Task::ready(Err(anyhow!("no permalink available")));
|
||||
}
|
||||
let file_path = worktree_path.join(file.path());
|
||||
return cx.spawn(|cx| async move {
|
||||
let provider_registry =
|
||||
cx.update(GitHostingProviderRegistry::default_global)?;
|
||||
get_permalink_in_rust_registry_src(provider_registry, file_path, selection)
|
||||
.map_err(|_| anyhow!("no permalink available"))
|
||||
});
|
||||
};
|
||||
|
||||
let path = file.path().clone();
|
||||
let path = match repo_entry.relativize(worktree, file.path()) {
|
||||
Ok(RepoPath(path)) => path,
|
||||
Err(e) => return Task::ready(Err(e)),
|
||||
};
|
||||
|
||||
cx.spawn(|cx| async move {
|
||||
const REMOTE_NAME: &str = "origin";
|
||||
|
@ -1238,7 +1271,7 @@ impl BufferStore {
|
|||
|
||||
let path = path
|
||||
.to_str()
|
||||
.context("failed to convert buffer path to string")?;
|
||||
.ok_or_else(|| anyhow!("failed to convert path to string"))?;
|
||||
|
||||
Ok(provider.build_permalink(
|
||||
remote,
|
||||
|
@ -2432,3 +2465,52 @@ fn deserialize_blame_buffer_response(
|
|||
remote_url: response.remote_url,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_permalink_in_rust_registry_src(
|
||||
provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
path: PathBuf,
|
||||
selection: Range<u32>,
|
||||
) -> Result<url::Url> {
|
||||
#[derive(Deserialize)]
|
||||
struct CargoVcsGit {
|
||||
sha1: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CargoVcsInfo {
|
||||
git: CargoVcsGit,
|
||||
path_in_vcs: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CargoPackage {
|
||||
repository: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CargoToml {
|
||||
package: CargoPackage,
|
||||
}
|
||||
|
||||
let Some((dir, cargo_vcs_info_json)) = path.ancestors().skip(1).find_map(|dir| {
|
||||
let json = std::fs::read_to_string(dir.join(".cargo_vcs_info.json")).ok()?;
|
||||
Some((dir, json))
|
||||
}) else {
|
||||
bail!("No .cargo_vcs_info.json found in parent directories")
|
||||
};
|
||||
let cargo_vcs_info = serde_json::from_str::<CargoVcsInfo>(&cargo_vcs_info_json)?;
|
||||
let cargo_toml = std::fs::read_to_string(dir.join("Cargo.toml"))?;
|
||||
let manifest = toml::from_str::<CargoToml>(&cargo_toml)?;
|
||||
let (provider, remote) = parse_git_remote_url(provider_registry, &manifest.package.repository)
|
||||
.ok_or_else(|| anyhow!("Failed to parse package.repository field of manifest"))?;
|
||||
let path = PathBuf::from(cargo_vcs_info.path_in_vcs).join(path.strip_prefix(dir).unwrap());
|
||||
let permalink = provider.build_permalink(
|
||||
remote,
|
||||
BuildPermalinkParams {
|
||||
sha: &cargo_vcs_info.git.sha1,
|
||||
path: &path.to_string_lossy(),
|
||||
selection: Some(selection),
|
||||
},
|
||||
);
|
||||
Ok(permalink)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue