Skip to content

Conversation

Joining7943
Copy link
Contributor

@Joining7943 Joining7943 commented Sep 5, 2022

I feel like the tail.rs file needs some refactoring to make it easier to implement new features. The file grew big over time, so I started with moving functions and structs around and moved them into new modules without changing much of the logic. The tests are all still passing, but there's much more potential for refactorings. Right now, I've spent just a few hours with it. I could dive deeper into the issue, however, I would appreciate to hear some feedback first and maybe you've got some additional ideas, since you've worked on tail much longer than I did.

EDIT:

Here's a quick overview of changes to tail in this step:

  • Extraction of smaller specialized modules from tail.rs. New modules are
    • paths: Containing logic around paths and inputs
    • args: Deal with argument parsing
    • follow/files: Logic around the paths followed by a Watcher
    • follow/watch: Contains the logic around following files and wraps a notify::Watcher
    • text: Small module to contain static strings used in every module
  • Extraction of structs and methods in general
  • Rewrite and compact the tail::uu_tail function
  • Deal with stdin only when necessary, so when it is given on the commandline.
  • Remove mutables from Settings . Settings only store the initial values.
  • Do not use PathBufs to store strings like "-"
  • Simplify nested structures where possible

This PR is part of a larger refactoring, so there is still work that I address in a following PR.

@tertsdiepraam
Copy link
Member

cc @jhscheer, who implemented #2695

Copy link
Member

@tertsdiepraam tertsdiepraam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactors are always welcome :) I can't look at this in depth now, but I have one small first comment. A quick scroll-through makes this look very promising!

build.rs Outdated
.as_bytes(),
)
.unwrap();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it makes more sense to re-export uu_app from tail? I.e.

pub use args::uu_app;

I do like that you split the args into a separate file though!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it makes more sense to re-export uu_app from tail?

Cool. I haven't seen that.

@jhscheer
Copy link
Contributor

jhscheer commented Sep 6, 2022

Hi,
I'm with Terts, refactors are always welcome. However, there're a few things to watch out for.

I feel like the tail.rs file needs some refactoring to make it easier to implement new features.

What features are you thinking of?

The file grew big over time, so I started with moving functions and structs around and moved them into new modules without changing much of the logic. The tests are all still passing, but there's much more potential for refactorings.

Right now there're two CI monitored tests that don't pass in this refactor.
A lot of tail.rs grew organically to pass various tests from GNU's test suite. I transferred most of GNU's tests to our test frame work, but passing the test frame work does not mean passing GNU's tests. And to complicate things even more, not all of GNU's test suite tests that uu_tail passes are monitored by the CI (i.e. they pass on a local Linux, but not in the CI container). I mentioned this in tail's README.
To summarise, please regularly run GNU's tests after bigger changes to see if the relevant tests still pass.

Right now, I've spent just a few hours with it.

Please wait for the other two open tail PRs to get merged before you do more refactoring, or you'll have to deal with merge conflicts.

@Joining7943
Copy link
Contributor Author

What features are you thinking of?

I had no specific features in mind. As far as I can see, uu_tail basically already provides all features gnu's tail does. I also meant to simplify fixing the todo()s and adding currently missing implementations for other platforms than linux, maybe also fixing some of the currently failing gnu tests.

Please wait for the other two open tail PRs to get merged before you do more refactoring, or you'll have to deal with merge conflicts.

Sure, no problem. After 2 of the gnu tests failed, which originally passed, I thought about writing more tests before continuing the refactorings. Also, I would like to continue your work, incorporating gnu's test suite concerning tail into our own test framework. I'll take care as much as possible that the refactorings don't break existent code and tests.

@Joining7943
Copy link
Contributor Author

Is breaking the test_tail.rs file in smaller related parts, like test_tail/test_tail_follow.rs, test_tail/test_tail_args etc. an option? It would simplify spotting missing tests. Naming the files test_tail_... won't break the new_ucmd, util_name macros. It's possible without changes in the build script, but then there would be a test_tail.rs file and a folder test_tail in the test directory. The other approach would involve a small addition to the build script which checks if there is a directory "test_tail" (in case of tail) in the test directory and if so adds the path test_tail/test_tail.rs to the test_modules.rs file. If there is no such directory the file test_tail.rs within the root of the test directory is added to test_modules like before. See also my last commit 4253aa4 as a quick solution.

@tertsdiepraam
Copy link
Member

I think we could do that, but I don't quite understand the advantage you're talking about. Could you explain it a bit more?

@jhscheer
Copy link
Contributor

jhscheer commented Sep 7, 2022

Is breaking the test_tail.rs file in smaller related parts, like test_tail/test_tail_follow.rs, test_tail/test_tail_args etc. an option? It would simplify spotting missing tests.

How?

If I'm only interested in a subset of tests I run, e.g.:

cargo test test_stdin --features "tail" --no-default-features
cargo test test_retry --features "tail" --no-default-features

etc.

@Joining7943
Copy link
Contributor Author

Joining7943 commented Sep 7, 2022

The main benefit in my opinion is an organizational one. There are smaller files, which can be filled with more tests which are already partly explained and categorized by the file name. Then names for the tests themselves can be shorter or more specific to the problem, what in turn makes it easier to spot missing tests, if any. This won't break running only subsets of tests. In addition, one can use for example the filter test_tail_follow to run all tests in the file test_tail/test_tail_follow.rs and the test names dont' have to necessarily include test_follow.

@jhscheer
Copy link
Contributor

jhscheer commented Sep 7, 2022

Then names for the tests themselves can be shorter or more specific to the problem, what in turn makes it easier to spot missing tests, if any. This won't break running only subsets of tests. In addition, one can use for example the filter test_tail_follow to run all tests in the file test_tail/test_tail_follow.rs and the test names dont' have to necessarily include test_follow.

But that can already be done with modules, like what you did with mod pipe_tests.

@Joining7943
Copy link
Contributor Author

Yes I did that and I didn't like it too much. I would have preferred moving the tests into an own file. However, I moved the tests into this mod to switch off the tests for windows in one go instead of annotating every single test. I don't see a reason keeping files big if there's no hard reason for it. Rust provides a nice way to split files into smaller units with ease and big files are just cumbersome to work with and most often unnecessary. If you think it's worth it, I'll move out the commit to an own pr and maybe fix the code duplication I introduce to the build file.

@tertsdiepraam
Copy link
Member

My 2 cents: breaking up tests over files is unnecessary, because I usually just search for the test I need to look at, which is actually easier in one single file. For non-test code I agree that splitting is good, of course.

Copy link
Member

@tertsdiepraam tertsdiepraam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few small comments, but looks good overall. Of course we can only proceed with this of all the tests mentioned by @jhscheer still pass.

}
}

pub struct WatcherService {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With a name like WatcherService, I think youre implying that it does more than it actually does. Currently it's just a wrapper around the watcher and orphans. The abstraction makes sense but only if you take it further by making follow and handle_event methods of this struct, so you don't need to expose the fields.

pub fn from(settings: &mut Settings) -> UResult<Self> {
let mut watcher_service = if settings.follow.is_some() {
let (watcher, rx) = start_watcher_thread(settings)?;
Self::new(Some(watcher), Some(rx))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these should always be handled together in 1 Option so we ensure we never set only one of the two. This goes for his they are stored in the WatcherService and how they are pleased around in functions like new.

watcher_service: WatcherService,
) -> UResult<()> {
let (mut watcher, rx, mut orphans) = (
watcher_service.watcher.unwrap(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way we can get rid of the unwraps? I presume this is okay because the WatcherService has these properties if and only if we're following?

use crate::text;
use core::option::Option;
use core::option::Option::{None, Some};
use core::result::Result::{Err, Ok};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these Option/Result things are in the prelude so don't need to be imported right?

}

/// Canonicalize `path` if it is not already an absolute path
fn canonicalize_path(path: &Path) -> PathBuf {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like it should be a free standing function

@Joining7943
Copy link
Contributor Author

@tertsdiepraam Thanks for your comments and review. Current state is wip and I've waited to proceed with this pr until the two bigger prs around tail are merged before I make greater changes, especially to the logic. Right now, I just have to move things around in case of a merge conflict. I'm changing the code step by step working towards a final solution, executing the test suite after each step. At the moment, constructs may look useless, incomplete or illplaced.

@tertsdiepraam
Copy link
Member

That sounds excellent! My comments were then a bit premature :)

@Joining7943
Copy link
Contributor Author

No problem :) There's still a lot to do and I'll keep pushing intermediate results, so if you like you can see where the journey goes. I'll welcome any suggestions.

@tertsdiepraam
Copy link
Member

Nice! Just remember to not make this PR too big. The bigger it gets the harder it is to review and there's not really a downside to having multiple successive PRs.

@Joining7943
Copy link
Contributor Author

@tertsdiepraam I'm ready for review if you are. There's still a lot more that can be done and currently there's a last error on macos concerning tail - < dir. Like you suggested this'll be an intermediate pr. I'll fix the error, but this doesn't involve bigger changes. I'm also going to write a more exhaustive list of changes in the initial post of this pr.

Copy link
Member

@tertsdiepraam tertsdiepraam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! Some more small nits, but the big picture looks great I think. Have you checked the compared GNU tests locally as well with main, to ensure nothing broke that's not in the CI?

Also did we break these recently or have they never passed? In both cases well done!

Warning: Congrats! The gnu test tests/tail-2/follow-name is no longer failing!
Warning: Congrats! The gnu test tests/tail-2/retry is no longer failing!
Warning: Congrats! The gnu test tests/tail-2/wait is no longer failing!

@@ -62,9 +130,10 @@ pub enum FollowMode {
Name,
}

// TODO: use getters instead of pub access to fields to make Settings not changeable from the outside ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary. Usually this kind of thing is only really helpful when you want to expose some API to other crates, but when it's all relatively small like this, it's fine like it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

let mut chunks = chunks::LinesChunkBuffer::new(*sep, *count);
chunks.fill(reader)?;
chunks.print(writer)?;
}
(FilterMode::Lines(count, sep), true) => {
let mut num_skip = (*count).max(1) - 1;
FilterMode::Lines(Signum::PlusZero, _) | FilterMode::Lines(Signum::Positive(1), _) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this cleanup of FilterMode!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks :)

Copy link
Member

@tertsdiepraam tertsdiepraam Sep 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realised you could use or-patterns here:

FilterLines::Lines(SigNum::PlusZero | SigNum::Positive(1), _)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. Looks a little bit more compact.

};
}

/// Read new data from `path` and print it to stdout
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you document the return value here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. There's a lot of documenation missing in general. I can address this at the end of the next iteration.

Comment on lines +267 to +266
match input.kind() {
InputKind::Stdin => continue,
InputKind::File(path) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
match input.kind() {
InputKind::Stdin => continue,
InputKind::File(path) => {
if let InputKind::File(path) = input.kind() {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you think it adds some clearity keeping the match written out, here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An if clearly conveys that we're only using one arm in my opinion. I see what you mean though. If you think this is clearer then you can leave it like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to keep it for now. It's just, that I'm not done with this part, and this little reminder helps me a little bit. I'll compact the match like you suggested in the next pr.


// main print loop
for path in &paths {
_read_some = watcher_service.files.tail_file(path, settings.verbose)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's going on with all the unused values? Can we remove them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure we can remove some or all of them, and this is certainly something we should address in the next pr.

}
} else if !(settings.stdin_is_pipe_or_fifo && settings.paths.len() == 1) {
follow(files, &settings, watcher_rx, orphans)?;
// TODO: can I do this check in watch::follow?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a lot of TODO notes in here, which is fine, but because this is an open project, they need some context for everyone else who is reading the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for that. I won't leave any TODOs when I'm finished, but this is just an intermediate pr, so I would like to keep them for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I found a solution, so we can both be happy. I removed all TODOs in tail, so they don't end up in the main branch. I'll revert that commit later. No big deal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you removed all TODOs, even those that were there before this refactor. This is very inconvenient.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I wasn't suggesting to remove them, I just wanted them to be a bit more verbose so other people might pick them up more easily if they come across them in the code :)

parse_size(size_string).map(|n| (n, starting_with))
}

// TODO: delete ?? not used anymore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please explain this todo. Is it about the function? If it's really not used it should indeed be removed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tangentially related (and should not be fixed in this PR), but it feels like the platform module should handle this case correctly for Windows.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've found that this function has no usages (anymore) within tail, so it's subject to deletion if I don't find any use for it.

fn from(matches: &ArgMatches) -> UResult<Self> {
let zero_term = matches.contains_id(options::ZERO_TERM);
let mode = if let Some(arg) = matches.value_of(options::BYTES) {
match parse_num(arg) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make parse_num return UResult<Signum>?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I was a little bit torn here between keeping the function general or optimizing it. But, I'm on your side and also tend to the latter, here.


/// Wrapper for HashMap::remove using Path::canonicalize
pub fn remove(&mut self, k: &Path) -> PathData {
self.map.remove(&Self::canonicalize_path(k)).unwrap()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is unwrap okay for these methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually don't think it is and tend to return an Option or Result from these methods, but I need to dig deeper through the watch module to figure this out. These functions are heavily used. I can address this issue together with refactoring watch in the next pr.

@Joining7943
Copy link
Contributor Author

Thanks for this great review!

Have you checked the compared GNU tests locally as well with main, to ensure nothing broke that's not in the CI?

Good, you're pointing that out. I didn't knew this about the CI, so I haven't run the tests locally for the last 30 commits or so. I'll run them tomorrow together with the fix for the failing test on macos.

Also did we break these recently or have they never passed? In both cases well done!
Thanks. Imho they pass since this refactoring and cleanup, but I haven't picked out these tests intentionally, so I can't say for sure.

@jhscheer
Copy link
Contributor

Cool! Some more small nits, but the big picture looks great I think. Have you checked the compared GNU tests locally as well with main, to ensure nothing broke that's not in the CI?

Also did we break these recently or have they never passed? In both cases well done!

Warning: Congrats! The gnu test tests/tail-2/follow-name is no longer failing!
Warning: Congrats! The gnu test tests/tail-2/retry is no longer failing!
Warning: Congrats! The gnu test tests/tail-2/wait is no longer failing!

Imho they pass since this refactoring and cleanup, but I haven't picked out these tests intentionally, so I can't say for sure.

There are 5 tests which are fixed but do not (always) pass the test suite if it's run inside the CI.
The reason for this is probably related to load/scheduling on the CI test VM.
The tests in question are:
- [x] `tail-2/F-vs-rename.sh`
- [x] `tail-2/follow-name.sh`
- [x] `tail-2/inotify-rotate.sh`
- [x] `tail-2/overlay-headers.sh`
- [x] `tail-2/retry.sh`

There maybe others but I stopped keeping track since I made the mention in the README.

@Joining7943 Joining7943 marked this pull request as ready for review September 14, 2022 06:39
Copy link
Contributor

@jhscheer jhscheer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except for the nit-picks, this looks very good to me.

Comment on lines +12 to +13
use std::collections::hash_map::Keys;
use std::collections::HashMap;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
use std::collections::hash_map::Keys;
use std::collections::HashMap;
use std::collections::{hash_map::Keys, HashMap};

Comment on lines +16 to +17
use std::sync::mpsc;
use std::sync::mpsc::{channel, Receiver};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
use std::sync::mpsc;
use std::sync::mpsc::{channel, Receiver};
use std::sync::mpsc::{self, channel, Receiver};

Comment on lines +21 to +24
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.

Comment on lines +65 to +68
match self.kind {
InputKind::File(_) => false,
InputKind::Stdin => true,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
match self.kind {
InputKind::File(_) => false,
InputKind::Stdin => true,
}
matches!(self.kind, InputKind::Stdin)

}
InputKind::File(_) | InputKind::Stdin => {
if cfg!(unix) {
PathBuf::from(text::DEV_STDIN).canonicalize().ok()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
PathBuf::from(text::DEV_STDIN).canonicalize().ok()
// Workaround to handle redirects, e.g. `touch f && tail -f - < f`
PathBuf::from(text::DEV_STDIN).canonicalize().ok()

}
#[cfg(windows)]
{
// use std::os::windows::prelude::*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// use std::os::windows::prelude::*;
// TODO: `file_index` requires unstable library feature `windows_by_handle`
// use std::os::windows::prelude::*;

fn uu_tail(mut settings: Settings) -> UResult<()> {
let dash = PathBuf::from(text::DASH);

fn uu_tail(settings: &Settings) -> UResult<()> {
// Mimic GNU's tail for `tail -F` and exit immediately
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Mimic GNU's tail for `tail -F` and exit immediately

if (settings.paths.is_empty() || settings.paths.contains(&dash)) && settings.follow_name() {
let mut input_service = InputService::from(settings);
let mut watcher_service = WatcherService::from(settings);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Mimic GNU's tail for `tail -F` and exit immediately

Comment on lines +383 to +384
// dbg!("bounded");
// dbg!(&settings.mode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// dbg!("bounded");
// dbg!(&settings.mode);

Comment on lines +421 to +422
// dbg!("unbounded");
// dbg!(&settings.mode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// dbg!("unbounded");
// dbg!(&settings.mode);

@Joining7943
Copy link
Contributor Author

Like I said this is an intermediate pr. @tertsdiepraam If there's nothing essentially wrong I would like to go on.

@tertsdiepraam
Copy link
Member

I think we should merge this once this round of (simple) comments have been fixed so that we don't lose track of them. And then we can move to the next PR.

@Joining7943
Copy link
Contributor Author

@tertsdiepraam I took care of every one of your comments, and then I proceeded. The other comments don't add any value, because I already addressed them on my new branch and are included in the follow up pr.

@tertsdiepraam tertsdiepraam merged commit 8c83988 into uutils:main Sep 17, 2022
@tertsdiepraam
Copy link
Member

Alright! Looking forward to the next one!

@jhscheer
Copy link
Contributor

The other comments don't add any value, because I already addressed them on my new branch and are included in the follow up pr.

Sorry what?

I did a review when this PR was marked ready for review.
Why do my comments not add any value? What does that even mean?

Where is this new branch?
Why did I spend time to address issues if they were already fixed some where?

How do you expect reviewers to keep track of issues they raise if the issues are not handled in the PR, but some where else?

@tertsdiepraam I think that merging a PR with open issues is very unusual.

@tertsdiepraam
Copy link
Member

I interpreted not adding value as already fixed in a larger change, so in a sense outdated. I see refactoring as an ongoing change anyway, so I think it's fine in this case, but it is indeed unusual. The diff is getting huge and I'd rather review smaller changes from this point onward. So, @Joining7943, please keep it small from now on :)

Joining7943 added a commit to Joining7943/uutil-coreutils that referenced this pull request Sep 27, 2022
Joining7943 added a commit to Joining7943/uutil-coreutils that referenced this pull request Sep 28, 2022
@Joining7943 Joining7943 deleted the refactor-tail branch September 29, 2022 20:49
Joining7943 added a commit to Joining7943/uutil-coreutils that referenced this pull request Sep 29, 2022
Joining7943 added a commit to Joining7943/uutil-coreutils that referenced this pull request Oct 1, 2022
Joining7943 added a commit to Joining7943/uutil-coreutils that referenced this pull request Oct 3, 2022
Joining7943 added a commit to Joining7943/uutil-coreutils that referenced this pull request Oct 3, 2022
Joining7943 added a commit to Joining7943/uutil-coreutils that referenced this pull request Oct 4, 2022
sylvestre pushed a commit to Joining7943/uutil-coreutils that referenced this pull request Oct 5, 2022
sylvestre added a commit that referenced this pull request Oct 6, 2022
`tests/tail`: Fix tests to reflect changes from the refactoring #3905
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants