changelog shortlog graph tags branches changeset files revisions annotate raw help

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

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