• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MitMaro / git-interactive-rebase-tool / 6883077488

15 Nov 2023 09:23PM UTC coverage: 93.248% (-0.4%) from 93.64%
6883077488

Pull #873

github

web-flow
Merge 0ab516642 into d7655157f
Pull Request #873: When editing in the middle of a rebase, dont clear on quit

45 of 72 new or added lines in 14 files covered. (62.5%)

1 existing line in 1 file now uncovered.

4792 of 5139 relevant lines covered (93.25%)

3.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.5
/src/git/src/repository.rs
1
use std::{
2
        fmt::{Debug, Formatter},
3
        path::{Path, PathBuf},
4
        sync::Arc,
5
};
6

7
use git2::{Oid, Signature};
8
use parking_lot::Mutex;
9

10
use crate::{
11
        commit_diff_loader::CommitDiffLoader,
12
        errors::{GitError, RepositoryLoadKind},
13
        Commit,
14
        CommitDiff,
15
        CommitDiffLoaderOptions,
16
        Config,
17
        Reference,
18
};
19

20
/// A light cloneable, simple wrapper around the `git2::Repository` struct
21
#[derive(Clone)]
22
pub struct Repository {
23
        repository: Arc<Mutex<git2::Repository>>,
24
}
25

26
impl Repository {
27
        /// Find and open an existing repository, respecting git environment variables. This will check
28
        /// for and use `$GIT_DIR`, and if unset will search for a repository starting in the current
29
        /// directory, walking to the root.
30
        ///
31
        /// # Errors
32
        /// Will result in an error if the repository cannot be opened.
33
        #[inline]
34
        pub fn open_from_env() -> Result<Self, GitError> {
2✔
35
                let repository = git2::Repository::open_from_env().map_err(|e| {
6✔
36
                        GitError::RepositoryLoad {
2✔
37
                                kind: RepositoryLoadKind::Environment,
2✔
38
                                cause: e,
×
39
                        }
40
                })?;
41
                Ok(Self {
2✔
42
                        repository: Arc::new(Mutex::new(repository)),
4✔
43
                })
44
        }
45

46
        /// Attempt to open an already-existing repository at `path`.
47
        ///
48
        /// # Errors
49
        /// Will result in an error if the repository cannot be opened.
50
        #[inline]
51
        pub fn open_from_path(path: &Path) -> Result<Self, GitError> {
2✔
52
                let repository = git2::Repository::open(path).map_err(|e| {
4✔
53
                        GitError::RepositoryLoad {
1✔
54
                                kind: RepositoryLoadKind::Path,
1✔
55
                                cause: e,
×
56
                        }
57
                })?;
58
                Ok(Self {
1✔
59
                        repository: Arc::new(Mutex::new(repository)),
2✔
60
                })
61
        }
62

63
        /// Load the git configuration for the repository.
64
        ///
65
        /// # Errors
66
        /// Will result in an error if the configuration is invalid.
67
        #[inline]
68
        pub fn load_config(&self) -> Result<Config, GitError> {
3✔
69
                self.repository
6✔
70
                        .lock()
71
                        .config()
72
                        .map_err(|e| GitError::ConfigLoad { cause: e })
×
73
        }
74

75
        /// Load a diff for a commit hash
76
        ///
77
        /// # Errors
78
        /// Will result in an error if the commit cannot be loaded.
79
        #[inline]
80
        pub fn load_commit_diff(&self, hash: &str, config: &CommitDiffLoaderOptions) -> Result<CommitDiff, GitError> {
2✔
81
                let oid = self
5✔
82
                        .repository
×
83
                        .lock()
84
                        .revparse_single(hash)
×
85
                        .map_err(|e| GitError::CommitLoad { cause: e })?
3✔
86
                        .id();
87
                let diff_loader_repository = Arc::clone(&self.repository);
2✔
88
                let loader = CommitDiffLoader::new(diff_loader_repository, config);
2✔
89
                // TODO this is ugly because it assumes one parent
90
                Ok(loader
7✔
UNCOV
91
                        .load_from_hash(oid)
×
92
                        .map_err(|e| GitError::CommitLoad { cause: e })?
3✔
93
                        .remove(0))
×
94
        }
95

96
        /// Find a reference by the reference name.
97
        ///
98
        /// # Errors
99
        /// Will result in an error if the reference cannot be found.
100
        #[inline]
101
        pub fn find_reference(&self, reference: &str) -> Result<Reference, GitError> {
1✔
102
                let repo = self.repository.lock();
1✔
103
                let git2_reference = repo
3✔
104
                        .find_reference(reference)
×
105
                        .map_err(|e| GitError::ReferenceNotFound { cause: e })?;
3✔
106
                Ok(Reference::from(&git2_reference))
2✔
107
        }
108

109
        /// Find a commit by a reference name.
110
        ///
111
        /// # Errors
112
        /// Will result in an error if the reference cannot be found or is not a commit.
113
        #[inline]
114
        pub fn find_commit(&self, reference: &str) -> Result<Commit, GitError> {
1✔
115
                let repo = self.repository.lock();
1✔
116
                let git2_reference = repo
3✔
117
                        .find_reference(reference)
×
118
                        .map_err(|e| GitError::ReferenceNotFound { cause: e })?;
3✔
119
                Commit::try_from(&git2_reference)
1✔
120
        }
121

122
        pub(crate) fn repo_path(&self) -> PathBuf {
1✔
123
                self.repository.lock().path().to_path_buf()
2✔
124
        }
125

126
        pub(crate) fn head_id(&self, head_name: &str) -> Result<Oid, git2::Error> {
2✔
127
                let repo = self.repository.lock();
2✔
128
                let ref_name = format!("refs/heads/{head_name}");
2✔
129
                let revision = repo.revparse_single(ref_name.as_str())?;
4✔
130
                Ok(revision.id())
4✔
131
        }
132

133
        pub(crate) fn commit_id_from_ref(&self, reference: &str) -> Result<Oid, git2::Error> {
1✔
134
                let repo = self.repository.lock();
1✔
135
                let commit = repo.find_reference(reference)?.peel_to_commit()?;
2✔
136
                Ok(commit.id())
1✔
137
        }
138

139
        pub(crate) fn add_path_to_index(&self, path: &Path) -> Result<(), git2::Error> {
1✔
140
                let repo = self.repository.lock();
1✔
141
                let mut index = repo.index()?;
2✔
142
                index.add_path(path)
1✔
143
        }
144

145
        pub(crate) fn remove_path_from_index(&self, path: &Path) -> Result<(), git2::Error> {
3✔
146
                let repo = self.repository.lock();
3✔
147
                let mut index = repo.index()?;
6✔
148
                index.remove_path(path)
3✔
149
        }
150

151
        pub(crate) fn create_commit_on_index(
3✔
152
                &self,
153
                reference: &str,
154
                author: &Signature<'_>,
155
                committer: &Signature<'_>,
156
                message: &str,
157
        ) -> Result<(), git2::Error> {
158
                let repo = self.repository.lock();
3✔
159
                let tree = repo.find_tree(repo.index()?.write_tree()?)?;
6✔
160
                let head = repo.find_reference(reference)?.peel_to_commit()?;
3✔
161
                _ = repo.commit(Some("HEAD"), author, committer, message, &tree, &[&head])?;
3✔
162
                Ok(())
3✔
163
        }
164

165
        #[cfg(test)]
166
        pub(crate) fn repository(&self) -> Arc<Mutex<git2::Repository>> {
2✔
167
                Arc::clone(&self.repository)
2✔
168
        }
169
}
170

171
impl From<git2::Repository> for Repository {
172
        #[inline]
173
        fn from(repository: git2::Repository) -> Self {
6✔
174
                Self {
175
                        repository: Arc::new(Mutex::new(repository)),
4✔
176
                }
177
        }
178
}
179

180
impl Debug for Repository {
181
        #[inline]
182
        fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
1✔
183
                f.debug_struct("Repository")
2✔
184
                        .field("[path]", &self.repository.lock().path())
2✔
185
                        .finish()
186
        }
187
}
188

189
// Paths in Windows makes these tests difficult, so disable
190
#[cfg(all(unix, test))]
191
mod tests {
192
        use std::env::set_var;
193

194
        use claims::assert_ok;
195
        use git2::{ErrorClass, ErrorCode};
196
        use testutils::assert_err_eq;
197

198
        use super::*;
199
        use crate::testutil::{commit_id_from_ref, create_commit, with_temp_bare_repository, with_temp_repository};
200

201
        #[test]
202
        #[serial_test::serial]
203
        fn open_from_env() {
204
                let path = Path::new(env!("CARGO_MANIFEST_DIR"))
205
                        .join("test")
206
                        .join("fixtures")
207
                        .join("simple");
208
                set_var("GIT_DIR", path.to_str().unwrap());
209
                assert_ok!(Repository::open_from_env());
210
        }
211

212
        #[test]
213
        #[serial_test::serial]
214
        fn open_from_env_error() {
215
                let path = Path::new(env!("CARGO_MANIFEST_DIR"))
216
                        .join("test")
217
                        .join("fixtures")
218
                        .join("does-not-exist");
219
                set_var("GIT_DIR", path.to_str().unwrap());
220
                assert_err_eq!(Repository::open_from_env(), GitError::RepositoryLoad {
221
                        kind: RepositoryLoadKind::Environment,
222
                        cause: git2::Error::new(
223
                                ErrorCode::NotFound,
224
                                ErrorClass::Os,
225
                                format!(
226
                                        "failed to resolve path '{}': No such file or directory",
227
                                        path.to_string_lossy()
228
                                )
229
                        ),
230
                });
231
        }
232

233
        #[test]
234
        fn open_from_path() {
235
                let path = Path::new(env!("CARGO_MANIFEST_DIR"))
236
                        .join("test")
237
                        .join("fixtures")
238
                        .join("simple");
239
                assert_ok!(Repository::open_from_path(&path));
240
        }
241

242
        #[test]
243
        fn open_from_path_error() {
244
                let path = Path::new(env!("CARGO_MANIFEST_DIR"))
245
                        .join("test")
246
                        .join("fixtures")
247
                        .join("does-not-exist");
248
                assert_err_eq!(Repository::open_from_path(&path), GitError::RepositoryLoad {
249
                        kind: RepositoryLoadKind::Path,
250
                        cause: git2::Error::new(
251
                                ErrorCode::NotFound,
252
                                ErrorClass::Os,
253
                                format!(
254
                                        "failed to resolve path '{}': No such file or directory",
255
                                        path.to_string_lossy()
256
                                )
257
                        ),
258
                });
259
        }
260

261
        #[test]
262
        fn load_config() {
263
                with_temp_bare_repository(|repo| {
264
                        assert_ok!(repo.load_config());
265
                });
266
        }
267

268
        #[test]
269
        fn load_commit_diff() {
270
                with_temp_repository(|repository| {
271
                        create_commit(&repository, None);
272
                        let id = commit_id_from_ref(&repository, "refs/heads/main");
273
                        assert_ok!(repository.load_commit_diff(id.to_string().as_str(), &CommitDiffLoaderOptions::new()));
274
                });
275
        }
276

277
        #[test]
278
        fn load_commit_diff_with_non_commit() {
279
                with_temp_repository(|repository| {
280
                        let blob_ref = {
281
                                let git2_repository = repository.repository();
282
                                let git2_lock = git2_repository.lock();
283
                                let blob = git2_lock.blob(b"foo").unwrap();
284
                                _ = git2_lock.reference("refs/blob", blob, false, "blob").unwrap();
285
                                blob.to_string()
286
                        };
287

288
                        assert_err_eq!(
289
                                repository.load_commit_diff(blob_ref.as_str(), &CommitDiffLoaderOptions::new()),
290
                                GitError::CommitLoad {
291
                                        cause: git2::Error::new(
292
                                                ErrorCode::NotFound,
293
                                                ErrorClass::Invalid,
294
                                                "the requested type does not match the type in the ODB",
295
                                        ),
296
                                }
297
                        );
298
                });
299
        }
300

301
        #[test]
302
        fn find_reference() {
303
                with_temp_repository(|repository| {
304
                        assert_ok!(repository.find_reference("refs/heads/main"));
305
                });
306
        }
307

308
        #[test]
309
        fn find_reference_error() {
310
                with_temp_repository(|repository| {
311
                        assert_err_eq!(
312
                                repository.find_reference("refs/heads/invalid"),
313
                                GitError::ReferenceNotFound {
314
                                        cause: git2::Error::new(
315
                                                ErrorCode::NotFound,
316
                                                ErrorClass::Reference,
317
                                                "reference 'refs/heads/invalid' not found",
318
                                        ),
319
                                }
320
                        );
321
                });
322
        }
323

324
        #[test]
325
        fn find_commit() {
326
                with_temp_repository(|repository| {
327
                        assert_ok!(repository.find_commit("refs/heads/main"));
328
                });
329
        }
330

331
        #[test]
332
        fn find_commit_error() {
333
                with_temp_repository(|repository| {
334
                        assert_err_eq!(
335
                                repository.find_commit("refs/heads/invalid"),
336
                                GitError::ReferenceNotFound {
337
                                        cause: git2::Error::new(
338
                                                ErrorCode::NotFound,
339
                                                ErrorClass::Reference,
340
                                                "reference 'refs/heads/invalid' not found",
341
                                        ),
342
                                }
343
                        );
344
                });
345
        }
346

347
        #[test]
348
        fn fmt() {
349
                with_temp_bare_repository(|repository| {
350
                        let formatted = format!("{repository:?}");
351
                        let path = repository.repo_path().canonicalize().unwrap();
352
                        assert_eq!(
353
                                formatted,
354
                                format!("Repository {{ [path]: \"{}/\" }}", path.to_str().unwrap())
355
                        );
356
                });
357
        }
358
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc