jj icon indicating copy to clipboard operation
jj copied to clipboard

cli: add support for push options

Open shikanime opened this issue 1 year ago • 15 comments

Previously, if you tried to run `jj rebase -s 'roots(mutable())' -d main@origin, you would suddenly get divergent changes because it had also rebased old versions of current commits.

Checklist

If applicable:

  • [x] I have updated CHANGELOG.md
  • [x] I have updated the documentation (README.md, docs/, demos/)
  • [x] I have updated the config schema (cli/src/config-schema.json)
  • [x] I have added/updated tests to cover my changes

shikanime avatar Aug 22 '24 16:08 shikanime

We don't use conventional commits here

Do we have that documented somewhere? I personally don't care much about the style as long as it's easy to understand. If we do have some rules or guidelines about the style, I think we should document them (again, maybe we have and I just don't remember where).

so if you'd touch the library it'd be lib: etc.

And I find this too coarse-grained, btw. It doesn't tell me much about the topic the commit affects.

martinvonz avatar Aug 22 '24 18:08 martinvonz

We don't use conventional commits here

Do we have that documented somewhere? I personally don't care much about the style as long as it's easy to understand. If we do have some rules or guidelines about the style, I think we should document them (again, maybe we have and I just don't remember where).

We don't have them documented somewhere, just about a rough analysis from me in the Discord here. Which I think implies this. I don't consider it a hard blocker, but I think uniformity is nice.

so if you'd touch the library it'd be lib: etc.

And I find this too coarse-grained, btw. It doesn't tell me much about the topic the commit affects.

It was a quick example, so that's my bad.

PhilipMetzger avatar Aug 22 '24 18:08 PhilipMetzger

I explored gitoxide, the Git server implementation used in tests, but couldn't found any functionality for handling push options. So far, I struggled to find a straightforward method for testing this feature.

shikanime avatar Aug 27 '24 08:08 shikanime

I don't have a good testing strategy either. Fake ssh command or something could be inserted to capture the wire protocol, but doing that wouldn't be simple.

Maybe okay to leave this option untested?

yuja avatar Aug 27 '24 11:08 yuja

Can you test by pushing to another folder?

joyously avatar Aug 27 '24 12:08 joyously

Hi, I would like to offer a test suggestion: setup a hook on the source repo that echos back the received options and capture the output through the callbacks.

  1. I only got it to work with the git subprocess setting.
  2. If this is an acceptable approach I could either create a new PR based on this one or let @shikanime finish this PR

Setup hooks

    std::process::Command::new("git")
        .arg("--git-dir")
        .arg(&setup.source_repo_dir)
        .args(["config", "receive.advertisePushOptions", "true"])
        .output()
        .unwrap();

    let hooks_dir = setup.source_repo_dir.join("hooks");
    fs::create_dir_all(&hooks_dir).unwrap();
    let hook_path = hooks_dir.join("pre-receive");
    let hook_content = r#"#!/bin/sh
    if [ -n "$GIT_PUSH_OPTION_COUNT" ] && [ "$GIT_PUSH_OPTION_COUNT" -gt 0 ]; then
        i=0
        while [ $i -lt "$GIT_PUSH_OPTION_COUNT" ]; do
            eval "option_value=\$GIT_PUSH_OPTION_$i"
            echo "Push-Option: $option_value"
            i=$((i + 1))
        done
    fi
    "#;
    fs::write(&hook_path, hook_content).unwrap();

    let mut perms = fs::metadata(&hook_path).unwrap().permissions();
    perms.set_mode(perms.mode() | 0o111);
    fs::set_permissions(&hook_path, perms).unwrap();

Configure callbacks and assert output

    let remote_output = Arc::new(std::sync::Mutex::new(Vec::new()));

    let output_clone = remote_output.clone();

    let mut sideband_closure = |data: &[u8]| {
        if let Ok(mut output) = output_clone.lock() {
            output.extend_from_slice(data);
        }
    };

    let mut callbacks = git::RemoteCallbacks::default();
    callbacks.sideband_progress = Some(&mut sideband_closure);

    let result = git::push_updates(
        setup.jj_repo.as_ref(),
        &git_settings,
        "origin".as_ref(),
        &[GitRefUpdate {
            qualified_name: "refs/heads/main".into(),
            expected_current_target: Some(setup.main_commit.id().clone()),
            new_target: Some(setup.child_of_main_commit.id().clone()),
        }],
        &["merge_request.create", "merge_request.draft"],
        callbacks,
    );

    let captured_bytes = remote_output.lock().unwrap();
    let captured_string = String::from_utf8_lossy(&captured_bytes);

    if subprocess {
        assert!(captured_string.contains("Push-Option: merge_request.create"));
        assert!(captured_string.contains("Push-Option: merge_request.draft"));
    }

bertwin avatar May 06 '25 09:05 bertwin

setup a hook on the source repo that echos back the received options and capture the output through the callbacks.

That sounds good to me. We're going to delete/deprecate the libgit2 code path soon.

Configure callbacks and assert output

I think we can just snapshot the remote: outputs.

yuja avatar May 07 '25 01:05 yuja

Sounds like a reasonable approach ദ്ദി(• ˕ •

shikanime avatar May 07 '25 07:05 shikanime

needs a rebase

nyabinary avatar Aug 11 '25 14:08 nyabinary

I think this should be extended to gerrit as well, it uses push options for different flags/votes https://gerrit-review.googlesource.com/Documentation/user-upload.html#push_options

seankhliao avatar Nov 02 '25 00:11 seankhliao

I won't keep this PR rebased yearly unless we decide to merge it or actively reject it.

shikanime avatar Nov 05 '25 01:11 shikanime

To give some additional input. It works well and is very convenient for creating gitlab merge requests.

The only thing to be aware of is that jj is capable of pushing multiple bookmarks at once, but it's not possible to specify options per bookmark.

This would create two draft merge requests, which is fine

jj git push --option merge_request.create --option merge_request.draft  -c @- -c @--

But something like this is not possible

jj git push --option merge_request.create --option merge_request.draft  -c @- --option merge_request.title="The title I want" -c @-- --option merge_request.title="Another title"

This is of course easily solved by pushing independently.

bertwin avatar Nov 05 '25 10:11 bertwin

I won't keep this PR rebased yearly unless we decide to merge it or actively reject it.

Sorry, I didn't realize that this was waiting for a review. The messages before today didn't make it clear to me that it was ready for a review. I just reviewed it and it looks good to me, thanks.

martinvonz avatar Nov 05 '25 16:11 martinvonz

@bertwin Yes, I thought about this a while ago too, but it would require a lot of unusual things, such as changes to the revset, named options or the ordering of flags. For now, we should keep things simple and consider a UX specifically designed for multi-branch workflows, including GHStack, Gerrit and Stack PR, when necessary. For now, pushing branches individually looks fine to me, as it is pretty similar to other tools.

shikanime avatar Nov 06 '25 21:11 shikanime

Hi @shikanime, given that Martin has approved, would you give it one last rebase to resolve the conflicts? It should be fine to merge then, in time for the 0.36 release (scheduled Dec 3).

I was just considering to implement push-options myself when I found this PR. :)

jgreitemann avatar Nov 22 '25 11:11 jgreitemann