···
1
-
From a85628df2f2bc1b46374de546052f624b09f172b Mon Sep 17 00:00:00 2001
2
-
From: Austin Seipp <aseipp@pobox.com>
3
-
Date: Thu, 18 Jan 2024 00:35:09 -0600
4
-
Subject: [PATCH] cli: basic `jj gerrit upload` implementation
6
-
This implements the most basic workflow for submitting changes to Gerrit,
7
-
through a verb called 'upload'. This verb is intended to be distinct from the word
8
-
'submit', which for Gerrit means 'merge a change into the repository.'
10
-
Given a list of revsets (specified by multiple `-r` options), this will parse
11
-
the footers of every commit, collect them, insert a `Change-Id` (if one doesn't
12
-
already exist), and then push them into the given remote.
14
-
Because the argument is a revset, you may submit entire trees of changes at
15
-
once, including multiple trees of independent changes, e.g.
17
-
jj gerrit upload -r foo:: -r baz::
19
-
There are many other improvements that can be applied on top of this, including
20
-
a ton of consistency and "does this make sense?" checks. However, it is flexible
21
-
and a good starting point, and you can in fact both submit and cycle reviews
22
-
with this interface.
24
-
Signed-off-by: Austin Seipp <aseipp@pobox.com>
27
-
cli/src/commands/gerrit/mod.rs | 57 +++++
28
-
cli/src/commands/gerrit/upload.rs | 384 +++++++++++++++++++++++++++++++
29
-
cli/src/commands/mod.rs | 7 +
30
-
cli/src/config-schema.json | 14 ++
31
-
cli/tests/cli-reference@.md.snap | 38 +++
32
-
cli/tests/runner.rs | 1 +
33
-
cli/tests/test_gerrit_upload.rs | 89 +++++++
34
-
8 files changed, 592 insertions(+)
35
-
create mode 100644 cli/src/commands/gerrit/mod.rs
36
-
create mode 100644 cli/src/commands/gerrit/upload.rs
37
-
create mode 100644 cli/tests/test_gerrit_upload.rs
39
-
diff --git a/CHANGELOG.md b/CHANGELOG.md
40
-
index 267b5ed303..9bc1029fcf 100644
43
-
@@ -107,6 +107,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
44
-
* The new command `jj redo` can progressively redo operations that were
45
-
previously undone by multiple calls to `jj undo`.
47
-
+* Gerrit support implemented with the new command `jj gerrit upload`
51
-
* `jj git clone` now correctly fetches all tags, unless `--fetch-tags` is
52
-
diff --git a/cli/src/commands/gerrit/mod.rs b/cli/src/commands/gerrit/mod.rs
53
-
new file mode 100644
54
-
index 0000000000..60abdb6702
56
-
+++ b/cli/src/commands/gerrit/mod.rs
58
-
+// Copyright 2024 The Jujutsu Authors
60
-
+// Licensed under the Apache License, Version 2.0 (the "License");
61
-
+// you may not use this file except in compliance with the License.
62
-
+// You may obtain a copy of the License at
64
-
+// https://www.apache.org/licenses/LICENSE-2.0
66
-
+// Unless required by applicable law or agreed to in writing, software
67
-
+// distributed under the License is distributed on an "AS IS" BASIS,
68
-
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
69
-
+// See the License for the specific language governing permissions and
70
-
+// limitations under the License.
72
-
+use std::fmt::Debug;
74
-
+use clap::Subcommand;
76
-
+use crate::cli_util::CommandHelper;
77
-
+use crate::command_error::CommandError;
78
-
+use crate::commands::gerrit;
81
-
+/// Interact with Gerrit Code Review.
82
-
+#[derive(Subcommand, Clone, Debug)]
83
-
+pub enum GerritCommand {
84
-
+ /// Upload changes to Gerrit for code review, or update existing changes.
86
-
+ /// Uploading in a set of revisions to Gerrit creates a single "change" for
87
-
+ /// each revision included in the revset. This change is then available for
88
-
+ /// review on your Gerrit instance.
90
-
+ /// This command modifies each commit in the revset to include a `Change-Id`
91
-
+ /// footer in its commit message if one does not already exist. Note that
92
-
+ /// this ID is NOT compatible with jj IDs, and is Gerrit-specific.
94
-
+ /// If a change already exists for a given revision (i.e. it contains the
95
-
+ /// same `Change-Id`), this command will update the contents of the existing
96
-
+ /// change to match.
98
-
+ /// Note: this command takes 1-or-more revsets arguments, each of which can
99
-
+ /// resolve to multiple revisions; so you may post trees or ranges of
100
-
+ /// commits to Gerrit for review all at once.
101
-
+ Upload(gerrit::upload::UploadArgs),
104
-
+pub fn cmd_gerrit(
106
-
+ command: &CommandHelper,
107
-
+ subcommand: &GerritCommand,
108
-
+) -> Result<(), CommandError> {
109
-
+ match subcommand {
110
-
+ GerritCommand::Upload(review) => gerrit::upload::cmd_upload(ui, command, review),
115
-
diff --git a/cli/src/commands/gerrit/upload.rs b/cli/src/commands/gerrit/upload.rs
116
-
new file mode 100644
117
-
index 0000000000..88c3ca5e97
119
-
+++ b/cli/src/commands/gerrit/upload.rs
121
-
+// Copyright 2024 The Jujutsu Authors
123
-
+// Licensed under the Apache License, Version 2.0 (the "License");
124
-
+// you may not use this file except in compliance with the License.
125
-
+// You may obtain a copy of the License at
127
-
+// https://www.apache.org/licenses/LICENSE-2.0
129
-
+// Unless required by applicable law or agreed to in writing, software
130
-
+// distributed under the License is distributed on an "AS IS" BASIS,
131
-
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132
-
+// See the License for the specific language governing permissions and
133
-
+// limitations under the License.
135
-
+use std::fmt::Debug;
136
-
+use std::io::Write as _;
138
-
+use std::sync::Arc;
141
-
+use indexmap::IndexMap;
142
-
+use itertools::Itertools as _;
143
-
+use jj_lib::backend::CommitId;
144
-
+use jj_lib::commit::Commit;
145
-
+use jj_lib::commit::CommitIteratorExt as _;
146
-
+use jj_lib::git::GitRefUpdate;
147
-
+use jj_lib::git::{self};
148
-
+use jj_lib::object_id::ObjectId as _;
149
-
+use jj_lib::repo::Repo as _;
150
-
+use jj_lib::revset::RevsetExpression;
151
-
+use jj_lib::settings::UserSettings;
152
-
+use jj_lib::store::Store;
153
-
+use jj_lib::trailer::Trailer;
154
-
+use jj_lib::trailer::parse_description_trailers;
156
-
+use crate::cli_util::CommandHelper;
157
-
+use crate::cli_util::RevisionArg;
158
-
+use crate::cli_util::short_commit_hash;
159
-
+use crate::command_error::CommandError;
160
-
+use crate::command_error::internal_error;
161
-
+use crate::command_error::user_error;
162
-
+use crate::command_error::user_error_with_hint;
163
-
+use crate::command_error::user_error_with_message;
164
-
+use crate::git_util::with_remote_git_callbacks;
165
-
+use crate::ui::Ui;
167
-
+#[derive(clap::Args, Clone, Debug)]
168
-
+pub struct UploadArgs {
169
-
+ /// The revset, selecting which commits are sent in to Gerrit. This can be
170
-
+ /// any arbitrary set of commits; they will be modified to include a
171
-
+ /// `Change-Id` footer if one does not already exist, and then sent off to
172
-
+ /// Gerrit for review.
173
-
+ #[arg(long, short = 'r')]
174
-
+ revisions: Vec<RevisionArg>,
176
-
+ /// The location where your changes are intended to land. This should be
177
-
+ /// an upstream branch.
178
-
+ #[arg(long = "remote-branch", short = 'b')]
179
-
+ remote_branch: Option<String>,
181
-
+ /// The Gerrit remote to push to. Can be configured with the `gerrit.remote`
182
-
+ /// repository option as well. This is typically a full SSH URL for your
183
-
+ /// Gerrit instance.
185
-
+ remote: Option<String>,
187
-
+ /// If true, do not actually add `Change-Id`s to commits, and do not push
188
-
+ /// the changes to Gerrit.
189
-
+ #[arg(long = "dry-run", short = 'n')]
193
-
+/// calculate push remote. The logic is:
194
-
+/// 1. If the user specifies `--remote`, use that
195
-
+/// 2. If the user has 'gerrit.remote' configured, use that
196
-
+/// 3. If there is a default push remote, use that
197
-
+/// 4. If the user has a remote named 'gerrit', use that
198
-
+/// 5. otherwise, bail out
199
-
+fn calculate_push_remote(
200
-
+ store: &Arc<Store>,
201
-
+ config: &UserSettings,
202
-
+ remote: Option<String>,
203
-
+) -> Result<String, CommandError> {
204
-
+ let git_repo = git::get_git_repo(store)?; // will fail if not a git repo
205
-
+ let remotes = git_repo.remote_names();
208
-
+ if let Some(remote) = remote {
209
-
+ if remotes.contains(BStr::new(&remote)) {
210
-
+ return Ok(remote);
212
-
+ return Err(user_error(format!(
213
-
+ "The remote '{remote}' (specified via `--remote`) does not exist",
218
-
+ if let Ok(remote) = config.get_string("gerrit.default-remote") {
219
-
+ if remotes.contains(BStr::new(&remote)) {
220
-
+ return Ok(remote);
222
-
+ return Err(user_error(format!(
223
-
+ "The remote '{remote}' (configured via `gerrit.default-remote`) does not exist",
228
-
+ if let Some(remote) = git_repo.remote_default_name(gix::remote::Direction::Push) {
229
-
+ return Ok(remote.to_string());
233
-
+ if remotes.iter().any(|r| **r == "gerrit") {
234
-
+ return Ok("gerrit".to_owned());
239
-
+ "No remote specified, and no 'gerrit' remote was found",
243
-
+/// Determine what Gerrit ref and remote to use. The logic is:
245
-
+/// 1. If the user specifies `--remote-branch branch`, use that
246
-
+/// 2. If the user has 'gerrit.default-remote-branch' configured, use that
247
-
+/// 3. Otherwise, bail out
248
-
+fn calculate_push_ref(
249
-
+ config: &UserSettings,
250
-
+ remote_branch: Option<String>,
251
-
+) -> Result<String, CommandError> {
253
-
+ if let Some(remote_branch) = remote_branch {
254
-
+ return Ok(remote_branch);
258
-
+ if let Ok(branch) = config.get_string("gerrit.default-remote-branch") {
259
-
+ return Ok(branch);
264
-
+ "No target branch specified via --remote-branch, and no 'gerrit.default-remote-branch' \
269
-
+pub fn cmd_upload(ui: &mut Ui, command: &CommandHelper, upload: &UploadArgs) -> Result<(), CommandError> {
270
-
+ let mut workspace_command = command.workspace_helper(ui)?;
272
-
+ let revisions: Vec<_> = workspace_command
273
-
+ .parse_union_revsets(ui, &upload.revisions)?
274
-
+ .evaluate_to_commits()?
276
-
+ if revisions.is_empty() {
277
-
+ writeln!(ui.status(), "No revisions to upload.")?;
283
-
+ .any(|commit| commit.id() == workspace_command.repo().store().root_commit_id())
285
-
+ return Err(user_error("Cannot upload the virtual 'root()' commit"));
288
-
+ workspace_command.check_rewritable(revisions.iter().ids())?;
290
-
+ // If you have the changes main -> A -> B, and then run `jj gerrit upload B`,
291
-
+ // then that uploads both A and B. Thus, we need to ensure that A also
292
-
+ // has a Change-ID.
293
-
+ // We make an assumption here that all immutable commits already have a
295
-
+ let to_upload: Vec<Commit> = workspace_command
296
-
+ .attach_revset_evaluator(
297
-
+ // I'm unsure, but this *might* have significant performance
298
-
+ // implications. If so, we can change it to a maximum depth.
299
-
+ Rc::new(RevsetExpression::Difference(
300
-
+ // Unfortunately, DagRange{root: immutable_heads, heads: commits}
301
-
+ // doesn't work if you're, for example, working on top of an
302
-
+ // immutable commit that isn't in immutable_heads().
303
-
+ Rc::new(RevsetExpression::Ancestors {
304
-
+ heads: RevsetExpression::commits(
305
-
+ revisions.iter().ids().cloned().collect::<Vec<_>>(),
307
-
+ generation: jj_lib::revset::GENERATION_RANGE_FULL,
308
-
+ parents_range: jj_lib::revset::PARENTS_RANGE_FULL,
310
-
+ workspace_command.env().immutable_expression().clone(),
313
-
+ .evaluate_to_commits()?
316
-
+ let mut tx = workspace_command.start_transaction();
317
-
+ let base_repo = tx.base_repo().clone();
318
-
+ let store = base_repo.store();
320
-
+ let old_heads = base_repo
322
-
+ .heads(&mut revisions.iter().ids())
323
-
+ .map_err(internal_error)?;
325
-
+ let git_settings = command.settings().git_settings()?;
326
-
+ let remote = calculate_push_remote(store, command.settings(), upload.remote.clone())?;
327
-
+ let remote_branch = calculate_push_ref(command.settings(), upload.remote_branch.clone())?;
329
-
+ // immediately error and reject any discardable commits, i.e. the
331
-
+ for commit in &to_upload {
332
-
+ if commit.is_discardable(tx.repo_mut())? {
333
-
+ return Err(user_error_with_hint(
335
-
+ "Refusing to upload commit {} because it is an empty commit with no description",
336
-
+ short_commit_hash(commit.id())
338
-
+ "Perhaps you squashed then ran upload? Maybe you meant to upload the parent commit \
339
-
+ instead (eg. @-)",
344
-
+ let mut old_to_new: IndexMap<CommitId, Commit> = IndexMap::new();
345
-
+ for commit_id in to_upload.iter().map(|c| c.id()).rev() {
346
-
+ let original_commit = store.get_commit(commit_id).unwrap();
347
-
+ let description = original_commit.description().to_owned();
348
-
+ let trailers = parse_description_trailers(&description);
350
-
+ let change_id_trailers: Vec<&Trailer> = trailers
352
-
+ .filter(|trailer| trailer.key == "Change-Id")
355
-
+ // There shouldn't be multiple change-ID fields. So just error out if
357
-
+ if change_id_trailers.len() > 1 {
358
-
+ return Err(user_error(format!(
359
-
+ "multiple Change-Id footers in commit {}",
360
-
+ short_commit_hash(commit_id)
364
-
+ // The user can choose to explicitly set their own change-ID to
365
-
+ // override the default change-ID based on the jj change-ID.
366
-
+ if let Some(trailer) = change_id_trailers.first() {
367
-
+ // Check the change-id format is correct.
368
-
+ if trailer.value.len() != 41 || !trailer.value.starts_with('I') {
369
-
+ // Intentionally leave the invalid change IDs as-is.
371
-
+ ui.warning_default(),
372
-
+ "warning: invalid Change-Id footer in commit {}",
373
-
+ short_commit_hash(original_commit.id()),
377
-
+ // map the old commit to itself
378
-
+ old_to_new.insert(original_commit.id().clone(), original_commit.clone());
382
-
+ // Gerrit change id is 40 chars, jj change id is 32, so we need padding.
383
-
+ // To be consistent with `format_gerrit_change_id_trailer``, we pad with
384
-
+ // 6a6a6964 (hex of "jjid").
385
-
+ let gerrit_change_id = format!("I6a6a6964{}", original_commit.change_id().hex());
387
-
+ let new_description = format!(
388
-
+ "{}{}Change-Id: {}\n",
389
-
+ description.trim(),
390
-
+ if trailers.is_empty() { "\n\n" } else { "\n" },
394
-
+ let new_parents = original_commit
397
-
+ let p = parent.unwrap();
398
-
+ if let Some(rewritten_parent) = old_to_new.get(p.id()) {
408
-
+ // rewrite the set of parents to point to the commits that were
409
-
+ // previously rewritten in toposort order
411
-
+ // TODO FIXME (aseipp): this whole dance with toposorting, calculating
412
-
+ // new_parents, and then doing rewrite_commit is roughly equivalent to
413
-
+ // what we do in duplicate.rs as well. we should probably refactor this?
414
-
+ let new_commit = tx
416
-
+ .rewrite_commit(&original_commit)
417
-
+ .set_description(new_description)
418
-
+ .set_parents(new_parents)
419
-
+ // Set the timestamp back to the timestamp of the original commit.
420
-
+ // Otherwise, `jj gerrit upload @ && jj gerrit upload @` will upload
421
-
+ // two patchsets with the only difference being the timestamp.
422
-
+ .set_committer(original_commit.committer().clone())
423
-
+ .set_author(original_commit.author().clone())
426
-
+ old_to_new.insert(original_commit.id().clone(), new_commit.clone());
428
-
+ writeln!(ui.stderr())?;
430
-
+ let remote_ref = format!("refs/for/{remote_branch}");
433
-
+ "Found {} heads to push to Gerrit (remote '{}'), target branch '{}'",
439
-
+ writeln!(ui.stderr())?;
441
-
+ // NOTE (aseipp): because we are pushing everything to the same remote ref,
442
-
+ // we have to loop and push each commit one at a time, even though
443
-
+ // push_updates in theory supports multiple GitRefUpdates at once, because
444
-
+ // we obviously can't push multiple heads to the same ref.
445
-
+ for head in &old_heads {
449
-
+ if upload.dry_run {
450
-
+ "Dry-run: Would push "
455
-
+ // We have to write the old commit here, because the until we finish
456
-
+ // the transaction (which we don't), the new commit is labelled as
458
-
+ tx.base_workspace_helper().write_commit_summary(
459
-
+ ui.stderr_formatter().as_mut(),
460
-
+ &store.get_commit(head).unwrap(),
462
-
+ writeln!(ui.stderr())?;
464
-
+ if upload.dry_run {
468
-
+ let new_commit = store
469
-
+ .get_commit(old_to_new.get(head).unwrap().id())
472
-
+ // how do we get better errors from the remote? 'git push' tells us
473
-
+ // about rejected refs AND ALSO '(nothing changed)' when there are no
474
-
+ // changes to push, but we don't get that here.
475
-
+ with_remote_git_callbacks(ui, |cb| {
476
-
+ git::push_updates(
481
-
+ qualified_name: remote_ref.clone().into(),
482
-
+ expected_current_target: None,
483
-
+ new_target: Some(new_commit.id().clone()),
488
-
+ // Despite the fact that a manual git push will error out with 'no new
489
-
+ // changes' if you're up to date, this git backend appears to silently
490
-
+ // succeed - no idea why.
491
-
+ // It'd be nice if we could distinguish this. We should ideally succeed,
492
-
+ // but give the user a warning.
493
-
+ .map_err(|err| match err {
494
-
+ git::GitPushError::NoSuchRemote(_)
495
-
+ | git::GitPushError::RemoteName(_)
496
-
+ | git::GitPushError::UnexpectedBackend(_) => user_error(err),
497
-
+ git::GitPushError::Subprocess(_) => {
498
-
+ user_error_with_message("Internal git error while pushing to gerrit", err)
505
-
diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs
506
-
index cdf3c9c3ad..cb7b4ca185 100644
507
-
--- a/cli/src/commands/mod.rs
508
-
+++ b/cli/src/commands/mod.rs
509
-
@@ -30,6 +30,8 @@ mod evolog;
512
-
#[cfg(feature = "git")]
514
-
+#[cfg(feature = "git")]
518
-
@@ -115,6 +117,9 @@ enum Command {
520
-
#[cfg(feature = "git")]
521
-
#[command(subcommand)]
522
-
+ Gerrit(gerrit::GerritCommand),
523
-
+ #[cfg(feature = "git")]
524
-
+ #[command(subcommand)]
525
-
Git(git::GitCommand),
526
-
Help(help::HelpArgs),
527
-
Interdiff(interdiff::InterdiffArgs),
528
-
@@ -180,6 +185,8 @@ pub fn run_command(ui: &mut Ui, command_helper: &CommandHelper) -> Result<(), Co
529
-
Command::File(args) => file::cmd_file(ui, command_helper, args),
530
-
Command::Fix(args) => fix::cmd_fix(ui, command_helper, args),
531
-
#[cfg(feature = "git")]
532
-
+ Command::Gerrit(sub_args) => gerrit::cmd_gerrit(ui, command_helper, sub_args),
533
-
+ #[cfg(feature = "git")]
534
-
Command::Git(args) => git::cmd_git(ui, command_helper, args),
535
-
Command::Help(args) => help::cmd_help(ui, command_helper, args),
536
-
Command::Interdiff(args) => interdiff::cmd_interdiff(ui, command_helper, args),
537
-
diff --git a/cli/src/config-schema.json b/cli/src/config-schema.json
538
-
index 887c34e2ba..d15b334ecf 100644
539
-
--- a/cli/src/config-schema.json
540
-
+++ b/cli/src/config-schema.json
541
-
@@ -490,6 +490,20 @@
546
-
+ "type": "object",
547
-
+ "description": "Settings for interacting with Gerrit",
549
-
+ "default-remote": {
550
-
+ "type": "string",
551
-
+ "description": "The Gerrit remote to interact with"
553
-
+ "default-remote-branch": {
554
-
+ "type": "string",
555
-
+ "description": "The default branch to propose changes for"
561
-
"description": "Tables of custom options to pass to the given merge tool (selected in ui.merge-editor)",
562
-
diff --git a/cli/tests/cli-reference@.md.snap b/cli/tests/cli-reference@.md.snap
563
-
index a97a0ffc55..dff8bcbe37 100644
564
-
--- a/cli/tests/cli-reference@.md.snap
565
-
+++ b/cli/tests/cli-reference@.md.snap
566
-
@@ -45,6 +45,8 @@ This document contains the help content for the `jj` command-line program.
567
-
* [`jj file track`↴](#jj-file-track)
568
-
* [`jj file untrack`↴](#jj-file-untrack)
569
-
* [`jj fix`↴](#jj-fix)
570
-
+* [`jj gerrit`↴](#jj-gerrit)
571
-
+* [`jj gerrit upload`↴](#jj-gerrit-upload)
572
-
* [`jj git`↴](#jj-git)
573
-
* [`jj git clone`↴](#jj-git-clone)
574
-
* [`jj git export`↴](#jj-git-export)
575
-
@@ -139,6 +141,7 @@ To get started, see the tutorial [`jj help -k tutorial`].
576
-
* `evolog` — Show how a change has evolved over time
577
-
* `file` — File operations
578
-
* `fix` — Update files with formatting fixes or other changes
579
-
+* `gerrit` — Interact with Gerrit Code Review
580
-
* `git` — Commands for working with Git remotes and the underlying Git repo
581
-
* `help` — Print this message or the help of the given subcommand(s)
582
-
* `interdiff` — Compare the changes of two commits
583
-
@@ -1177,6 +1180,41 @@ output of the first tool.
589
-
+Interact with Gerrit Code Review
591
-
+**Usage:** `jj gerrit <COMMAND>`
593
-
+###### **Subcommands:**
595
-
+* `upload` — Upload changes to Gerrit for code review, or update existing changes
599
-
+## `jj gerrit upload`
601
-
+Upload changes to Gerrit for code review, or update existing changes.
603
-
+Uploading in a set of revisions to Gerrit creates a single "change" for each revision included in the revset. This change is then available for review on your Gerrit instance.
605
-
+This command modifies each commit in the revset to include a `Change-Id` footer in its commit message if one does not already exist. Note that this ID is NOT compatible with jj IDs, and is Gerrit-specific.
607
-
+If a change already exists for a given revision (i.e. it contains the same `Change-Id`), this command will update the contents of the existing change to match.
609
-
+Note: this command takes 1-or-more revsets arguments, each of which can resolve to multiple revisions; so you may post trees or ranges of commits to Gerrit for review all at once.
611
-
+**Usage:** `jj gerrit upload [OPTIONS]`
613
-
+###### **Options:**
615
-
+* `-r`, `--revisions <REVISIONS>` — The revset, selecting which commits are sent in to Gerrit. This can be any arbitrary set of commits; they will be modified to include a `Change-Id` footer if one does not already exist, and then sent off to Gerrit for review
616
-
+* `-b`, `--remote-branch <REMOTE_BRANCH>` — The location where your changes are intended to land. This should be an upstream branch
617
-
+* `--remote <REMOTE>` — The Gerrit remote to push to. Can be configured with the `gerrit.remote` repository option as well. This is typically a full SSH URL for your Gerrit instance
618
-
+* `-n`, `--dry-run` — If true, do not actually add `Change-Id`s to commits, and do not push the changes to Gerrit
624
-
Commands for working with Git remotes and the underlying Git repo
625
-
diff --git a/cli/tests/runner.rs b/cli/tests/runner.rs
626
-
index 88c1ca2319..f228da5e70 100644
627
-
--- a/cli/tests/runner.rs
628
-
+++ b/cli/tests/runner.rs
629
-
@@ -37,6 +37,7 @@ mod test_file_show_command;
630
-
mod test_file_track_untrack_commands;
631
-
mod test_fix_command;
632
-
mod test_generate_md_cli_help;
633
-
+mod test_gerrit_upload;
634
-
mod test_git_clone;
635
-
mod test_git_colocated;
636
-
mod test_git_fetch;
637
-
diff --git a/cli/tests/test_gerrit_upload.rs b/cli/tests/test_gerrit_upload.rs
638
-
new file mode 100644
639
-
index 0000000000..71543cedd8
641
-
+++ b/cli/tests/test_gerrit_upload.rs
643
-
+// Copyright 2025 The Jujutsu Authors
645
-
+// Licensed under the Apache License, Version 2.0 (the "License");
646
-
+// you may not use this file except in compliance with the License.
647
-
+// You may obtain a copy of the License at
649
-
+// https://www.apache.org/licenses/LICENSE-2.0
651
-
+// Unless required by applicable law or agreed to in writing, software
652
-
+// distributed under the License is distributed on an "AS IS" BASIS,
653
-
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
654
-
+// See the License for the specific language governing permissions and
655
-
+// limitations under the License.
657
-
+use crate::common::TestEnvironment;
658
-
+use crate::common::create_commit;
661
-
+fn test_gerrit_upload_dryrun() {
662
-
+ let test_env = TestEnvironment::default();
663
-
+ test_env.run_jj_in(".", ["git", "init", "repo"]).success();
664
-
+ let work_dir = test_env.work_dir("repo");
666
-
+ create_commit(&work_dir, "a", &[]);
667
-
+ create_commit(&work_dir, "b", &["a"]);
668
-
+ create_commit(&work_dir, "c", &["a"]);
669
-
+ let output = work_dir.run_jj(["gerrit", "upload", "-r", "b"]);
670
-
+ insta::assert_snapshot!(output, @r###"
671
-
+ ------- stderr -------
672
-
+ Error: No remote specified, and no 'gerrit' remote was found
677
-
+ // With remote specified but.
678
-
+ test_env.add_config(r#"gerrit.default-remote="origin""#);
679
-
+ let output = work_dir.run_jj(["gerrit", "upload", "-r", "b"]);
680
-
+ insta::assert_snapshot!(output, @r###"
681
-
+ ------- stderr -------
682
-
+ Error: The remote 'origin' (configured via `gerrit.default-remote`) does not exist
687
-
+ let output = work_dir.run_jj(["gerrit", "upload", "-r", "b", "--remote=origin"]);
688
-
+ insta::assert_snapshot!(output, @r###"
689
-
+ ------- stderr -------
690
-
+ Error: The remote 'origin' (specified via `--remote`) does not exist
695
-
+ let output = work_dir.run_jj([
700
-
+ "http://example.com/repo/foo",
702
-
+ insta::assert_snapshot!(output, @"");
703
-
+ let output = work_dir.run_jj(["gerrit", "upload", "-r", "b", "--remote=origin"]);
704
-
+ insta::assert_snapshot!(output, @r###"
705
-
+ ------- stderr -------
706
-
+ Error: No target branch specified via --remote-branch, and no 'gerrit.default-remote-branch' was found
711
-
+ test_env.add_config(r#"gerrit.default-remote-branch="main""#);
712
-
+ let output = work_dir.run_jj(["gerrit", "upload", "-r", "b", "--dry-run"]);
713
-
+ insta::assert_snapshot!(output, @r###"
714
-
+ ------- stderr -------
716
-
+ Found 1 heads to push to Gerrit (remote 'origin'), target branch 'main'
718
-
+ Dry-run: Would push zsuskuln 123b4d91 b | b
722
-
+ let output = work_dir.run_jj(["gerrit", "upload", "-r", "b", "--dry-run", "-b", "other"]);
723
-
+ insta::assert_snapshot!(output, @r###"
724
-
+ ------- stderr -------
726
-
+ Found 1 heads to push to Gerrit (remote 'origin'), target branch 'other'
728
-
+ Dry-run: Would push zsuskuln 123b4d91 b | b