read_url/git/
url.rs

1use super::{
2    super::{context::*, url::*, util::*},
3    git_url::*,
4};
5
6impl URL for GitUrl {
7    fn context(&self) -> &UrlContext {
8        &*self.context
9    }
10
11    fn query(&self) -> Option<UrlQuery> {
12        self.repository_url.query()
13    }
14
15    fn fragment(&self) -> Option<String> {
16        self.repository_url.fragment()
17    }
18
19    fn format(&self) -> Option<String> {
20        get_format_from_path(&self.path)
21    }
22
23    fn base(&self) -> Option<UrlRef> {
24        get_relative_path_parent(&self.path).map(|path| self.new_with(path).into())
25    }
26
27    fn relative(&self, path: &str) -> UrlRef {
28        self.new_with(self.path.join(path)).into()
29    }
30
31    #[cfg(feature = "blocking")]
32    fn conform(&mut self) -> Result<(), super::super::UrlError> {
33        self.conform_path()
34    }
35
36    #[cfg(feature = "async")]
37    fn conform_async(&self) -> Result<ConformFuture, super::super::UrlError> {
38        use super::super::errors::*;
39
40        async fn conform_async(mut url: GitUrl) -> Result<UrlRef, UrlError> {
41            url.conform_path()?;
42            Ok(url.into())
43        }
44
45        Ok(Box::pin(conform_async(self.clone())))
46    }
47
48    #[cfg(feature = "blocking")]
49    fn open(&self) -> Result<ReadRef, super::super::UrlError> {
50        Ok(Box::new(self.open_cursor()?))
51    }
52
53    #[cfg(feature = "async")]
54    fn open_async(&self) -> Result<OpenFuture, super::super::UrlError> {
55        use super::super::errors::*;
56
57        async fn open_async(url: GitUrl) -> Result<AsyncReadRef, UrlError> {
58            Ok(Box::pin(url.open_cursor()?))
59        }
60
61        Ok(Box::pin(open_async(self.clone())))
62    }
63}
64
65#[cfg(any(feature = "blocking", feature = "async"))]
66impl GitUrl {
67    fn open_cursor(&self) -> Result<std::io::Cursor<Vec<u8>>, super::super::UrlError> {
68        use {
69            super::{super::errors::*, errors::*},
70            gix::*,
71            std::{io::Cursor, num::*},
72            tracing::info,
73        };
74
75        // Interpret fragment
76        let (commit, ref_name) = match self.fragment() {
77            Some(fragment) => {
78                // Is it a commit hash?
79                match ObjectId::from_hex(fragment.as_bytes()) {
80                    Ok(object) => {
81                        info!("using commit: {}", object);
82                        (Some(object), None)
83                    }
84
85                    // No, so we'll consider it a reference name (branch or tag)
86                    Err(_) => {
87                        info!("using reference name: {}", fragment);
88                        (None, Some(fragment))
89                    }
90                }
91            }
92
93            None => (None, None),
94        };
95
96        let repository = if self.repository_gix_url.scheme == url::Scheme::File {
97            if commit.is_some() || ref_name.is_some() {
98                return Err(UrlError::UnsupportedFormat("fragment cannot be used with local git repositories".into()));
99            }
100
101            // Use local path
102            info!("opening local repository: {}", self.repository_gix_url);
103
104            let path = self.repository_gix_url.path.to_string();
105            open(path).map_err(GitError::from)?
106        } else {
107            let (directory, existing) = self.context.cache.directory(&self.repository_url.to_string(), "git-")?;
108            let directory = directory.lock()?;
109
110            if existing {
111                info!("opening cached repository: {}", directory.display());
112
113                open(directory.clone()).map_err(GitError::from)?
114            } else {
115                info!("cloning repository to: {}", directory.display());
116
117                let mut prepare_fetch = prepare_clone_bare(self.repository_gix_url.clone(), directory.clone())
118                    .map_err(GitError::from)?
119                    .configure_remote(|remote| Ok(remote));
120
121                if commit.is_none() {
122                    // Without a specific commit we can get away with a shallow clone
123                    let one = NonZeroU32::new(1).expect("NonZeroU32::new");
124                    prepare_fetch = prepare_fetch
125                        .with_shallow(remote::fetch::Shallow::DepthAtRemote(one))
126                        .with_ref_name(ref_name.as_ref()) // branch or tag (option)
127                        .map_err(GitError::from)?;
128                }
129
130                let (repository, _) =
131                    prepare_fetch.fetch_only(progress::Discard, &interrupt::IS_INTERRUPTED).map_err(GitError::from)?;
132
133                repository
134            }
135        };
136
137        // Note: the entire tree's data will be in memory
138        let tree = match commit {
139            // Use a specific commit
140            Some(commit) => {
141                let commit = repository.find_commit(commit).map_err(GitError::from)?;
142                commit.tree().map_err(GitError::from)?
143            }
144
145            // Use the HEAD (tip of the branch)
146            None => repository.head_tree().map_err(GitError::from)?,
147        };
148
149        let entry = tree
150            .lookup_entry_by_path(self.path.as_str())
151            .map_err(GitError::from)?
152            .ok_or_else(|| UrlError::new_io_not_found(self))?;
153
154        // Note: the entire object's data will be in memory,
155        // but at least we can "take" it without cloning
156        let object = entry.object().map_err(GitError::from)?;
157        let mut blob = object.try_into_blob().map_err(GitError::from)?;
158        let data = blob.take_data();
159
160        Ok(Cursor::new(data))
161    }
162}
163
164#[cfg(any(feature = "blocking", feature = "async"))]
165impl GitUrl {
166    fn conform_path(&mut self) -> Result<(), super::super::UrlError> {
167        self.path = self.path.normalize();
168        Ok(())
169    }
170}