Skip to content

Commit 1232724

Browse files
authored
Merge pull request #28 from Kobzol/post-commands
Implement post-pull commands
2 parents 8675869 + 0bde81a commit 1232724

File tree

6 files changed

+77
-14
lines changed

6 files changed

+77
-14
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ If you need to specify a more complex Josh `filter`, use `filter` field in the c
1717

1818
The `init` command will also create an empty `rust-version` file (if it doesn't already exist) that stores the last upstream `rustc` SHA that was synced in the subtree.
1919

20+
The [`josh-sync.example.toml`](josh-sync.example.toml) file contains all the things that can be configured.
21+
2022
## Performing pull
2123

2224
A pull operation fetches changes to the subtree subdirectory that were performed in `rust-lang/rust` and merges them into the subtree repository. After performing a pull, a pull request is sent against the *subtree repository*. We *pull from rustc*.
@@ -25,15 +27,20 @@ A pull operation fetches changes to the subtree subdirectory that were performed
2527
2) Create a new branch that will be used for the subtree PR, e.g. `pull`
2628
3) Run `rustc-josh-sync pull`
2729
4) Send a PR to the subtree repository
28-
- Note that `rustc-josh-sync` can do this for you if you have the [gh](https://cli.github.com/) CLI tool installed.
30+
31+
- Note that `rustc-josh-sync` can do this for you if you have the [gh](https://cli.github.com/) CLI tool installed.
32+
33+
You can also configure a set of postprocessing operations to be performed after a successful pull using the `post-pull` configuration.
2934

3035
## Performing push
3136

3237
A push operation takes changes performed in the subtree repository and merges them into the subtree subdirectory of the `rust-lang/rust` repository. After performing a push, a push request is sent against the *rustc repository*. We *push to rustc*.
3338

3439
1) Checkout the latest default branch of the subtree
3540
2) Run `rustc-josh-sync push <branch> <your-github-username>`
36-
- The branch with the push contents will be created in `https://github.com/<your-github-username>/rust` fork, in the `<branch>` branch.
41+
42+
- The branch with the push contents will be created in `https://github.com/<your-github-username>/rust` fork, in the `<branch>` branch.
43+
3744
3) Send a PR to [rust-lang/rust]
3845

3946
## Automating pulls on CI

josh-sync.example.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,10 @@ path = "library/stdarch"
88
# Optionally, you can specify the complete Josh filter for complex cases
99
# Note that this option is mutually exclusive with `path`
1010
#filter = ...
11+
12+
# Optionally, you can specify a set of commands executed after a successful pull.
13+
# If the executed command changes the local git state (performs some modifications to files that
14+
# were already tracked), then a new commit with the given message will be created.
15+
#[[post-pull]]
16+
#cmd = ["cargo", "fmt"]
17+
#commit-message = "reformat"

src/bin/rustc_josh_sync.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ fn main() -> anyhow::Result<()> {
8484
repo: "<repository-name>".to_string(),
8585
path: Some("<relative-subtree-path>".to_string()),
8686
filter: None,
87+
post_pull: vec![],
8788
};
8889
config
8990
.write(Path::new(DEFAULT_CONFIG_PATH))

src/config.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use anyhow::Context;
22
use std::path::Path;
33

44
#[derive(serde::Serialize, serde::Deserialize, Clone)]
5+
#[serde(rename_all = "kebab-case")]
56
pub struct JoshConfig {
67
#[serde(default = "default_org")]
78
pub org: String,
@@ -12,6 +13,24 @@ pub struct JoshConfig {
1213
/// Optional filter specification for Josh.
1314
/// It cannot be used together with `path`.
1415
pub filter: Option<String>,
16+
/// Operation(s) that should be performed after a pull.
17+
/// Can be used to post-process the state of the repository after a pull happens.
18+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
19+
pub post_pull: Vec<PostPullOperation>,
20+
}
21+
22+
/// Execute an operation after a pull, and if something changes in the local git state,
23+
/// perform a commit.
24+
#[derive(serde::Serialize, serde::Deserialize, Clone)]
25+
#[serde(rename_all = "kebab-case")]
26+
pub struct PostPullOperation {
27+
/// Execute a command with these arguments
28+
/// At least one argument has to be present.
29+
/// You can run e.g. bash if you want to do more complicated stuff.
30+
pub cmd: Vec<String>,
31+
/// If the git state has changed after `cmd`, add all changes to the index (`git add -u`)
32+
/// and create a commit with the following commit message.
33+
pub commit_message: String,
1534
}
1635

1736
impl JoshConfig {

src/sync.rs

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::SyncContext;
2+
use crate::config::PostPullOperation;
23
use crate::josh::JoshProxy;
34
use crate::utils::{ensure_clean_git_state, prompt};
45
use crate::utils::{get_current_head_sha, run_command_at};
@@ -65,7 +66,7 @@ impl GitSync {
6566
.to_owned()
6667
};
6768

68-
ensure_clean_git_state(self.verbose);
69+
ensure_clean_git_state(self.verbose)?;
6970

7071
// Make sure josh is running.
7172
let josh = self
@@ -220,17 +221,21 @@ After you fix the conflicts, `git add` the changes and run `git merge --continue
220221
// But it can be more tricky - we can have only empty merge/rollup merge commits from
221222
// rustc, so a merge was created, but the in-tree diff can still be empty.
222223
// In that case we also bail.
223-
// `git diff --exit-code` "succeeds" if the diff is empty.
224-
if run_command(
225-
&["git", "diff", "--exit-code", &sha_pre_merge],
226-
self.verbose,
227-
)
228-
.is_ok()
229-
{
224+
if self.has_empty_diff(&sha_pre_merge) {
230225
eprintln!("Only empty changes were pulled. Rolling back the preparation commit.");
231226
return Err(RustcPullError::NothingToPull);
232227
}
233228

229+
println!("Pull finished! Current HEAD is {current_sha}");
230+
231+
if !self.context.config.post_pull.is_empty() {
232+
println!("Running post-pull operation(s)");
233+
234+
for op in &self.context.config.post_pull {
235+
self.run_post_pull_op(&op)?;
236+
}
237+
}
238+
234239
git_reset.disarm();
235240

236241
// Check that the number of roots did not change.
@@ -241,14 +246,13 @@ After you fix the conflicts, `git add` the changes and run `git merge --continue
241246
.into());
242247
}
243248

244-
println!("Pull finished! Current HEAD is {current_sha}");
245249
Ok(PullResult {
246250
merge_commit_message: merge_message,
247251
})
248252
}
249253

250254
pub fn rustc_push(&self, username: &str, branch: &str) -> anyhow::Result<()> {
251-
ensure_clean_git_state(self.verbose);
255+
ensure_clean_git_state(self.verbose)?;
252256

253257
let base_upstream_sha = self.context.last_upstream_sha.clone().unwrap_or_default();
254258

@@ -341,6 +345,27 @@ After you fix the conflicts, `git add` the changes and run `git merge --continue
341345

342346
Ok(())
343347
}
348+
349+
fn has_empty_diff(&self, baseline_sha: &str) -> bool {
350+
// `git diff --exit-code` "succeeds" if the diff is empty.
351+
run_command(&["git", "diff", "--exit-code", baseline_sha], self.verbose).is_ok()
352+
}
353+
354+
fn run_post_pull_op(&self, op: &PostPullOperation) -> anyhow::Result<()> {
355+
let head = get_current_head_sha(self.verbose)?;
356+
run_command(op.cmd.iter().map(|s| s.as_str()).collect::<Vec<_>>(), true)?;
357+
if !self.has_empty_diff(&head) {
358+
println!(
359+
"`{}` changed something, committing with message `{}`",
360+
op.cmd.join(" "),
361+
op.commit_message
362+
);
363+
run_command(["git", "add", "-u"], self.verbose)?;
364+
run_command(["git", "commit", "-m", &op.commit_message], self.verbose)?;
365+
}
366+
367+
Ok(())
368+
}
344369
}
345370

346371
/// Find a rustc repo we can do our push preparation in.

src/utils.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,17 @@ fn run_command_inner<'a, Args: AsRef<[&'a str]>>(
6969
}
7070

7171
/// Fail if there are files that need to be checked in.
72-
pub fn ensure_clean_git_state(verbose: bool) {
72+
pub fn ensure_clean_git_state(verbose: bool) -> anyhow::Result<()> {
7373
let read = run_command(
7474
["git", "status", "--untracked-files=no", "--porcelain"],
7575
verbose,
7676
)
7777
.expect("cannot figure out if git state is clean");
78-
assert!(read.is_empty(), "working directory must be clean");
78+
if !read.is_empty() {
79+
Err(anyhow::anyhow!("working directory must be clean"))
80+
} else {
81+
Ok(())
82+
}
7983
}
8084

8185
pub fn get_current_head_sha(verbose: bool) -> anyhow::Result<String> {

0 commit comments

Comments
 (0)