chore: bump npins dependencies #90

deleted
opened by a.starrysky.fyi targeting main from private/minion/push-yoovuypynnmm

I'm here all day until we have auto-PRs set up for tangled

Changed files
+36 -790
packetmix
+1 -1
packetmix/homes/development/jujutsu.nix
··· 24 24 ingredient.scriptfs.enable = true; # used for signing configuration 25 25 programs.jujutsu = { 26 26 enable = true; 27 - package = project.packages.jujutsu.result.${system}; 27 + package = project.inputs.nixos-unstable.result.${system}.jujutsu; 28 28 settings = { 29 29 aliases = { 30 30 init = [
+35 -35
packetmix/npins/sources.json
··· 61 61 }, 62 62 "branch": "main", 63 63 "submodules": false, 64 - "revision": "d239f0bcf5e7ed391c8ced32e13aaa4c1324a757", 65 - "url": "https://github.com/bluesky-social/atproto/archive/d239f0bcf5e7ed391c8ced32e13aaa4c1324a757.tar.gz", 66 - "hash": "sha256-PzZhBAGwrjItNGgUTXsP3fceEXlxKWj5+OsaSnK3UbI=" 64 + "revision": "4c15fb47cec26060bff2e710e95869a90c9d7fdd", 65 + "url": "https://github.com/bluesky-social/atproto/archive/4c15fb47cec26060bff2e710e95869a90c9d7fdd.tar.gz", 66 + "hash": "sha256-FhWxdePEeBX2At70YdmjiYT8oXIvJ+ry3VdtfcuBUBU=" 67 67 }, 68 68 "catppuccin": { 69 69 "type": "Git", ··· 74 74 }, 75 75 "branch": "main", 76 76 "submodules": false, 77 - "revision": "5f7dc8bab8af6ba612ef8dc7cd44e38ba6cfd51a", 78 - "url": "https://github.com/catppuccin/nix/archive/5f7dc8bab8af6ba612ef8dc7cd44e38ba6cfd51a.tar.gz", 79 - "hash": "sha256-aHN6dAD72IsNvNlzU3nbV4DJRb1qPvURgWIzHeYsBbc=" 77 + "revision": "eeada12912d80d04733383d231a9d66172858718", 78 + "url": "https://github.com/catppuccin/nix/archive/eeada12912d80d04733383d231a9d66172858718.tar.gz", 79 + "hash": "sha256-2fzYq/m2PXie5WZO5LhyiZrTIUdUFp1SCLZAwvPL5xo=" 80 80 }, 81 81 "collabora-gtimelog": { 82 82 "type": "Git", ··· 102 102 "version_upper_bound": null, 103 103 "release_prefix": null, 104 104 "submodules": false, 105 - "version": "v1.19.15", 106 - "revision": "daba1ab7bdf57377fb04e174a7527406c422578d", 107 - "url": "https://api.github.com/repos/9001/copyparty/tarball/refs/tags/v1.19.15", 108 - "hash": "sha256-FjS5UA4jWko/2+lkyzGOVhhZOEA8oTIoQ7GIcRj3ebM=" 105 + "version": "v1.19.16", 106 + "revision": "cd3feaac86123ecf8c73b3201b27da40faf9110c", 107 + "url": "https://api.github.com/repos/9001/copyparty/tarball/refs/tags/v1.19.16", 108 + "hash": "sha256-ZpQInliAuJIT4DJ/X3U0Tjvg+hweqU9FlRfR/UF9YFo=" 109 109 }, 110 110 "core": { 111 111 "type": "Git", ··· 154 154 }, 155 155 "branch": "master", 156 156 "submodules": false, 157 - "revision": "004753ae6b04c4b18aa07192c1106800aaacf6c3", 158 - "url": "https://github.com/nix-community/home-manager/archive/004753ae6b04c4b18aa07192c1106800aaacf6c3.tar.gz", 159 - "hash": "sha256-CcT3QvZ74NGfM+lSOILcCEeU+SnqXRvl1XCRHenZ0Us=" 157 + "revision": "1a09eb84fa9e33748432a5253102d01251f72d6d", 158 + "url": "https://github.com/nix-community/home-manager/archive/1a09eb84fa9e33748432a5253102d01251f72d6d.tar.gz", 159 + "hash": "sha256-uqbhyXtqMbYIiMqVqUhNdSuh9AEEkiasoK3mIPIVRhk=" 160 160 }, 161 161 "impermanence": { 162 162 "type": "Git", ··· 197 197 }, 198 198 "branch": "main", 199 199 "submodules": false, 200 - "revision": "1c4e77387ad42686517850da5fb98d8cac11adb7", 201 - "url": "https://git.lix.systems/lix-project/lix/archive/1c4e77387ad42686517850da5fb98d8cac11adb7.tar.gz", 202 - "hash": "sha256-bF0zdh6b56OBlYQ3Lsu+nFk+L5F28Mw7xzeYQU9V964=" 200 + "revision": "42691f0d943b43842e69b76608cbe4416e35e94e", 201 + "url": "https://git.lix.systems/lix-project/lix/archive/42691f0d943b43842e69b76608cbe4416e35e94e.tar.gz", 202 + "hash": "sha256-PthY1vlykNZmuKHQS01Qunut1fdM0imk5Myg3hcr8Oc=" 203 203 }, 204 204 "lix-module": { 205 205 "type": "Git", ··· 298 298 }, 299 299 "branch": "main", 300 300 "submodules": false, 301 - "revision": "d425163158a96a26924597574316a627d2e982aa", 302 - "url": "https://github.com/sodiboo/niri-flake/archive/d425163158a96a26924597574316a627d2e982aa.tar.gz", 303 - "hash": "sha256-xhUr1oMQwL/8h8xnPi5QxUHRFDHoCofhw8Jy7qTD4BY=" 301 + "revision": "0d12957ebc8e272e3fc3830549edbb1ad63c34d4", 302 + "url": "https://github.com/sodiboo/niri-flake/archive/0d12957ebc8e272e3fc3830549edbb1ad63c34d4.tar.gz", 303 + "hash": "sha256-ZGEBkK8ZQ370ifJO+1TOQ87m9Gmj52uzqcqysd/lolI=" 304 304 }, 305 305 "nix-index-database": { 306 306 "type": "Git", ··· 311 311 }, 312 312 "branch": "main", 313 313 "submodules": false, 314 - "revision": "ec7a78cb0e098832d8acac091a4df393259c4839", 315 - "url": "https://github.com/nix-community/nix-index-database/archive/ec7a78cb0e098832d8acac091a4df393259c4839.tar.gz", 316 - "hash": "sha256-WZf+FhebP2/1pK2np5xj/NuDjD6fXK2BHnq/tPUN18o=" 314 + "revision": "0ca69684091aa3a6b1fe994c4afeff305b15e915", 315 + "url": "https://github.com/nix-community/nix-index-database/archive/0ca69684091aa3a6b1fe994c4afeff305b15e915.tar.gz", 316 + "hash": "sha256-8NI1SqntLfKl6Q0Luemc3aIboezSJElofUrqipF5g78=" 317 317 }, 318 318 "nix-monitored": { 319 319 "type": "Git", ··· 331 331 "nixos-unstable": { 332 332 "type": "Channel", 333 333 "name": "nixos-unstable", 334 - "url": "https://releases.nixos.org/nixos/unstable/nixos-25.11pre868392.e9f00bd89398/nixexprs.tar.xz", 335 - "hash": "sha256-Lz9jvhswQu/niKVttNvOds0w+OS+2x63NivPVJng5G4=" 334 + "url": "https://releases.nixos.org/nixos/unstable/nixos-25.11pre873798.c9b6fb798541/nixexprs.tar.xz", 335 + "hash": "sha256-Dzw+6wNlteBG6go9KYZyVjpl6CkzjKzmXrneAF5mqI0=" 336 336 }, 337 337 "nixpkgs": { 338 338 "type": "Channel", 339 339 "name": "nixos-25.05", 340 - "url": "https://releases.nixos.org/nixos/25.05/nixos-25.05.810656.5b5be50345d4/nixexprs.tar.xz", 341 - "hash": "sha256-1t/IP8E71w1r6aXjX/GBU/ZbbJo3GrSuUjQNYH11gJU=" 340 + "url": "https://releases.nixos.org/nixos/25.05/nixos-25.05.810859.20c4598c84a6/nixexprs.tar.xz", 341 + "hash": "sha256-1WvIF4N7Jj1ppU9ocZZBpsQdiVsHVzVxNt9L2+7FwPM=" 342 342 }, 343 343 "npins": { 344 344 "type": "Git", ··· 349 349 }, 350 350 "branch": "master", 351 351 "submodules": false, 352 - "revision": "e49bcf58d66fa7f586aff687feae72c23e429672", 353 - "url": "https://github.com/andir/npins/archive/e49bcf58d66fa7f586aff687feae72c23e429672.tar.gz", 354 - "hash": "sha256-ZLrVWa7WU+IUXPAd6mR7B9c6FDZSiGgW8ClfW0SxbMs=" 352 + "revision": "1c1ebf233c30e14ff34472c609ded8eb988753d8", 353 + "url": "https://github.com/andir/npins/archive/1c1ebf233c30e14ff34472c609ded8eb988753d8.tar.gz", 354 + "hash": "sha256-yCaqGejBcWoibCRj2vRrlj5uPI7lyCGS6ivCiGeKNSo=" 355 355 }, 356 356 "scriptfs": { 357 357 "type": "Git", ··· 374 374 }, 375 375 "branch": "master", 376 376 "submodules": false, 377 - "revision": "32b7c17e266e0da0f0e1e6e6a3821cd8dd3b8362", 377 + "revision": "eaa11ecb2112c90113a8ee49a8aa05be327b13ad", 378 378 "url": null, 379 - "hash": "sha256-Q/UnCYR8NgO5xoevJJxyCa3ZQnB/5c62F1IBxFKbF3Q=" 379 + "hash": "sha256-lLyX1f4fSG1IYsjcsUKKCAOHpa3ZTFexJMRc/0K2g3c=" 380 380 }, 381 381 "treefmt-nix": { 382 382 "type": "Git", ··· 402 402 "version_upper_bound": null, 403 403 "release_prefix": null, 404 404 "submodules": false, 405 - "version": "v1.1.0", 406 - "revision": "226f7f5c2d66be2acc5617cf37943075cb2ba693", 407 - "url": "https://api.github.com/repos/abenz1267/walker/tarball/refs/tags/v1.1.0", 408 - "hash": "sha256-fjd8N5lnZ1ee3xRCUQ2eHXiIncwe5DGj8N3MDfrYMeE=" 405 + "version": "v2.2.0", 406 + "revision": "cbacfea97aaf2a3408db45e499411df19f29a0c1", 407 + "url": "https://api.github.com/repos/abenz1267/walker/tarball/refs/tags/v2.2.0", 408 + "hash": "sha256-cSRd4ncUWjB59nRqY0X0eXioOIL7q7PwgOQggE54lTI=" 409 409 } 410 410 }, 411 411 "version": 6
-732
packetmix/packages/jujutsu/7245-jj-gerrit-upload.patch
··· 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 5 - 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.' 9 - 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. 13 - 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. 16 - 17 - jj gerrit upload -r foo:: -r baz:: 18 - 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. 23 - 24 - Signed-off-by: Austin Seipp <aseipp@pobox.com> 25 - --- 26 - CHANGELOG.md | 2 + 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 38 - 39 - diff --git a/CHANGELOG.md b/CHANGELOG.md 40 - index 267b5ed303..9bc1029fcf 100644 41 - --- a/CHANGELOG.md 42 - +++ b/CHANGELOG.md 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`. 46 - 47 - +* Gerrit support implemented with the new command `jj gerrit upload` 48 - + 49 - ### Fixed bugs 50 - 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 55 - --- /dev/null 56 - +++ b/cli/src/commands/gerrit/mod.rs 57 - @@ -0,0 +1,57 @@ 58 - +// Copyright 2024 The Jujutsu Authors 59 - +// 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 63 - +// 64 - +// https://www.apache.org/licenses/LICENSE-2.0 65 - +// 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. 71 - + 72 - +use std::fmt::Debug; 73 - + 74 - +use clap::Subcommand; 75 - + 76 - +use crate::cli_util::CommandHelper; 77 - +use crate::command_error::CommandError; 78 - +use crate::commands::gerrit; 79 - +use crate::ui::Ui; 80 - + 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. 85 - + /// 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. 89 - + /// 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. 93 - + /// 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. 97 - + /// 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), 102 - +} 103 - + 104 - +pub fn cmd_gerrit( 105 - + ui: &mut Ui, 106 - + command: &CommandHelper, 107 - + subcommand: &GerritCommand, 108 - +) -> Result<(), CommandError> { 109 - + match subcommand { 110 - + GerritCommand::Upload(review) => gerrit::upload::cmd_upload(ui, command, review), 111 - + } 112 - +} 113 - + 114 - +mod upload; 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 118 - --- /dev/null 119 - +++ b/cli/src/commands/gerrit/upload.rs 120 - @@ -0,0 +1,384 @@ 121 - +// Copyright 2024 The Jujutsu Authors 122 - +// 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 126 - +// 127 - +// https://www.apache.org/licenses/LICENSE-2.0 128 - +// 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. 134 - + 135 - +use std::fmt::Debug; 136 - +use std::io::Write as _; 137 - +use std::rc::Rc; 138 - +use std::sync::Arc; 139 - + 140 - +use bstr::BStr; 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; 155 - + 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; 166 - + 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>, 175 - + 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>, 180 - + 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. 184 - + #[arg(long)] 185 - + remote: Option<String>, 186 - + 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')] 190 - + dry_run: bool, 191 - +} 192 - + 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(); 206 - + 207 - + // case 1 208 - + if let Some(remote) = remote { 209 - + if remotes.contains(BStr::new(&remote)) { 210 - + return Ok(remote); 211 - + } 212 - + return Err(user_error(format!( 213 - + "The remote '{remote}' (specified via `--remote`) does not exist", 214 - + ))); 215 - + } 216 - + 217 - + // case 2 218 - + if let Ok(remote) = config.get_string("gerrit.default-remote") { 219 - + if remotes.contains(BStr::new(&remote)) { 220 - + return Ok(remote); 221 - + } 222 - + return Err(user_error(format!( 223 - + "The remote '{remote}' (configured via `gerrit.default-remote`) does not exist", 224 - + ))); 225 - + } 226 - + 227 - + // case 3 228 - + if let Some(remote) = git_repo.remote_default_name(gix::remote::Direction::Push) { 229 - + return Ok(remote.to_string()); 230 - + } 231 - + 232 - + // case 4 233 - + if remotes.iter().any(|r| **r == "gerrit") { 234 - + return Ok("gerrit".to_owned()); 235 - + } 236 - + 237 - + // case 5 238 - + Err(user_error( 239 - + "No remote specified, and no 'gerrit' remote was found", 240 - + )) 241 - +} 242 - + 243 - +/// Determine what Gerrit ref and remote to use. The logic is: 244 - +/// 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> { 252 - + // case 1 253 - + if let Some(remote_branch) = remote_branch { 254 - + return Ok(remote_branch); 255 - + } 256 - + 257 - + // case 2 258 - + if let Ok(branch) = config.get_string("gerrit.default-remote-branch") { 259 - + return Ok(branch); 260 - + } 261 - + 262 - + // case 3 263 - + Err(user_error( 264 - + "No target branch specified via --remote-branch, and no 'gerrit.default-remote-branch' \ 265 - + was found", 266 - + )) 267 - +} 268 - + 269 - +pub fn cmd_upload(ui: &mut Ui, command: &CommandHelper, upload: &UploadArgs) -> Result<(), CommandError> { 270 - + let mut workspace_command = command.workspace_helper(ui)?; 271 - + 272 - + let revisions: Vec<_> = workspace_command 273 - + .parse_union_revsets(ui, &upload.revisions)? 274 - + .evaluate_to_commits()? 275 - + .try_collect()?; 276 - + if revisions.is_empty() { 277 - + writeln!(ui.status(), "No revisions to upload.")?; 278 - + return Ok(()); 279 - + } 280 - + 281 - + if revisions 282 - + .iter() 283 - + .any(|commit| commit.id() == workspace_command.repo().store().root_commit_id()) 284 - + { 285 - + return Err(user_error("Cannot upload the virtual 'root()' commit")); 286 - + } 287 - + 288 - + workspace_command.check_rewritable(revisions.iter().ids())?; 289 - + 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 294 - + // Change-ID. 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<_>>(), 306 - + ), 307 - + generation: jj_lib::revset::GENERATION_RANGE_FULL, 308 - + parents_range: jj_lib::revset::PARENTS_RANGE_FULL, 309 - + }), 310 - + workspace_command.env().immutable_expression().clone(), 311 - + )), 312 - + ) 313 - + .evaluate_to_commits()? 314 - + .try_collect()?; 315 - + 316 - + let mut tx = workspace_command.start_transaction(); 317 - + let base_repo = tx.base_repo().clone(); 318 - + let store = base_repo.store(); 319 - + 320 - + let old_heads = base_repo 321 - + .index() 322 - + .heads(&mut revisions.iter().ids()) 323 - + .map_err(internal_error)?; 324 - + 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())?; 328 - + 329 - + // immediately error and reject any discardable commits, i.e. the 330 - + // the empty wcc 331 - + for commit in &to_upload { 332 - + if commit.is_discardable(tx.repo_mut())? { 333 - + return Err(user_error_with_hint( 334 - + format!( 335 - + "Refusing to upload commit {} because it is an empty commit with no description", 336 - + short_commit_hash(commit.id()) 337 - + ), 338 - + "Perhaps you squashed then ran upload? Maybe you meant to upload the parent commit \ 339 - + instead (eg. @-)", 340 - + )); 341 - + } 342 - + } 343 - + 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); 349 - + 350 - + let change_id_trailers: Vec<&Trailer> = trailers 351 - + .iter() 352 - + .filter(|trailer| trailer.key == "Change-Id") 353 - + .collect(); 354 - + 355 - + // There shouldn't be multiple change-ID fields. So just error out if 356 - + // there is. 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) 361 - + ))); 362 - + } 363 - + 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. 370 - + writeln!( 371 - + ui.warning_default(), 372 - + "warning: invalid Change-Id footer in commit {}", 373 - + short_commit_hash(original_commit.id()), 374 - + )?; 375 - + } 376 - + 377 - + // map the old commit to itself 378 - + old_to_new.insert(original_commit.id().clone(), original_commit.clone()); 379 - + continue; 380 - + } 381 - + 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()); 386 - + 387 - + let new_description = format!( 388 - + "{}{}Change-Id: {}\n", 389 - + description.trim(), 390 - + if trailers.is_empty() { "\n\n" } else { "\n" }, 391 - + gerrit_change_id 392 - + ); 393 - + 394 - + let new_parents = original_commit 395 - + .parents() 396 - + .map(|parent| { 397 - + let p = parent.unwrap(); 398 - + if let Some(rewritten_parent) = old_to_new.get(p.id()) { 399 - + rewritten_parent 400 - + } else { 401 - + &p 402 - + } 403 - + .id() 404 - + .clone() 405 - + }) 406 - + .collect(); 407 - + 408 - + // rewrite the set of parents to point to the commits that were 409 - + // previously rewritten in toposort order 410 - + // 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 415 - + .repo_mut() 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()) 424 - + .write()?; 425 - + 426 - + old_to_new.insert(original_commit.id().clone(), new_commit.clone()); 427 - + } 428 - + writeln!(ui.stderr())?; 429 - + 430 - + let remote_ref = format!("refs/for/{remote_branch}"); 431 - + writeln!( 432 - + ui.stderr(), 433 - + "Found {} heads to push to Gerrit (remote '{}'), target branch '{}'", 434 - + old_heads.len(), 435 - + remote, 436 - + remote_branch, 437 - + )?; 438 - + 439 - + writeln!(ui.stderr())?; 440 - + 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 { 446 - + write!( 447 - + ui.stderr(), 448 - + "{}", 449 - + if upload.dry_run { 450 - + "Dry-run: Would push " 451 - + } else { 452 - + "Pushing " 453 - + } 454 - + )?; 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 457 - + // "hidden". 458 - + tx.base_workspace_helper().write_commit_summary( 459 - + ui.stderr_formatter().as_mut(), 460 - + &store.get_commit(head).unwrap(), 461 - + )?; 462 - + writeln!(ui.stderr())?; 463 - + 464 - + if upload.dry_run { 465 - + continue; 466 - + } 467 - + 468 - + let new_commit = store 469 - + .get_commit(old_to_new.get(head).unwrap().id()) 470 - + .unwrap(); 471 - + 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( 477 - + tx.repo_mut(), 478 - + &git_settings, 479 - + remote.as_ref(), 480 - + &[GitRefUpdate { 481 - + qualified_name: remote_ref.clone().into(), 482 - + expected_current_target: None, 483 - + new_target: Some(new_commit.id().clone()), 484 - + }], 485 - + cb, 486 - + ) 487 - + }) 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) 499 - + } 500 - + })?; 501 - + } 502 - + 503 - + Ok(()) 504 - +} 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; 510 - mod file; 511 - mod fix; 512 - #[cfg(feature = "git")] 513 - +mod gerrit; 514 - +#[cfg(feature = "git")] 515 - mod git; 516 - mod help; 517 - mod interdiff; 518 - @@ -115,6 +117,9 @@ enum Command { 519 - Fix(fix::FixArgs), 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 @@ 542 - } 543 - } 544 - }, 545 - + "gerrit": { 546 - + "type": "object", 547 - + "description": "Settings for interacting with Gerrit", 548 - + "properties": { 549 - + "default-remote": { 550 - + "type": "string", 551 - + "description": "The Gerrit remote to interact with" 552 - + }, 553 - + "default-remote-branch": { 554 - + "type": "string", 555 - + "description": "The default branch to propose changes for" 556 - + } 557 - + } 558 - + }, 559 - "merge-tools": { 560 - "type": "object", 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. 584 - 585 - 586 - 587 - +## `jj gerrit` 588 - + 589 - +Interact with Gerrit Code Review 590 - + 591 - +**Usage:** `jj gerrit <COMMAND>` 592 - + 593 - +###### **Subcommands:** 594 - + 595 - +* `upload` — Upload changes to Gerrit for code review, or update existing changes 596 - + 597 - + 598 - + 599 - +## `jj gerrit upload` 600 - + 601 - +Upload changes to Gerrit for code review, or update existing changes. 602 - + 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. 604 - + 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. 606 - + 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. 608 - + 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. 610 - + 611 - +**Usage:** `jj gerrit upload [OPTIONS]` 612 - + 613 - +###### **Options:** 614 - + 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 619 - + 620 - + 621 - + 622 - ## `jj git` 623 - 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 640 - --- /dev/null 641 - +++ b/cli/tests/test_gerrit_upload.rs 642 - @@ -0,0 +1,89 @@ 643 - +// Copyright 2025 The Jujutsu Authors 644 - +// 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 648 - +// 649 - +// https://www.apache.org/licenses/LICENSE-2.0 650 - +// 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. 656 - + 657 - +use crate::common::TestEnvironment; 658 - +use crate::common::create_commit; 659 - + 660 - +#[test] 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"); 665 - + 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 673 - + [EOF] 674 - + [exit status: 1] 675 - + "###); 676 - + 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 683 - + [EOF] 684 - + [exit status: 1] 685 - + "###); 686 - + 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 691 - + [EOF] 692 - + [exit status: 1] 693 - + "###); 694 - + 695 - + let output = work_dir.run_jj([ 696 - + "git", 697 - + "remote", 698 - + "add", 699 - + "origin", 700 - + "http://example.com/repo/foo", 701 - + ]); 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 707 - + [EOF] 708 - + [exit status: 1] 709 - + "###); 710 - + 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 ------- 715 - + 716 - + Found 1 heads to push to Gerrit (remote 'origin'), target branch 'main' 717 - + 718 - + Dry-run: Would push zsuskuln 123b4d91 b | b 719 - + [EOF] 720 - + "###); 721 - + 722 - + let output = work_dir.run_jj(["gerrit", "upload", "-r", "b", "--dry-run", "-b", "other"]); 723 - + insta::assert_snapshot!(output, @r###" 724 - + ------- stderr ------- 725 - + 726 - + Found 1 heads to push to Gerrit (remote 'origin'), target branch 'other' 727 - + 728 - + Dry-run: Would push zsuskuln 123b4d91 b | b 729 - + [EOF] 730 - + "###); 731 - +} 732 -
-4
packetmix/packages/jujutsu/7245-jj-gerrit-upload.patch.license
··· 1 - SPDX-FileCopyrightText: 2025 Austin Seipp <aseipp@pobox.com> 2 - SPDX-FileCopyrightText: 2025 Matt Stark <msta@google.com> 3 - 4 - SPDX-License-Identifier: Apache-2.0
-18
packetmix/packages/jujutsu/default.nix
··· 1 - # SPDX-FileCopyrightText: 2025 FreshlyBakedCake 2 - # 3 - # SPDX-License-Identifier: MIT 4 - 5 - { config, ... }: 6 - { 7 - config.packages.jujutsu = { 8 - systems = [ "x86_64-linux" ]; 9 - package = 10 - { 11 - system, 12 - ... 13 - }: 14 - config.inputs.nixos-unstable.result.${system}.jujutsu.overrideAttrs (prevAttrs: { 15 - patches = (prevAttrs.patches or [ ]) ++ [ ./7245-jj-gerrit-upload.patch ]; 16 - }); 17 - }; 18 - }