]>
Commit | Line | Data |
---|---|---|
0a29b90c FG |
1 | use crate::{clone::PrepareCheckout, Repository}; |
2 | ||
3 | /// | |
4 | pub mod main_worktree { | |
5 | use std::{path::PathBuf, sync::atomic::AtomicBool}; | |
6 | ||
0a29b90c FG |
7 | use crate::{clone::PrepareCheckout, Progress, Repository}; |
8 | ||
9 | /// The error returned by [`PrepareCheckout::main_worktree()`]. | |
10 | #[derive(Debug, thiserror::Error)] | |
11 | #[allow(missing_docs)] | |
12 | pub enum Error { | |
13 | #[error("Repository at \"{}\" is a bare repository and cannot have a main worktree checkout", git_dir.display())] | |
14 | BareRepository { git_dir: PathBuf }, | |
15 | #[error("The object pointed to by HEAD is not a treeish")] | |
16 | NoHeadTree(#[from] crate::object::peel::to_kind::Error), | |
17 | #[error("Could not create index from tree at {id}")] | |
18 | IndexFromTree { | |
19 | id: gix_hash::ObjectId, | |
20 | source: gix_traverse::tree::breadthfirst::Error, | |
21 | }, | |
22 | #[error(transparent)] | |
23 | WriteIndex(#[from] gix_index::file::write::Error), | |
24 | #[error(transparent)] | |
25 | CheckoutOptions(#[from] crate::config::checkout_options::Error), | |
26 | #[error(transparent)] | |
4b012472 | 27 | IndexCheckout(#[from] gix_worktree_state::checkout::Error), |
0a29b90c FG |
28 | #[error("Failed to reopen object database as Arc (only if thread-safety wasn't compiled in)")] |
29 | OpenArcOdb(#[from] std::io::Error), | |
30 | #[error("The HEAD reference could not be located")] | |
31 | FindHead(#[from] crate::reference::find::existing::Error), | |
32 | #[error("The HEAD reference could not be located")] | |
33 | PeelHeadToId(#[from] crate::head::peel::Error), | |
34 | } | |
35 | ||
36 | /// The progress ids used in [`PrepareCheckout::main_worktree()`]. | |
37 | /// | |
38 | /// Use this information to selectively extract the progress of interest in case the parent application has custom visualization. | |
39 | #[derive(Debug, Copy, Clone)] | |
40 | pub enum ProgressId { | |
41 | /// The amount of files checked out thus far. | |
42 | CheckoutFiles, | |
43 | /// The amount of bytes written in total, the aggregate of the size of the content of all files thus far. | |
44 | BytesWritten, | |
45 | } | |
46 | ||
47 | impl From<ProgressId> for gix_features::progress::Id { | |
48 | fn from(v: ProgressId) -> Self { | |
49 | match v { | |
50 | ProgressId::CheckoutFiles => *b"CLCF", | |
51 | ProgressId::BytesWritten => *b"CLCB", | |
52 | } | |
53 | } | |
54 | } | |
55 | ||
56 | /// Modification | |
57 | impl PrepareCheckout { | |
58 | /// Checkout the main worktree, determining how many threads to use by looking at `checkout.workers`, defaulting to using | |
59 | /// on thread per logical core. | |
60 | /// | |
61 | /// Note that this is a no-op if the remote was empty, leaving this repository empty as well. This can be validated by checking | |
62 | /// if the `head()` of the returned repository is not unborn. | |
781aab86 | 63 | pub fn main_worktree<P>( |
0a29b90c | 64 | &mut self, |
781aab86 | 65 | mut progress: P, |
0a29b90c | 66 | should_interrupt: &AtomicBool, |
781aab86 FG |
67 | ) -> Result<(Repository, gix_worktree_state::checkout::Outcome), Error> |
68 | where | |
69 | P: gix_features::progress::NestedProgress, | |
70 | P::SubProgress: gix_features::progress::NestedProgress + 'static, | |
71 | { | |
72 | self.main_worktree_inner(&mut progress, should_interrupt) | |
73 | } | |
74 | ||
75 | fn main_worktree_inner( | |
76 | &mut self, | |
77 | progress: &mut dyn gix_features::progress::DynNestedProgress, | |
78 | should_interrupt: &AtomicBool, | |
79 | ) -> Result<(Repository, gix_worktree_state::checkout::Outcome), Error> { | |
80 | let _span = gix_trace::coarse!("gix::clone::PrepareCheckout::main_worktree()"); | |
0a29b90c FG |
81 | let repo = self |
82 | .repo | |
83 | .as_ref() | |
84 | .expect("still present as we never succeeded the worktree checkout yet"); | |
85 | let workdir = repo.work_dir().ok_or_else(|| Error::BareRepository { | |
86 | git_dir: repo.git_dir().to_owned(), | |
87 | })?; | |
4b012472 | 88 | let root_tree = match repo.head()?.try_peel_to_id_in_place()? { |
0a29b90c FG |
89 | Some(id) => id.object().expect("downloaded from remote").peel_to_tree()?.id, |
90 | None => { | |
91 | return Ok(( | |
92 | self.repo.take().expect("still present"), | |
781aab86 | 93 | gix_worktree_state::checkout::Outcome::default(), |
0a29b90c FG |
94 | )) |
95 | } | |
96 | }; | |
4b012472 FG |
97 | let index = gix_index::State::from_tree(&root_tree, &repo.objects).map_err(|err| Error::IndexFromTree { |
98 | id: root_tree, | |
99 | source: err, | |
100 | })?; | |
0a29b90c FG |
101 | let mut index = gix_index::File::from_state(index, repo.index_path()); |
102 | ||
781aab86 FG |
103 | let mut opts = repo |
104 | .config | |
105 | .checkout_options(repo, gix_worktree::stack::state::attributes::Source::IdMapping)?; | |
0a29b90c FG |
106 | opts.destination_is_initially_empty = true; |
107 | ||
781aab86 FG |
108 | let mut files = progress.add_child_with_id("checkout".to_string(), ProgressId::CheckoutFiles.into()); |
109 | let mut bytes = progress.add_child_with_id("writing".to_string(), ProgressId::BytesWritten.into()); | |
0a29b90c FG |
110 | |
111 | files.init(Some(index.entries().len()), crate::progress::count("files")); | |
112 | bytes.init(None, crate::progress::bytes()); | |
113 | ||
114 | let start = std::time::Instant::now(); | |
781aab86 | 115 | let outcome = gix_worktree_state::checkout( |
0a29b90c FG |
116 | &mut index, |
117 | workdir, | |
4b012472 | 118 | repo.objects.clone().into_arc()?, |
781aab86 FG |
119 | &files, |
120 | &bytes, | |
0a29b90c FG |
121 | should_interrupt, |
122 | opts, | |
123 | )?; | |
124 | files.show_throughput(start); | |
125 | bytes.show_throughput(start); | |
126 | ||
127 | index.write(Default::default())?; | |
128 | Ok((self.repo.take().expect("still present"), outcome)) | |
129 | } | |
130 | } | |
131 | } | |
132 | ||
133 | /// Access | |
134 | impl PrepareCheckout { | |
135 | /// Get access to the repository while the checkout isn't yet completed. | |
136 | /// | |
137 | /// # Panics | |
138 | /// | |
139 | /// If the checkout is completed and the [`Repository`] was already passed on to the caller. | |
140 | pub fn repo(&self) -> &Repository { | |
141 | self.repo | |
142 | .as_ref() | |
143 | .expect("present as checkout operation isn't complete") | |
144 | } | |
145 | } | |
146 | ||
147 | /// Consumption | |
148 | impl PrepareCheckout { | |
149 | /// Persist the contained repository as is even if an error may have occurred when checking out the main working tree. | |
150 | pub fn persist(mut self) -> Repository { | |
151 | self.repo.take().expect("present and consumed once") | |
152 | } | |
153 | } | |
154 | ||
155 | impl Drop for PrepareCheckout { | |
156 | fn drop(&mut self) { | |
157 | if let Some(repo) = self.repo.take() { | |
158 | std::fs::remove_dir_all(repo.work_dir().unwrap_or_else(|| repo.path())).ok(); | |
159 | } | |
160 | } | |
161 | } | |
162 | ||
163 | impl From<PrepareCheckout> for Repository { | |
164 | fn from(prep: PrepareCheckout) -> Self { | |
165 | prep.persist() | |
166 | } | |
167 | } |