osm-git

A WIP POC based on the idea from https://blog.andygol.co.ua/en/2023/05/07/osm-2-0-api-using-git/
git clone git://archive.git.mtrnord.blog/MTRNord/osm-git.git
Log | Files | Refs | LICENSE

mod.rs (4622B)


      1 use std::{io::Write, path::Path};
      2 
      3 use color_eyre::eyre::Result;
      4 use git2::{Oid, Repository, Signature};
      5 use tracing::{info, warn};
      6 
      7 /// Initialize the git repository
      8 ///
      9 /// If the git repository already exists, open it. Otherwise, create it.
     10 ///
     11 /// If the git repository is created, generate the README.md file from the template.
     12 ///
     13 /// # Arguments
     14 ///
     15 /// * `git_repo_path` - The path to the git repository
     16 /// * `data_url` - The URL to the OSM data server
     17 /// * `changeset_url` - The URL to the OSM changeset server
     18 ///
     19 /// # Returns
     20 ///
     21 /// * `Result<Repository>` - The git repository
     22 pub fn init_git_repository(
     23     git_repo_path: &str,
     24     data_url: &str,
     25     author: &Signature,
     26 ) -> Result<Repository> {
     27     // Check if the git repo already exists
     28     if std::path::Path::new(git_repo_path).exists() {
     29         info!("Git repository already exists at {}", git_repo_path);
     30         // Open the git repo
     31         let repository = Repository::open(git_repo_path)?;
     32 
     33         return Ok(repository);
     34     }
     35 
     36     info!("Initializing git repository at {}", git_repo_path);
     37 
     38     // Create the git repo if it doesn't exist
     39     let repository = Repository::init(git_repo_path)?;
     40 
     41     generate_readme_from_template(&repository, data_url)?;
     42 
     43     // Commit the README.md file
     44     commit(
     45         &repository,
     46         vec!["README.md".to_string()],
     47         vec![],
     48         "Create the README.md",
     49         author,
     50         author,
     51     )?;
     52     Ok(repository)
     53 }
     54 
     55 /// Generate the README.md file from the template and write it to the git repo
     56 pub fn generate_readme_from_template(repository: &Repository, data_url: &str) -> Result<()> {
     57     let template_file = include_str!("../../templates/README.md");
     58 
     59     // Replace the template variables with the actual values
     60     let template_file = template_file.replace("$server_url", data_url);
     61 
     62     // Get the version of this binary
     63     let version = env!("CARGO_PKG_VERSION");
     64     let template_file = template_file.replace("$version", version);
     65 
     66     // Write the README.md file in the git repo (parent of .git directory)
     67     let path = repository
     68         .path()
     69         .parent()
     70         .expect("Git repository path is not valid");
     71     let readme_file_path = path.join("README.md");
     72     info!(
     73         "Generating README.md file at {}",
     74         readme_file_path
     75             .to_str()
     76             .expect("README.md file path is not valid")
     77     );
     78     let mut readme_file = std::fs::File::create(readme_file_path)?;
     79     readme_file.write_all(template_file.as_bytes())?;
     80     readme_file.sync_all()?;
     81 
     82     info!("README.md file generated");
     83 
     84     Ok(())
     85 }
     86 
     87 /// Helper for creating a git commit
     88 pub fn commit(
     89     repository: &Repository,
     90     added_or_changed_files: Vec<String>,
     91     removed_files: Vec<String>,
     92     message: &str,
     93     author: &Signature,
     94     committer: &Signature,
     95 ) -> Result<Oid> {
     96     let tree_id = {
     97         let mut index = repository.index()?;
     98         for file in added_or_changed_files {
     99             let file_path = Path::new(&file);
    100             let path = if file_path.starts_with(repository.path().parent().unwrap()) {
    101                 Path::new(&file).strip_prefix(repository.path().parent().unwrap())?
    102             } else {
    103                 Path::new(&file)
    104             };
    105             // TODO: I am tired to actually debug this so we just do a sanity check if the file exists
    106             if file_path.exists() {
    107                 index.add_path(path)?;
    108             } else {
    109                 warn!(
    110                     "File {} does not exist but was meant to be added",
    111                     path.to_str().unwrap()
    112                 );
    113             }
    114         }
    115         for file in removed_files {
    116             let file_path = Path::new(&file);
    117             let path = if file_path.starts_with(repository.path().parent().unwrap()) {
    118                 Path::new(&file).strip_prefix(repository.path().parent().unwrap())?
    119             } else {
    120                 Path::new(&file)
    121             };
    122             // We check if it was tracked before. If not we don't need to remove it
    123             if index.get_path(path, 0).is_some() {
    124                 index.remove_path(path)?;
    125             }
    126         }
    127         index.write()?;
    128         index.write_tree()?
    129     };
    130     let tree = repository.find_tree(tree_id)?;
    131     let head_id = repository.refname_to_id("HEAD");
    132     if let Ok(head_id) = head_id {
    133         let parent = repository.find_commit(head_id)?;
    134 
    135         let oid = repository.commit(Some("HEAD"), author, committer, message, &tree, &[&parent])?;
    136         Ok(oid)
    137     } else {
    138         let oid = repository.commit(Some("HEAD"), author, committer, message, &tree, &[])?;
    139         Ok(oid)
    140     }
    141 }