mirror of
https://github.com/martinvonz/jj.git
synced 2025-05-05 23:42:50 +00:00
cli: git push: add --skip-private flag
This commit is contained in:
parent
3318d172ff
commit
2435d27eaf
@ -43,6 +43,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
* `jj op log -d` now has an alias for `jj op log --op-diff`.
|
||||
|
||||
* `jj git push` now has a flag `--skip-private` to continue pushing other
|
||||
bookmarks which don't contain private commits.
|
||||
|
||||
### Fixed bugs
|
||||
|
||||
## [0.27.0] - 2025-03-05
|
||||
|
@ -140,6 +140,16 @@ pub struct GitPushArgs {
|
||||
/// commits are eligible to be pushed.
|
||||
#[arg(long)]
|
||||
allow_private: bool,
|
||||
/// Skip pushing bookmarks that depend on private commits
|
||||
///
|
||||
/// If this is omitted, the command will error if any of the bookmarks to
|
||||
/// be pushed depend of private commits.
|
||||
///
|
||||
/// The set of private commits can be configured by the
|
||||
/// `git.private-commits` setting. The default is `none()`, meaning all
|
||||
/// commits are eligible to be pushed.
|
||||
#[arg(long, conflicts_with = "allow_private")]
|
||||
skip_private: bool,
|
||||
/// Push bookmarks pointing to these commits (can be repeated)
|
||||
#[arg(
|
||||
long,
|
||||
@ -322,6 +332,13 @@ pub fn cmd_git_push(
|
||||
&remote
|
||||
);
|
||||
}
|
||||
|
||||
let skipped_private_bookmarks = if args.skip_private {
|
||||
retain_bookmarks_without_private_commits(ui, &mut bookmark_updates, &remote, &tx)?
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
if bookmark_updates.is_empty() {
|
||||
writeln!(ui.status(), "Nothing changed.")?;
|
||||
return Ok(());
|
||||
@ -361,7 +378,12 @@ pub fn cmd_git_push(
|
||||
|
||||
if let Some(mut formatter) = ui.status_formatter() {
|
||||
writeln!(formatter, "Changes to push to {remote}:")?;
|
||||
print_commits_ready_to_push(formatter.as_mut(), tx.repo(), &bookmark_updates)?;
|
||||
print_commits_ready_to_push(
|
||||
formatter.as_mut(),
|
||||
tx.repo(),
|
||||
&bookmark_updates,
|
||||
skipped_private_bookmarks,
|
||||
)?;
|
||||
}
|
||||
|
||||
if args.dry_run {
|
||||
@ -380,6 +402,65 @@ pub fn cmd_git_push(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes any bookmarks from the `bookmark_updates` if they require private
|
||||
/// commits to be pushed.
|
||||
///
|
||||
/// Returns the list of bookmarks that were removed, for informational logging.
|
||||
fn retain_bookmarks_without_private_commits(
|
||||
ui: &Ui,
|
||||
bookmark_updates: &mut Vec<(String, BookmarkPushUpdate)>,
|
||||
remote: &str,
|
||||
tx: &WorkspaceCommandTransaction,
|
||||
) -> Result<Vec<String>, CommandError> {
|
||||
let workspace_helper = tx.base_workspace_helper();
|
||||
let repo = workspace_helper.repo();
|
||||
let settings = workspace_helper.settings();
|
||||
|
||||
let old_heads = repo
|
||||
.view()
|
||||
.remote_bookmarks(remote)
|
||||
.flat_map(|(_, old_head)| old_head.target.added_ids())
|
||||
.cloned()
|
||||
.collect_vec();
|
||||
|
||||
let wont_be_pushed_revset = RevsetExpression::commits(old_heads)
|
||||
.union(workspace_helper.env().immutable_heads_expression());
|
||||
|
||||
let private_revset_str = RevisionArg::from(settings.get_string("git.private-commits")?);
|
||||
let is_private = workspace_helper
|
||||
.parse_revset(ui, &private_revset_str)?
|
||||
.evaluate()?
|
||||
.containing_fn();
|
||||
|
||||
let mut remaining_bookmark_updates = Vec::with_capacity(bookmark_updates.len());
|
||||
let mut skipped_bookmarks = vec![];
|
||||
|
||||
'bookmark_loop: for (bookmark, update) in bookmark_updates.drain(..) {
|
||||
let Some(new_head) = update.new_target.clone() else {
|
||||
// bookmark deletion won't lead to private commits being pushed
|
||||
remaining_bookmark_updates.push((bookmark, update));
|
||||
continue;
|
||||
};
|
||||
|
||||
let commits_to_push =
|
||||
wont_be_pushed_revset.range(&RevsetExpression::commits(vec![new_head]));
|
||||
|
||||
for commit in workspace_helper
|
||||
.attach_revset_evaluator(commits_to_push)
|
||||
.evaluate_to_commits()?
|
||||
{
|
||||
if is_private(commit?.id())? {
|
||||
skipped_bookmarks.push(bookmark);
|
||||
continue 'bookmark_loop;
|
||||
}
|
||||
}
|
||||
remaining_bookmark_updates.push((bookmark, update));
|
||||
}
|
||||
|
||||
*bookmark_updates = remaining_bookmark_updates;
|
||||
Ok(skipped_bookmarks)
|
||||
}
|
||||
|
||||
/// Validates that the commits that will be pushed are ready (have authorship
|
||||
/// information, are not conflicted, etc.).
|
||||
///
|
||||
@ -465,6 +546,9 @@ fn validate_commits_ready_to_push(
|
||||
error.add_hint(format!(
|
||||
"Configured git.private-commits: '{private_revset_str}'",
|
||||
));
|
||||
error.add_hint(
|
||||
"Consider --skip-private to skip pushing bookmarks with private commits",
|
||||
);
|
||||
}
|
||||
return Err(error);
|
||||
}
|
||||
@ -529,6 +613,7 @@ fn print_commits_ready_to_push(
|
||||
formatter: &mut dyn Formatter,
|
||||
repo: &dyn Repo,
|
||||
bookmark_updates: &[(String, BookmarkPushUpdate)],
|
||||
skipped_private_bookmarks: Vec<String>,
|
||||
) -> io::Result<()> {
|
||||
let to_direction = |old_target: &CommitId, new_target: &CommitId| {
|
||||
assert_ne!(old_target, new_target);
|
||||
@ -584,6 +669,12 @@ fn print_commits_ready_to_push(
|
||||
}
|
||||
}
|
||||
}
|
||||
for bookmark_name in skipped_private_bookmarks {
|
||||
writeln!(
|
||||
formatter,
|
||||
" Skipped bookmark {bookmark_name} with private commits"
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
source: cli/tests/test_generate_md_cli_help.rs
|
||||
description: "AUTO-GENERATED FILE, DO NOT EDIT. This cli reference is generated by a test as an `insta` snapshot. MkDocs includes this snapshot from docs/cli-reference.md."
|
||||
snapshot_kind: text
|
||||
---
|
||||
<!-- BEGIN MARKDOWN-->
|
||||
|
||||
@ -1264,6 +1265,11 @@ Before the command actually moves, creates, or deletes a remote bookmark, it mak
|
||||
* `--allow-empty-description` — Allow pushing commits with empty descriptions
|
||||
* `--allow-private` — Allow pushing commits that are private
|
||||
|
||||
The set of private commits can be configured by the `git.private-commits` setting. The default is `none()`, meaning all commits are eligible to be pushed.
|
||||
* `--skip-private` — Skip pushing bookmarks that depend on private commits
|
||||
|
||||
If this is omitted, the command will error if any of the bookmarks to be pushed depend of private commits.
|
||||
|
||||
The set of private commits can be configured by the `git.private-commits` setting. The default is `none()`, meaning all commits are eligible to be pushed.
|
||||
* `-r`, `--revisions <REVSETS>` — Push bookmarks pointing to these commits (can be repeated)
|
||||
* `-c`, `--change <REVSETS>` — Push this commit by creating a bookmark based on its change ID (can be repeated)
|
||||
|
@ -113,6 +113,7 @@ fn test_git_private_commits_block_pushing() {
|
||||
Error: Won't push commit aa3058ff8663 since it is private
|
||||
Hint: Rejected commit: yqosqzyt aa3058ff main* | (empty) private 1
|
||||
Hint: Configured git.private-commits: 'description(glob:'private*')'
|
||||
Hint: Consider --skip-private to skip pushing bookmarks with private commits
|
||||
[EOF]
|
||||
[exit status: 1]
|
||||
");
|
||||
@ -150,6 +151,7 @@ fn test_git_private_commits_can_be_overridden() {
|
||||
Error: Won't push commit aa3058ff8663 since it is private
|
||||
Hint: Rejected commit: yqosqzyt aa3058ff main* | (empty) private 1
|
||||
Hint: Configured git.private-commits: 'description(glob:'private*')'
|
||||
Hint: Consider --skip-private to skip pushing bookmarks with private commits
|
||||
[EOF]
|
||||
[exit status: 1]
|
||||
");
|
||||
@ -218,6 +220,7 @@ fn test_git_private_commits_not_directly_in_line_block_pushing() {
|
||||
Error: Won't push commit f1253a9b1ea9 since it is private
|
||||
Hint: Rejected commit: yqosqzyt f1253a9b (empty) private 1
|
||||
Hint: Configured git.private-commits: 'description(glob:'private*')'
|
||||
Hint: Consider --skip-private to skip pushing bookmarks with private commits
|
||||
[EOF]
|
||||
[exit status: 1]
|
||||
");
|
||||
@ -360,6 +363,7 @@ fn test_git_private_commits_are_evaluated_separately_for_each_remote() {
|
||||
Error: Won't push commit 36b7ecd11ad9 since it is private
|
||||
Hint: Rejected commit: znkkpsqq 36b7ecd1 (empty) private 1
|
||||
Hint: Configured git.private-commits: 'description(glob:'private*')'
|
||||
Hint: Consider --skip-private to skip pushing bookmarks with private commits
|
||||
[EOF]
|
||||
[exit status: 1]
|
||||
");
|
||||
|
@ -2295,6 +2295,55 @@ fn test_git_push_sign_on_push() {
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_git_push_private_commits() {
|
||||
let (test_env, workspace_root) = set_up();
|
||||
test_env.add_config(r#"git.private-commits = "description('private')""#);
|
||||
test_env
|
||||
.run_jj_in(&workspace_root, ["new", "bookmark1", "-m", "public"])
|
||||
.success();
|
||||
test_env
|
||||
.run_jj_in(&workspace_root, ["bookmark", "set", "bookmark1", "-r@"])
|
||||
.success();
|
||||
test_env
|
||||
.run_jj_in(&workspace_root, ["new", "bookmark2", "-m", "private"])
|
||||
.success();
|
||||
test_env
|
||||
.run_jj_in(&workspace_root, ["bookmark", "set", "bookmark2", "-r@"])
|
||||
.success();
|
||||
|
||||
// fails due to private commits on bookmark2
|
||||
let output = test_env.run_jj_in(&workspace_root, ["git", "push", "--all"]);
|
||||
insta::assert_snapshot!(output, @r"
|
||||
------- stderr -------
|
||||
Error: Won't push commit f98b6dcb4760 since it is private
|
||||
Hint: Rejected commit: znkkpsqq f98b6dcb bookmark2* | (empty) private
|
||||
Hint: Configured git.private-commits: 'description('private')'
|
||||
Hint: Consider --skip-private to skip pushing bookmarks with private commits
|
||||
[EOF]
|
||||
[exit status: 1]
|
||||
");
|
||||
|
||||
// succeeds, but skips bookmark2
|
||||
let output = test_env.run_jj_in(&workspace_root, ["git", "push", "--all", "--skip-private"]);
|
||||
insta::assert_snapshot!(output, @r"
|
||||
------- stderr -------
|
||||
Changes to push to origin:
|
||||
Move forward bookmark bookmark1 from d13ecdbda2a2 to 8677da72ba01
|
||||
Skipped bookmark bookmark2 with private commits
|
||||
[EOF]
|
||||
");
|
||||
|
||||
// succeeds, pushing bookmark2 with its private commits
|
||||
let output = test_env.run_jj_in(&workspace_root, ["git", "push", "--all", "--allow-private"]);
|
||||
insta::assert_snapshot!(output, @r"
|
||||
------- stderr -------
|
||||
Changes to push to origin:
|
||||
Move forward bookmark bookmark2 from 8476341eb395 to f98b6dcb4760
|
||||
[EOF]
|
||||
");
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn get_bookmark_output(test_env: &TestEnvironment, repo_path: &Path) -> CommandOutput {
|
||||
// --quiet to suppress deleted bookmarks hint
|
||||
|
Loading…
x
Reference in New Issue
Block a user