98286406 Andy McCall 1 month ago ↶
1
2
3
use super :: * ;
4
5
# [ derive ( Debug, Default, Clone, PartialEq, Eq ) ]
6
pub struct CheckoutReport {
7
pub written : usize ,
8
pub removed : usize ,
9
pub conflicted : bool ,
10
}
11
12
impl Repo {
13
14
15
16
pub fn checkout ( & self , heads : & [ChangeHash], discard : bool , command : & str ) -> Result< CheckoutReport> {
17
let snap = self .snapshot ( command) ? ;
18
if ! discard & & ( ! snap.wc.tree_ops.is_empty ( ) | | ! snap.wc.edits.is_empty ( ) ) {
19
return fail ( " the working copy has changes; commit them first or pass --discard" ) ;
20
}
21
let target_state = self .load_state ( heads) ? ;
22
let heads = target_state.maximal ( heads) ;
23
let target = target_state.materialize ( ) ;
cdb5beba Andy McCall 1 month ago ↶
24
let attrs = Repo:: state_attributes( & target) ;
98286406 Andy McCall 1 month ago ↶
25
let current = self .walk_working_copy ( ) ? ;
26
let mut report = CheckoutReport:: default( ) ;
27
28
29
let mut dirs_to_prune: BTreeSet< PathBuf> = BTreeSet:: new( ) ;
30
for path in current.keys ( ) {
31
if ! target.files.contains_key ( path) {
32
let full = self .safe_path ( path) ? ;
33
fs:: remove_file( & full) ? ;
34
report.removed + = 1 ;
35
if let Some ( parent) = full.parent ( ) {
36
dirs_to_prune.insert ( parent.to_path_buf ( ) ) ;
37
}
38
}
39
}
7421309f Andy McCall 1 month ago ↶
40
98286406 Andy McCall 1 month ago ↶
41
for ( path, f) in & target.files {
7421309f Andy McCall 1 month ago ↶
42
if ! self .in_scope ( path) {
43
continue ;
44
}
98286406 Andy McCall 1 month ago ↶
45
match & f.content {
46
FileContent:: Dir => continue ,
47
FileContent:: Text { bytes, .. } => {
cdb5beba Andy McCall 1 month ago ↶
48
let a = attrs.lookup ( path) ;
49
let on_disk: std:: borrow:: Cow< [ u8 ] > = if a.eol = = Some ( crate :: attributes:: Eol:: Crlf) { crate :: attributes:: to_crlf( bytes) .into ( ) } else { bytes.as_slice ( ) .into ( ) } ;
50
let same = current.get ( path) .is_some_and ( | c | c.kind = = NodeKind:: File & & c.content = = * on_disk & & c.exec = = f.mode.exec ) ;
98286406 Andy McCall 1 month ago ↶
51
if ! same {
cdb5beba Andy McCall 1 month ago ↶
52
self .write_file ( path, & on_disk, f.mode.exec) ? ;
98286406 Andy McCall 1 month ago ↶
53
report.written + = 1 ;
54
}
cdb5beba Andy McCall 1 month ago ↶
55
if a.lockable & & ! self .is_locked ( path) {
56
let _ = set_readonly ( & self .root.join ( path) , true ) ;
57
}
98286406 Andy McCall 1 month ago ↶
58
}
59
FileContent:: Opaque { blobs } => {
60
let Some ( blob) = blobs.first ( ) else { continue } ;
61
let bytes = self .read_blob ( blob) ? ;
62
if f.kind = = NodeKind:: Symlink {
63
let same = current.get ( path) .is_some_and ( | c | c.kind = = NodeKind:: Symlink & & c.content = = bytes ) ;
64
if ! same {
65
self .write_symlink ( path, & bytes) ? ;
66
report.written + = 1 ;
67
}
68
} else {
69
let same = current.get ( path) .is_some_and ( | c | c.kind = = NodeKind:: File & & c.content = = bytes & & c.exec = = f.mode.exec ) ;
70
if ! same {
71
self .write_file ( path, & bytes, f.mode.exec) ? ;
72
report.written + = 1 ;
73
}
cdb5beba Andy McCall 1 month ago ↶
74
if attrs.lookup ( path) .lockable & & ! self .is_locked ( path) {
75
let _ = set_readonly ( & self .root.join ( path) , true ) ;
76
}
98286406 Andy McCall 1 month ago ↶
77
}
78
if blobs.len ( ) > 1 {
79
for ( i, b) in blobs.iter ( ) .enumerate ( ) .skip ( 1 ) {
80
self .write_file ( & format! ( " {path} .oxy-conflict-{} " , i + 1 ) , & self .read_blob ( b) ? , f.mode.exec) ? ;
81
}
82
}
83
}
84
}
85
}
86
87
for d in dirs_to_prune.iter ( ) .rev ( ) {
88
let mut d = d.clone ( ) ;
89
while d ! = self .root & & fs:: read_dir( & d) .map ( | mut i | i.next ( ) .is_none ( ) ) .unwrap_or ( false ) {
90
fs:: remove_dir( & d) ? ;
91
let Some ( p) = d.parent ( ) else { break } ;
92
d = p.to_path_buf ( ) ;
93
}
94
}
95
96
let ( op_ref, op) = self .head_op ( ) ? ;
6d3eefbc Andy McCall 1 month ago ↶
97
let wc = self .new_working_copy ( heads.clone ( ) , " " ) ;
98286406 Andy McCall 1 month ago ↶
98
let wc_hash = self .store.put_change ( & wc) ? ;
99
let mut view = op.view.clone ( ) ;
100
view.workspaces.insert ( self .workspace, wc_hash) ;
101
self .record_op ( & [ op_ref] , command, view) ? ;
102
report.conflicted = target.is_conflicted ( ) ;
135212de Andy McCall 1 month ago ↶
103
self .write_manifest ( report.conflicted, false ) ? ;
6d3eefbc Andy McCall 1 month ago ↶
104
let _ = self .run_hook (
105
" post-checkout" ,
106
& format! ( " {{ \" event\" :\" post-checkout\" ,\" heads\" :{} ,\" written\" :{} ,\" removed\" :{} }} " , json_array ( heads.iter ( ) .map ( | h | json_str ( & h.to_hex ( ) ) ) ) , report.written, report.removed) ,
107
) ;
98286406 Andy McCall 1 month ago ↶
108
Ok ( report)
109
}
110
111
112
113
fn safe_path ( & self , rel : & str ) -> Result< PathBuf> {
114
let mut full = self .root.clone ( ) ;
115
let comps: Vec< & str > = rel.split ( ' /' ) .collect ( ) ;
116
for ( i, comp) in comps.iter ( ) .enumerate ( ) {
117
if comp.is_empty ( ) | | * comp = = " ." | | * comp = = " .." | | comp.contains ( ' \\ ' ) {
118
return fail ( format! ( " refusing unsafe path {rel:?} " ) ) ;
119
}
120
if i = = 0 & & * comp = = OXY_DIR {
121
return fail ( format! ( " refusing to write inside {OXY_DIR} : {rel:?} " ) ) ;
122
}
123
if i + 1 < comps.len ( ) {
124
full.push ( comp) ;
125
if let Ok ( m) = fs:: symlink_metadata( & full) {
126
if m.file_type ( ) .is_symlink ( ) {
127
return fail ( format! ( " refusing to write through symlink {} " , full.display ( ) ) ) ;
128
}
129
}
130
} else {
131
full.push ( comp) ;
132
}
133
}
134
Ok ( full)
135
}
136
137
fn write_file ( & self , rel : & str , bytes : & [u8 ], exec : bool ) -> Result< ( ) > {
138
let full = self .safe_path ( rel) ? ;
139
if let Some ( parent) = full.parent ( ) {
140
fs:: create_dir_all( parent) ? ;
141
}
142
if fs:: symlink_metadata( & full) .is_ok_and ( | m | m.file_type ( ) .is_symlink ( ) | | m.is_dir ( ) ) {
143
remove_any ( & full) ? ;
144
}
cdb5beba Andy McCall 1 month ago ↶
145
if fs:: metadata( & full) .is_ok_and ( | m | m.permissions ( ) .readonly ( ) ) {
146
set_readonly ( & full, false ) ? ;
147
}
98286406 Andy McCall 1 month ago ↶
148
fs:: write( & full, bytes) ? ;
149
set_executable ( & full, exec) ? ;
150
Ok ( ( ) )
151
}
152
153
fn write_symlink ( & self , rel : & str , target : & [u8 ]) -> Result< ( ) > {
154
let full = self .safe_path ( rel) ? ;
155
if let Some ( parent) = full.parent ( ) {
156
fs:: create_dir_all( parent) ? ;
157
}
158
if fs:: symlink_metadata( & full) .is_ok ( ) {
159
remove_any ( & full) ? ;
160
}
161
# [ cfg ( unix ) ]
162
{
163
std:: os:: unix:: fs:: symlink( String :: from_utf8_lossy( target) .as_ref ( ) , & full) ? ;
164
Ok ( ( ) )
165
}
166
# [ cfg ( not ( unix ) ) ]
167
{
168
169
fs:: write( & full, target) ? ;
170
Ok ( ( ) )
171
}
172
}
173
}