archlinux-downgrade / downgrade

Downgrade packages in Arch Linux

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Non-interactive downgrading with version-filtering

atreyasha opened this issue · comments

Hi @pbrisbin,

Making a new issue for a new idea. Besides the current downgrading dependencies idea in #83, I would like to implement an additional command line option for non-interactive functionality, and this option can be called --non-interactive.

Basically, this option will enable lazy users to downgrade package(s) with just some basic instructions and without having to select any option from the table. Since downgrade is by design an interactive tool, this option would be non-default. Some examples of this option's functionality using our staple vim:

  1. downgrade vim=8.2 --non-interactive: this will result in the latest version after fuzzy match to be automatically selected and installed.

  2. downgrade "vim(<=|>=|<|>)8.2" --non-interactive: this will result in either the latest version before (<|<=) or the latest version after (>|>=) to be automatically selected and installed.

Note: For all of these options, first priority is install via cache, if not via ALA.

I find this option useful because it enables downgrade to be used without any user input, so users who want to write scripts to return their system to a certain state could easily use this non-interactive tool. WDYT?

I definitely like this idea!

Generally, I would like to push on one of our existing core behaviors, which is:

If you can invoke downgrade such that there's only one option, it's handled non-interactively

What do you think of supporting a ~ at the end of any operator,

downgrade vim=~8.2
# filter to the latest version in 8.2.x

downgrade vim>~8.2
# filter to earliest version after 8.2 (exclusive)

downgrade vim>=~8.2
# filter to earliest version after 8.2 (inclusive)

downgrade vim<~8.2
# filter to latest version latest version before 8.2 (exclusive)

downgrade vim<=~8.2
# filter to latest version latest version before 8.2 (inclusive)

And, as always, if this filtering reduces to a single option (which all ~ filtering would), it is handled non-interactively.

For all of these options, first priority is install via cache, if not via ALA.

Again, I'd like to establish and rely on a core principal, instead of defining new and specific behavior. What if, the following were just how it always worked:

If ever the same package-version appears in ALA and Cache, only the Cache option is presented

That seems like a reasonable and useful truism of downgrade to me. And if it were true, the situation you describe just always applies, rather than being something special to a new --non-interactive option. And it ensures that if you filter down to the same option in Cache and ALA, only the Cache option is shown, and oh hey that's just one option, so it's handled non-interactively.

In all of these cases, the prompt-to-ignore would fire. To address that, I think we should support a new --[no-]ignore option so that prompt can be avoided (either way) by command line flag. I like this better than --no-interactive because it's more specific and can be useful even if you want to be interactive generally, but know ahead of time if you plan to include adding the package to ignores or not.

Generally, I would like to push on one of our existing core behaviors, which is:

If you can invoke downgrade such that there's only one option, it's handled non-interactively

Yes, I agree that it is good to stick as close as possible to this behaviour.

And, as always, if this filtering reduces to a single option (which all ~ filtering would), it is handled non-interactively.

I generally like the idea of filtering to one example with an additional, for example ~ , operator. Just a couple of concerns here for me, which I would like to discuss.

  1. (=|<=|>=|<|>)~ seems very close to the bash regex conditional =~ (perhaps by design?), and I fear that it might give off the sense that it is related to regex.

  2. I fear that the ~ operator or any additional operator after the version-filtering operator might make the whole concept of version-filtering more obscure; in a way that having another option such as --no-interactive will not. I feel like users might be more accustomed to playing around with command-line options than operators. But then again why should we not offer something new.

  3. Ideally, I would like our filtering operators to stay consistent with those present in pacman, and these are exclusively (=|<=|>=|<|>). Also for implementing #83, we would need any potential operators to be in the (=|<=|>=|<|>) set. But in defense of ~, we can simply regex out the special operator in case we want to see the version filter behind it.

I would say I am 60% convinced of using a special extra operator, but I would need to address these points.

Again, I'd like to establish and rely on a core principal, instead of defining new and specific behavior. What if, the following were just how it always worked:

If ever the same package-version appears in ALA and Cache, only the Cache option is presented

Tbh, I like the default verbose behaviour where both possible ALA and cache locations are printed even if there is only one package. I just feel that the default interactive behaviour of downgrade should be as verbose as possible, since we expect the user to be making conscious choices.

Perhaps this is over-engineering, but what if we add an extra command-line option for this behaviour, something like --prefer-cached. This would perform such a preference operation over all possible packages.

In all of these cases, the prompt-to-ignore would fire. To address that, I think we should support a new --[no-]ignore option so that prompt can be avoided (either way) by command line flag. I like this better than --no-interactive because it's more specific and can be useful even if you want to be interactive generally, but know ahead of time if you plan to include adding the package to ignores or not.

Hmm, yes adding the two command-line options of --ignore and --no-ignore would be more modular and would allow for other applications.

If we follow the above changes, we would essentially be able to construct non-interactive behaviour in a modular fashion, which I find cool. So for example, downgrading vim to just above or including version 8.2 in a non-interactive manner would be:

$ downgrade "vim>=~8.2" --prefer-cached --ignore

What do you think about this? I am personally almost convinced, just need to address those 3 points above and also this point related to --prefer-cached.

or any additional operator after the version-filtering operator might make the whole concept of version-filtering more obscure ... I would like our filtering operators to stay consistent with those present in pacman

These are very fair points. The introduction of ~ did seem a bit outside the box to me, so I'm not surprised there's some concern over user confusion.

What do you think of keeping the operator behavior the same, but adding the CLI options --first and --last to pre-select the first (or last) option? I think this is the best of both worlds: building on the "if there's only one" behavior, but also not complicating the operator "language".

This is really just splitting --non-interactive into two flags that pre-specify which way to automatically make the choice, a similar move as breaking out --[no-]ignore.

Downgrade vim non-interactively to oldest version at or later than 8.2, adding to Ignores.

% downgrade vim>=8.2 --first --ignore

Downgrade vim non-interactively to the latest version before 8.2, not adding to Ignores

% downgrade vim<8.2 --last --no-ignore

Sidenote: < with --last is really the killer feature for me. If you know what versions broke your shit, you know ahead of time that you'd want to downgrade "<$that_version" --last --ignore.

A couple of concerns:

  • "first" vs "last" is coupled to our current sorting of newer first

    We could address this by accepting --latest or --oldest instead

  • This could allow "unreasonable" combinations

    I think >(=) with --first and <(=) with --last, which is exactly how you proposed --non-interactive should work, are the only reasonable use cases. Other combinations make less sense (to me), but maybe there are use-cases users might fine, so the flexibility is valuable? On the other hand, do I prefer the separate options because it's simpler to implement because it pushes the complexity of getting the combination correct onto the user? That's not great.

  • We still need to solve the same package version in Cache and ALA

    I believe we always sort cache first, so --first would implicitly prefer cache, and --last would implicitly prefer ALA. This means that if you need <, and so you need --last, you're not stuck unable to prefer Cache.

    Personally, I don't find it useful to ever prefer ALA. They are literally the same package, except one is already on your filesystem... why incur the download cost? The only case I can think of is if you have a corrupt package on your filesystem you need to avoid. This is an edge-case IMO, and can be worked around easily by removing that package ahead of time (e.g -Scc).

    I may pull this point out into a separate Issue for further discussion, and consider this feature as blocked on wherever we land there.

What do you think of keeping the operator behavior the same, but adding the CLI options --first and --last to pre-select the first (or last) option? I think this is the best of both worlds: building on the "if there's only one" behavior, but also not complicating the operator "language".

This sounds like a good compromise; keeping operators simple and using CLI options to our benefit.

"first" vs "last" is coupled to our current sorting of newer first
We could address this by accepting --latest or --oldest instead

True, --latest and --oldest sound more appropriate.

This could allow "unreasonable" combinations

= could also be a reasonable use case. For example, downgrade vim=8.2 would fuzzy match to all possible 8.2 version prefixes and we could add a preference with --oldest or --newest to give us a single option.

>|< could also still be reasonable. If we are sure we need to avoid a certain version and choose everything else above or below it, then --latest and --oldest would still make sense in non-interactively selecting one package out of the valid package set.

We still need to solve the same package version in Cache and ALA

Yes given that we sort cache first then ALA, this would be a problem. But I am pretty sure we can find a quick workaround for this. Maybe I will code up a solution first and we can take it from there.

Also, in terms of priority, I am leaning towards making two PRs in the upcoming release. One to address this issue with non-interactive behaviour and possibly one more to address #119, which I find to be an important fix (if the OP is not doing a PR that is). I think #83 can stay on as lower priority for now.

Would it be okay if I re-open #119 and rename it to something more precise?

True, --latest and --oldest sound more appropriate.

👍

For example, downgrade vim=8.2 would fuzzy match to all possible 8.2 version prefixes

Is that how this works today? Is it just because that's how vercmp works?

Also, in terms of priority

All fine with me, I totally defer to you.

Is that how this works today? Is it just because that's how vercmp works?

This is because of line 139 in downgrade (see https://github.com/pbrisbin/downgrade/blob/master/downgrade#L139).

This was by design so that downgrade does not fail when an incomplete package version is given. But if a complete package version is given, it will match the correct (single) package version.

All fine with me, I totally defer to you.

Awesome. In case the OP wants to add a PR with that issue, I will leave the issue alone and make a new one addressing the specific components.

Nice touch on the projects as well.

This is because of line 139 in downgrade (see https://github.com/pbrisbin/downgrade/blob/master/downgrade#L139).

Oh I missed that! I found it surprising (that = doesn't just mean =), but I suppose the unexpected behavior would result in multiple results and present the list to the user, so it's not particularly problematic.

So now with #121 merged, we can address some other points in this issue:

Personally, I don't find it useful to ever prefer ALA. They are literally the same package, except one is already on your filesystem... why incur the download cost? The only case I can think of is if you have a corrupt package on your filesystem you need to avoid. This is an edge-case IMO, and can be worked around easily by removing that package ahead of time (e.g -Scc).

Hmm, yes this makes sense. I think to truly allow for non-interactive behaviour, we would have to incorporate this as a feature within a PR addressing this issue.

Line 256 already defaults to direct installation if only a single candidate is present.

Maybe we could add a conditional/function before this line, to check if all candidates presented have the same package version (eg. single package both in the cache and ALA). In that case, the remote option will be discarded, which will trigger a direct installation in the next line. (There could be a problem here with multiple cache locations, maybe we just select the first cache location).

This conditional/function could also be used to pick out the latest and oldest version.

This seems the least intrusive to me, compared to modifying the functions that create the candidates array.

I will present something in this direction in a PR addressing this issue.

We could address this by accepting --latest or --oldest instead

This is really just splitting --non-interactive into two flags that pre-specify which way to automatically make the choice, a similar move as breaking out --[no-]ignore.

I agree with these. So here we would be adding 4 additional command-line options without arguments, specifically (--latest | --oldest),(--ignore | --noignore).

In order to trigger non-interactive behaviour, the user would have to select an option from either one or both of these groups.

a. If multiple options from both cache and ALA are present in candidates, then the user would need to select an option from both groups to trigger non-interactive behaviour.

b. If a single package version, possibly from both cache and ALA, is present in candidates, then the user would need to select an option only from the (--ignore | --noignore) group to trigger non-interactive behaviour (assuming the above-stated feature which discards the remote candidate is already implemented)

I think >(=) with --first and <(=) with --last, which is exactly how you proposed --non-interactive should work, are the only reasonable use cases. Other combinations make less sense (to me), but maybe there are use-cases users might fine, so the flexibility is valuable? On the other hand, do I prefer the separate options because it's simpler to implement because it pushes the complexity of getting the combination correct onto the user? That's not great.

Now, --latest and --oldest still make sense for all operators, including =. Even with the change in the definition of =, we still conform to the equality standards of vercmp, where a package version would match with all epochs if no epoch is provided in the reference version. That is to say downgrade foo=1.5 would trigger equality for both foo-1.5-1 and foo-1.5-2.

I will try something out in a PR for us to review.

Maybe we could add a conditional/function before this line

FWIW, I was hoping we could achieve this through more pipe-line stages in building candidates. For example, I think we already have a sort step, and I think it sorts Cache above ALA. If so, we could just apply -u on package-version in that step and any case of Cache and ALA for the same package version would filter to just the Cache entry on its own. 🤷

FWIW, I was hoping we could achieve this through more pipe-line stages in building candidates. For example, I think we already have a sort step, and I think it sorts Cache above ALA. If so, we could just apply -u on package-version in that step and any case of Cache and ALA for the same package version would filter to just the Cache entry on its own.

Pipe-line sounds much more elegant than a conditional for sure 😄 Will try my best to work on something pipe-able; we can take it step-by-step from there.

Hi! Today, I wanted to undo a whole set of updates using a commandline such as cat /var/log/pacman.log | sed -n 's/^\[2023-10-04.*\] \[ALPM\] upgraded \(.*\) (\(.*\) -> \(.*\))$/\1=\2/p' | xargs -r sudo bin/downgrade (look at the pacman log to downgrade everything to the version from which it was updated today).
Since the update I wanted to undo contained a large number of packages (170 haskell packages alone), an interactive downgrade would not be fun at all but for some reason, both ala and my cache missed certain packages.

I ended up using the --cached-only option, then downgrading the one package that was missing from my local cache and then downgrade using --cached-only. I've written a pr ( #217 ) that allows for nonteractive downgrades by simply picking the first candidate whatsoever.

Update: as per the discussion in the pr, it picks the latest version in cache that matches the version filter or -- if nothing matches in cache or --ala-only was specified -- the latest version in ala that matches the version filter

Thanks for tackling this! Looking over this discussion, it seems like we did foresee your case and had suggested a design to address it:

What do you think of keeping the operator behavior the same, but adding the CLI options --first and --last to pre-select the first (or last) option? I think this is the best of both worlds: building on the "if there's only one" behavior, but also not complicating the operator "language".

This sounds like a good compromise; keeping operators simple and using CLI options to our benefit.

"first" vs "last" is coupled to our current sorting of newer first
We could address this by accepting --latest or --oldest instead

True, --latest and --oldest sound more appropriate.

So, as per our conversation there, I think if you renamed your option to --latest it would meet (half of) this design exactly, and we'll be good to merge. It'd be great if you'd implement --oldest too, but I'd merge without it. Sound good?

Just to make sure I understand, you could say this more simply, right?

as per the discussion in the pr, it picks the latest version in cache that matches the version filter or -- if nothing matches in cache or --ala-only was specified -- the latest version in ala that matches the version filter in the result set

What's is or is not available, in cache or ala, and the -only options just inform the result set itself, --latest just does the obvious thing with whatever it is.

Just to make sure I understand, you could say this more simply, right?

as per the discussion in the pr, it picks the latest version in cache that matches the version filter or -- if nothing matches in cache or --ala-only was specified -- the latest version in ala that matches the version filter in the result set

What's is or is not available, in cache or ala, and the -only options just inform the result set itself, --latest just does the obvious thing with whatever it is.

Sadly not. As per my usecase where a precise version is specified, I want to prefer packages in the cache. As looking up in ala is slow and costly, I want to skip even looking up ala if there's any cached match for the filter. Iff either --ala-only or --cache-only was given, then your simplyfication would be correct.

In the pr manpage, I put it this way:

Never prompt for version or location, pick automatically instead. Allows for
noninteractive downgrades when used in conjunction with --ignore
If a package matching the version filter is found in the cache, pick the
latest matching version from cache. Íf no matches are found in cache or
--ala-only was specified, then pick the latest matching version from ala.

Ah, thanks for clarifying. So it sounds like we're discussing two implementations:

  1. "Simple": as described here, add --latest and --oldest options to select the first or last result non-interactively
  2. "Complicated": as your PR does, add an option that understands cache-vs-ala and does {thing you described} as part of selecting "latest"

The Simple option does function, but you don't prefer it because:

looking up in ala is slow and costly

In order to avoid even the lookup in ALA when something is available in cache (not just avoid selecting it), you need the option to interact with the searching itself, hence: "complicated".

This is a hard trade-off for me as maintainer. The Complicated option is very complicated, both for the user to understand what the option does w.r.t. searching and cache-vs-ALA, and in the implementation, when compared to the Simple option on the same attributes. And it's hard to quantify how the Simple option "[still works, but] is slow and costly" in order to weigh its downside against that.

Just to cover all the bases, would it have been possible for you to get all the behaviors you wanted with something like this:

sed -n 's/^\[2023-10-04.*\] \[ALPM\] upgraded \(.*\) (\(.*\) -> \(.*\))$/\1=\2/p' /var/log/pacman.log |
  while read -r target; do
    if ! sudo bin/downgrade "$target" --ignore --cache-only; then
      # not found in cache, try ALA
      sudo bin/downgrade "$target" --ignore --ala-only
    fi
  done

I have an idea. Let's add the following options:

  1. --latest Automatically select the latest option from the results
  2. --oldest Automatically select the oldest option from the results
  3. --cache-preferred Like --cache-only, but still search ALA if there are no results

These are isolated, simple, flexible, understandable, and when used together (I think) do what you wanted.

EDIT: actually, when you use exact version bounds in the target, you wouldn't even need the latest/oldest options for your use-case. --cache-preferred is enough.

WDYT?

I wrote a pr that suits my needs without really reading through this thread. From a user perspective, I would consider my approach an "I don't care", hence the name suggestions --any-candidate or --autoselect.

While you typed your comment I just wanted to make precisely the same suggestion for --cache-preferred. Updating my pr accordingly should be easy.

These options might also come in handy in interactive cases.

These options might also come in handy in interactive cases.

Agreed. I quite like --cache-preferred just as a general feature. Thanks for engaging on the design side of this!

The pr is a draft and I'll mark it ready for review once these changes are done. Thank you for your prompt responses 🙂

the pr is ready for review :)