git_policy_cli/subvolume/
subvolume_arguments.rs1use std::ffi::OsString;
16
17pub const MIGRATE_HELP: &str = "\
19Usage: git cli-git migrate-to-subvolume [--dry-run] [--no-nested-worktrees] [--no-refresh-indexes]
20 git cli-git migrate-to-subvolume --rollback [--discard-unreadable] [--dry-run]
21 git cli-git migrate-to-subvolume --remove-old [--discard-unreadable] [--dry-run]
22
23Convert the repository's main checkout into a btrfs subvolume at the same path, so that creating a worktree can start
24from a snapshot. Nothing is spawned: the command calls the btrfs ioctls, clones file data, copies the tree, swaps the
25old and new directories atomically in one step, and rewrites the Git index stat fields so the first `git status` stays
26fast. Every step is recorded beside the checkout; if the command is stopped at any point, run it again and it resumes.
27The original tree is kept until you delete it.
28
29Options:
30 --dry-run Check the conditions and print the plan; change nothing.
31 --rollback Undo a migration, finished or not: carry back newer files, restore the original directory
32 and delete the new subvolume.
33 --remove-old Delete the original tree of a finished migration (slow: every file is unlinked).
34 --discard-unreadable With --remove-old: also delete the files this user may not read, which were not copied.
35 With --rollback: also delete the files only the new subvolume holds that this user may not
36 read, which cannot be carried back.
37 --no-nested-worktrees Keep the linked worktrees inside the checkout as ordinary directories (default: each becomes
38 a nested subvolume).
39 --no-refresh-indexes Leave the Git index files as copied (the first git status of each worktree then hashes every
40 file).
41 --help Show this help.
42
43Before you run it, stop agent sessions and editors that have their working directory inside the checkout: after the
44swap they would keep writing into the old tree. The dry run lists them. Run it on a machine with the checkout's
45filesystem mounted with enough free space; `git cli-git doctor` explains the mount option that makes removal fast.
46";
47
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
58pub enum MigrateMode {
59 Migrate,
61 Rollback,
63 RemoveOld,
65}
66
67#[derive(Clone, Copy, Debug, Eq, PartialEq)]
77pub struct MigrateArguments {
78 pub mode: MigrateMode,
80 pub dry_run: bool,
82 pub nested_worktrees: bool,
84 pub refresh_indexes: bool,
86 pub discard_unreadable: bool,
88 pub help: bool,
90}
91
92pub fn parse_migrate_arguments(arguments: &[OsString]) -> Result<MigrateArguments, String> {
103 let mut parsed: MigrateArguments = MigrateArguments {
104 mode: MigrateMode::Migrate,
105 dry_run: false,
106 nested_worktrees: true,
107 refresh_indexes: true,
108 discard_unreadable: false,
109 help: false,
110 };
111 let (mut rollback, mut remove_old, mut migrate_switch): (bool, bool, bool) = (false, false, false);
112 for token in arguments {
113 let text: &str = token.to_str().ok_or_else(|| { return String::from("an argument is not valid UTF-8 text; the options are ASCII"); })?;
114 match text {
115 "--dry-run" => parsed.dry_run = true,
116 "--rollback" => rollback = true,
117 "--remove-old" => remove_old = true,
118 "--discard-unreadable" => parsed.discard_unreadable = true,
119 "--no-nested-worktrees" => {
120 parsed.nested_worktrees = false;
121 migrate_switch = true;
122 }
123 "--no-refresh-indexes" => {
124 parsed.refresh_indexes = false;
125 migrate_switch = true;
126 }
127 "--help" | "-h" => parsed.help = true,
128 other => return Err(format!("unknown argument {other}; the options are listed by `git cli-git migrate-to-subvolume --help`")),
129 }
130 }
131 if parsed.help {
132 return Ok(parsed);
133 }
134 if rollback && remove_old {
135 return Err(String::from("--rollback and --remove-old exclude each other: --rollback restores the original tree, --remove-old deletes it. Run one, then the other if you still want it."));
136 }
137 if (rollback || remove_old) && migrate_switch {
138 return Err(String::from("--no-nested-worktrees and --no-refresh-indexes change how the migration runs, so they apply only without --rollback and --remove-old."));
139 }
140 if parsed.discard_unreadable && !remove_old && !rollback {
141 return Err(String::from("--discard-unreadable applies only with --remove-old or --rollback."));
142 }
143 if rollback {
144 parsed.mode = MigrateMode::Rollback;
145 }
146 if remove_old {
147 parsed.mode = MigrateMode::RemoveOld;
148 }
149 return Ok(parsed);
150}
151
152#[cfg(test)]
154#[path = "subvolume_arguments_tests.rs"]
155mod tests;