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}
;
16 use termcolor
::Color
::Green
;
17 use termcolor
::ColorSpec
;
19 use crate::core
::dependency
::DepKind
;
20 use crate::core
::manifest
::ManifestMetadata
;
21 use crate::core
::resolver
::CliFeatures
;
22 use crate::core
::source
::Source
;
23 use crate::core
::{Package, SourceId, Workspace}
;
25 use crate::sources
::{RegistrySource, SourceConfigMap, CRATES_IO_DOMAIN, CRATES_IO_REGISTRY}
;
26 use crate::util
::config
::{self, Config, SslVersionConfig, SslVersionConfigRange}
;
27 use crate::util
::errors
::CargoResult
;
28 use crate::util
::important_paths
::find_root_manifest_for_wd
;
29 use crate::util
::IntoUrl
;
30 use crate::{drop_print, drop_println, version}
;
34 /// Registry settings loaded from config files.
36 /// This is loaded based on the `--registry` flag and the config settings.
38 pub struct RegistryConfig
{
39 /// The index URL. If `None`, use crates.io.
40 pub index
: Option
<String
>,
41 /// The authentication token.
42 pub token
: Option
<String
>,
43 /// Process used for fetching a token.
44 pub credential_process
: Option
<(PathBuf
, Vec
<String
>)>,
47 pub struct PublishOpts
<'cfg
> {
48 pub config
: &'cfg Config
,
49 pub token
: Option
<String
>,
50 pub index
: Option
<String
>,
52 pub allow_dirty
: bool
,
53 pub jobs
: Option
<u32>,
54 pub to_publish
: ops
::Packages
,
55 pub targets
: Vec
<String
>,
57 pub registry
: Option
<String
>,
58 pub cli_features
: CliFeatures
,
61 pub fn publish(ws
: &Workspace
<'_
>, opts
: &PublishOpts
<'_
>) -> CargoResult
<()> {
62 let specs
= opts
.to_publish
.to_package_id_specs(ws
)?
;
63 let mut pkgs
= ws
.members_with_features(&specs
, &opts
.cli_features
)?
;
65 let (pkg
, cli_features
) = pkgs
.pop().unwrap();
67 let mut publish_registry
= opts
.registry
.clone();
68 if let Some(ref allowed_registries
) = *pkg
.publish() {
69 if publish_registry
.is_none() && allowed_registries
.len() == 1 {
70 // If there is only one allowed registry, push to that one directly,
71 // even though there is no registry specified in the command.
72 let default_registry
= &allowed_registries
[0];
73 if default_registry
!= CRATES_IO_REGISTRY
{
74 // Don't change the registry for crates.io and don't warn the user.
75 // crates.io will be defaulted even without this.
76 opts
.config
.shell().note(&format
!(
77 "Found `{}` as only allowed registry. Publishing to it automatically.",
80 publish_registry
= Some(default_registry
.clone());
84 let reg_name
= publish_registry
86 .unwrap_or_else(|| CRATES_IO_REGISTRY
.to_string());
87 if !allowed_registries
.contains(®_name
) {
89 "`{}` cannot be published.\n\
90 The registry `{}` is not listed in the `publish` value in Cargo.toml.",
97 let (mut registry
, _reg_cfg
, reg_id
) = registry(
105 verify_dependencies(pkg
, ®istry
, reg_id
)?
;
107 // Prepare a tarball, with a non-suppressible warning if metadata
108 // is missing since this is being put online.
109 let tarball
= ops
::package_one(
116 check_metadata
: true,
117 allow_dirty
: opts
.allow_dirty
,
118 to_package
: ops
::Packages
::Default
,
119 targets
: opts
.targets
.clone(),
121 cli_features
: cli_features
,
128 .status("Uploading", pkg
.package_id().to_string())?
;
141 fn verify_dependencies(
144 registry_src
: SourceId
,
145 ) -> CargoResult
<()> {
146 for dep
in pkg
.dependencies().iter() {
147 if super::check_dep_has_version(dep
, true)?
{
150 // TomlManifest::prepare_for_publish will rewrite the dependency
151 // to be just the `version` field.
152 if dep
.source_id() != registry_src
{
153 if !dep
.source_id().is_registry() {
154 // Consider making SourceId::kind a public type that we can
155 // exhaustively match on. Using match can help ensure that
156 // every kind is properly handled.
157 panic
!("unexpected source kind for dependency {:?}", dep
);
159 // Block requests to send to crates.io with alt-registry deps.
160 // This extra hostname check is mostly to assist with testing,
161 // but also prevents someone using `--index` to specify
162 // something that points to crates.io.
163 if registry_src
.is_default_registry() || registry
.host_is_crates_io() {
164 bail
!("crates cannot be published to crates.io with dependencies sourced from other\n\
165 registries. `{}` needs to be published to crates.io before publishing this crate.\n\
166 (crate `{}` is pulled from {})",
180 registry
: &mut Registry
,
181 registry_id
: SourceId
,
183 ) -> CargoResult
<()> {
188 // Skip dev-dependency without version.
189 dep
.is_transitive() || dep
.specified_req()
192 // If the dependency is from a different registry, then include the
193 // registry in the dependency.
194 let dep_registry_id
= match dep
.registry_id() {
196 None
=> SourceId
::crates_io(config
)?
,
198 // In the index and Web API, None means "from the same registry"
199 // whereas in Cargo.toml, it means "from crates.io".
200 let dep_registry
= if dep_registry_id
!= registry_id
{
201 Some(dep_registry_id
.url().to_string())
206 Ok(NewCrateDependency
{
207 optional
: dep
.is_optional(),
208 default_features
: dep
.uses_default_features(),
209 name
: dep
.package_name().to_string(),
210 features
: dep
.features().iter().map(|s
| s
.to_string()).collect(),
211 version_req
: dep
.version_req().to_string(),
212 target
: dep
.platform().map(|s
| s
.to_string()),
213 kind
: match dep
.kind() {
214 DepKind
::Normal
=> "normal",
215 DepKind
::Build
=> "build",
216 DepKind
::Development
=> "dev",
219 registry
: dep_registry
,
220 explicit_name_in_toml
: dep
.explicit_name_in_toml().map(|s
| s
.to_string()),
223 .collect
::<CargoResult
<Vec
<NewCrateDependency
>>>()?
;
224 let manifest
= pkg
.manifest();
225 let ManifestMetadata
{
238 } = *manifest
.metadata();
239 let readme_content
= readme
242 paths
::read(&pkg
.root().join(readme
))
243 .with_context(|| format
!("failed to read `readme` file for package `{}`", pkg
))
246 if let Some(ref file
) = *license_file
{
247 if !pkg
.root().join(file
).exists() {
248 bail
!("the license file `{}` does not exist", file
)
252 // Do not upload if performing a dry run
254 config
.shell().warn("aborting upload due to dry run")?
;
258 let string_features
= match manifest
.original().features() {
259 Some(features
) => features
261 .map(|(feat
, values
)| {
264 values
.iter().map(|fv
| fv
.to_string()).collect(),
267 .collect
::<BTreeMap
<String
, Vec
<String
>>>(),
268 None
=> BTreeMap
::new(),
271 let warnings
= registry
274 name
: pkg
.name().to_string(),
275 vers
: pkg
.version().to_string(),
277 features
: string_features
,
278 authors
: authors
.clone(),
279 description
: description
.clone(),
280 homepage
: homepage
.clone(),
281 documentation
: documentation
.clone(),
282 keywords
: keywords
.clone(),
283 categories
: categories
.clone(),
284 readme
: readme_content
,
285 readme_file
: readme
.clone(),
286 repository
: repository
.clone(),
287 license
: license
.clone(),
288 license_file
: license_file
.clone(),
289 badges
: badges
.clone(),
290 links
: links
.clone(),
294 .with_context(|| format
!("failed to publish to registry at {}", registry
.host()))?
;
296 if !warnings
.invalid_categories
.is_empty() {
298 "the following are not valid category slugs and were \
299 ignored: {}. Please see https://crates.io/category_slugs \
300 for the list of all category slugs. \
302 warnings
.invalid_categories
.join(", ")
304 config
.shell().warn(&msg
)?
;
307 if !warnings
.invalid_badges
.is_empty() {
309 "the following are not valid badges and were ignored: {}. \
310 Either the badge type specified is unknown or a required \
311 attribute is missing. Please see \
312 https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata \
313 for valid badge types and their required attributes.",
314 warnings
.invalid_badges
.join(", ")
316 config
.shell().warn(&msg
)?
;
319 if !warnings
.other
.is_empty() {
320 for msg
in warnings
.other
{
321 config
.shell().warn(&msg
)?
;
328 /// Returns the index and token from the config file for the given registry.
330 /// `registry` is typically the registry specified on the command-line. If
331 /// `None`, `index` is set to `None` to indicate it should use crates.io.
332 pub fn registry_configuration(
334 registry
: Option
<&str>,
335 ) -> CargoResult
<RegistryConfig
> {
336 let err_both
= |token_key
: &str, proc_key
: &str| {
338 "both `{TOKEN_KEY}` and `{PROC_KEY}` \
339 were specified in the config\n\
340 Only one of these values may be set, remove one or the other to proceed.",
341 TOKEN_KEY
= token_key
,
345 // `registry.default` is handled in command-line parsing.
346 let (index
, token
, process
) = match registry
{
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_deref().or_else(|| index
.as_deref());
422 let sid
= get_source_id(config
, opt_index
, registry
.as_deref())?
;
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.
895 fn get_source_id(config
: &Config
, index
: Option
<&str>, reg
: Option
<&str>) -> CargoResult
<SourceId
> {
897 (Some(r
), _
) => SourceId
::alt_registry(config
, r
),
898 (_
, Some(i
)) => SourceId
::for_registry(&i
.into_url()?
),
900 let map
= SourceConfigMap
::new(config
)?
;
901 let src
= map
.load(SourceId
::crates_io(config
)?
, &HashSet
::new())?
;
902 Ok(src
.replaced_source_id())
910 index
: Option
<String
>,
913 ) -> CargoResult
<()> {
914 fn truncate_with_ellipsis(s
: &str, max_width
: usize) -> String
{
915 // We should truncate at grapheme-boundary and compute character-widths,
916 // yet the dependencies on unicode-segmentation and unicode-width are
918 let mut chars
= s
.chars();
919 let mut prefix
= (&mut chars
).take(max_width
- 1).collect
::<String
>();
920 if chars
.next().is_some() {
926 let (mut registry
, _
, source_id
) = registry(config
, None
, index
, reg
, false, false)?
;
927 let (crates
, total_crates
) = registry
.search(query
, limit
).with_context(|| {
929 "failed to retrieve search results from the registry at {}",
936 .map(|krate
| format
!("{} = \"{}\"", krate
.name
, krate
.max_version
))
937 .collect
::<Vec
<String
>>();
939 let description_margin
= names
.iter().map(|s
| s
.len() + 4).max().unwrap_or_default();
941 let description_length
= cmp
::max(80, 128 - description_margin
);
943 let descriptions
= crates
.iter().map(|krate
| {
947 .map(|desc
| truncate_with_ellipsis(&desc
.replace("\n", " "), description_length
))
950 for (name
, description
) in names
.into_iter().zip(descriptions
) {
951 let line
= match description
{
953 let space
= repeat(' '
)
954 .take(description_margin
- name
.len())
955 .collect
::<String
>();
956 name
+ &space
+ "# " + &desc
960 let mut fragments
= line
.split(query
).peekable();
961 while let Some(fragment
) = fragments
.next() {
962 let _
= config
.shell().write_stdout(fragment
, &ColorSpec
::new());
963 if fragments
.peek().is_some() {
966 .write_stdout(query
, &ColorSpec
::new().set_bold(true).set_fg(Some(Green
)));
969 let _
= config
.shell().write_stdout("\n", &ColorSpec
::new());
972 let search_max_limit
= 100;
973 if total_crates
> limit
&& limit
< search_max_limit
{
974 let _
= config
.shell().write_stdout(
976 "... and {} crates more (use --limit N to see more)\n",
981 } else if total_crates
> limit
&& limit
>= search_max_limit
{
982 let extra
= if source_id
.is_default_registry() {
984 " (go to https://crates.io/search?q={} to see more)",
985 percent_encode(query
.as_bytes(), NON_ALPHANUMERIC
)
990 let _
= config
.shell().write_stdout(
991 format_args
!("... and {} crates more{}\n", total_crates
- limit
, extra
),