1 use std
::collections
::{BTreeMap, HashSet}
;
3 use std
::io
::{self, BufRead}
;
5 use std
::path
::PathBuf
;
7 use std
::time
::Duration
;
10 use anyhow
::{bail, format_err, Context as _}
;
11 use cargo_util
::paths
;
12 use crates_io
::{self, NewCrate, NewCrateDependency, Registry}
;
13 use curl
::easy
::{Easy, InfoType, SslOpt, SslVersion}
;
14 use log
::{log, Level}
;
15 use percent_encoding
::{percent_encode, NON_ALPHANUMERIC}
;
17 use crate::core
::dependency
::DepKind
;
18 use crate::core
::manifest
::ManifestMetadata
;
19 use crate::core
::resolver
::CliFeatures
;
20 use crate::core
::source
::Source
;
21 use crate::core
::{Package, SourceId, Workspace}
;
23 use crate::sources
::{RegistrySource, SourceConfigMap, CRATES_IO_DOMAIN, CRATES_IO_REGISTRY}
;
24 use crate::util
::config
::{self, Config, SslVersionConfig, SslVersionConfigRange}
;
25 use crate::util
::errors
::CargoResult
;
26 use crate::util
::important_paths
::find_root_manifest_for_wd
;
27 use crate::util
::validate_package_name
;
28 use crate::util
::IntoUrl
;
29 use crate::{drop_print, drop_println, version}
;
33 /// Registry settings loaded from config files.
35 /// This is loaded based on the `--registry` flag and the config settings.
37 pub struct RegistryConfig
{
38 /// The index URL. If `None`, use crates.io.
39 pub index
: Option
<String
>,
40 /// The authentication token.
41 pub token
: Option
<String
>,
42 /// Process used for fetching a token.
43 pub credential_process
: Option
<(PathBuf
, Vec
<String
>)>,
46 pub struct PublishOpts
<'cfg
> {
47 pub config
: &'cfg Config
,
48 pub token
: Option
<String
>,
49 pub index
: Option
<String
>,
51 pub allow_dirty
: bool
,
52 pub jobs
: Option
<u32>,
53 pub to_publish
: ops
::Packages
,
54 pub targets
: Vec
<String
>,
56 pub registry
: Option
<String
>,
57 pub cli_features
: CliFeatures
,
60 pub fn publish(ws
: &Workspace
<'_
>, opts
: &PublishOpts
<'_
>) -> CargoResult
<()> {
61 let specs
= opts
.to_publish
.to_package_id_specs(ws
)?
;
62 let mut pkgs
= ws
.members_with_features(&specs
, &opts
.cli_features
)?
;
64 let (pkg
, cli_features
) = pkgs
.pop().unwrap();
66 let mut publish_registry
= opts
.registry
.clone();
67 if let Some(ref allowed_registries
) = *pkg
.publish() {
68 if publish_registry
.is_none() && allowed_registries
.len() == 1 {
69 // If there is only one allowed registry, push to that one directly,
70 // even though there is no registry specified in the command.
71 let default_registry
= &allowed_registries
[0];
72 if default_registry
!= CRATES_IO_REGISTRY
{
73 // Don't change the registry for crates.io and don't warn the user.
74 // crates.io will be defaulted even without this.
75 opts
.config
.shell().note(&format
!(
76 "Found `{}` as only allowed registry. Publishing to it automatically.",
79 publish_registry
= Some(default_registry
.clone());
83 let reg_name
= publish_registry
85 .unwrap_or_else(|| CRATES_IO_REGISTRY
.to_string());
86 if !allowed_registries
.contains(®_name
) {
88 "`{}` cannot be published.\n\
89 The registry `{}` is not listed in the `publish` value in Cargo.toml.",
96 let (mut registry
, _reg_cfg
, reg_id
) = registry(
104 verify_dependencies(pkg
, ®istry
, reg_id
)?
;
106 // Prepare a tarball, with a non-suppressible warning if metadata
107 // is missing since this is being put online.
108 let tarball
= ops
::package_one(
115 check_metadata
: true,
116 allow_dirty
: opts
.allow_dirty
,
117 to_package
: ops
::Packages
::Default
,
118 targets
: opts
.targets
.clone(),
120 cli_features
: cli_features
,
127 .status("Uploading", pkg
.package_id().to_string())?
;
140 fn verify_dependencies(
143 registry_src
: SourceId
,
144 ) -> CargoResult
<()> {
145 for dep
in pkg
.dependencies().iter() {
146 if super::check_dep_has_version(dep
, true)?
{
149 // TomlManifest::prepare_for_publish will rewrite the dependency
150 // to be just the `version` field.
151 if dep
.source_id() != registry_src
{
152 if !dep
.source_id().is_registry() {
153 // Consider making SourceId::kind a public type that we can
154 // exhaustively match on. Using match can help ensure that
155 // every kind is properly handled.
156 panic
!("unexpected source kind for dependency {:?}", dep
);
158 // Block requests to send to crates.io with alt-registry deps.
159 // This extra hostname check is mostly to assist with testing,
160 // but also prevents someone using `--index` to specify
161 // something that points to crates.io.
162 if registry_src
.is_default_registry() || registry
.host_is_crates_io() {
163 bail
!("crates cannot be published to crates.io with dependencies sourced from other\n\
164 registries. `{}` needs to be published to crates.io before publishing this crate.\n\
165 (crate `{}` is pulled from {})",
179 registry
: &mut Registry
,
180 registry_id
: SourceId
,
182 ) -> CargoResult
<()> {
187 // Skip dev-dependency without version.
188 dep
.is_transitive() || dep
.specified_req()
191 // If the dependency is from a different registry, then include the
192 // registry in the dependency.
193 let dep_registry_id
= match dep
.registry_id() {
195 None
=> SourceId
::crates_io(config
)?
,
197 // In the index and Web API, None means "from the same registry"
198 // whereas in Cargo.toml, it means "from crates.io".
199 let dep_registry
= if dep_registry_id
!= registry_id
{
200 Some(dep_registry_id
.url().to_string())
205 Ok(NewCrateDependency
{
206 optional
: dep
.is_optional(),
207 default_features
: dep
.uses_default_features(),
208 name
: dep
.package_name().to_string(),
209 features
: dep
.features().iter().map(|s
| s
.to_string()).collect(),
210 version_req
: dep
.version_req().to_string(),
211 target
: dep
.platform().map(|s
| s
.to_string()),
212 kind
: match dep
.kind() {
213 DepKind
::Normal
=> "normal",
214 DepKind
::Build
=> "build",
215 DepKind
::Development
=> "dev",
218 registry
: dep_registry
,
219 explicit_name_in_toml
: dep
.explicit_name_in_toml().map(|s
| s
.to_string()),
222 .collect
::<CargoResult
<Vec
<NewCrateDependency
>>>()?
;
223 let manifest
= pkg
.manifest();
224 let ManifestMetadata
{
237 } = *manifest
.metadata();
238 let readme_content
= readme
241 paths
::read(&pkg
.root().join(readme
))
242 .with_context(|| format
!("failed to read `readme` file for package `{}`", pkg
))
245 if let Some(ref file
) = *license_file
{
246 if !pkg
.root().join(file
).exists() {
247 bail
!("the license file `{}` does not exist", file
)
251 // Do not upload if performing a dry run
253 config
.shell().warn("aborting upload due to dry run")?
;
257 let string_features
= match manifest
.original().features() {
258 Some(features
) => features
260 .map(|(feat
, values
)| {
263 values
.iter().map(|fv
| fv
.to_string()).collect(),
266 .collect
::<BTreeMap
<String
, Vec
<String
>>>(),
267 None
=> BTreeMap
::new(),
270 let warnings
= registry
273 name
: pkg
.name().to_string(),
274 vers
: pkg
.version().to_string(),
276 features
: string_features
,
277 authors
: authors
.clone(),
278 description
: description
.clone(),
279 homepage
: homepage
.clone(),
280 documentation
: documentation
.clone(),
281 keywords
: keywords
.clone(),
282 categories
: categories
.clone(),
283 readme
: readme_content
,
284 readme_file
: readme
.clone(),
285 repository
: repository
.clone(),
286 license
: license
.clone(),
287 license_file
: license_file
.clone(),
288 badges
: badges
.clone(),
289 links
: links
.clone(),
293 .with_context(|| format
!("failed to publish to registry at {}", registry
.host()))?
;
295 if !warnings
.invalid_categories
.is_empty() {
297 "the following are not valid category slugs and were \
298 ignored: {}. Please see https://crates.io/category_slugs \
299 for the list of all category slugs. \
301 warnings
.invalid_categories
.join(", ")
303 config
.shell().warn(&msg
)?
;
306 if !warnings
.invalid_badges
.is_empty() {
308 "the following are not valid badges and were ignored: {}. \
309 Either the badge type specified is unknown or a required \
310 attribute is missing. Please see \
311 https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata \
312 for valid badge types and their required attributes.",
313 warnings
.invalid_badges
.join(", ")
315 config
.shell().warn(&msg
)?
;
318 if !warnings
.other
.is_empty() {
319 for msg
in warnings
.other
{
320 config
.shell().warn(&msg
)?
;
327 /// Returns the index and token from the config file for the given registry.
329 /// `registry` is typically the registry specified on the command-line. If
330 /// `None`, `index` is set to `None` to indicate it should use crates.io.
331 pub fn registry_configuration(
333 registry
: Option
<&str>,
334 ) -> CargoResult
<RegistryConfig
> {
335 let err_both
= |token_key
: &str, proc_key
: &str| {
337 "both `{TOKEN_KEY}` and `{PROC_KEY}` \
338 were specified in the config\n\
339 Only one of these values may be set, remove one or the other to proceed.",
340 TOKEN_KEY
= token_key
,
344 // `registry.default` is handled in command-line parsing.
345 let (index
, token
, process
) = match registry
{
347 validate_package_name(registry
, "registry name", "")?
;
348 let index
= Some(config
.get_registry_index(registry
)?
.to_string());
349 let token_key
= format
!("registries.{}.token", registry
);
350 let token
= config
.get_string(&token_key
)?
.map(|p
| p
.val
);
351 let process
= if config
.cli_unstable().credential_process
{
352 let mut proc_key
= format
!("registries.{}.credential-process", registry
);
353 let mut process
= config
.get
::<Option
<config
::PathAndArgs
>>(&proc_key
)?
;
354 if process
.is_none() && token
.is_none() {
355 // This explicitly ignores the global credential-process if
356 // the token is set, as that is "more specific".
357 proc_key
= String
::from("registry.credential-process");
358 process
= config
.get
::<Option
<config
::PathAndArgs
>>(&proc_key
)?
;
359 } else if process
.is_some() && token
.is_some() {
360 return err_both(&token_key
, &proc_key
);
366 (index
, token
, process
)
369 // Use crates.io default.
370 config
.check_registry_index_not_set()?
;
371 let token
= config
.get_string("registry.token")?
.map(|p
| p
.val
);
372 let process
= if config
.cli_unstable().credential_process
{
374 config
.get
::<Option
<config
::PathAndArgs
>>("registry.credential-process")?
;
375 if token
.is_some() && process
.is_some() {
376 return err_both("registry.token", "registry.credential-process");
382 (None
, token
, process
)
386 let credential_process
=
387 process
.map(|process
| (process
.path
.resolve_program(config
), process
.args
));
396 /// Returns the `Registry` and `Source` based on command-line and config settings.
398 /// * `token`: The token from the command-line. If not set, uses the token
400 /// * `index`: The index URL from the command-line. This is ignored if
401 /// `registry` is set.
402 /// * `registry`: The registry name from the command-line. If neither
403 /// `registry`, or `index` are set, then uses `crates-io`, honoring
404 /// `[source]` replacement if defined.
405 /// * `force_update`: If `true`, forces the index to be updated.
406 /// * `validate_token`: If `true`, the token must be set.
409 token
: Option
<String
>,
410 index
: Option
<String
>,
411 registry
: Option
<String
>,
413 validate_token
: bool
,
414 ) -> CargoResult
<(Registry
, RegistryConfig
, SourceId
)> {
415 if index
.is_some() && registry
.is_some() {
416 // Otherwise we would silently ignore one or the other.
417 bail
!("both `--index` and `--registry` should not be set at the same time");
419 // Parse all configuration options
420 let reg_cfg
= registry_configuration(config
, registry
.as_deref())?
;
421 let opt_index
= reg_cfg
.index
.as_ref().or_else(|| index
.as_ref());
422 let sid
= get_source_id(config
, opt_index
, registry
.as_ref())?
;
423 if !sid
.is_remote_registry() {
425 "{} does not support API commands.\n\
426 Check for a source-replacement in .cargo/config.",
431 let _lock
= config
.acquire_package_cache_lock()?
;
432 let mut src
= RegistrySource
::remote(sid
, &HashSet
::new(), config
);
433 // Only update the index if the config is not available or `force` is set.
434 let cfg
= src
.config();
435 let mut updated_cfg
= || {
437 .with_context(|| format
!("failed to update {}", sid
))?
;
441 let cfg
= if force_update
{
444 cfg
.or_else(|_
| updated_cfg())?
447 cfg
.and_then(|cfg
| cfg
.api
)
448 .ok_or_else(|| format_err
!("{} does not support API commands", sid
))?
450 let token
= if validate_token
{
453 bail
!("command-line argument --index requires --token to be specified");
457 // Check `is_default_registry` so that the crates.io index can
458 // change config.json's "api" value, and this won't affect most
459 // people. It will affect those using source replacement, but
460 // hopefully that's a relatively small set of users.
462 && reg_cfg
.token
.is_some()
463 && registry
.is_none()
464 && !sid
.is_default_registry()
465 && !crates_io
::is_url_crates_io(&api_host
)
468 "using `registry.token` config value with source \
469 replacement is deprecated\n\
470 This may become a hard error in the future; \
471 see <https://github.com/rust-lang/cargo/issues/xxx>.\n\
472 Use the --token command-line flag to remove this warning.",
474 reg_cfg
.token
.clone()
476 let token
= auth
::auth_token(
479 reg_cfg
.token
.as_deref(),
480 reg_cfg
.credential_process
.as_ref(),
490 let handle
= http_handle(config
)?
;
491 Ok((Registry
::new_handle(api_host
, token
, handle
), reg_cfg
, sid
))
494 /// Creates a new HTTP handle with appropriate global configuration for cargo.
495 pub fn http_handle(config
: &Config
) -> CargoResult
<Easy
> {
496 let (mut handle
, timeout
) = http_handle_and_timeout(config
)?
;
497 timeout
.configure(&mut handle
)?
;
501 pub fn http_handle_and_timeout(config
: &Config
) -> CargoResult
<(Easy
, HttpTimeout
)> {
504 "attempting to make an HTTP request, but --frozen was \
508 if !config
.network_allowed() {
509 bail
!("can't make HTTP request in the offline mode")
512 // The timeout option for libcurl by default times out the entire transfer,
513 // but we probably don't want this. Instead we only set timeouts for the
514 // connect phase as well as a "low speed" timeout so if we don't receive
515 // many bytes in a large-ish period of time then we time out.
516 let mut handle
= Easy
::new();
517 let timeout
= configure_http_handle(config
, &mut handle
)?
;
518 Ok((handle
, timeout
))
521 pub fn needs_custom_http_transport(config
: &Config
) -> CargoResult
<bool
> {
522 Ok(http_proxy_exists(config
)?
523 || *config
.http_config()?
!= Default
::default()
524 || env
::var_os("HTTP_TIMEOUT").is_some())
527 /// Configure a libcurl http handle with the defaults options for Cargo
528 pub fn configure_http_handle(config
: &Config
, handle
: &mut Easy
) -> CargoResult
<HttpTimeout
> {
529 let http
= config
.http_config()?
;
530 if let Some(proxy
) = http_proxy(config
)?
{
531 handle
.proxy(&proxy
)?
;
533 if let Some(cainfo
) = &http
.cainfo
{
534 let cainfo
= cainfo
.resolve_path(config
);
535 handle
.cainfo(&cainfo
)?
;
537 if let Some(check
) = http
.check_revoke
{
538 handle
.ssl_options(SslOpt
::new().no_revoke(!check
))?
;
541 if let Some(user_agent
) = &http
.user_agent
{
542 handle
.useragent(user_agent
)?
;
544 handle
.useragent(&format
!("cargo {}", version()))?
;
547 fn to_ssl_version(s
: &str) -> CargoResult
<SslVersion
> {
548 let version
= match s
{
549 "default" => SslVersion
::Default
,
550 "tlsv1" => SslVersion
::Tlsv1
,
551 "tlsv1.0" => SslVersion
::Tlsv10
,
552 "tlsv1.1" => SslVersion
::Tlsv11
,
553 "tlsv1.2" => SslVersion
::Tlsv12
,
554 "tlsv1.3" => SslVersion
::Tlsv13
,
556 "Invalid ssl version `{}`,\
557 choose from 'default', 'tlsv1', 'tlsv1.0', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'.",
563 if let Some(ssl_version
) = &http
.ssl_version
{
565 SslVersionConfig
::Single(s
) => {
566 let version
= to_ssl_version(s
.as_str())?
;
567 handle
.ssl_version(version
)?
;
569 SslVersionConfig
::Range(SslVersionConfigRange { min, max }
) => {
570 let min_version
= min
572 .map_or(Ok(SslVersion
::Default
), |s
| to_ssl_version(s
))?
;
573 let max_version
= max
575 .map_or(Ok(SslVersion
::Default
), |s
| to_ssl_version(s
))?
;
576 handle
.ssl_min_max_version(min_version
, max_version
)?
;
581 if let Some(true) = http
.debug
{
582 handle
.verbose(true)?
;
583 log
::debug
!("{:#?}", curl
::Version
::get());
584 handle
.debug_function(|kind
, data
| {
585 let (prefix
, level
) = match kind
{
586 InfoType
::Text
=> ("*", Level
::Debug
),
587 InfoType
::HeaderIn
=> ("<", Level
::Debug
),
588 InfoType
::HeaderOut
=> (">", Level
::Debug
),
589 InfoType
::DataIn
=> ("{", Level
::Trace
),
590 InfoType
::DataOut
=> ("}", Level
::Trace
),
591 InfoType
::SslDataIn
| InfoType
::SslDataOut
=> return,
594 match str::from_utf8(data
) {
596 for mut line
in s
.lines() {
597 if line
.starts_with("Authorization:") {
598 line
= "Authorization: [REDACTED]";
599 } else if line
[..line
.len().min(10)].eq_ignore_ascii_case("set-cookie") {
600 line
= "set-cookie: [REDACTED]";
602 log
!(level
, "http-debug: {} {}", prefix
, line
);
608 "http-debug: {} ({} bytes of data)",
617 HttpTimeout
::new(config
)
621 pub struct HttpTimeout
{
623 pub low_speed_limit
: u32,
627 pub fn new(config
: &Config
) -> CargoResult
<HttpTimeout
> {
628 let config
= config
.http_config()?
;
629 let low_speed_limit
= config
.low_speed_limit
.unwrap_or(10);
632 .or_else(|| env
::var("HTTP_TIMEOUT").ok().and_then(|s
| s
.parse().ok()))
635 dur
: Duration
::new(seconds
, 0),
640 pub fn configure(&self, handle
: &mut Easy
) -> CargoResult
<()> {
641 // The timeout option for libcurl by default times out the entire
642 // transfer, but we probably don't want this. Instead we only set
643 // timeouts for the connect phase as well as a "low speed" timeout so
644 // if we don't receive many bytes in a large-ish period of time then we
646 handle
.connect_timeout(self.dur
)?
;
647 handle
.low_speed_time(self.dur
)?
;
648 handle
.low_speed_limit(self.low_speed_limit
)?
;
653 /// Finds an explicit HTTP proxy if one is available.
655 /// Favor cargo's `http.proxy`, then git's `http.proxy`. Proxies specified
656 /// via environment variables are picked up by libcurl.
657 fn http_proxy(config
: &Config
) -> CargoResult
<Option
<String
>> {
658 let http
= config
.http_config()?
;
659 if let Some(s
) = &http
.proxy
{
660 return Ok(Some(s
.clone()));
662 if let Ok(cfg
) = git2
::Config
::open_default() {
663 if let Ok(s
) = cfg
.get_string("http.proxy") {
670 /// Determine if an http proxy exists.
672 /// Checks the following for existence, in order:
674 /// * cargo's `http.proxy`
675 /// * git's `http.proxy`
676 /// * `http_proxy` env var
677 /// * `HTTP_PROXY` env var
678 /// * `https_proxy` env var
679 /// * `HTTPS_PROXY` env var
680 fn http_proxy_exists(config
: &Config
) -> CargoResult
<bool
> {
681 if http_proxy(config
)?
.is_some() {
684 Ok(["http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY"]
686 .any(|v
| env
::var(v
).is_ok()))
690 pub fn registry_login(
692 token
: Option
<String
>,
694 ) -> CargoResult
<()> {
695 let (registry
, reg_cfg
, _
) = registry(config
, token
.clone(), None
, reg
.clone(), false, false)?
;
697 let token
= match token
{
698 Some(token
) => token
,
702 "please paste the API Token found on {}/me below",
705 let mut line
= String
::new();
706 let input
= io
::stdin();
709 .read_line(&mut line
)
710 .with_context(|| "failed to read stdin")?
;
711 // Automatically remove `cargo login` from an inputted token to
712 // allow direct pastes from `registry.host()`/me.
713 line
.replace("cargo login", "").trim().to_string()
717 if let Some(old_token
) = ®_cfg
.token
{
718 if old_token
== &token
{
719 config
.shell().status("Login", "already logged in")?
;
727 reg_cfg
.credential_process
.as_ref(),
732 config
.shell().status(
735 "token for `{}` saved",
736 reg
.as_ref().map_or(CRATES_IO_DOMAIN
, String
::as_str
)
742 pub fn registry_logout(config
: &Config
, reg
: Option
<String
>) -> CargoResult
<()> {
743 let (registry
, reg_cfg
, _
) = registry(config
, None
, None
, reg
.clone(), false, false)?
;
744 let reg_name
= reg
.as_deref().unwrap_or(CRATES_IO_DOMAIN
);
745 if reg_cfg
.credential_process
.is_none() && reg_cfg
.token
.is_none() {
746 config
.shell().status(
748 format
!("not currently logged in to `{}`", reg_name
),
754 reg_cfg
.credential_process
.as_ref(),
758 config
.shell().status(
761 "token for `{}` has been removed from local storage",
768 pub struct OwnersOptions
{
769 pub krate
: Option
<String
>,
770 pub token
: Option
<String
>,
771 pub index
: Option
<String
>,
772 pub to_add
: Option
<Vec
<String
>>,
773 pub to_remove
: Option
<Vec
<String
>>,
775 pub registry
: Option
<String
>,
778 pub fn modify_owners(config
: &Config
, opts
: &OwnersOptions
) -> CargoResult
<()> {
779 let name
= match opts
.krate
{
780 Some(ref name
) => name
.clone(),
782 let manifest_path
= find_root_manifest_for_wd(config
.cwd())?
;
783 let ws
= Workspace
::new(&manifest_path
, config
)?
;
784 ws
.current()?
.package_id().name().to_string()
788 let (mut registry
, _
, _
) = registry(
792 opts
.registry
.clone(),
797 if let Some(ref v
) = opts
.to_add
{
798 let v
= v
.iter().map(|s
| &s
[..]).collect
::<Vec
<_
>>();
799 let msg
= registry
.add_owners(&name
, &v
).with_context(|| {
801 "failed to invite owners to crate `{}` on registry at {}",
807 config
.shell().status("Owner", msg
)?
;
810 if let Some(ref v
) = opts
.to_remove
{
811 let v
= v
.iter().map(|s
| &s
[..]).collect
::<Vec
<_
>>();
814 .status("Owner", format
!("removing {:?} from crate {}", v
, name
))?
;
815 registry
.remove_owners(&name
, &v
).with_context(|| {
817 "failed to remove owners from crate `{}` on registry at {}",
825 let owners
= registry
.list_owners(&name
).with_context(|| {
827 "failed to list owners of crate `{}` on registry at {}",
832 for owner
in owners
.iter() {
833 drop_print
!(config
, "{}", owner
.login
);
834 match (owner
.name
.as_ref(), owner
.email
.as_ref()) {
835 (Some(name
), Some(email
)) => drop_println
!(config
, " ({} <{}>)", name
, email
),
836 (Some(s
), None
) | (None
, Some(s
)) => drop_println
!(config
, " ({})", s
),
837 (None
, None
) => drop_println
!(config
),
847 krate
: Option
<String
>,
848 version
: Option
<String
>,
849 token
: Option
<String
>,
850 index
: Option
<String
>,
853 ) -> CargoResult
<()> {
854 let name
= match krate
{
857 let manifest_path
= find_root_manifest_for_wd(config
.cwd())?
;
858 let ws
= Workspace
::new(&manifest_path
, config
)?
;
859 ws
.current()?
.package_id().name().to_string()
862 let version
= match version
{
864 None
=> bail
!("a version must be specified to yank"),
867 let (mut registry
, _
, _
) = registry(config
, token
, index
, reg
, true, true)?
;
872 .status("Unyank", format
!("{}:{}", name
, version
))?
;
873 registry
.unyank(&name
, &version
).with_context(|| {
875 "failed to undo a yank from the registry at {}",
882 .status("Yank", format
!("{}:{}", name
, version
))?
;
884 .yank(&name
, &version
)
885 .with_context(|| format
!("failed to yank from the registry at {}", registry
.host()))?
;
891 /// Gets the SourceId for an index or registry setting.
893 /// The `index` and `reg` values are from the command-line or config settings.
894 /// If both are None, returns the source for crates.io.
897 index
: Option
<&String
>,
898 reg
: Option
<&String
>,
899 ) -> CargoResult
<SourceId
> {
901 (Some(r
), _
) => SourceId
::alt_registry(config
, r
),
902 (_
, Some(i
)) => SourceId
::for_registry(&i
.into_url()?
),
904 let map
= SourceConfigMap
::new(config
)?
;
905 let src
= map
.load(SourceId
::crates_io(config
)?
, &HashSet
::new())?
;
906 Ok(src
.replaced_source_id())
914 index
: Option
<String
>,
917 ) -> CargoResult
<()> {
918 fn truncate_with_ellipsis(s
: &str, max_width
: usize) -> String
{
919 // We should truncate at grapheme-boundary and compute character-widths,
920 // yet the dependencies on unicode-segmentation and unicode-width are
922 let mut chars
= s
.chars();
923 let mut prefix
= (&mut chars
).take(max_width
- 1).collect
::<String
>();
924 if chars
.next().is_some() {
930 let (mut registry
, _
, source_id
) = registry(config
, None
, index
, reg
, false, false)?
;
931 let (crates
, total_crates
) = registry
.search(query
, limit
).with_context(|| {
933 "failed to retrieve search results from the registry at {}",
940 .map(|krate
| format
!("{} = \"{}\"", krate
.name
, krate
.max_version
))
941 .collect
::<Vec
<String
>>();
943 let description_margin
= names
.iter().map(|s
| s
.len() + 4).max().unwrap_or_default();
945 let description_length
= cmp
::max(80, 128 - description_margin
);
947 let descriptions
= crates
.iter().map(|krate
| {
951 .map(|desc
| truncate_with_ellipsis(&desc
.replace("\n", " "), description_length
))
954 for (name
, description
) in names
.into_iter().zip(descriptions
) {
955 let line
= match description
{
957 let space
= repeat(' '
)
958 .take(description_margin
- name
.len())
959 .collect
::<String
>();
960 name
+ &space
+ "# " + &desc
964 drop_println
!(config
, "{}", line
);
967 let search_max_limit
= 100;
968 if total_crates
> limit
&& limit
< search_max_limit
{
971 "... and {} crates more (use --limit N to see more)",
974 } else if total_crates
> limit
&& limit
>= search_max_limit
{
975 let extra
= if source_id
.is_default_registry() {
977 " (go to https://crates.io/search?q={} to see more)",
978 percent_encode(query
.as_bytes(), NON_ALPHANUMERIC
)
985 "... and {} crates more{}",
986 total_crates
- limit
,