railmux 0.4.0.dev29__tar.gz → 0.4.0.dev30__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {railmux-0.4.0.dev29/src/railmux.egg-info → railmux-0.4.0.dev30}/PKG-INFO +1 -1
- railmux-0.4.0.dev30/src/railmux/__init__.py +1 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/windows_install_log.py +6 -5
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/windows_msys2.py +38 -9
- railmux-0.4.0.dev30/src/railmux/windows_paths.py +73 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30/src/railmux.egg-info}/PKG-INFO +1 -1
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux.egg-info/SOURCES.txt +2 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_windows_install_log.py +19 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_windows_msys2.py +67 -0
- railmux-0.4.0.dev30/tests/test_windows_paths.py +83 -0
- railmux-0.4.0.dev29/src/railmux/__init__.py +0 -1
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/LICENSE +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/MANIFEST.in +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/README.md +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/pyproject.toml +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/setup.cfg +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/__main__.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/atomic_file.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/background_index.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/cli.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/codex_index.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/config.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/config_cli.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/diagnostics.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/discovery.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/display_transport.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/entrypoint.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/fast_display_client.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/fast_display_history.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/fast_display_input.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/fast_display_protocol.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/fast_display_server.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/favorites.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/fuzzy.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/help_workspace.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/launcher.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/legacy_sessions.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/local_clipboard.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/local_open.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/models.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/modes.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/mouse_manager.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/orphan_marker.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/pane_surface.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/path_codec.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/provider_paths.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/remote_config.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/renames.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/restart_state.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/runtime_config.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/scroll_agent.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/scroll_manager.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/selection_isolation.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/self_update.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/session_cache.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/session_index.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/session_lease.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/setting_contracts.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/settings.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ssh_args.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ssh_compat.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ssh_display_diagnostics.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ssh_doctor.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/system_deps.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/terminal_status.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/tmux_binding_manager.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/tmux_ctl.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/tmux_health.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/tmux_server.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/tool_panes.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/transcript.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ui/__init__.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ui/_widgets.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ui/app.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ui/keymap.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ui/modals.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ui/projects_pane.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ui/running_pane.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ui/sessions_pane.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ui/sidebar.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ui/statusbar.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/ui/workspace.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/windows_attach_relay.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/windows_bootstrap.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/windows_pacman.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux/windows_ui_transition.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux.egg-info/dependency_links.txt +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux.egg-info/entry_points.txt +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux.egg-info/requires.txt +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/src/railmux.egg-info/top_level.txt +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_agent_workspace.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_atomic_file.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_background_index.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_cli.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_codex_delete.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_codex_index.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_codex_lineage.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_codex_transcript.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_config.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_config_cli.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_denested_transport_benchmark.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_diagnostics.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_discovery.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_display_transport.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_fast_full_window.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_focus_highlight.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_fullscreen_binding.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_fuzzy.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_help_agent.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_help_workspace.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_history_cleanup.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_keymap.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_launcher.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_layout_profile.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_legacy_sessions.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_local_clipboard.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_local_open.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_modals.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_mode_selection.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_modes.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_mouse_manager.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_new_project.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_options.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_orphan_recovery.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_pane_surface.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_path_codec.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_projects_pane.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_provider_paths.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_release_notes.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_release_workflow.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_remote_config.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_renames.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_restart_state.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_running_pane.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_running_sort.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_runtime_config.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_scroll_agent.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_scroll_manager.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_selection_isolation.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_self_update.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_session_cache.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_session_index.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_session_lease.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_sessions_pane.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_setting_contracts.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_settings.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_sidebar.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_soft_quit.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_ssh_args.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_ssh_compat.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_ssh_display_benchmark.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_ssh_display_diagnostics.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_ssh_doctor.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_status.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_statusbar.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_system_deps.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_terminal_size.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_terminal_status.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_termux_local_touch.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_tmux_binding_manager.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_tmux_ctl.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_tmux_health.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_tmux_integration.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_tmux_server.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_tmux_status.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_tool_panes.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_transcript.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_utf8_state.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_web_demo_recorder.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_wheel_smoke.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_widgets.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_windows_attach_relay.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_windows_bootstrap.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_windows_pacman.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_windows_tmux_lifecycle.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_windows_ui_transition.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_windows_wrapper_contract.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tests/test_yolo_prompt.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tools/denested_transport_benchmark.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tools/release_notes.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tools/wheel_smoke.py +0 -0
- {railmux-0.4.0.dev29 → railmux-0.4.0.dev30}/tools/windows_archive_smoke.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0.dev30"
|
|
@@ -22,6 +22,7 @@ from railmux.terminal_status import (
|
|
|
22
22
|
STYLE_WARNING,
|
|
23
23
|
styled,
|
|
24
24
|
)
|
|
25
|
+
from railmux.windows_paths import managed_windows_data_root
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
_LOG_KEEP_COUNT = 5
|
|
@@ -79,14 +80,14 @@ def install_log_path(
|
|
|
79
80
|
*,
|
|
80
81
|
version: str,
|
|
81
82
|
clock: Callable[[], float] = time.time,
|
|
83
|
+
data_root: Path | None = None,
|
|
82
84
|
) -> Path:
|
|
83
|
-
|
|
84
|
-
if
|
|
85
|
-
raise OSError("
|
|
85
|
+
root = managed_windows_data_root(environ) if data_root is None else data_root
|
|
86
|
+
if root is None:
|
|
87
|
+
raise OSError("a non-virtualized Windows data directory is unavailable")
|
|
86
88
|
stamp = time.strftime("%Y%m%dT%H%M%SZ", time.gmtime(clock()))
|
|
87
89
|
return (
|
|
88
|
-
|
|
89
|
-
/ "Railmux"
|
|
90
|
+
root
|
|
90
91
|
/ "logs"
|
|
91
92
|
/ f"install-{version}-{stamp}-{os.getpid()}.log"
|
|
92
93
|
)
|
|
@@ -42,6 +42,10 @@ from railmux.windows_pacman import (
|
|
|
42
42
|
validate_transaction_package_mirrors,
|
|
43
43
|
write_msys_only_pacman_config,
|
|
44
44
|
)
|
|
45
|
+
from railmux.windows_paths import (
|
|
46
|
+
legacy_local_app_data_root,
|
|
47
|
+
managed_windows_data_root,
|
|
48
|
+
)
|
|
45
49
|
from railmux.terminal_status import STYLE_ACCENT, STYLE_MUTED, styled
|
|
46
50
|
|
|
47
51
|
|
|
@@ -554,17 +558,17 @@ class Msys2Runtime:
|
|
|
554
558
|
|
|
555
559
|
|
|
556
560
|
def _managed_base(environ: Mapping[str, str]) -> Path | None:
|
|
557
|
-
|
|
558
|
-
if
|
|
561
|
+
data_root = managed_windows_data_root(environ)
|
|
562
|
+
if data_root is None:
|
|
559
563
|
return None
|
|
560
|
-
return
|
|
564
|
+
return data_root / "runtimes"
|
|
561
565
|
|
|
562
566
|
|
|
563
567
|
def _managed_cache(environ: Mapping[str, str]) -> Path | None:
|
|
564
|
-
|
|
565
|
-
if
|
|
568
|
+
data_root = managed_windows_data_root(environ)
|
|
569
|
+
if data_root is None:
|
|
566
570
|
return None
|
|
567
|
-
return
|
|
571
|
+
return data_root / "cache"
|
|
568
572
|
|
|
569
573
|
|
|
570
574
|
def _path_is_link_or_reparse(path: Path) -> bool:
|
|
@@ -595,6 +599,7 @@ def _prepare_cached_archive(
|
|
|
595
599
|
cache: Path,
|
|
596
600
|
*,
|
|
597
601
|
downloader: Downloader | None,
|
|
602
|
+
prior_caches: Sequence[Path] = (),
|
|
598
603
|
) -> tuple[Path, str]:
|
|
599
604
|
cache.mkdir(parents=True, exist_ok=True)
|
|
600
605
|
archive = cache / MSYS2_ARCHIVE_NAME
|
|
@@ -604,6 +609,18 @@ def _prepare_cached_archive(
|
|
|
604
609
|
archive.unlink(missing_ok=True)
|
|
605
610
|
except OSError as exc:
|
|
606
611
|
raise RuntimeInstallError("could not replace the private archive cache") from exc
|
|
612
|
+
for prior_cache in prior_caches:
|
|
613
|
+
prior_archive = prior_cache / MSYS2_ARCHIVE_NAME
|
|
614
|
+
if prior_archive == archive or not _archive_cache_is_valid(prior_archive):
|
|
615
|
+
continue
|
|
616
|
+
try:
|
|
617
|
+
shutil.copyfile(prior_archive, archive)
|
|
618
|
+
except OSError:
|
|
619
|
+
archive.unlink(missing_ok=True)
|
|
620
|
+
continue
|
|
621
|
+
if _archive_cache_is_valid(archive):
|
|
622
|
+
return archive, "verified prior local cache"
|
|
623
|
+
archive.unlink(missing_ok=True)
|
|
607
624
|
source = download_from_sources(
|
|
608
625
|
archive,
|
|
609
626
|
MSYS2_ARCHIVE_SHA256,
|
|
@@ -1178,7 +1195,9 @@ def apply_managed_runtime_prune(
|
|
|
1178
1195
|
"""Re-plan under the install lock, then remove only unchanged candidates."""
|
|
1179
1196
|
base = _managed_base(environ)
|
|
1180
1197
|
if base is None:
|
|
1181
|
-
raise RuntimeInstallError(
|
|
1198
|
+
raise RuntimeInstallError(
|
|
1199
|
+
"a non-virtualized Windows user data directory is unavailable"
|
|
1200
|
+
)
|
|
1182
1201
|
effective_lock = install_lock if lock_factory is None else lock_factory
|
|
1183
1202
|
with effective_lock(base):
|
|
1184
1203
|
current = plan_managed_runtime_prune(
|
|
@@ -2629,11 +2648,16 @@ def install_managed_runtime(
|
|
|
2629
2648
|
base = _managed_base(environ)
|
|
2630
2649
|
cache_base = _managed_cache(environ)
|
|
2631
2650
|
if base is None or cache_base is None:
|
|
2632
|
-
raise RuntimeInstallError(
|
|
2651
|
+
raise RuntimeInstallError(
|
|
2652
|
+
"a non-virtualized Windows user data directory is unavailable"
|
|
2653
|
+
)
|
|
2654
|
+
data_root = base.parent
|
|
2633
2655
|
try:
|
|
2634
2656
|
base.mkdir(parents=True, exist_ok=True)
|
|
2635
2657
|
cache_base.mkdir(parents=True, exist_ok=True)
|
|
2636
|
-
log_path = install_log_path(
|
|
2658
|
+
log_path = install_log_path(
|
|
2659
|
+
environ, version=version, data_root=data_root
|
|
2660
|
+
)
|
|
2637
2661
|
except OSError as exc:
|
|
2638
2662
|
raise RuntimeInstallError("could not create the private runtime log") from exc
|
|
2639
2663
|
final_root = base / "shared" / MSYS2_RUNTIME_ID
|
|
@@ -2749,6 +2773,11 @@ def install_managed_runtime(
|
|
|
2749
2773
|
archive, source = _prepare_cached_archive(
|
|
2750
2774
|
cache_base,
|
|
2751
2775
|
downloader=downloader,
|
|
2776
|
+
prior_caches=tuple(
|
|
2777
|
+
legacy / "cache"
|
|
2778
|
+
for legacy in (legacy_local_app_data_root(environ),)
|
|
2779
|
+
if legacy is not None and legacy != data_root
|
|
2780
|
+
),
|
|
2752
2781
|
)
|
|
2753
2782
|
reporter.done(source)
|
|
2754
2783
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Stable native-Windows storage paths for the managed runtime.
|
|
2
|
+
|
|
3
|
+
Microsoft Store and other MSIX-packaged Python interpreters can virtualize new
|
|
4
|
+
writes beneath AppData. Railmux's runtime contains executables that must be
|
|
5
|
+
opened by child processes outside that virtualized view, so packaged Python
|
|
6
|
+
uses a user-profile directory instead.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import ctypes
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
from collections.abc import Mapping
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_ERROR_INSUFFICIENT_BUFFER = 122
|
|
19
|
+
_APPMODEL_ERROR_NO_PACKAGE = 15700
|
|
20
|
+
_PACKAGED_PROFILE_DIRECTORY = ".railmux"
|
|
21
|
+
_PACKAGED_WINDOWS_DIRECTORY = "windows"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _has_windows_package_identity() -> bool:
|
|
25
|
+
"""Return whether this process has an MSIX/AppX package identity."""
|
|
26
|
+
if os.name != "nt":
|
|
27
|
+
return False
|
|
28
|
+
try:
|
|
29
|
+
get_family = ctypes.windll.kernel32.GetCurrentPackageFamilyName
|
|
30
|
+
length = ctypes.c_uint32(0)
|
|
31
|
+
result = int(get_family(ctypes.byref(length), None))
|
|
32
|
+
except (AttributeError, OSError, TypeError, ValueError):
|
|
33
|
+
return _looks_like_packaged_executable(sys.executable)
|
|
34
|
+
if result == _APPMODEL_ERROR_NO_PACKAGE:
|
|
35
|
+
return False
|
|
36
|
+
if result == _ERROR_INSUFFICIENT_BUFFER and length.value > 0:
|
|
37
|
+
return True
|
|
38
|
+
# A Store Python launcher and interpreter both have characteristic physical
|
|
39
|
+
# paths. Keep this fallback fail-safe if the package API is unavailable or
|
|
40
|
+
# returns an unexpected platform error.
|
|
41
|
+
return _looks_like_packaged_executable(sys.executable)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _looks_like_packaged_executable(executable: str) -> bool:
|
|
45
|
+
normalized = executable.replace("/", "\\").casefold()
|
|
46
|
+
return "\\windowsapps\\" in normalized or (
|
|
47
|
+
"\\appdata\\local\\packages\\" in normalized and "\\localcache\\" in normalized
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def managed_windows_data_root(environ: Mapping[str, str]) -> Path | None:
|
|
52
|
+
"""Select one data root visible to both Python and MSYS2 child processes."""
|
|
53
|
+
if _has_windows_package_identity():
|
|
54
|
+
user_profile = environ.get("USERPROFILE", "").strip()
|
|
55
|
+
if not user_profile:
|
|
56
|
+
return None
|
|
57
|
+
return (
|
|
58
|
+
Path(user_profile)
|
|
59
|
+
/ _PACKAGED_PROFILE_DIRECTORY
|
|
60
|
+
/ _PACKAGED_WINDOWS_DIRECTORY
|
|
61
|
+
)
|
|
62
|
+
local_app_data = environ.get("LOCALAPPDATA", "").strip()
|
|
63
|
+
if not local_app_data:
|
|
64
|
+
return None
|
|
65
|
+
return Path(local_app_data) / "Railmux"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def legacy_local_app_data_root(environ: Mapping[str, str]) -> Path | None:
|
|
69
|
+
"""Return the pre-packaged-Python-fix location for read-only cache reuse."""
|
|
70
|
+
local_app_data = environ.get("LOCALAPPDATA", "").strip()
|
|
71
|
+
if not local_app_data:
|
|
72
|
+
return None
|
|
73
|
+
return Path(local_app_data) / "Railmux"
|
|
@@ -63,6 +63,7 @@ src/railmux/windows_bootstrap.py
|
|
|
63
63
|
src/railmux/windows_install_log.py
|
|
64
64
|
src/railmux/windows_msys2.py
|
|
65
65
|
src/railmux/windows_pacman.py
|
|
66
|
+
src/railmux/windows_paths.py
|
|
66
67
|
src/railmux/windows_tmux_lifecycle.py
|
|
67
68
|
src/railmux/windows_ui_transition.py
|
|
68
69
|
src/railmux.egg-info/PKG-INFO
|
|
@@ -168,6 +169,7 @@ tests/test_windows_bootstrap.py
|
|
|
168
169
|
tests/test_windows_install_log.py
|
|
169
170
|
tests/test_windows_msys2.py
|
|
170
171
|
tests/test_windows_pacman.py
|
|
172
|
+
tests/test_windows_paths.py
|
|
171
173
|
tests/test_windows_tmux_lifecycle.py
|
|
172
174
|
tests/test_windows_ui_transition.py
|
|
173
175
|
tests/test_windows_wrapper_contract.py
|
|
@@ -223,6 +223,25 @@ def test_log_path_is_private_versioned_and_utc(tmp_path, monkeypatch):
|
|
|
223
223
|
)
|
|
224
224
|
|
|
225
225
|
|
|
226
|
+
def test_log_path_uses_packaged_python_non_virtualized_root(tmp_path, monkeypatch):
|
|
227
|
+
monkeypatch.setattr(
|
|
228
|
+
windows_install_log,
|
|
229
|
+
"managed_windows_data_root",
|
|
230
|
+
lambda _environ: tmp_path / ".railmux" / "windows",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
path = install_log_path(
|
|
234
|
+
{
|
|
235
|
+
"LOCALAPPDATA": str(tmp_path / "AppData" / "Local"),
|
|
236
|
+
"USERPROFILE": str(tmp_path),
|
|
237
|
+
},
|
|
238
|
+
version="0.4.0.dev30",
|
|
239
|
+
clock=lambda: 0,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
assert path.parent == tmp_path / ".railmux" / "windows" / "logs"
|
|
243
|
+
|
|
244
|
+
|
|
226
245
|
def test_default_command_runner_uses_no_stdin_and_captures_utf8(tmp_path):
|
|
227
246
|
stream = io.StringIO()
|
|
228
247
|
path = tmp_path / "install-0.4.0.dev7-20260803T000000Z-4.log"
|
|
@@ -628,6 +628,22 @@ def test_preview_versions_share_one_base_but_use_separate_app_layers(tmp_path):
|
|
|
628
628
|
)
|
|
629
629
|
|
|
630
630
|
|
|
631
|
+
def test_packaged_python_runtime_root_avoids_local_app_data(tmp_path, monkeypatch):
|
|
632
|
+
safe = tmp_path / ".railmux" / "windows"
|
|
633
|
+
monkeypatch.setattr(
|
|
634
|
+
windows_msys2, "managed_windows_data_root", lambda _environ: safe
|
|
635
|
+
)
|
|
636
|
+
environ = {
|
|
637
|
+
"LOCALAPPDATA": str(tmp_path / "AppData" / "Local"),
|
|
638
|
+
"USERPROFILE": str(tmp_path),
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
assert managed_root(environ) == (
|
|
642
|
+
safe / "runtimes" / "shared" / windows_msys2.MSYS2_RUNTIME_ID
|
|
643
|
+
)
|
|
644
|
+
assert windows_msys2._managed_cache(environ) == safe / "cache"
|
|
645
|
+
|
|
646
|
+
|
|
631
647
|
def test_explicit_user_runtime_is_probed_but_never_requires_managed_marker(tmp_path):
|
|
632
648
|
root = tmp_path / "用户-owned-msys"
|
|
633
649
|
runtime = make_runtime(root, managed=False)
|
|
@@ -1268,6 +1284,57 @@ def test_verified_base_archive_cache_avoids_a_second_download(tmp_path, monkeypa
|
|
|
1268
1284
|
assert source == "verified local cache"
|
|
1269
1285
|
|
|
1270
1286
|
|
|
1287
|
+
def test_packaged_python_reuses_verified_pre_fix_archive_cache(tmp_path, monkeypatch):
|
|
1288
|
+
payload = b"verified prior cache archive"
|
|
1289
|
+
monkeypatch.setattr(windows_msys2, "MSYS2_ARCHIVE_SIZE", len(payload))
|
|
1290
|
+
monkeypatch.setattr(
|
|
1291
|
+
windows_msys2,
|
|
1292
|
+
"MSYS2_ARCHIVE_SHA256",
|
|
1293
|
+
hashlib.sha256(payload).hexdigest(),
|
|
1294
|
+
)
|
|
1295
|
+
prior = tmp_path / "virtualized-cache"
|
|
1296
|
+
prior.mkdir()
|
|
1297
|
+
(prior / MSYS2_ARCHIVE_NAME).write_bytes(payload)
|
|
1298
|
+
current = tmp_path / "non-virtualized-cache"
|
|
1299
|
+
|
|
1300
|
+
selected, source = windows_msys2._prepare_cached_archive(
|
|
1301
|
+
current,
|
|
1302
|
+
downloader=lambda *_args: pytest.fail("prior cache should avoid downloading"),
|
|
1303
|
+
prior_caches=(prior,),
|
|
1304
|
+
)
|
|
1305
|
+
|
|
1306
|
+
assert selected.read_bytes() == payload
|
|
1307
|
+
assert source == "verified prior local cache"
|
|
1308
|
+
|
|
1309
|
+
|
|
1310
|
+
def test_packaged_python_never_reuses_unverified_pre_fix_archive(
|
|
1311
|
+
tmp_path, monkeypatch
|
|
1312
|
+
):
|
|
1313
|
+
payload = b"expected archive"
|
|
1314
|
+
monkeypatch.setattr(windows_msys2, "MSYS2_ARCHIVE_SIZE", len(payload))
|
|
1315
|
+
monkeypatch.setattr(
|
|
1316
|
+
windows_msys2,
|
|
1317
|
+
"MSYS2_ARCHIVE_SHA256",
|
|
1318
|
+
hashlib.sha256(payload).hexdigest(),
|
|
1319
|
+
)
|
|
1320
|
+
prior = tmp_path / "virtualized-cache"
|
|
1321
|
+
prior.mkdir()
|
|
1322
|
+
(prior / MSYS2_ARCHIVE_NAME).write_bytes(b"tampered archive")
|
|
1323
|
+
current = tmp_path / "non-virtualized-cache"
|
|
1324
|
+
|
|
1325
|
+
def downloader(_url, destination, _sha256):
|
|
1326
|
+
destination.write_bytes(payload)
|
|
1327
|
+
|
|
1328
|
+
selected, source = windows_msys2._prepare_cached_archive(
|
|
1329
|
+
current,
|
|
1330
|
+
downloader=downloader,
|
|
1331
|
+
prior_caches=(prior,),
|
|
1332
|
+
)
|
|
1333
|
+
|
|
1334
|
+
assert selected.read_bytes() == payload
|
|
1335
|
+
assert source in {label for label, _url in MSYS2_ARCHIVE_SOURCES}
|
|
1336
|
+
|
|
1337
|
+
|
|
1271
1338
|
def _write_test_tar(path: Path, entries: list[tuple[tarfile.TarInfo, bytes]]) -> None:
|
|
1272
1339
|
with tarfile.open(path, mode="w:xz") as archive:
|
|
1273
1340
|
for member, payload in entries:
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from types import SimpleNamespace
|
|
2
|
+
import ctypes
|
|
3
|
+
|
|
4
|
+
from railmux import windows_paths
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_unpacked_python_keeps_local_app_data_contract(tmp_path, monkeypatch):
|
|
8
|
+
monkeypatch.setattr(windows_paths, "_has_windows_package_identity", lambda: False)
|
|
9
|
+
local = tmp_path / "AppData" / "Local"
|
|
10
|
+
|
|
11
|
+
root = windows_paths.managed_windows_data_root(
|
|
12
|
+
{"LOCALAPPDATA": str(local), "USERPROFILE": str(tmp_path)}
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
assert root == local / "Railmux"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_packaged_python_avoids_virtualized_app_data(tmp_path, monkeypatch):
|
|
19
|
+
monkeypatch.setattr(windows_paths, "_has_windows_package_identity", lambda: True)
|
|
20
|
+
local = tmp_path / "AppData" / "Local"
|
|
21
|
+
|
|
22
|
+
root = windows_paths.managed_windows_data_root(
|
|
23
|
+
{"LOCALAPPDATA": str(local), "USERPROFILE": str(tmp_path)}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
assert root == tmp_path / ".railmux" / "windows"
|
|
27
|
+
assert local not in root.parents
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_packaged_python_never_falls_back_to_virtualized_app_data(
|
|
31
|
+
tmp_path, monkeypatch
|
|
32
|
+
):
|
|
33
|
+
monkeypatch.setattr(windows_paths, "_has_windows_package_identity", lambda: True)
|
|
34
|
+
|
|
35
|
+
assert (
|
|
36
|
+
windows_paths.managed_windows_data_root({"LOCALAPPDATA": str(tmp_path)}) is None
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_packaged_executable_path_fallback_is_narrow():
|
|
41
|
+
assert windows_paths._looks_like_packaged_executable(
|
|
42
|
+
r"C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.12\python.exe"
|
|
43
|
+
)
|
|
44
|
+
assert windows_paths._looks_like_packaged_executable(
|
|
45
|
+
r"C:\Users\u\AppData\Local\Packages\Python.Package\LocalCache\python.exe"
|
|
46
|
+
)
|
|
47
|
+
assert not windows_paths._looks_like_packaged_executable(
|
|
48
|
+
r"C:\Users\u\AppData\Local\Programs\Python\Python312\python.exe"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_windows_package_identity_api_distinguishes_packaged_process(monkeypatch):
|
|
53
|
+
def get_family(length, _buffer):
|
|
54
|
+
ctypes.cast(length, ctypes.POINTER(ctypes.c_uint32)).contents.value = 64
|
|
55
|
+
return 122
|
|
56
|
+
|
|
57
|
+
monkeypatch.setattr(windows_paths.os, "name", "nt")
|
|
58
|
+
monkeypatch.setattr(
|
|
59
|
+
windows_paths.ctypes,
|
|
60
|
+
"windll",
|
|
61
|
+
SimpleNamespace(
|
|
62
|
+
kernel32=SimpleNamespace(GetCurrentPackageFamilyName=get_family)
|
|
63
|
+
),
|
|
64
|
+
raising=False,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
assert windows_paths._has_windows_package_identity()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_windows_package_identity_api_preserves_unpacked_python(monkeypatch):
|
|
71
|
+
monkeypatch.setattr(windows_paths.os, "name", "nt")
|
|
72
|
+
monkeypatch.setattr(
|
|
73
|
+
windows_paths.ctypes,
|
|
74
|
+
"windll",
|
|
75
|
+
SimpleNamespace(
|
|
76
|
+
kernel32=SimpleNamespace(
|
|
77
|
+
GetCurrentPackageFamilyName=lambda _length, _buffer: 15700
|
|
78
|
+
)
|
|
79
|
+
),
|
|
80
|
+
raising=False,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
assert not windows_paths._has_windows_package_identity()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.0.dev29"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|