1use std::{fmt, path};
2
3use anyhow::*;
4
5pub struct Repo {
6 git_repo: git2::Repository,
7 pub work_dir: path::PathBuf,
8 pub head: String,
9 pub subrepos: Vec<Repo>,
10}
11
12impl Repo {
13 pub fn new(work_dir: &path::Path, head_name: Option<&str>) -> Result<Self> {
14 println!("Reading repo at `{}`", work_dir.display());
15
16 let git_repo = git2::Repository::open(work_dir)
17 .with_context(|| format!("Cannot open repo at `{}`", work_dir.display()))?;
18
19 let head = match head_name {
20 Some(name) => String::from(name),
21 None => {
22 if git_repo.head_detached().with_context(|| {
23 format!(
24 "Cannot determine head state for repo at `{}`",
25 work_dir.display()
26 )
27 })? {
28 bail!(
29 "Cannot operate on a detached head for repo at `{}`",
30 work_dir.display()
31 )
32 }
33
34 String::from(git_repo.head().with_context(|| {
35 format!(
36 "Cannot find the head branch for repo at `{}`. Is it detached?",
37 work_dir.display()
38 )
39 })?.shorthand().with_context(|| {
40 format!(
41 "Cannot find a human readable representation of the head ref for repo at `{}`",
42 work_dir.display(),
43 )
44 })?)
45 },
46 };
47
48 let subrepos = git_repo
49 .submodules()
50 .with_context(|| {
51 format!(
52 "Cannot load submodules for repo at `{}`",
53 work_dir.display()
54 )
55 })?
56 .iter()
57 .map(|submodule| Repo::new(&work_dir.join(submodule.path()), Some(&head)))
58 .collect::<Result<Vec<Repo>>>()?;
59
60 println!("Successfully read repo at `{}`", work_dir.display());
61
62 Ok(Repo {
63 git_repo,
64 work_dir: path::PathBuf::from(work_dir),
65 head,
66 subrepos,
67 })
68 }
69
70 pub fn get_subrepo_by_path(&self, subrepo_path: &path::PathBuf) -> Option<&Repo> {
71 self.subrepos
72 .iter()
73 .find(|subrepo| (subrepo.work_dir == self.work_dir.join(subrepo_path)))
74 }
75
76 pub fn sync(&self) -> Result<()> {
77 self.switch(&self.head)?;
78 Ok(())
79 }
80
81 pub fn switch(&self, head: &str) -> Result<()> {
82 self.git_repo.set_head(&self.resolve_reference(head)?)?;
83 self.git_repo.checkout_head(None)?;
84 Ok(())
85 }
86
87 fn resolve_reference(&self, short_name: &str) -> Result<String> {
88 Ok(self
89 .git_repo
90 .resolve_reference_from_short_name(short_name)?
91 .name()
92 .with_context(|| {
93 format!(
94 "Cannot resolve head reference for repo at `{}`",
95 self.work_dir.display()
96 )
97 })?
98 .to_owned())
99 }
100}
101
102impl fmt::Debug for Repo {
103 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
104 f.debug_struct("Repo")
105 .field("work_dir", &self.work_dir)
106 .field("head", &self.head)
107 .field("subrepos", &self.subrepos)
108 .finish()
109 }
110}