Skip to content

Don't abort when repos.json and repos_filtered.json are not present #376

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

tpwo
Copy link
Contributor

@tpwo tpwo commented May 22, 2025

These files are not present if all-repos exits unexpectedly, e.g. due to SIGINT.

Then, the error message:

output_dir should only contain repos.json, repos_filtered.json, and directories

is shown, as these two files are missing.

If they're recreated manually as empty, the error is gone and all-repos-clone works again, so the current check can be less strict.

These files are not present if `all-repos` exits unexpectedly, e.g. due
to SIGINT.

Then, the error message:

    output_dir should only contain repos.json, repos_filtered.json, and directories

is shown, as these two files are missing. If they're recreated manually
as empty, the error is gone and `all-repos-clone` works again.
Comment on lines 56 to 58
if not (
contents >= REPOS_JSON_FILES and
all(
os.path.isdir(os.path.join(output_dir, d))
Copy link
Owner

Choose a reason for hiding this comment

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

yeah this is definitely not correct -- this breaks the safeguard that's in place

a better fix would be to avoid the situation which breaks the invariant (perhaps in all-repos-clone ?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it. Now I back up both files when all-repos-clone is running and restore them if anything goes wrong (and a newer version of a file wasn't created).

I also added status messages of what's going on. What do you think?

tpwo added 4 commits May 24, 2025 00:23
Top level try..except was added, so both files can be restored when
all-repos-clone encounters any issues. Files are restored only if new
version hasn't appeared.
@asottile
Copy link
Owner

probably best to write this as a context manager which only does something on error

also on error the state is ~sort of unknown so it is probably best to write empty json files rather than restoring the previous ones (as they may refer to removed repositories)

as CI indicates you'll also need to write some tests for this behaviour

@tpwo
Copy link
Contributor Author

tpwo commented Jun 3, 2025

Done. What do you think?

Comment on lines 95 to 110
@contextlib.contextmanager
def safe_write_json(paths: tuple[str, str]) -> Generator[None]:
try:
yield
except KeyboardInterrupt:
print('Aborting...')
for path in paths:
with open(path, 'w') as f:
f.write('{}')
except Exception:
for path in paths:
with open(path, 'w') as f:
f.write('{}')
raise


Copy link
Owner

Choose a reason for hiding this comment

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

the word "safe" usually isn't a good name for a function (often that's exactly where to look for unsafe things) -- it should probably say something more specifically about what it does (specifically it restores blank files on failure)

I think you can also combine the two except blocks by using except BaseException (this will include SystemExit / KeyboardInterrupt / other crashes)

I also had another idea while thinking about this

what instead of attempting to restore these files, all-repos instead wrote a little marker file that's just always there? that would make this check a lot more consistent (does .all-repos exist? cool we're good to go!) but we'd also need to think about how to upgrade an existing setup to have that file (perhaps by using the same logic that's already there?)

what do you think of this other idea?

sorry again for taking so long on this. I have had zero free time recently and been back and forth across the country travelling :(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

what do you think of this other idea?

Sounds cool, but let's make sure we're on the same page ;)

we'd also need to think about how to upgrade an existing setup to have that file (perhaps by using the same logic that's already there?)

By "the same logic", you mean _check_output_dir?

def _check_output_dir(output_dir: str) -> None:
if os.path.exists(output_dir):
contents = set(os.listdir(output_dir))
if not contents: # empty dir is ok!
return
if not (
contents >= REPOS_JSON_FILES and
all(
os.path.isdir(os.path.join(output_dir, d))
for d in contents - REPOS_JSON_FILES
)
):
raise SystemExit(
'output_dir should only contain repos.json, '
'repos_filtered.json, and directories',
)

So the new flow in _check_output_dir would be:

  • 1st check: "Empty dir is ok"
  • 2nd check: Check only for .all-repos file, if it's present we're good to go
  • 3rd check: (upgrade existing setup) use the current JSON files check from _check_output_dir, and create .all-repos file if other files are there
  • Fail otherwise

Do I get it right?

Copy link
Owner

Choose a reason for hiding this comment

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

yeah I think that's right

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, now it's a lot simpler :)

Coverage is at 100% w/o new tests, but do you think we need anything additional 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.

Should I update readme as well? I see the directory structure is listed there. E.g.

all-repos/README.md

Lines 204 to 210 in ee3e0a0

output/
+--- repos.json
+--- repos_filtered.json
+--- {repo_key1}/
+--- {repo_key2}/
+--- {repo_key3}/
```

Copy link
Owner

Choose a reason for hiding this comment

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

of course there should be a test -- otherwise how do we know it works and does what you intend to fix!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hard to disagree ;)

I added two tests: one checking for the marker file, and the other checking if it's recreated after prior removal. This simulates the upgrade behavior for existing setups

@@ -138,6 +138,7 @@ def main(argv: Sequence[str] | None = None) -> int:
f.write(json.dumps(repos))
with open(config.repos_filtered_path, 'w') as f:
f.write(json.dumps(repos_filtered))
open(os.path.join(config.output_dir, '.all-repos'), 'w').close()
Copy link
Owner

Choose a reason for hiding this comment

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

why is this here and the other place? surely it should be one or the other right?

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.

2 participants