add option to select remote for push

tzemanovic
Mar 30, 2026, 5:43 PM
O46EFE5J4KQLD2YQURNCDL5C42WIGVAINUWWZC3CTQDC4A5Q6IPAC

Dependencies

  • [2] JZXYSIYD channel selection!
  • [3] WAOGSCOJ very nice refactor, wip adding channels logs
  • [4] EJPSD5XO shared allowed actions conditions between update and view
  • [5] YK3MOJJL chonky refactor, wip other channels logs & diffs
  • [6] 7WCB5YQJ refactor msgs and modules
  • [7] CULHFNIV add error report view
  • [8] LFEMJYYD start of to_record selection
  • [9] YGZ3VCW4 add push
  • [10] ODCT4QJN add pull
  • [11] UTDTZCTX pull+push status, add info reports
  • [12] TEDT26JQ add push and pull sub-menus
  • [13] YRGDFHAB project dir picker
  • [14] LPSUBGUB add projects picker
  • [15] IMJQ4PML fix and refactor action bindings
  • [16] 5BAPU7K6 dir picker key navigation
  • [17] TMDH7GPV dir picker scrollables handling + confirmation
  • [18] T4UECD3S picking projects key nav and scrollable
  • [19] J3AD2D2J improve push and pull keys
  • [20] BGOQFXP7 update pijul
  • [21] IOXNOVX2 allow to initiate a new repo
  • [22] XQDYES5M add support for git import
  • [23] E7HE2UFT handle multiple or no pijul identity
  • [24] DNTMUCMO allow to compare local with remote records
  • [25] UR4J677R nav for log changes and refactors
  • [26] BJ3CYLUT allow to reset changed file
  • [27] ACDXXAX2 refactor main's updates into smaller fns
  • [28] OLT666N4 fix screenshot test to include status, fix failed test report
  • [29] 6E6MSENZ allow to add untracked files recursively
  • [30] MORKDJUE use allowed actions binding for key subs
  • [31] PKLUHYE4 allow to copy change hash
  • [32] IFQPVMBD error handling for repo actions
  • [33] 2SLTGWP6 add change files diffs to-record selection
  • [34] YBJRDOTC make all repo actions async
  • [35] 5O4FWCFP add tests to_record selection and improve it
  • [36] 23SFYK4Q big view refactor into a new crate
  • [37] OQ6HSAWH show record log
  • [*] SWWE2R6M display basic repo stuff
  • [*] OPXFZKEB view tests setup
  • [*] 6YZAVBWU Initial commit

Change contents

  • edit in libflorescence/src/repo.rs at line 146
    [11.18]
    [11.18]
    remote: String,
  • edit in libflorescence/src/repo.rs at line 193
    [11.133]
    [11.133]
    remote: String,
  • replacement in libflorescence/src/repo.rs at line 593
    [3.2458][11.516:695]()
    MsgIn::Push { channel } => {
    let result = push(&internal_state.path, &channel).await;
    let _ = msg_out_tx.send(MsgOut::Pushed { channel, result });
    [3.2458]
    [10.132]
    MsgIn::Push { remote, channel } => {
    let result = push(&internal_state.path, &remote, &channel).await;
    let _ = msg_out_tx.send(MsgOut::Pushed {
    remote,
    channel,
    result,
    });
  • replacement in libflorescence/src/repo.rs at line 1426
    [9.2125][11.876:955]()
    async fn push(repo_path: &Path, channel_name: &str) -> Result<(), PushError> {
    [9.2125]
    [9.2188]
    async fn push(
    repo_path: &Path,
    remote_name: &str,
    channel_name: &str,
    ) -> Result<(), PushError> {
  • edit in libflorescence/src/repo.rs at line 1434
    [20.610][20.610:679](),[20.679][9.2419:2444](),[9.2419][9.2419:2444](),[9.2444][11.1030:1078](),[11.1078][9.2477:2484](),[9.2477][9.2477:2484]()
    let remote_name = if let Some(ref def) = config.default_remote {
    def
    } else {
    return Err(anyhow!("Missing remote"))?;
    };
  • replacement in libflorescence/src/repo.rs at line 1558
    [10.1240][24.2752:2775]()
    remote_unrecs,
    [10.1240]
    [10.1266]
    remote_unrecs: _,
  • edit in inflorescence_view/src/view.rs at line 46
    [23.152]
    [4.404]
    SubMenuPushSelectRemote(String),
  • replacement in inflorescence_view/src/view.rs at line 63
    [4.487][12.530027:530048]()
    sub_menu: _,
    [4.487]
    [7.159]
    sub_menu,
  • replacement in inflorescence_view/src/view.rs at line 67
    [13.18889][13.18889:18905](),[13.18905][14.13267:13403](),[14.13403][13.18905:18957](),[13.18905][13.18905:18957](),[13.18957][14.13404:13479]()
    match sub {
    model::SubState::PickingProject(state) => {
    picking_project(state, *window_size, allowed_actions, report)
    }
    model::SubState::PickingRepoDir(state) => {
    picking_repo_dir(state, *window_size, allowed_actions, report)
    [13.18889]
    [13.19028]
    if let Some(sub) = sub_menu {
    match sub {
    model::SubMenu::Push {
    opt: Some(opt),
    remote,
    } => match opt {
    model::PushOption::SelectingRemote => {
    return push_selecting_remote(
    state,
    *window_size,
    allowed_actions,
    report,
    remote,
    sub_menu,
    );
    }
    },
    model::SubMenu::Push { opt: None, .. }
    | model::SubMenu::Pull
    | model::SubMenu::ResetChange
    | model::SubMenu::InitRepo { .. }
    | model::SubMenu::ImportFromGit { .. }
    | model::SubMenu::Add { .. }
    | model::SubMenu::CompareRemote { .. } => {}
  • edit in inflorescence_view/src/view.rs at line 92
    [13.19038]
    [13.19038]
    }
    match sub {
    model::SubState::PickingProject(state) => picking_project(
    state,
    *window_size,
    allowed_actions,
    report,
    sub_menu,
    ),
    model::SubState::PickingRepoDir(state) => picking_repo_dir(
    state,
    *window_size,
    allowed_actions,
    report,
    sub_menu,
    ),
  • edit in inflorescence_view/src/view.rs at line 115
    [13.19217]
    [13.19217]
    sub_menu,
  • edit in inflorescence_view/src/view.rs at line 125
    [14.13650]
    [14.13650]
    sub_menu: &'a Option<model::SubMenu>,
  • replacement in inflorescence_view/src/view.rs at line 191
    [14.14794][14.14794:14871]()
    add_actions_and_report(None, main, window_size, allowed_actions, report)
    [14.14794]
    [14.14871]
    add_actions_and_report(
    None,
    main,
    window_size,
    allowed_actions,
    report,
    sub_menu,
    )
  • edit in inflorescence_view/src/view.rs at line 206
    [13.19404]
    [13.19404]
    sub_menu: &'a Option<model::SubMenu>,
  • replacement in inflorescence_view/src/view.rs at line 245
    [13.19739][13.19739:19816]()
    add_actions_and_report(None, main, window_size, allowed_actions, report)
    [13.19739]
    [13.19816]
    add_actions_and_report(
    None,
    main,
    window_size,
    allowed_actions,
    report,
    sub_menu,
    )
  • edit in inflorescence_view/src/view.rs at line 261
    [13.20005]
    [13.20005]
    sub_menu: &'a Option<model::SubMenu>,
  • edit in inflorescence_view/src/view.rs at line 281
    [13.20367]
    [13.20367]
    sub_menu,
  • edit in inflorescence_view/src/view.rs at line 305
    [23.762]
    [23.762]
    sub_menu,
  • edit in inflorescence_view/src/view.rs at line 315
    [7.247]
    [4.653]
    sub_menu,
  • edit in inflorescence_view/src/view.rs at line 328
    [23.1218]
    [23.1218]
    sub_menu,
  • edit in inflorescence_view/src/view.rs at line 338
    [2.3730]
    [23.1243]
    }
    fn push_selecting_remote<'a>(
    state: &'a model::State,
    window_size: iced::Size,
    allowed_actions: &'a [action::Binding],
    report: &'a report::Container,
    remote: &'a Option<String>,
    sub_menu: &'a Option<model::SubMenu>,
    ) -> Element<'a, Msg, Theme> {
    let main = if let model::SubState::ManagingRepo(state) = &state.sub
    && let Some(model::ReadyState { repo, .. }) = model::is_ready(state)
    {
    let repo::State {
    remotes: repo::Remotes { default, other },
    ..
    } = repo;
    let mut cols = Vec::with_capacity(default.iter().len() + other.len());
    for name in default.iter().chain(other) {
    cols.push(el(button(text(name))
    .on_press_with(|| Msg::SubMenuPushSelectRemote(name.clone()))
    .class(if Some(name) == remote.as_ref() {
    theme::Button::Selected
    } else {
    theme::Button::Normal
    })));
    }
    el(column(cols))
    } else {
    el(row([]))
    };
    let main = el(container(main).width(Length::Fill).height(Length::Fill));
    add_actions_and_report(
    None,
    main,
    window_size,
    allowed_actions,
    report,
    sub_menu,
    )
  • edit in inflorescence_view/src/view.rs at line 410
    [13.20736]
    [13.20736]
    sub_menu: &'a Option<model::SubMenu>,
  • replacement in inflorescence_view/src/view.rs at line 412
    [13.20767][13.20767:20847]()
    let actions_inner = above_actions.unwrap_or_else(|| column([]).spacing(4));
    [13.20767]
    [13.20847]
    let mut actions_inner =
    above_actions.unwrap_or_else(|| column([]).spacing(4));
    if let Some(sub) = sub_menu {
    match sub {
    model::SubMenu::Push {
    opt: _,
    remote: Some(remote),
    } => {
    actions_inner =
    actions_inner.push(el(text(format!("Remote: {remote}"))));
    }
    model::SubMenu::Push { remote: None, .. }
    | model::SubMenu::Pull
    | model::SubMenu::ResetChange
    | model::SubMenu::InitRepo { .. }
    | model::SubMenu::ImportFromGit { .. }
    | model::SubMenu::Add { .. }
    | model::SubMenu::CompareRemote { .. } => {}
    }
    }
  • edit in inflorescence_view/src/view.rs at line 453
    [7.283]
    [3.4380]
    sub_menu: &'a Option<model::SubMenu>,
  • edit in inflorescence_view/src/view.rs at line 1841
    [13.21407]
    [13.21407]
    sub_menu,
  • replacement in inflorescence_view/src/view.rs at line 1972
    [11.4477][11.4477:4512]()
    Job::Push { channel } => {
    [11.4477]
    [11.4512]
    Job::Push { remote, channel } => {
  • replacement in inflorescence_view/src/view.rs at line 1974
    [11.4556][11.4556:4592]()
    el(text("Pushing"))
    [11.4556]
    [11.4592]
    el(text("Pushing to remote {remote}"))
  • replacement in inflorescence_view/src/view.rs at line 1976
    [11.4613][11.4613:4668]()
    el(text(format!("Pushing {channel}")))
    [11.4613]
    [11.4668]
    el(text(format!(
    "Pushing to remote {remote}, channel {channel}"
    )))
  • replacement in inflorescence_view/src/view/test.rs at line 327
    [12.531692][12.531692:531732]()
    let sub_menu = Some(SubMenu::Push);
    [12.531692]
    [13.23217]
    let sub_menu = Some(SubMenu::Push {
    remote: None,
    opt: None,
    });
  • replacement in inflorescence_model/src/model.rs at line 84
    [12.531948][12.531948:531958]()
    Push,
    [12.531948]
    [12.531958]
    Push {
    remote: Option<String>,
    opt: Option<PushOption>,
    },
  • edit in inflorescence_model/src/model.rs at line 103
    [24.18171]
    [12.531968]
    }
    #[derive(Clone, Debug, PartialEq, Eq, Hash)]
    pub enum PushOption {
    SelectingRemote,
  • replacement in inflorescence_model/src/model.rs at line 238
    [11.5288][11.5288:5318]()
    Push { channel: String },
    [11.5288]
    [24.19259]
    Push { remote: String, channel: String },
  • edit in inflorescence_model/src/action.rs at line 58
    [12.532010]
    [23.2285]
    SubMenuPushOption(model::PushOption),
  • replacement in inflorescence_model/src/action.rs at line 121
    [8.13523][12.532011:532079]()
    (EnterSubMenu(left), EnterSubMenu(right)) => left == right,
    [8.13523]
    [23.2306]
    (EnterSubMenu(left), EnterSubMenu(right)) => {
    core::mem::discriminant(left) == core::mem::discriminant(right)
    }
    (SubMenuPushOption(left), SubMenuPushOption(right)) => left == right,
  • edit in inflorescence_model/src/action.rs at line 147
    [12.532119]
    [23.2357]
    (SubMenuPushOption(_), _) => false,
  • replacement in inflorescence_model/src/action.rs at line 195
    [12.532243][12.532243:532317](),[12.532317][19.43821:44066]()
    model::SubMenu::Push => {
    // TODO add options
    vec![
    Binding {
    keys_str: "Enter | S-p",
    keys: ModKeys::Two(
    ModKey {
    key: Key::Named(Named::Enter),
    [12.532243]
    [19.44066]
    model::SubMenu::Push { remote: _, opt } => {
    if opt.is_none() {
    vec![
    Binding {
    keys_str: "Enter | S-p",
    keys: ModKeys::Two(
    ModKey {
    key: Key::Named(Named::Enter),
    mods: Mods::NONE,
    },
    ModKey {
    key: Key::Character("p".into()),
    mods: Mods::SHIFT,
    },
    ),
    label: "confirm push",
    msg: Some(FilteredMsg::Confirm),
    },
    Binding {
    keys_str: "r",
    keys: ModKeys::One(ModKey {
    key: Key::Character("r".into()),
  • replacement in inflorescence_model/src/action.rs at line 218
    [19.44116][19.44116:44533]()
    },
    ModKey {
    key: Key::Character("p".into()),
    mods: Mods::SHIFT,
    },
    ),
    label: "confirm push",
    msg: Some(FilteredMsg::Confirm),
    },
    cancel(),
    ]
    [19.44116]
    [12.532373]
    }),
    label: "select remote",
    msg: Some(FilteredMsg::SubMenuPushOption(
    model::PushOption::SelectingRemote,
    )),
    },
    cancel(),
    ]
    } else {
    vec![
    confirm("confirm remote selection"),
    down(),
    up(),
    cancel(),
    ]
    }
  • replacement in inflorescence_model/src/action.rs at line 774
    [17.832][17.832:935]()
    msg: can_push
    .then_some(FilteredMsg::EnterSubMenu(model::SubMenu::Push)),
    [17.832]
    [15.569]
    msg: can_push.then_some(FilteredMsg::EnterSubMenu(
    model::SubMenu::Push {
    remote: None,
    opt: None,
    },
    )),
  • replacement in inflorescence_model/src/action.rs at line 1231
    [11.6297][11.6297:6411]()
    model::Job::Pull { channel: c } | model::Job::Push { channel: c } => {
    c != channel
    }
    [11.6297]
    [24.22521]
    model::Job::Pull { channel: c }
    | model::Job::Push {
    remote: _,
    channel: c,
    } => c != channel,
  • replacement in inflorescence/src/main.rs at line 470
    [23.3238][23.3238:3305]()
    | action::FilteredMsg::ReloadIdentity => Task::none(),
    [23.3238]
    [18.4920]
    | action::FilteredMsg::ReloadIdentity
    | action::FilteredMsg::SubMenuPushOption(_) => Task::none(),
  • replacement in inflorescence/src/main.rs at line 491
    [23.3339][23.3339:3395]()
    | view::Msg::SelectIdentity(_) => Task::none(),
    [23.3339]
    [14.21507]
    | view::Msg::SelectIdentity(_)
    | view::Msg::SubMenuPushSelectRemote(_) => Task::none(),
  • replacement in inflorescence/src/main.rs at line 958
    [22.34135][22.34135:34180]()
    model::SubMenu::Push
    [22.34135]
    [22.34180]
    model::SubMenu::Push { .. }
  • replacement in inflorescence/src/main.rs at line 999
    [23.8725][23.8725:8792]()
    | action::FilteredMsg::ReloadIdentity => Task::none(),
    [23.8725]
    [16.19107]
    | action::FilteredMsg::ReloadIdentity
    | action::FilteredMsg::SubMenuPushOption(_) => Task::none(),
  • replacement in inflorescence/src/main.rs at line 1016
    [23.8829][23.8829:8885]()
    | view::Msg::SelectIdentity(_) => Task::none(),
    [23.8829]
    [13.3616655]
    | view::Msg::SelectIdentity(_)
    | view::Msg::SubMenuPushSelectRemote(_) => Task::none(),
  • edit in inflorescence/src/main.rs at line 1147
    [23.9917]
    [23.9917]
    }
    Task::none()
    }
    view::Msg::SubMenuPushSelectRemote(selection) => {
    if let Some(model::SubMenu::Push { remote, .. }) = sub_menu {
    *remote = Some(selection);
  • replacement in inflorescence/src/main.rs at line 1171
    [21.5957][21.5957:6029]()
    Some(model::SubMenu::Push) => push(state, model, sub_menu),
    [21.5957]
    [21.6029]
    Some(model::SubMenu::Push { remote, opt }) => {
    if opt.is_some() {
    *sub_menu = Some(model::SubMenu::Push {
    remote: remote.clone(),
    opt: None,
    });
    Task::none()
    } else {
    push(state, model, sub_menu, report, remote.clone())
    }
    }
  • replacement in inflorescence/src/main.rs at line 1319
    [6.12836][13.3618057:3618093]()
    if sub_menu.is_some() {
    [6.12836]
    [13.3618093]
    if let Some(sub) = sub_menu {
    match sub {
    model::SubMenu::Push { remote: _, opt } => {
    if opt.is_some() {
    *sub_menu = Some(model::SubMenu::Push {
    remote: None,
    opt: None,
    });
    }
    }
    model::SubMenu::Pull
    | model::SubMenu::ResetChange
    | model::SubMenu::InitRepo { .. }
    | model::SubMenu::ImportFromGit { .. }
    | model::SubMenu::Add { .. }
    | model::SubMenu::CompareRemote { .. } => {}
    }
  • edit in inflorescence/src/main.rs at line 1369
    [6.12886]
    [3.84791]
    if let Some(sub) = sub_menu {
    match sub {
    model::SubMenu::Push {
    remote,
    opt: Some(opt),
    } => {
    if let Some(ReadyState { repo, .. }) =
    model::is_ready_mut(model)
    {
    return sub_menu_push_opt_selection(
    msg, remote, opt, repo,
    );
    }
    }
    model::SubMenu::Push { .. }
    | model::SubMenu::Pull
    | model::SubMenu::ResetChange
    | model::SubMenu::InitRepo { .. }
    | model::SubMenu::ImportFromGit { .. }
    | model::SubMenu::Add { .. }
    | model::SubMenu::CompareRemote { .. } => {}
    }
    }
  • edit in inflorescence/src/main.rs at line 1586
    [13.3619359]
    [8.18160]
    Task::none()
    }
    action::FilteredMsg::SubMenuPushOption(new_opt) => {
    if let Some(model::SubMenu::Push { opt, .. }) = sub_menu {
    *opt = Some(new_opt);
    }
  • edit in inflorescence/src/main.rs at line 1747
    [5.64717]
    [23.12722]
    }
    fn sub_menu_push_opt_selection(
    msg: selection::Msg,
    remote: &mut Option<String>,
    opt: &mut model::PushOption,
    repo: &mut repo::State,
    ) -> Task<Msg> {
    let repo::Remotes { default, other } = &repo.remotes;
    let names: Vec<_> = default.iter().chain(other).collect();
    match opt {
    model::PushOption::SelectingRemote => match msg {
    selection::Msg::PressDir(dir) => match dir {
    selection::Dir::Down => {
    if let Some(current) = remote {
    let ix =
    names.iter().enumerate().find_map(|(ix, name)| {
    (*name == current).then_some(ix)
    });
    if let Some(ix) = ix
    && ix < names.len().saturating_sub(1)
    {
    *remote = names.into_iter().nth(ix + 1).cloned();
    return Task::none();
    }
    }
    *remote = names.into_iter().next().cloned();
    }
    selection::Dir::Up => {
    if let Some(current) = remote {
    let ix =
    names.iter().enumerate().find_map(|(ix, name)| {
    (*name == current).then_some(ix)
    });
    if let Some(ix) = ix
    && ix > 0
    {
    *remote = names.into_iter().nth(ix - 1).cloned();
    return Task::none();
    }
    }
    *remote = names.into_iter().next_back().cloned();
    }
    selection::Dir::Right | selection::Dir::Left => {}
    },
    selection::Msg::AltPressDir(_) => {}
    },
    }
    Task::none()
  • edit in inflorescence/src/main.rs at line 1816
    [13.3619516]
    [21.8095]
    report: &mut report::Container,
    remote: Option<String>,
  • replacement in inflorescence/src/main.rs at line 1820
    [13.3619623][12.534445:534615](),[12.534445][12.534445:534615]()
    jobs.insert(model::Job::Push {
    channel: repo.channel.clone(),
    });
    state
    .repo_tx_in
    .send(repo::MsgIn::Push {
    [13.3619623]
    [12.534615]
    if let Some(remote) =
    remote.clone().or_else(|| repo.remotes.default.clone())
    {
    jobs.insert(model::Job::Push {
    remote: remote.clone(),
  • replacement in inflorescence/src/main.rs at line 1826
    [12.534662][12.534662:534700]()
    })
    .unwrap();
    [12.534662]
    [12.534700]
    });
    state
    .repo_tx_in
    .send(repo::MsgIn::Push {
    remote,
    channel: repo.channel.clone(),
    })
    .unwrap();
    } else {
    report::show_err(
    report,
    "Cannot push as there is no default remote configured."
    .to_string(),
    );
    }
  • replacement in inflorescence/src/main.rs at line 2026
    [9.5932][11.7990:8044]()
    repo::MsgOut::Pushed { channel, result } => {
    [9.5932]
    [14.25327]
    repo::MsgOut::Pushed {
    remote,
    channel,
    result,
    } => {
  • replacement in inflorescence/src/main.rs at line 2077
    [14.25804][14.25804:25862]()
    jobs.swap_remove(&Job::Push { channel });
    [14.25804]
    [11.8524]
    jobs.swap_remove(&Job::Push { remote, channel });