Skip to content

Commit dd3995a

Browse files
committed
Block publishing on dirty directories
Dearest reviewer, This is part of #841 which is about the publishing and tagging flow. This pull request just prevents publishing with uncommitted or untracked files. I have included a flag for turning this off. There are two new test. More details about the full flow at #2443 . I plan on doing more work on the flow, however, I felt this was useful enough to do stand alone. When I tried the flow before I was doing to much at once. Thanks! Becker [Updated] Moved from config to flag Using open_ext to sub crates Include the file names in error message include flag in error Move to discover off of open_ext formatting
1 parent 48fd9de commit dd3995a

File tree

5 files changed

+178
-16
lines changed

5 files changed

+178
-16
lines changed

src/bin/publish.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub struct Options {
88
flag_token: Option<String>,
99
flag_manifest_path: Option<String>,
1010
flag_verbose: Option<bool>,
11+
flag_allow_untracked: bool,
1112
flag_quiet: Option<bool>,
1213
flag_color: Option<String>,
1314
flag_no_verify: bool,
@@ -26,6 +27,7 @@ Options:
2627
--no-verify Don't verify package tarball before publish
2728
--manifest-path PATH Path to the manifest of the package to publish
2829
-v, --verbose Use verbose output
30+
--allow-untracked Allows publishing with untracked and uncommitted files
2931
-q, --quiet No output printed to stdout
3032
--color WHEN Coloring: auto, always, never
3133
@@ -40,10 +42,11 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
4042
flag_host: host,
4143
flag_manifest_path,
4244
flag_no_verify: no_verify,
45+
flag_allow_untracked: allow_untracked,
4346
..
4447
} = options;
4548

4649
let root = try!(find_root_manifest_for_wd(flag_manifest_path.clone(), config.cwd()));
47-
try!(ops::publish(&root, config, token, host, !no_verify));
50+
try!(ops::publish(&root, config, token, host, !no_verify, allow_untracked));
4851
Ok(None)
4952
}

src/cargo/ops/registry.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,13 @@ pub fn publish(manifest_path: &Path,
3232
config: &Config,
3333
token: Option<String>,
3434
index: Option<String>,
35-
verify: bool) -> CargoResult<()> {
35+
verify: bool, allow_untracked: bool) -> CargoResult<()> {
3636
let pkg = try!(Package::for_path(&manifest_path, config));
3737

38+
if !allow_untracked {
39+
try!(check_directory_cleanliness());
40+
}
41+
3842
if !pkg.publish() {
3943
bail!("some crates cannot be published.\n\
4044
`{}` is marked as unpublishable", pkg.name());
@@ -55,6 +59,41 @@ pub fn publish(manifest_path: &Path,
5559
Ok(())
5660
}
5761

62+
fn check_git_cleanliness(repo: &git2::Repository) -> CargoResult<()> {
63+
let mut opts = git2::StatusOptions::new();
64+
opts.include_untracked(true).recurse_untracked_dirs(true);
65+
let status = try!(repo.statuses(Some(&mut opts)));
66+
let files:Vec<String> = status.iter().map(|entry| {
67+
let file = entry.index_to_workdir().unwrap()
68+
.old_file().path().unwrap().display();
69+
format!("{}", file)
70+
}).collect();
71+
match files.len() {
72+
0 => {
73+
Ok(())
74+
},
75+
num @ _ => {
76+
77+
Err(human(format!("{} uncommited or untacked files \
78+
that need to be addressed before publishing. to force the publish command \
79+
include --allow-untracked\nproblem files:\n{}", num, files.join("\n"))))
80+
}
81+
}
82+
}
83+
84+
fn open_git_repo() -> Result<git2::Repository, git2::Error> {
85+
let current_path = Path::new(".");
86+
git2::Repository::discover(current_path)
87+
}
88+
89+
fn check_directory_cleanliness() -> CargoResult<()> {
90+
if let Ok(repo) = open_git_repo() {
91+
check_git_cleanliness(&repo)
92+
} else {
93+
Ok(())
94+
}
95+
}
96+
5897
fn verify_dependencies(pkg: &Package, registry_src: &SourceId)
5998
-> CargoResult<()> {
6099
for dep in pkg.dependencies().iter() {

tests/test_bad_config.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use support::{project, execs};
1+
use support::{project, execs, paths};
22
use support::registry::Package;
3+
use support::git::repo;
34
use hamcrest::assert_that;
45

56
fn setup() {}
@@ -57,7 +58,8 @@ Caused by:
5758
});
5859

5960
test!(bad3 {
60-
let foo = project("foo")
61+
let root = paths::root().join("bad3");
62+
let p = repo(&root)
6163
.file("Cargo.toml", r#"
6264
[package]
6365
name = "foo"
@@ -69,7 +71,11 @@ test!(bad3 {
6971
[http]
7072
proxy = true
7173
"#);
72-
assert_that(foo.cargo_process("publish").arg("-v"),
74+
p.build();
75+
76+
let mut cargo = ::cargo_process();
77+
cargo.cwd(p.root());
78+
assert_that(cargo.arg("publish").arg("-v"),
7379
execs().with_status(101).with_stderr("\
7480
[ERROR] invalid configuration for key `http.proxy`
7581
expected a string, but found a boolean in [..]config

tests/test_cargo_publish.rs

Lines changed: 117 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use flate2::read::GzDecoder;
77
use tar::Archive;
88
use url::Url;
99

10-
use support::{project, execs};
10+
use support::execs;
1111
use support::paths;
1212
use support::git::repo;
1313

@@ -36,8 +36,100 @@ fn setup() {
3636
.build();
3737
}
3838

39+
test!(uncommited_git_files_allowed {
40+
let root = paths::root().join("uncommited_git_files_allowed");
41+
let p = repo(&root)
42+
.file("Cargo.toml", r#"
43+
[project]
44+
name = "foo"
45+
version = "0.0.1"
46+
authors = []
47+
license = "MIT"
48+
description = "foo"
49+
"#)
50+
.nocommit_file("bad","file")
51+
.file("src/main.rs", "fn main() {}");
52+
p.build();
53+
let mut cargo = ::cargo_process();
54+
cargo.cwd(p.root());
55+
assert_that(cargo.arg("publish").arg("--allow-untracked").arg("--no-verify"),
56+
execs().with_status(0).with_stdout(&format!("\
57+
[UPDATING] registry `{reg}`
58+
[PACKAGING] foo v0.0.1 ({dir})
59+
[UPLOADING] foo v0.0.1 ({dir})",
60+
dir = p.url(),
61+
reg = registry())));
62+
});
63+
64+
test!(uncommited_git_files_error_from_sub_crate {
65+
let root = paths::root().join("sub_uncommited_git");
66+
let p = repo(&root)
67+
.file("Cargo.toml", r#"
68+
[project]
69+
name = "foo"
70+
version = "0.0.1"
71+
authors = []
72+
license = "MIT"
73+
description = "foo"
74+
"#)
75+
.nocommit_file("bad.txt","file")
76+
.file("src/main.rs", "fn main() {}")
77+
.file("sub/lib.rs", "pub fn l() {}")
78+
.file("sub/Cargo.toml", r#"
79+
[package]
80+
name = "crates-io"
81+
version = "0.2.0"
82+
authors = []
83+
license = "MIT/Apache-2.0"
84+
repository = "https://github.com/rust-lang/cargo"
85+
description = """
86+
"""
87+
88+
[lib]
89+
name = "crates_io"
90+
path = "lib.rs"
91+
"#);
92+
p.build();
93+
94+
let mut cargo = ::cargo_process();
95+
cargo.cwd(p.root().join("sub"));
96+
assert_that(cargo.arg("publish").arg("--no-verify"),
97+
execs().with_status(101).with_stderr(&"\
98+
[ERROR] 1 uncommited or untacked files that need to be addressed before \
99+
publishing. to force the publish command include --allow-untracked
100+
problem files:
101+
bad.txt",
102+
));
103+
});
104+
105+
test!(uncommited_git_files_error {
106+
let root = paths::root().join("uncommited_git_files_error");
107+
let p = repo(&root)
108+
.file("Cargo.toml", r#"
109+
[project]
110+
name = "foo"
111+
version = "0.0.1"
112+
authors = []
113+
license = "MIT"
114+
description = "foo"
115+
"#)
116+
.nocommit_file("bad.txt","file")
117+
.file("src/main.rs", "fn main() {}");
118+
p.build();
119+
120+
let mut cargo = ::cargo_process();
121+
cargo.cwd(p.root());
122+
assert_that(cargo.arg("publish").arg("--no-verify"),
123+
execs().with_status(101).with_stderr_contains(&"\
124+
[ERROR] 1 uncommited or untacked files that need to be addressed before \
125+
publishing. to force the publish command include --allow-untracked
126+
problem files:",
127+
).with_stderr_contains(&"bad.txt"));
128+
});
129+
39130
test!(simple {
40-
let p = project("foo")
131+
let root = paths::root().join("simple");
132+
let p = repo(&root)
41133
.file("Cargo.toml", r#"
42134
[project]
43135
name = "foo"
@@ -47,8 +139,12 @@ test!(simple {
47139
description = "foo"
48140
"#)
49141
.file("src/main.rs", "fn main() {}");
142+
p.build();
143+
144+
let mut cargo = ::cargo_process();
145+
cargo.cwd(p.root());
50146

51-
assert_that(p.cargo_process("publish").arg("--no-verify"),
147+
assert_that(cargo.arg("publish").arg("--no-verify"),
52148
execs().with_status(0).with_stdout(&format!("\
53149
[UPDATING] registry `{reg}`
54150
[PACKAGING] foo v0.0.1 ({dir})
@@ -84,7 +180,8 @@ test!(simple {
84180
});
85181

86182
test!(git_deps {
87-
let p = project("foo")
183+
let root = paths::root().join("git_deps");
184+
let p = repo(&root)
88185
.file("Cargo.toml", r#"
89186
[project]
90187
name = "foo"
@@ -97,16 +194,20 @@ test!(git_deps {
97194
git = "git://path/to/nowhere"
98195
"#)
99196
.file("src/main.rs", "fn main() {}");
197+
p.build();
100198

101-
assert_that(p.cargo_process("publish").arg("-v").arg("--no-verify"),
199+
let mut cargo = ::cargo_process();
200+
cargo.cwd(p.root());
201+
assert_that(cargo.arg("publish").arg("-v").arg("--no-verify"),
102202
execs().with_status(101).with_stderr("\
103203
[ERROR] all dependencies must come from the same source.
104204
dependency `foo` comes from git://path/to/nowhere instead
105205
"));
106206
});
107207

108208
test!(path_dependency_no_version {
109-
let p = project("foo")
209+
let root = paths::root().join("path_dependency_no_version");
210+
let p = repo(&root)
110211
.file("Cargo.toml", r#"
111212
[project]
112213
name = "foo"
@@ -126,16 +227,20 @@ test!(path_dependency_no_version {
126227
authors = []
127228
"#)
128229
.file("bar/src/lib.rs", "");
230+
p.build();
129231

130-
assert_that(p.cargo_process("publish"),
232+
let mut cargo = ::cargo_process();
233+
cargo.cwd(p.root());
234+
assert_that(cargo.arg("publish"),
131235
execs().with_status(101).with_stderr("\
132236
[ERROR] all path dependencies must have a version specified when publishing.
133237
dependency `bar` does not specify a version
134238
"));
135239
});
136240

137241
test!(unpublishable_crate {
138-
let p = project("foo")
242+
let root = paths::root().join("unpublishable_crate");
243+
let p = repo(&root)
139244
.file("Cargo.toml", r#"
140245
[project]
141246
name = "foo"
@@ -146,8 +251,11 @@ test!(unpublishable_crate {
146251
publish = false
147252
"#)
148253
.file("src/main.rs", "fn main() {}");
254+
p.build();
149255

150-
assert_that(p.cargo_process("publish"),
256+
let mut cargo = ::cargo_process();
257+
cargo.cwd(p.root());
258+
assert_that(cargo.arg("publish"),
151259
execs().with_status(101).with_stderr("\
152260
[ERROR] some crates cannot be published.
153261
`foo` is marked as unpublishable

tests/test_cargo_registry.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::fs::{self, File};
22
use std::io::prelude::*;
33

44
use support::{project, execs};
5+
use support::git::repo;
56
use support::paths::{self, CargoPathExt};
67
use support::registry::{self, Package};
78
use support::git;
@@ -550,7 +551,8 @@ test!(login_with_no_cargo_dir {
550551

551552
test!(bad_license_file {
552553
Package::new("foo", "1.0.0").publish();
553-
let p = project("all")
554+
let root = paths::root().join("bad_license_file");
555+
let p = repo(&root)
554556
.file("Cargo.toml", r#"
555557
[project]
556558
name = "foo"
@@ -563,7 +565,11 @@ test!(bad_license_file {
563565
.file("src/main.rs", r#"
564566
fn main() {}
565567
"#);
566-
assert_that(p.cargo_process("publish").arg("-v"),
568+
p.build();
569+
570+
let mut cargo = ::cargo_process();
571+
cargo.cwd(p.root());
572+
assert_that(cargo.arg("publish").arg("-v"),
567573
execs().with_status(101)
568574
.with_stderr("\
569575
[ERROR] the license file `foo` does not exist"));

0 commit comments

Comments
 (0)