Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,18 @@ credential stops the launch and says so rather than falling back to your default
the whole point of naming one. [docs/workspace-tools.md](docs/workspace-tools.md) has the
precedence order and what a profile does not change.

`--from <ref>` cuts a new branch from that ref instead of from the repository's default branch:

```bash
dl blooop/devlaunch@fix/123 --from develop
```

It only means something when the branch does not exist yet. Given a branch that is already there,
`dl` refuses rather than ignoring the flag, since silently opening the existing branch would look
like the base had been honoured. Like `--claude-profile`, and unlike `--devcontainer`, it is per
launch and is **not** stored with the workspace: a base names a one-time event, not something
every later launch should redo.

`dl --help` is the complete reference and is kept in step with the binary by a test.

## aid: an agent instead of a shell
Expand Down
8 changes: 7 additions & 1 deletion rust/aid/src/rewrite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,13 @@ const REMOTE_CONTROL_NO: &[&str] = &["0", "false", "off", "no"];
/// `aid --claude-profile work` still lists its session under whichever account the
/// container is signed in to. Two credentials, and this one is the token forwarded
/// into the session.
const DL_VALUE_OPTIONS: &[&str] = &["--devcontainer", "--claude-profile"];
///
/// **`--from` is listed for the same reason and no other: `aid` has no `--from`
/// of its own** (that is a separate change), but a line typed as `aid --from
/// develop owner/repo fix it` still has to read "develop" as the base rather
/// than as the first word of the prompt, so dl's own grammar is recognised here
/// exactly as `--claude-profile`'s is.
const DL_VALUE_OPTIONS: &[&str] = &["--devcontainer", "--claude-profile", "--from"];

/// The modifier the suffix options take, peeled only in their company.
///
Expand Down
10 changes: 5 additions & 5 deletions rust/devlaunch-core/completions/dl.bash
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ _dl_completion() {
# The retired spellings (--stop, --autorm) are absent by rule rather than by
# hand: the grammar marks them `hide = true`, and the test drops every hidden
# flag, so a spelling this build only still answers for is never offered.
local global_opts="--ls --install --refresh --prune --reconcile --purge --herdr-shell --herdr-setup --herdr-env --herdr-workspace --rm --devcontainer --claude-profile --claude-profiles --help -h --version"
local global_opts="--ls --install --refresh --prune --reconcile --purge --herdr-shell --herdr-setup --herdr-env --herdr-workspace --rm --devcontainer --claude-profile --from --claude-profiles --help -h --version"
if [[ "$cmd" == aid ]]; then
global_opts="--claude --codex --gemini --model --effort --devcontainer --claude-profile --help -h --version"
global_opts="--claude --codex --gemini --model --effort --devcontainer --claude-profile --from --help -h --version"
fi

# Workspace subcommands
Expand All @@ -82,7 +82,7 @@ _dl_completion() {
local ws_cmds="up stop kill rm rme code restart recreate reset dotfiles --rm --"

# Options that take a value; a variant name, a profile name or a path follows.
local value_opts="--devcontainer --claude-profile --herdr-workspace"
local value_opts="--devcontainer --claude-profile --herdr-workspace --from"

# aid's own value-taking flags, from `AGENT_VALUE_OPTIONS`. Nothing completes
# their values: the models and efforts are each agent's to list, they change
Expand Down Expand Up @@ -115,7 +115,7 @@ _dl_completion() {
# would mean a second exception rather than a wider `spec_follows` -- the
# thing that follows is not a spec, and the branch below that handles `./`
# is inside the spec position.
local spec_follows="--rm --devcontainer --claude-profile"
local spec_follows="--rm --devcontainer --claude-profile --from"
if [[ "$cmd" == aid ]]; then
# aid's own, from `parse_aid_args`: it reads an agent flag, a remote
# control flag or a dl value option and keeps looking for the spec. The
Expand All @@ -124,7 +124,7 @@ _dl_completion() {
# takes a value, so on `aid --unknown-taking-a-value foo owner/repo` it
# calls `foo` the spec, and completing a slot aid itself cannot place is
# worse than completing nothing.
spec_follows="--claude --codex --gemini --remote-control --remote --no-remote-control --no-remote --model --effort --devcontainer --claude-profile"
spec_follows="--claude --codex --gemini --remote-control --remote --no-remote-control --no-remote --model --effort --devcontainer --claude-profile --from"
fi

if [[ "${prev}" == "--herdr-workspace" ]]; then
Expand Down
2 changes: 2 additions & 0 deletions rust/devlaunch-core/public-api.api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ pub fn devlaunch_core::flows::kept_copies::KeptCopies::fmt(&self, &mut core::fmt
impl core::marker::StructuralPartialEq for devlaunch_core::flows::kept_copies::KeptCopies
pub struct devlaunch_core::api::Launch<'a, 'r, 'l>
impl<'a, 'r, 'l> devlaunch_core::flows::launch::Launch<'a, 'r, 'l>
pub fn devlaunch_core::flows::launch::Launch<'a, 'r, 'l>::from_ref(self, core::option::Option<alloc::string::String>) -> Self
pub fn devlaunch_core::flows::launch::Launch<'a, 'r, 'l>::new(&'a mut devlaunch_core::flows::listing::CommandContext<'r>, &'a mut devlaunch_core::flows::lifecycle::Refresh<'l>, &'a mut dyn devlaunch_core::flows::launch::ColdMachinery<'r>, &'a dyn devlaunch_core::flows::launch::Provision, &'a devlaunch_core::flows::launch::Host, &'a mut dyn core::ops::function::FnMut(&str), &'a mut dyn devlaunch_core::notices::Notices<devlaunch_core::flows::launch::LaunchNotice>) -> Self
pub fn devlaunch_core::flows::launch::Launch<'a, 'r, 'l>::recognised_as(self, core::option::Option<devlaunch_core::domain::workspace_id::WorkspaceId>) -> Self
pub fn devlaunch_core::flows::launch::Launch<'a, 'r, 'l>::run(&mut self, &str, &devlaunch_core::flows::launch::LaunchVerb, core::option::Option<&devlaunch_core::domain::spec::DevcontainerPath>) -> core::result::Result<devlaunch_core::flows::launch::Launched, devlaunch_core::flows::launch::LaunchAborted>
Expand Down Expand Up @@ -719,6 +720,7 @@ pub fn devlaunch_core::flows::launch::Host::fmt(&self, &mut core::fmt::Formatter
impl core::marker::StructuralPartialEq for devlaunch_core::flows::launch::Host
pub struct devlaunch_core::flows::launch::Launch<'a, 'r, 'l>
impl<'a, 'r, 'l> devlaunch_core::flows::launch::Launch<'a, 'r, 'l>
pub fn devlaunch_core::flows::launch::Launch<'a, 'r, 'l>::from_ref(self, core::option::Option<alloc::string::String>) -> Self
pub fn devlaunch_core::flows::launch::Launch<'a, 'r, 'l>::new(&'a mut devlaunch_core::flows::listing::CommandContext<'r>, &'a mut devlaunch_core::flows::lifecycle::Refresh<'l>, &'a mut dyn devlaunch_core::flows::launch::ColdMachinery<'r>, &'a dyn devlaunch_core::flows::launch::Provision, &'a devlaunch_core::flows::launch::Host, &'a mut dyn core::ops::function::FnMut(&str), &'a mut dyn devlaunch_core::notices::Notices<devlaunch_core::flows::launch::LaunchNotice>) -> Self
pub fn devlaunch_core::flows::launch::Launch<'a, 'r, 'l>::recognised_as(self, core::option::Option<devlaunch_core::domain::workspace_id::WorkspaceId>) -> Self
pub fn devlaunch_core::flows::launch::Launch<'a, 'r, 'l>::run(&mut self, &str, &devlaunch_core::flows::launch::LaunchVerb, core::option::Option<&devlaunch_core::domain::spec::DevcontainerPath>) -> core::result::Result<devlaunch_core::flows::launch::Launched, devlaunch_core::flows::launch::LaunchAborted>
Expand Down
4 changes: 4 additions & 0 deletions rust/devlaunch-core/public-api.rest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3177,7 +3177,11 @@ impl core::marker::StructuralPartialEq for devlaunch_core::flows::session_manage
pub fn devlaunch_core::flows::session_manager::pane_destination(&dyn devlaunch_runner::Runner) -> devlaunch_core::flows::session_manager::PaneDestination
pub mod devlaunch_core::flows::workspace_clone
pub enum devlaunch_core::flows::workspace_clone::EnsureBranchError
pub devlaunch_core::flows::workspace_clone::EnsureBranchError::BaseNotResolved
pub devlaunch_core::flows::workspace_clone::EnsureBranchError::BaseNotResolved::base: alloc::string::String
pub devlaunch_core::flows::workspace_clone::EnsureBranchError::Branch(devlaunch_core::flows::branch_manager::BranchError)
pub devlaunch_core::flows::workspace_clone::EnsureBranchError::BranchAlreadyExists
pub devlaunch_core::flows::workspace_clone::EnsureBranchError::BranchAlreadyExists::branch: alloc::string::String
pub devlaunch_core::flows::workspace_clone::EnsureBranchError::WrongRepoLock(devlaunch_core::flows::repo_manager::WrongRepoLock)
impl core::clone::Clone for devlaunch_core::flows::workspace_clone::EnsureBranchError
pub fn devlaunch_core::flows::workspace_clone::EnsureBranchError::clone(&self) -> devlaunch_core::flows::workspace_clone::EnsureBranchError
Expand Down
31 changes: 29 additions & 2 deletions rust/devlaunch-core/src/flows/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4473,14 +4473,16 @@ pub(crate) fn prepare(
cold: &mut dyn ColdMachinery<'_>,
workspace: &WorkspaceId,
remote_url: &str,
from: Option<&str>,
notices: &mut dyn Notices<LaunchNotice>,
) -> Result<Placement, NotPrepared> {
let opened = cold.open().map_err(NotPrepared::Cold)?;
let prepared = opened.clones.prepare_cold(
let prepared = opened.clones.prepare_cold_from(
opened.storage,
workspace.owner(),
workspace.repo(),
workspace.git_ref(),
from,
remote_url,
&mut as_cache(notices),
);
Expand Down Expand Up @@ -4749,6 +4751,8 @@ pub struct Launch<'a, 'r, 'l> {
/// What the caller already knows this workspace is, for a launch that names it
/// by id. See [`Self::recognised_as`].
recognised: Option<WorkspaceId>,
/// `--from <base>`, for this launch and no other. See [`Self::from_ref`].
from: Option<String>,
}

impl<'a, 'r, 'l> Launch<'a, 'r, 'l> {
Expand All @@ -4772,6 +4776,7 @@ impl<'a, 'r, 'l> Launch<'a, 'r, 'l> {
claude_seen: ClaudeSeen::new(),
notices,
recognised: None,
from: None,
}
}

Expand Down Expand Up @@ -4805,6 +4810,21 @@ impl<'a, 'r, 'l> Launch<'a, 'r, 'l> {
self
}

/// `--from <base>`: cut a new branch from `base` instead of the default
/// branch.
///
/// Per launch, like [`Host::with_claude_profile`] and unlike
/// `--devcontainer`: a base describes an event that happened once, not what
/// the workspace *is*, so it rides this one call and is never read back off
/// a record. Reaches only [`Launch::place_triple`]'s cold arm -- a launch
/// that resolves warm, or a bare workspace name, has no branch left to cut
/// and this is not consulted.
#[must_use]
pub fn from_ref(mut self, base: Option<String>) -> Self {
self.from = base;
self
}

/// Run one launch.
pub fn run(
&mut self,
Expand Down Expand Up @@ -4938,7 +4958,13 @@ impl<'a, 'r, 'l> Launch<'a, 'r, 'l> {
Ok(Ok(placement))
}
Resolution::Cold { workspace } => {
match prepare(self.cold, &workspace, &remote_url, &mut *self.notices) {
match prepare(
self.cold,
&workspace,
&remote_url,
self.from.as_deref(),
&mut *self.notices,
) {
Ok(placement) => Ok(Ok(placement)),
Err(error) => Ok(Err(LaunchRefusal::NotPrepared {
owner,
Expand Down Expand Up @@ -10911,6 +10937,7 @@ mod tests {
&mut MetadataWillNotOpen,
&workspace,
"git@github.com:blooop/devlaunch.git",
None,
&mut no_notices(),
);

Expand Down
Loading
Loading