changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / rust/lib/obj/src/config/repo/hg.rs

changeset 17: c7165d93a9eb
child: 0ccbbd142694
author: ellis <ellis@rwest.io>
date: Sun, 22 Oct 2023 23:03:15 -0400
permissions: -rw-r--r--
description: add obj and net src
1 use super::SubRepo;
2 use crate::Result;
3 use hg_parser::{file_content, FileType, ManifestEntryDetails, MercurialRepository, Revision};
4 
5 use logger::log::{error, info, trace};
6 use serde::{Deserialize, Serialize};
7 use std::{
8  collections::HashMap,
9  fs::File,
10  io::{LineWriter, Read, Write},
11  net::SocketAddr,
12  path::{Path, PathBuf},
13  time::Instant,
14 };
15 
16 /// Mercurial configuration type -- corresponds to .hgrc file
17 ///
18 /// TODO set 'default' and 'default_push' paths
19 #[derive(Serialize, Deserialize, Debug, PartialEq)]
20 pub struct MercurialConfig {
21  pub ui: HashMap<String, String>,
22  pub extensions: Option<HashMap<String, Option<String>>>,
23  pub paths: Option<HashMap<String, String>>,
24  pub web: HgwebConfig,
25 }
26 
27 impl MercurialConfig {
28  pub fn handle<P: AsRef<Path>>(path: P) -> MercurialRepository {
29  MercurialRepository::open(path).unwrap()
30  }
31 }
32 
33 /// Mercurial '.hgsub' file handle, which is just a list of PATH=SOURCE pairs.
34 pub struct HgSubFile {
35  pub path: PathBuf, // path to the .hgsub file
36  pub subrepos: Vec<SubRepo>, // Vec containing `SubRepo` handles
37 }
38 
39 impl HgSubFile {
40  /// Create a new '.hgsub' file handle
41  pub fn new() -> Self {
42  HgSubFile {
43  path: PathBuf::from(".hgsub"),
44  subrepos: vec![],
45  }
46  }
47  /// ensure that the path is in a hg repo.
48  fn parent_is_hg(&self) -> bool {
49  if self.path.parent().unwrap().join(".hg").exists() {
50  true
51  } else {
52  false
53  }
54  }
55  /// insert a subrepo into this HgSubFile. does not clone the source
56  /// or ensure that path exists. Takes an optional argument of 'hg'
57  /// or 'git' to indicate the subrepo-type. Value can be ommitted to
58  pub fn insert(&mut self, path: &str, source: &str, vcs: Option<&str>) -> Result<()> {
59  let mut prefix = "";
60  // set prefix based on vcs (repo type)
61  if let Some(i) = vcs {
62  match i {
63  "hg" => prefix = "hg",
64  "git" => prefix = "git",
65  _ => {
66  error!("failed to recognize repo type")
67  }
68  }
69  }
70 
71  let source = format!("{}:{}", prefix, source);
72 
73  let subrepo = SubRepo {
74  vcs: vcs.unwrap().to_string(),
75  origin: source.to_string(),
76  path: path.to_string(),
77  };
78 
79  self.subrepos.push(subrepo);
80  Ok(())
81  }
82 
83  /// Save subs to `.hgsub` file specified in the `path` field of
84  /// this struct. This will overwrite any existing file at that path.
85  pub fn save(self) -> Result<()> {
86  match self.parent_is_hg() {
87  true => {
88  let mut file = File::open(self.path).unwrap();
89  for i in self.subrepos.iter() {
90  write!(file, "{} = {}", i.path, i.origin)?;
91  }
92  }
93  false => {
94  error!("Parent is not a Mercurial repo!")
95  }
96  }
97  Ok(())
98  }
99 
100  /// Sort the full contents of a .hgsub file alphabetically.
101  pub fn sort(self) -> Result<Self> {
102  let mut fd = File::open(&self.path)?;
103  let len = fd.metadata().unwrap().len() as usize;
104  let mut bufr = String::with_capacity(len);
105  fd.read_to_string(&mut bufr).unwrap(); // This reads the entire file into memory.
106  drop(fd); // drop the old readonly file descriptor
107 
108  let mut subs = Vec::new();
109 
110  trace!("starting sort of {:?} lines", &self.path.canonicalize()?);
111  for line in bufr.lines() {
112  subs.push(line.as_bytes());
113  }
114  subs.sort_unstable();
115  trace!("lines have been sorted");
116 
117  let file = File::create(&self.path)?;
118  let mut file = LineWriter::new(file);
119  for sub in subs.iter() {
120  file.write_all(sub)?;
121  file.write_all(b"\n")?;
122  }
123 
124  file.flush()?;
125  info!("sorted {:?}", &self.path.canonicalize()?);
126 
127  Ok(self)
128  }
129 }
130 
131 impl Default for HgSubFile {
132  fn default() -> Self {
133  HgSubFile {
134  path: PathBuf::from(".hgsub"),
135  subrepos: vec![],
136  }
137  }
138 }
139 
140 /// Hgweb configuration type
141 ///
142 /// Based on the configuration file for 'hgweb' scripts.
143 ///
144 /// We don't store the file path in a field because all HgwebConfig
145 /// values are relative to env::current_dir()
146 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
147 pub struct HgwebConfig {
148  pub name: String,
149  pub contact: String,
150  pub description: String,
151  pub extensions: Vec<String>,
152  pub socket: SocketAddr,
153  pub paths: HashMap<PathBuf, PathBuf>,
154 }
155 
156 impl Default for HgwebConfig {
157  fn default() -> Self {
158  HgwebConfig {
159  name: "".to_string(),
160  contact: "".to_string(),
161  description: "".to_string(),
162  extensions: vec![],
163  socket: "0.0.0.0:0"
164  .parse()
165  .expect("could not parse hgweb socketaddr"),
166  paths: HashMap::new(),
167  }
168  }
169 }
170 
171 /// from hg_parser crate docs - given a mercurial repo as a path,
172 /// exports to git fast-import format (to stdout)
173 pub fn export_hg_git<P: AsRef<Path>>(path: P) -> Result<()> {
174  let start = Instant::now();
175  let repo = MercurialRepository::open(path).expect("could not open repo path");
176 
177  let stdout = std::io::stdout();
178  let mut writer = stdout.lock();
179 
180  for changeset in &repo {
181  let revision = changeset.revision;
182  eprintln!("rev: {:?}", revision);
183 
184  let header = &changeset.header;
185  let mut branch = None;
186  let mut closed = false;
187  for (key, value) in &header.extra {
188  if key == b"branch" {
189  branch = Some(value.as_slice());
190  }
191 
192  if key == b"close" && value == b"1" {
193  closed = true;
194  }
195  }
196 
197  let mut branch: Vec<_> = branch.unwrap_or_else(|| b"master").into();
198  for b in branch.iter_mut() {
199  if *b == b' ' {
200  *b = b'-';
201  }
202  }
203 
204  let user = String::from_utf8_lossy(&header.user);
205  let desc = String::from_utf8_lossy(&header.comment);
206 
207  let time = header.time.timestamp_secs();
208  let timezone = header.time.tz_offset_secs();
209  let tz = format!("{:+03}{:02}", -timezone / 3600, ((-timezone % 3600) / 60));
210 
211  write!(writer, "reset refs/heads/")?;
212  writer.write_all(&mut branch)?;
213  write!(writer, "\ncommit refs/heads/")?;
214  writer.write_all(&mut branch)?;
215  writeln!(writer, "\nmark :{}", mark(revision))?;
216 
217  writeln!(writer, "author {} {} {}", user, time, tz)?;
218  writeln!(writer, "committer {} {} {}", user, time, tz)?;
219  writeln!(writer, "data {}", desc.len() + 1)?;
220  writeln!(writer, "{}\n", desc)?;
221 
222  match (header.p1, header.p2) {
223  (Some(p1), Some(p2)) => {
224  writeln!(writer, "from :{}", mark(p1))?;
225  writeln!(writer, "merge :{}", mark(p2))?;
226  }
227  (Some(p), None) | (None, Some(p)) => {
228  writeln!(writer, "from :{}", mark(p))?;
229  }
230  _ => (),
231  }
232 
233  for mut file in changeset.files {
234  match (file.data, file.manifest_entry) {
235  (None, None) => {
236  write!(writer, "D ")?;
237  writer.write_all(&mut file.path)?;
238  writeln!(writer)?;
239  }
240  (Some(data), Some(manifest_entry)) => {
241  write!(
242  writer,
243  "M {} inline ",
244  match manifest_entry.details {
245  ManifestEntryDetails::File(FileType::Symlink) => "120000",
246  ManifestEntryDetails::File(FileType::Executable) => "100755",
247  ManifestEntryDetails::Tree | ManifestEntryDetails::File(FileType::Regular) =>
248  "100644",
249  }
250  )?;
251  writer.write_all(&mut file.path)?;
252  let data = file_content(&data);
253  writeln!(writer, "\ndata {}", data.len())?;
254  writer.write_all(&data[..])?;
255  }
256  _ => panic!("Wrong file data!"),
257  }
258  }
259 
260  if closed {
261  write!(writer, "reset refs/tags/archive/")?;
262  writer.write_all(&mut branch)?;
263  writeln!(writer, "\nfrom :{}\n", mark(revision))?;
264 
265  write!(writer, "reset refs/heads/")?;
266  writer.write_all(&mut branch)?;
267  writeln!(writer, "\nfrom 0000000000000000000000000000000000000000\n")?;
268  }
269  }
270 
271  for (rev, tag) in repo.tags().unwrap() {
272  eprintln!("export tag {}", tag.name);
273  writeln!(writer, "reset refs/tags/{}", tag.name).unwrap();
274  writeln!(writer, "from :{}", mark(rev)).unwrap();
275  writeln!(writer).unwrap();
276  }
277 
278  eprintln!("Done. Elapsed: {:?}", start.elapsed());
279  Ok(())
280 }
281 
282 fn mark<R: Into<Revision>>(rev: R) -> usize {
283  (rev.into() + 1).0 as usize
284 }