|
| 1 | +use hg_parser::{file_content, FileType, ManifestEntryDetails, MercurialRepository, Revision}; |
| 2 | + |
| 3 | +use std::env::args; |
| 4 | +use std::io::Write; |
| 5 | +use std::path::{Path, PathBuf}; |
| 6 | +use std::string::ParseError; |
| 7 | +use std::time::Instant; |
| 8 | + |
| 9 | +fn main() -> Result<(), Error> { |
| 10 | + let path: PathBuf = args().nth(1).expect("path not provided").parse()?; |
| 11 | + export_repo(path) |
| 12 | +} |
| 13 | + |
| 14 | +fn export_repo<P: AsRef<Path>>(path: P) -> Result<(), Error> { |
| 15 | + let start = Instant::now(); |
| 16 | + let repo = MercurialRepository::open(path)?; |
| 17 | + |
| 18 | + let stdout = std::io::stdout(); |
| 19 | + let mut writer = stdout.lock(); |
| 20 | + |
| 21 | + for changeset in &repo { |
| 22 | + let revision = changeset.revision; |
| 23 | + eprintln!("rev: {:?}", revision); |
| 24 | + |
| 25 | + let header = &changeset.header; |
| 26 | + let mut branch = None; |
| 27 | + let mut closed = false; |
| 28 | + for (key, value) in &header.extra { |
| 29 | + if key == b"branch" { |
| 30 | + branch = Some(value.as_slice()); |
| 31 | + } |
| 32 | + |
| 33 | + if key == b"close" && value == b"1" { |
| 34 | + closed = true; |
| 35 | + } |
| 36 | + } |
| 37 | + |
| 38 | + let mut branch: Vec<_> = branch.unwrap_or_else(|| b"master").into(); |
| 39 | + for b in branch.iter_mut() { |
| 40 | + if *b == b' ' { |
| 41 | + *b = b'-'; |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + let user = String::from_utf8_lossy(&header.user); |
| 46 | + let desc = String::from_utf8_lossy(&header.comment); |
| 47 | + |
| 48 | + let time = header.time.timestamp_secs(); |
| 49 | + let timezone = header.time.tz_offset_secs(); |
| 50 | + let tz = format!("{:+03}{:02}", -timezone / 3600, ((-timezone % 3600) / 60)); |
| 51 | + |
| 52 | + write!(writer, "reset refs/heads/")?; |
| 53 | + writer.write_all(&mut branch)?; |
| 54 | + write!(writer, "\ncommit refs/heads/")?; |
| 55 | + writer.write_all(&mut branch)?; |
| 56 | + writeln!(writer, "\nmark :{}", mark(revision))?; |
| 57 | + |
| 58 | + writeln!(writer, "author {} {} {}", user, time, tz)?; |
| 59 | + writeln!(writer, "committer {} {} {}", user, time, tz)?; |
| 60 | + writeln!(writer, "data {}", desc.len() + 1)?; |
| 61 | + writeln!(writer, "{}\n", desc)?; |
| 62 | + |
| 63 | + match (header.p1, header.p2) { |
| 64 | + (Some(p1), Some(p2)) => { |
| 65 | + writeln!(writer, "from :{}", mark(p1))?; |
| 66 | + writeln!(writer, "merge :{}", mark(p2))?; |
| 67 | + } |
| 68 | + (Some(p), None) | (None, Some(p)) => { |
| 69 | + writeln!(writer, "from :{}", mark(p))?; |
| 70 | + } |
| 71 | + _ => (), |
| 72 | + } |
| 73 | + |
| 74 | + for mut file in changeset.files { |
| 75 | + match (file.data, file.manifest_entry) { |
| 76 | + (None, None) => { |
| 77 | + write!(writer, "D ")?; |
| 78 | + writer.write_all(&mut file.path)?; |
| 79 | + writeln!(writer)?; |
| 80 | + } |
| 81 | + (Some(data), Some(manifest_entry)) => { |
| 82 | + write!( |
| 83 | + writer, |
| 84 | + "M {} inline ", |
| 85 | + match manifest_entry.details { |
| 86 | + ManifestEntryDetails::File(FileType::Symlink) => "120000", |
| 87 | + ManifestEntryDetails::File(FileType::Executable) => "100755", |
| 88 | + ManifestEntryDetails::Tree |
| 89 | + | ManifestEntryDetails::File(FileType::Regular) => "100644", |
| 90 | + } |
| 91 | + )?; |
| 92 | + writer.write_all(&mut file.path)?; |
| 93 | + let data = file_content(&data); |
| 94 | + writeln!(writer, "\ndata {}", data.len())?; |
| 95 | + writer.write_all(&data[..])?; |
| 96 | + } |
| 97 | + _ => panic!("Wrong file data!"), |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + if closed { |
| 102 | + write!(writer, "reset refs/tags/archive/")?; |
| 103 | + writer.write_all(&mut branch)?; |
| 104 | + writeln!(writer, "\nfrom :{}\n", mark(revision))?; |
| 105 | + |
| 106 | + write!(writer, "reset refs/heads/")?; |
| 107 | + writer.write_all(&mut branch)?; |
| 108 | + writeln!(writer, "\nfrom 0000000000000000000000000000000000000000\n")?; |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + for (rev, tag) in repo.tags().unwrap() { |
| 113 | + eprintln!("export tag {}", tag.name); |
| 114 | + writeln!(writer, "reset refs/tags/{}", tag.name).unwrap(); |
| 115 | + writeln!(writer, "from :{}", mark(rev)).unwrap(); |
| 116 | + writeln!(writer).unwrap(); |
| 117 | + } |
| 118 | + |
| 119 | + eprintln!("Done. Elapsed: {:?}", start.elapsed()); |
| 120 | + Ok(()) |
| 121 | +} |
| 122 | + |
| 123 | +fn mark<R: Into<Revision>>(rev: R) -> usize { |
| 124 | + (rev.into() + 1).0 as usize |
| 125 | +} |
| 126 | + |
| 127 | +#[derive(Debug)] |
| 128 | +#[allow(dead_code)] |
| 129 | +enum Error { |
| 130 | + MercurialRepoException(hg_parser::ErrorKind), |
| 131 | + Parse(ParseError), |
| 132 | + IO(std::io::Error), |
| 133 | +} |
| 134 | + |
| 135 | +impl From<ParseError> for Error { |
| 136 | + fn from(value: ParseError) -> Self { |
| 137 | + Error::Parse(value) |
| 138 | + } |
| 139 | +} |
| 140 | + |
| 141 | +impl From<std::io::Error> for Error { |
| 142 | + fn from(value: std::io::Error) -> Self { |
| 143 | + Error::IO(value) |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +impl From<hg_parser::ErrorKind> for Error { |
| 148 | + fn from(value: hg_parser::ErrorKind) -> Self { |
| 149 | + Error::MercurialRepoException(value) |
| 150 | + } |
| 151 | +} |
0 commit comments