1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Conservative tree checkout with literal blob bytes and native POSIX modes.
//!
//! [`Repository::checkout_tree`](crate::Repository::checkout_tree) takes an explicit clean
//! baseline and target, with default [`Limits`] and no cancellation;
//! [`Repository::checkout_tree_with`](crate::Repository::checkout_tree_with) also takes limits and
//! a cancellation flag. [`Report`] records completed operations and
//! index publication; [`Failure`] preserves that report and cleanup failures when a later phase
//! stops. Inspect the actual worktree before recovery because mutations are not rolled back.
//!
//! [`Repository::checkout_tree_configured`](crate::Repository::checkout_tree_configured) uses
//! `core.symlinks` from the repository configuration: false writes symlink target bytes as regular
//! files while preserving symlink entries in the index. Baseline content is still verified,
//! including assume-unchanged entries. Raw checkout always writes native symlinks.
//!
//! The selected tree replaces a clean baseline and index without changing HEAD or references.
//! Pass the tree represented by the current index as the baseline, or `None` for an empty index
//! (including a no-checkout clone). A nonempty baseline with a missing index is rejected. An unborn
//! HEAD needs no special treatment because HEAD is neither read nor switched. Staged changes
//! relative to the supplied baseline, conflicts and dirty tracked paths are refused, even on
//! unchanged paths.
//!
//! Callers must exclude other worktree writers and renames of the root/ancestors throughout the
//! call, and protect repository metadata, objects and mount topology from replacement. The index
//! lock excludes cooperating index writers. Descriptor-relative no-follow traversal, exact names,
//! content verification and repeated identity checks detect observed changes, but POSIX has no
//! conditional rename/unlink by inode: arbitrary writers racing the final check are not excluded.
//! Hardlink/mount attacks and ABA replacements are outside this contract. No batch atomicity,
//! automatic rollback, crash durability, branch switching or force mode is provided;
//! [`Repository::reset`](crate::Repository::reset) with
//! [`ResetMode::Hard`](crate::reset::ResetMode::Hard) uses a variant that discards local changes.
//!
//! Linux/macOS only, on filesystems with native hard links, symlinks and POSIX modes. Colons,
//! backslashes and non-ASCII names are ordinary name bytes.
//!
//! Names follow the working tree's volume. Case folds when `core.ignoreCase` is true or the
//! working tree's root resolves `.GIT` to its `.git` entry; Unicode normalization folds on macOS,
//! the only platform where Git reads `core.precomposeUnicode`. Target paths sharing a folded name
//! are resolved as Git resolves them and listed in [`Report::collisions`]; see [`Collision`]. A
//! file and a directory sharing a name, a spelling change between baseline and target of one name
//! (such as a case-only rename), and any existing entry the volume merges with a wanted name are
//! refused before mutation.
//!
//! Both platforms reject unsafe components and the
//! metadata names Git refuses: `.git` in any ASCII case, the NTFS spellings of `.git` (`git~1`,
//! trailing dots or spaces, `:stream`) unless `core.protectNTFS` is false, and on macOS the HFS+
//! spellings with ignorable code points unless `core.protectHFS` is false. Also refused are
//! gitlinks, nested repositories and index extensions other than `TREE`, `FSMN`, `UNTR`, `EOIE`
//! and `IEOT`. Intent-to-add and skip-worktree
//! entries require caller policy and are refused. Path components are limited to 255 bytes. Windows
//! returns unsupported. Raw checkout preserves blob bytes without attributes, filters, EOL
//! conversion, ignores or config overrides;
//! [`Repository::checkout_tree_with_filters`](crate::Repository::checkout_tree_with_filters)
//! writes regular files through a [`filter::Pipeline`](crate::filter::Pipeline) instead. Symlink
//! blobs must be nonempty, NUL-free and at most 1024 bytes. Mode changes replace files, with
//! permissions 0644/0755; ownership, ACLs and other metadata are not preserved.
//!
//! Preparation also projects the nested-repository marker policy across the planned operations,
//! including retained siblings and directories. A non-root directory containing `HEAD`, `objects`
//! and `refs` (including ASCII case aliases) is refused even when these are ordinary files. This
//! deliberately excludes some valid Git trees so checkout cannot trigger its own live guard after
//! starting to mutate the worktree. The live guards remain active at every mutation boundary.
//!
//! Preparation holds the index lock, verifies trees/blobs, clean tracked content, names and
//! obstructions, then constructs the replacement index before worktree mutation. Mutation removes
//! changed tracked leaves, removes only known empty directories needed for directory-to-file
//! transitions, creates missing directories and installs leaves. Each operation rechecks its path
//! and ancestors. Publication rechecks all target content and the index before renaming the index
//! lock. Preparation and publication enumerate each directory's names once; mutation instead
//! creates every directory and leaf exclusively, so a name that appeared after preparation,
//! including a filesystem case or normalization alias, is refused rather than replaced. Leaves are
//! written under their final names, as Git writes them, so a concurrent reader can observe a file
//! whose content is still being written. An install is reported only once its content is
//! complete; a failed write removes the partial leaf while it is still the file checkout created.
//! Derived `TREE`, `FSMN`, `UNTR`, `EOIE` and `IEOT` caches are discarded, including on
//! unchanged checkouts. Git can rebuild them; girt does not consult fsmonitor or untracked caches.
//! New index entries have zero cached stat words and cleared assume-valid flags; Git may
//! need `git update-index --refresh` before stat-only plumbing such as `git diff-files`.
//! Unrelated content and empty directories are retained. Failures report completed operations
//! in execution order, the active stage and owned artifacts whose cleanup failed. Earlier
//! operations remain applied; inspect the report and actual files before recovery, rather than
//! blindly retrying.
//!
//! [`crate::Repository::checkout_merge`] uses the same guarded execution with a prepared marker
//! tree and original conflict stages. It validates the separate staged replacement before
//! working-file mutation and publishes it under the held index guard. See [`crate::merge`].
//!
//! # Example
//!
//! Materialize a tree into a new working tree whose index is still absent, as after a no-checkout
//! clone. HEAD and references are unchanged:
//!
//! ```
//! use girt::{EntryMode, InitKind, ObjectFormat, Repository, Tree, TreeEntry};
//!
//! # if cfg!(not(any(target_os = "linux", target_os = "macos"))) {
//! # return Ok(());
//! # }
//! let directory = tempfile::tempdir()?;
//! let repository = Repository::init(
//! ObjectFormat::Sha1,
//! directory.path().join("w"),
//! InitKind::Worktree,
//! )?;
//!
//! let loose = repository.loose_objects();
//! let id = loose.write_blob(b"#!/bin/sh\necho hello\n")?;
//! let entry = TreeEntry {
//! mode: EntryMode::Executable,
//! name: b"hello.sh".to_vec(),
//! id,
//! };
//! let tree = loose.write_tree(&Tree::new(ObjectFormat::Sha1, vec![entry])?)?;
//!
//! // `None` baseline: the current index is empty or absent.
//! repository.checkout_tree(None, Some(tree))?;
//!
//! let file = repository
//! .worktree()
//! .expect("ordinary repository")
//! .join("hello.sh");
//! assert_eq!(std::fs::read(&file)?, b"#!/bin/sh\necho hello\n");
//!
//! #[cfg(unix)]
//! {
//! use std::os::unix::fs::PermissionsExt;
//! assert_ne!(std::fs::metadata(&file)?.permissions().mode() & 0o111, 0);
//! }
//!
//! let index = repository
//! .read_index(Default::default())?
//! .expect("checkout published an index");
//! assert_eq!(index.entries()[0].path, b"hello.sh");
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
pub use *;
pub use ;