bundle_sources/
bundle.rs

1// Copyright 2020 Ian Jackson
2// SPDX-License-Identifier: GPL-3.0-or-later
3// There is NO WARRANTY.
4
5use crate::imports::*;
6use crate::utils::*;
7use crate::html::*;
8
9type E = anyhow::Error;
10
11pub trait Component : Debug {
12  /// creates <outbasepath>[.<extension>] (eg, <basename>.tar)
13  ///  from whatever self refers to
14  /// invocation directory is unspecified
15  /// returns None or ".<extension>"
16  /// if adding .<extension> may also create <basename> temporarily
17  fn default_desc(&self) -> Html;
18  #[throws(E)]
19  fn bundle(&self, outbasepath : &String) -> Option<String>;
20}
21
22#[derive(Debug)]
23pub struct FileComponent(pub String);
24impl Component for FileComponent {
25  fn default_desc(&self) -> Html {
26    Html(format!(r#"file {}"#, Html::from_literal(&self.0).0))
27  }
28  #[throws(E)]
29  fn bundle(&self, outbasepath : &String) -> Option<String> {
30    let src = &self.0;
31    fs::copy(src, outbasepath)?;
32    None
33  }
34}
35
36#[derive(Debug)]
37pub struct DirectoryComponent(Box<dyn Component>);
38impl DirectoryComponent {
39  #[throws(E)]
40  pub fn new(src : String) -> DirectoryComponent {
41    let is_git = match fs::metadata(src.to_owned() + "/.git") {
42      Ok(_) => true,
43      Err(e) if e.kind() == NotFound => false,
44      Err(e) => throw!(e),
45    };
46
47    DirectoryComponent(if is_git {
48      Box::new( GitComponent(src) )
49    } else {
50      Box::new( RawDirectoryComponent(src) )
51    })
52  }
53}
54impl Component for DirectoryComponent {
55  fn default_desc(&self) -> Html { self.0.default_desc() }
56  #[throws(E)]
57  fn bundle(&self, outbasepath : &String) -> Option<String> {
58    self.0.bundle(outbasepath)?
59  }
60}
61
62#[derive(Debug)]
63pub struct RawDirectoryComponent(pub String);
64impl Component for RawDirectoryComponent {
65  fn default_desc(&self) -> Html {
66    Html(format!(r#"directory {}"#, Html::from_literal(&self.0).0))
67  }
68  #[throws(E)]
69  fn bundle(&self, outbasepath : &String) -> Option<String> {
70    let src = &self.0;
71    let mut split = src.rsplitn(2,'/');
72    let srcleaf = split.next().unwrap();
73    let dir = split.next().ok_or_else(||
74      anyhow!("trying to bundle directory with no parent in pathname"))?;
75    let outfile = outbasepath.to_owned() + ".tar";
76    let stdout = File::create(&outfile)?;
77    let mut cmd = tar_command();
78    cmd
79      .current_dir(&dir)
80      .stdout(stdout)
81      .args(&["-cf","-","--",srcleaf]);
82    run_cmd(cmd).cxm(||format!("tar for raw directory {:?}", &dir))?;
83    Some(".tar".to_owned())
84  }
85}
86
87#[derive(Debug)]
88pub struct GitComponent(pub String);
89impl Component for GitComponent {
90  fn default_desc(&self) -> Html {
91    Html(format!(r#"git working directory {}"#,
92                 Html::from_literal(&self.0).0))
93  }
94  #[throws(E)]
95  fn bundle(&self, outbasepath : &String) -> Option<String> {
96    let src = &self.0;
97    let gitclone = r#"#!/bin/bash
98      set -e
99      set -o pipefail
100
101      src="$1"; shift
102      dest="$1"; shift
103
104      rm -rf "$dest"
105      mkdir "$dest"
106      cd "$dest"
107      desta=$(pwd)
108
109      git init -q
110
111      git fetch -q "$src" HEAD
112      git update-ref refs/heads/master FETCH_HEAD
113
114      (
115        set -e
116        cd "$src"
117        git ls-files --exclude-standard -oc -z
118      ) | (
119        cd "$src"
120        cpio -p0 -dum --no-preserve-owner --quiet "$desta"
121      )
122
123      git reset -q
124
125      cd ..
126    "#;
127
128    let mut c = Command::new("bash");
129    c.args(&["-ec",gitclone,"x",&src,outbasepath]);
130    run_cmd(c).context("git fetch, copy etc. script")?;
131
132    let r = RawDirectoryComponent(outbasepath.clone()).bundle(outbasepath)?;
133    remove_dir_all(outbasepath)?;
134    r
135  }
136}