Skip to main content

xensec_internal_tools/
githelp.rs

1// Copyright 2021 Citrix
2// SPDX-License-Identifier: MIT OR Apache-2.0
3// There is NO WARRANTY.
4
5use crate::prelude::*;
6
7#[derive(Debug)]
8pub enum RefSpec<'r> {
9  Def,
10  Refname(&'r str),
11  Tree(&'r git2::Tree<'r>),
12}
13impl Default for RefSpec<'_> { fn default() -> Self { RefSpec::Def } }
14
15pub trait Repo {
16  fn g2(&self) -> &git2::Repository;
17  fn dir(&self) -> &str;
18  fn cfg(&self) -> &Config;
19
20  fn def_refname(&self) -> &str;
21
22  #[throws(AE)]
23  fn get_tree_maybe(&self, refname: &str) -> Option<git2::Tree> {
24    let tree = (||{
25      let oid = match self.g2().refname_to_id(refname) {
26        Err(e) if e.code() == git2::ErrorCode::NotFound => return Ok(None),
27        x => x.context("resolve ref")?,
28      };
29      let commit = self.g2().find_commit(oid).context("get commit")?;
30      let tree = commit.tree().context("get tree")?;
31      Ok::<_,AE>(Some(tree))
32    })().with_context(|| refname.to_string())?;
33    tree
34  }
35
36  #[throws(AE)]
37  fn get_tree<'r>(&'r self, refspec: RefSpec<'r>) -> git2::Tree<'r> {
38    let refname = match refspec {
39      RefSpec::Def => self.def_refname(),
40      RefSpec::Refname(n) => n,
41      RefSpec::Tree(t) => return t.clone(),
42    };
43    self.get_tree_maybe(refname)?
44      .ok_or_else(|| anyhow!("ref {:?} not found", refname))?
45  }
46
47  #[throws(AE)]
48  fn file(&self, refspec: RefSpec, path: &str) -> Option<String> {
49    let tree = self.get_tree(refspec)?;
50    (||{
51      let ent = match tree.get_path(path.as_ref()) {
52        Err(e) if e.code() == git2::ErrorCode::NotFound => return Ok(None),
53        x => x,
54      }.context("get_path")?;
55      let obj = ent.to_object(self.g2()).context("to_object")?;
56      let blob = obj.into_blob()
57        .map_err(|e| anyhow!("not a blob, but {:?}", &e.kind()))?;
58      let r = str::from_utf8(blob.content()).context("from_utf8")?;
59      Ok::<_,AE>(Some(r.to_owned()))
60    })()
61      .with_context(|| path.to_string())
62      .context("look up path")?
63  }
64
65  #[throws(AE)]
66  fn files(&self, refspec: RefSpec, path: &str) -> Vec<String> {
67    let tree = self.get_tree(refspec)?;
68    (||{
69      let ent = tree.get_path(path.as_ref()).context("get_path")?;
70      let obj = ent.to_object(self.g2()).context("to_object")?;
71      let tree = obj.into_tree()
72        .map_err(|e| anyhow!("not a tree, but {:?}", &e.kind()))?;
73      tree.iter().map(|ent| Ok::<_,AE>(
74        ent.name()
75          .ok_or_else(|| anyhow!("bad entry in {}, not utf-8: {:?}",
76                                 path, ent.name_bytes()))?
77          .to_owned()          
78      ))
79        .collect::<Result<Vec<_>,_>>()
80    })()
81      .with_context(|| path.to_string())
82      .context("enumerate dir")?
83  }
84
85  #[throws(AE)]
86  fn run_git(&mut self, want: bool, doing: &str,
87             u: &mut dyn FnMut(&mut Command) -> Result<(), AE>) {
88    let mut cmd = if want {
89      self.cfg().git_command()?
90    } else if log_enabled!(log::Level::Debug) {
91      let mut cmd = Command::new("sh");
92      cmd.args(&["-c",r#"exec >&2; echo "$*";"#,"-","# git"]);
93      cmd
94    } else {
95      let mut cmd = Command::new("true");
96      cmd.arg("--");
97      cmd
98    };
99
100    cmd.current_dir(self.dir());
101    if let Some(v) = &self.cfg().git_ssh_command {
102      cmd.env("GIT_SSH_COMMAND", v);
103    }
104
105    u(&mut cmd)?;
106
107    let show = format!("cmd {:?}", &cmd);
108
109    if want { debug!("{}, {}", &doing, &show); }
110
111    let st = cmd.status().context("spawn git")?;
112    if ! st.success() { throw!(
113      anyhow!("git failed: {}", st)
114        .context(show.clone())
115        .context(self.dir().to_owned())
116    ) }
117  }
118}
119
120impl cache::Cache {
121  #[throws(AE)]
122  pub fn run_git_fetch_pull(&mut self, refresh: RefreshKind,
123                            repo: &mut dyn Repo,
124                            args1: &[&str],
125                            remote_name: &str,
126                            branch_pats: &[&str],
127                            extra_refspecs: &[&str])
128                            -> Option<RefreshingToken>
129  {
130    let want = self.cfg.want_refresh(refresh, None);
131
132    repo.run_git(want.is_some(), "updating", &mut |cmd: &mut Command| {
133
134      cmd.args(args1);
135      if ! log_enabled!(log::Level::Debug) { cmd.arg("--quiet"); }
136
137      cmd.arg(remote_name);
138      for pat in branch_pats {
139        cmd.arg(format!(
140          "+refs/heads/{pat}:refs/remotes/{remote}/{pat}",
141          pat=pat, remote=remote_name,
142        ));
143      }
144      cmd.args(extra_refspecs);
145
146      Ok::<_,AE>(())
147    })
148      .context("update from remote git repo")?;
149
150    want
151  }
152}