← back to jvasile__git-remote-dokuwiki

Function bodies 46 total

All specs Real LLM only Function bodies
main function · rust · L2-L40 (39 LOC)
build.rs
fn main() {
    // Get git commit hash with dirty flag
    let git_hash = Command::new("git")
        .args(["describe", "--always", "--dirty"])
        .output()
        .ok()
        .and_then(|o| {
            if o.status.success() {
                String::from_utf8(o.stdout).ok()
            } else {
                None
            }
        })
        .map(|s| s.trim().to_string())
        .unwrap_or_else(|| "unknown".to_string());

    // Get build date (UTC)
    let build_date = Command::new("date")
        .args(["-u", "+%Y-%m-%d"])
        .output()
        .ok()
        .and_then(|o| {
            if o.status.success() {
                String::from_utf8(o.stdout).ok()
            } else {
                None
            }
        })
        .map(|s| s.trim().to_string())
        .unwrap_or_else(|| "unknown".to_string());

    println!("cargo:rustc-env=GIT_HASH={}", git_hash);
    println!("cargo:rustc-env=BUILD_DATE={}", build_date);

    // Rebuild if git HEAD changes
 
new function · rust · L95-L144 (50 LOC)
src/dokuwiki.rs
    pub fn new(wiki_url: &str, user: &str, verbosity: Verbosity) -> Result<Self> {
        let wiki_url = wiki_url.trim_end_matches('/').to_string();
        let rpc_url = format!("{}/lib/exe/jsonrpc.php", wiki_url);
        let url: url::Url = rpc_url.parse().context("Invalid wiki URL")?;

        let load_path = get_cookie_load_path();

        let mut has_loaded_cookies = false;
        let cookie_store = if let Ok(ref path) = load_path {
            if path.exists() {
                match load_netscape_cookies(path, &url) {
                    Ok(store) => {
                        has_loaded_cookies = true;
                        store
                    }
                    Err(e) => {
                        eprintln!("Failed to load cookies from {:?}: {}", path, e);
                        CookieStore::new(None)
                    }
                }
            } else {
                eprintln!("Cookie file not found: {:?}", path);
                CookieStore::new(None)
save_cookies function · rust · L147-L152 (6 LOC)
src/dokuwiki.rs
    fn save_cookies(&self) -> Result<()> {
        let store = self.cookie_store.read().unwrap();
        let url: url::Url = self.rpc_url.parse().unwrap();
        let domain = url.host_str().unwrap_or("localhost");
        save_netscape_cookies(&store, &self.cookie_path, domain)
    }
get_cookie_header function · rust · L155-L163 (9 LOC)
src/dokuwiki.rs
    fn get_cookie_header(&self) -> String {
        let store = self.cookie_store.read().unwrap();
        let url: url::Url = self.rpc_url.parse().unwrap();
        store
            .get_request_values(&url)
            .map(|(name, value)| format!("{}={}", name, value))
            .collect::<Vec<_>>()
            .join("; ")
    }
store_cookies function · rust · L166-L174 (9 LOC)
src/dokuwiki.rs
    fn store_cookies(&self, response: &reqwest::blocking::Response) {
        for cookie_header in response.headers().get_all(SET_COOKIE) {
            if let Ok(cookie_str) = cookie_header.to_str() {
                let url: url::Url = self.rpc_url.parse().unwrap();
                let mut store = self.cookie_store.write().unwrap();
                let _ = store.parse(cookie_str, &url);
            }
        }
    }
call_inner function · rust · L177-L239 (63 LOC)
src/dokuwiki.rs
    fn call_inner(&mut self, method: &str, params: Value) -> Result<Value> {
        let request = JsonRpcRequest {
            jsonrpc: "2.0",
            method: method.to_string(),
            params,
            id: self.request_id,
        };
        self.request_id += 1;

        let cookie_header = self.get_cookie_header();

        let mut req = self.client.post(&self.rpc_url)
            .header(CONTENT_TYPE, "application/json");
        if !cookie_header.is_empty() {
            req = req.header(COOKIE, cookie_header);
        }

        let body = serde_json::to_string(&request)?;
        let response = req
            .body(body)
            .send()
            .map_err(|e| anyhow!("HTTP request failed: {}", e))?;

        self.store_cookies(&response);

        let status = response.status();
        let body_text = response.text().map_err(|e| anyhow!("Failed to read response body: {}", e))?;

        if !status.is_success() {
            return Err(anyhow!("HTTP error {}:
call function · rust · L242-L255 (14 LOC)
src/dokuwiki.rs
    pub fn call(&mut self, method: &str, params: Value) -> Result<Value> {
        match self.call_inner(method, params.clone()) {
            Ok(value) => Ok(value),
            Err(e) => {
                let err_str = e.to_string();
                if err_str.contains("401") || err_str.contains("Unauthorized") || err_str.contains("not logged in") {
                    self.reauthenticate()?;
                    self.call_inner(method, params)
                } else {
                    Err(e)
                }
            }
        }
    }
Repobility — same analyzer, your code, free for public repos · /scan/
reauthenticate function · rust · L258-L272 (15 LOC)
src/dokuwiki.rs
    fn reauthenticate(&mut self) -> Result<()> {
        self.verbosity.info("Session expired, re-authenticating...");

        if self.cookie_path.exists() {
            let _ = std::fs::remove_file(&self.cookie_path);
        }
        *self.cookie_store.write().unwrap() = CookieStore::new(None);

        let (user, password) = self.get_credentials()?;
        self.login(&user, &password)?;
        self.user = user;
        self.save_cookies()?;

        Ok(())
    }
wiki_host function · rust · L279-L284 (6 LOC)
src/dokuwiki.rs
    pub fn wiki_host(&self) -> &str {
        self.wiki_url
            .strip_prefix("https://")
            .or_else(|| self.wiki_url.strip_prefix("http://"))
            .unwrap_or(&self.wiki_url)
    }
ensure_authenticated function · rust · L287-L313 (27 LOC)
src/dokuwiki.rs
    pub fn ensure_authenticated(&mut self) -> Result<()> {
        if self.has_cached_session() {
            self.verbosity.info(&format!("Using cached session for {}", self.user));
            // If cookies were loaded from env var but we're saving to .git/, copy them
            if !self.cookie_path.exists() {
                let _ = self.save_cookies();
            }
        } else {
            let (user, password) = self.get_credentials()?;
            self.login(&user, &password)?;
            self.user = user;
            self.save_cookies()?;
        }

        // Check API version
        let version = self.get_api_version()?;
        if version < MIN_API_VERSION {
            return Err(anyhow!(
                "DokuWiki API version {} is too old. Minimum required: {}. Please upgrade DokuWiki.",
                version,
                MIN_API_VERSION
            ));
        }

        self.verbosity.debug(&format!("API version: {}", version));
        Ok(())
    }
get_credentials function · rust · L316-L380 (65 LOC)
src/dokuwiki.rs
    fn get_credentials(&self) -> Result<(String, String)> {
        use std::env;
        use std::process::{Command, Stdio};

        if let Ok(password) = env::var("DOKUWIKI_PASSWORD") {
            let user = if self.user.is_empty() {
                env::var("DOKUWIKI_USER").unwrap_or_else(|_| "admin".to_string())
            } else {
                self.user.clone()
            };
            self.verbosity.info("Using credentials from environment");
            return Ok((user, password));
        }

        let url: url::Url = self.rpc_url.parse()?;
        let host = url.host_str().unwrap_or("unknown");

        let mut input = format!("protocol=https\nhost={}\n", host);
        if !self.user.is_empty() {
            input.push_str(&format!("username={}\n", self.user));
        }
        input.push('\n');

        let mut child = Command::new("git")
            .args(["credential", "fill"])
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .std
login function · rust · L383-L394 (12 LOC)
src/dokuwiki.rs
    fn login(&mut self, user: &str, password: &str) -> Result<()> {
        let result = self.call_inner("core.login", json!({
            "user": user,
            "pass": password
        }))?;

        match result.as_bool() {
            Some(true) => Ok(()),
            Some(false) => Err(anyhow!("Login failed: invalid credentials")),
            _ => Err(anyhow!("Unexpected login response: {:?}", result)),
        }
    }
get_all_pages function · rust · L403-L409 (7 LOC)
src/dokuwiki.rs
    pub fn get_all_pages(&mut self) -> Result<Vec<PageInfo>> {
        let result = self.call("dokuwiki.getPagelist", json!({
            "ns": "",
            "opts": { "depth": 0 }
        }))?;
        parse_page_list(&result)
    }
get_page_list function · rust · L412-L418 (7 LOC)
src/dokuwiki.rs
    pub fn get_page_list(&mut self, namespace: &str) -> Result<Vec<PageInfo>> {
        let result = self.call("dokuwiki.getPagelist", json!({
            "ns": namespace,
            "opts": { "depth": 0 }
        }))?;
        parse_page_list(&result)
    }
get_recent_changes function · rust · L421-L449 (29 LOC)
src/dokuwiki.rs
    pub fn get_recent_changes(&mut self, since: i64) -> Result<Vec<PageVersion>> {
        let result = self.call("core.getRecentPageChanges", json!({
            "timestamp": since
        }))?;

        let arr = result.as_array().ok_or_else(|| anyhow!("Expected array"))?;

        let mut changes = Vec::new();
        for item in arr {
            let page_id = item["id"].as_str().unwrap_or_default().to_string();
            let version = item["revision"].as_i64().unwrap_or(0);
            let author = item["author"].as_str().unwrap_or_default().to_string();
            let summary = item["summary"].as_str().unwrap_or_default().to_string();
            let revision_type = item["type"].as_str().unwrap_or("E").to_string();

            if !page_id.is_empty() {
                changes.push(PageVersion {
                    page_id: Some(page_id),
                    version,
                    author,
                    summary,
                    _size: 0,
                    revis
Repobility · MCP-ready · https://repobility.com
get_page_versions function · rust · L452-L478 (27 LOC)
src/dokuwiki.rs
    pub fn get_page_versions(&mut self, page_id: &str) -> Result<Vec<PageVersion>> {
        let result = self.call("core.getPageHistory", json!({
            "page": page_id
        }))?;

        let arr = result.as_array().ok_or_else(|| anyhow!("Expected array"))?;

        let mut versions = Vec::new();
        for item in arr {
            let version = item["revision"].as_i64().unwrap_or(0);
            let author = item["author"].as_str().unwrap_or_default().to_string();
            let summary = item["summary"].as_str().unwrap_or_default().to_string();
            let _size = item["sizechange"].as_i64().unwrap_or(0);
            let revision_type = item["type"].as_str().unwrap_or("E").to_string();

            versions.push(PageVersion {
                page_id: None,
                version,
                author,
                summary,
                _size,
                revision_type,
            });
        }

        Ok(versions)
    }
get_page_version function · rust · L481-L490 (10 LOC)
src/dokuwiki.rs
    pub fn get_page_version(&mut self, page_id: &str, version: i64) -> Result<String> {
        let result = self.call("core.getPage", json!({
            "page": page_id,
            "rev": version
        }))?;

        result.as_str()
            .map(|s| s.to_string())
            .ok_or_else(|| anyhow!("Expected string from getPage"))
    }
put_page function · rust · L493-L500 (8 LOC)
src/dokuwiki.rs
    pub fn put_page(&mut self, page_id: &str, content: &str, summary: &str) -> Result<()> {
        self.call("core.savePage", json!({
            "page": page_id,
            "text": content,
            "summary": summary
        }))?;
        Ok(())
    }
get_attachments function · rust · L503-L529 (27 LOC)
src/dokuwiki.rs
    pub fn get_attachments(&mut self, namespace: &str) -> Result<Vec<MediaInfo>> {
        let result = self.call("core.listMedia", json!({
            "namespace": namespace,
            "depth": 0  // 0 = unlimited depth, list all media recursively
        }))?;

        let arr = result.as_array().ok_or_else(|| anyhow!("Expected array"))?;

        let mut media = Vec::new();
        for item in arr {
            let id = item["id"].as_str().unwrap_or_default().to_string();
            let _size = item["size"].as_i64().unwrap_or(0);
            let revision = item["rev"].as_i64().unwrap_or(0);
            let author = item["user"].as_str().unwrap_or_default().to_string();

            if !id.is_empty() {
                media.push(MediaInfo {
                    id,
                    _size,
                    revision,
                    author,
                });
            }
        }

        Ok(media)
    }
get_recent_media_changes function · rust · L532-L557 (26 LOC)
src/dokuwiki.rs
    pub fn get_recent_media_changes(&mut self, since: i64) -> Result<Vec<MediaInfo>> {
        let result = self.call("core.getRecentMediaChanges", json!({
            "timestamp": since
        }))?;

        let arr = result.as_array().ok_or_else(|| anyhow!("Expected array"))?;

        let mut media = Vec::new();
        for item in arr {
            let id = item["id"].as_str().unwrap_or_default().to_string();
            let _size = item["size"].as_i64().unwrap_or(0);
            let revision = item["revision"].as_i64().unwrap_or(0);
            let author = item["author"].as_str().unwrap_or_default().to_string();

            if !id.is_empty() {
                media.push(MediaInfo {
                    id,
                    _size,
                    revision,
                    author,
                });
            }
        }

        Ok(media)
    }
get_media_versions function · rust · L560-L583 (24 LOC)
src/dokuwiki.rs
    pub fn get_media_versions(&mut self, media_id: &str) -> Result<Vec<MediaVersion>> {
        let result = self.call("core.getMediaHistory", json!({
            "media": media_id
        }))?;

        let arr = result.as_array().ok_or_else(|| anyhow!("Expected array"))?;

        let mut versions = Vec::new();
        for item in arr {
            let version = item["revision"].as_i64().unwrap_or(0);
            let author = item["author"].as_str().unwrap_or_default().to_string();
            let summary = item["summary"].as_str().unwrap_or_default().to_string();
            let revision_type = item["type"].as_str().unwrap_or("E").to_string();

            versions.push(MediaVersion {
                version,
                author,
                summary,
                revision_type,
            });
        }

        Ok(versions)
    }
get_attachment_version function · rust · L586-L597 (12 LOC)
src/dokuwiki.rs
    pub fn get_attachment_version(&mut self, media_id: &str, version: i64) -> Result<Vec<u8>> {
        let result = self.call("core.getMedia", json!({
            "media": media_id,
            "rev": version
        }))?;

        let base64_data = result.as_str()
            .ok_or_else(|| anyhow!("Expected base64 string from getMedia"))?;

        BASE64.decode(base64_data)
            .map_err(|e| anyhow!("Failed to decode base64: {}", e))
    }
put_attachment function · rust · L600-L609 (10 LOC)
src/dokuwiki.rs
    pub fn put_attachment(&mut self, media_id: &str, data: &[u8], overwrite: bool) -> Result<()> {
        let base64_data = BASE64.encode(data);

        self.call("core.saveMedia", json!({
            "media": media_id,
            "base64": base64_data,
            "overwrite": overwrite
        }))?;
        Ok(())
    }
Repobility analyzer · published findings · https://repobility.com
get_repo_cookie_path function · rust · L619-L631 (13 LOC)
src/dokuwiki.rs
fn get_repo_cookie_path() -> Result<PathBuf> {
    let output = std::process::Command::new("git")
        .args(["rev-parse", "--git-dir"])
        .output()
        .context("Failed to find .git directory")?;

    if !output.status.success() {
        return Err(anyhow!("Not in a git repository"));
    }

    let git_dir = String::from_utf8_lossy(&output.stdout).trim().to_string();
    Ok(PathBuf::from(git_dir).join("dokuwiki-cookies.txt"))
}
get_cookie_load_path function · rust · L634-L639 (6 LOC)
src/dokuwiki.rs
fn get_cookie_load_path() -> Result<PathBuf> {
    if let Ok(cookie_path) = std::env::var("DOKUWIKI_COOKIE_FILE") {
        return Ok(PathBuf::from(cookie_path));
    }
    get_repo_cookie_path()
}
load_netscape_cookies function · rust · L642-L681 (40 LOC)
src/dokuwiki.rs
fn load_netscape_cookies(path: &std::path::Path, url: &url::Url) -> Result<CookieStore> {
    use std::io::BufRead;

    let file = fs::File::open(path)?;
    let reader = std::io::BufReader::new(file);
    let mut store = CookieStore::new(None);

    for line in reader.lines() {
        let line = line?;
        let line = line.trim();

        // Skip comments and empty lines (but not #HttpOnly_ lines)
        if line.is_empty() || (line.starts_with('#') && !line.starts_with("#HttpOnly_")) {
            continue;
        }

        let fields: Vec<&str> = line.split('\t').collect();
        if fields.len() < 7 {
            continue;
        }

        let domain = fields[0].trim_start_matches("#HttpOnly_");
        let _host_only = fields[1];
        let path = fields[2];
        let secure = fields[3].eq_ignore_ascii_case("true");
        let _expiry = fields[4];
        let name = fields[5];
        let value = fields[6];

        // Build a Set-Cookie header string
        let mu
save_netscape_cookies function · rust · L684-L716 (33 LOC)
src/dokuwiki.rs
fn save_netscape_cookies(store: &CookieStore, path: &std::path::Path, domain: &str) -> Result<()> {
    use std::io::Write;

    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }

    let mut file = fs::File::create(path)?;
    writeln!(file, "# Netscape HTTP Cookie File")?;
    writeln!(file, "# https://curl.se/docs/http-cookies.html")?;
    writeln!(file, "# This file was generated by git-remote-dokuwiki")?;
    writeln!(file)?;

    // Build a URL for this domain to query cookies
    let url: url::Url = format!("https://{}/", domain).parse().unwrap();

    for cookie in store.matches(&url) {
        let http_only_prefix = if cookie.http_only().unwrap_or(false) { "#HttpOnly_" } else { "" };
        let domain_str = cookie.domain().unwrap_or(domain);
        let host_only = if domain_str.starts_with('.') { "FALSE" } else { "TRUE" };
        let path = cookie.path().unwrap_or("/");
        let secure = if cookie.secure().unwrap_or(false) { "TRUE" } else {
parse_page_list function · rust · L717-L741 (25 LOC)
src/dokuwiki.rs
fn parse_page_list(result: &Value) -> Result<Vec<PageInfo>> {
    let arr = result.as_array().ok_or_else(|| anyhow!("Expected array"))?;

    let mut pages = Vec::new();
    for item in arr {
        let id = item["id"].as_str().unwrap_or_default().to_string();
        let revision = item["rev"].as_i64().unwrap_or(0);
        let _last_modified = item["mtime"].as_i64().unwrap_or(0);
        let author = item["user"].as_str().unwrap_or_default().to_string();
        let _size = item["size"].as_i64().unwrap_or(0);

        if !id.is_empty() {
            pages.push(PageInfo {
                id,
                revision,
                _last_modified,
                author,
                _size,
            });
        }
    }

    Ok(pages)
}
push_error function · rust · L14-L35 (22 LOC)
src/fast_export.rs
fn push_error(failed_item: &str, error: Error, pushed: &[String], pending: &[String]) -> Error {
    let mut msg = format!("Push failed while trying to {}\nError: {}\n", failed_item, error);

    if !pushed.is_empty() {
        msg.push_str("\nSuccessfully pushed:\n");
        for item in pushed {
            msg.push_str(&format!("  - {}\n", item));
        }
    }

    if !pending.is_empty() {
        msg.push_str("\nNot yet pushed:\n");
        for item in pending {
            msg.push_str(&format!("  - {}\n", item));
        }
    }

    msg.push_str("\nThe wiki may be in an inconsistent state. ");
    msg.push_str("Fix the issue and push again to complete the update.");

    anyhow!(msg)
}
get_last_revision_timestamp function · rust · L38-L50 (13 LOC)
src/fast_export.rs
fn get_last_revision_timestamp() -> Option<i64> {
    let output = Command::new("git")
        .args(["config", "dokuwiki.lastRevision"])
        .output()
        .ok()?;

    if !output.status.success() {
        return None;
    }

    let timestamp_str = String::from_utf8_lossy(&output.stdout);
    timestamp_str.trim().parse().ok()
}
path_to_page_id function · rust · L60-L74 (15 LOC)
src/fast_export.rs
fn path_to_page_id(path: &str, namespace: Option<&str>, extension: &str) -> Option<String> {
    // Only handle files with the configured extension
    let suffix = format!(".{}", extension);
    let path = path.strip_suffix(&suffix)?;

    // Convert path separators to colons
    let page_id = path.replace('/', ":");

    // Prepend namespace if specified
    if let Some(ns) = namespace {
        Some(format!("{}:{}", ns, page_id))
    } else {
        Some(page_id)
    }
}
Repobility (the analyzer behind this table) · https://repobility.com
path_to_media_id function · rust · L83-L93 (11 LOC)
src/fast_export.rs
fn path_to_media_id(path: &str, namespace: Option<&str>) -> Option<String> {
    // Convert path separators to colons
    let media_id = path.replace('/', ":");

    // Prepend namespace if specified
    if let Some(ns) = namespace {
        Some(format!("{}:{}", ns, media_id))
    } else {
        Some(media_id)
    }
}
page_id_to_path function · rust · L22-L38 (17 LOC)
src/fast_import.rs
fn page_id_to_path(page_id: &str, namespace: Option<&str>, extension: &str) -> String {
    let mut id = page_id.to_string();

    // Strip namespace prefix if present
    if let Some(ns) = namespace {
        if let Some(stripped) = id.strip_prefix(&format!("{}:", ns)) {
            id = stripped.to_string();
        }
    }

    // Convert colons to path separators and add extension
    let parts: Vec<&str> = id.split(':').collect();
    let mut path = parts.join("/");
    path.push('.');
    path.push_str(extension);
    path
}
media_id_to_path function · rust · L41-L54 (14 LOC)
src/fast_import.rs
fn media_id_to_path(media_id: &str, namespace: Option<&str>) -> String {
    let mut id = media_id.to_string();

    // Strip namespace prefix if present
    if let Some(ns) = namespace {
        if let Some(stripped) = id.strip_prefix(&format!("{}:", ns)) {
            id = stripped.to_string();
        }
    }

    // Convert colons to path separators
    let parts: Vec<&str> = id.split(':').collect();
    parts.join("/")
}
main function · rust · L33-L110 (78 LOC)
src/main.rs
fn main() -> Result<()> {
    let args: Vec<String> = env::args().collect();

    // Handle --version flag
    if args.len() == 2 && (args[1] == "--version" || args[1] == "-V") {
        println!("git-remote-dokuwiki {}", version_string());
        return Ok(());
    }

    if args.len() < 3 {
        eprintln!("git-remote-dokuwiki {}", version_string());
        eprintln!();
        eprintln!("Usage: git-remote-dokuwiki <remote-name> <url>");
        eprintln!("This is a git remote helper and should be invoked by git.");
        eprintln!();
        eprintln!("Examples:");
        eprintln!("  git clone dokuwiki::[email protected]");
        eprintln!("  git clone dokuwiki::[email protected]/namespace");
        std::process::exit(1);
    }

    let _remote_name = &args[1];
    let url = &args[2];
    let verbosity = Verbosity::from_env();

    let mut helper = RemoteHelper::new(url, verbosity)?;

    let stdin = io::stdin();
    let mut stdin = stdin.lock();
    let mut stdo
new function · rust · L123-L130 (8 LOC)
src/main.rs
    fn new(url: &str, verbosity: Verbosity) -> Result<Self> {
        let (wiki_url, user, namespace, extension) = parse_url(url)?;

        let mut client = DokuWikiClient::new(&wiki_url, &user, verbosity)?;
        client.ensure_authenticated()?;

        Ok(Self { client, namespace, extension, imported: false, verbosity, depth: None, dry_run: false })
    }
set_option function · rust · L140-L168 (29 LOC)
src/main.rs
    fn set_option(&mut self, name: &str, value: &str, out: &mut impl Write) -> Result<()> {
        match name {
            "verbosity" => {
                // git sends: no flag=1, -v=2, -vv=3
                if let Ok(level) = value.parse::<u8>() {
                    self.verbosity.set_level(level);
                }
                writeln!(out, "ok")?;
            }
            "depth" => {
                if let Ok(d) = value.parse::<u32>() {
                    self.depth = Some(d);
                }
                writeln!(out, "ok")?;
            }
            "dry-run" => {
                if value == "true" {
                    self.dry_run = true;
                }
                writeln!(out, "ok")?;
            }
            _ => {
                // Unsupported option
                writeln!(out, "unsupported")?;
            }
        }
        Ok(())
    }
list function · rust · L169-L196 (28 LOC)
src/main.rs
    fn list<W: Write>(&mut self, out: &mut W) -> Result<()> {
        // DokuWiki doesn't have branches, we simulate a single 'main' branch
        // Check if there are new changes on the wiki since our last fetch
        let has_new_changes = if let Some(since) = self.get_latest_commit_timestamp() {
            // Check for changes newer than our last known revision
            self.client.get_recent_changes(since + 1)
                .map(|changes| !changes.is_empty())
                .unwrap_or(false)
        } else {
            true // No previous fetch, need to import
        };

        if has_new_changes {
            // Return ? to force git to call import
            writeln!(out, "@refs/heads/main HEAD")?;
            writeln!(out, "? refs/heads/main")?;
        } else if let Some(sha) = self.get_main_sha() {
            // No changes, return the current SHA
            writeln!(out, "{} refs/heads/main", sha)?;
        } else {
            // No SHA yet, need to import
  
import function · rust · L197-L218 (22 LOC)
src/main.rs
    fn import<W: Write>(&mut self, _ref_name: &str, out: &mut W) -> Result<()> {
        if self.imported {
            return Ok(());
        }

        // Check if we already have commits - get the latest timestamp and SHA
        let since_timestamp = self.get_latest_commit_timestamp();
        let parent_sha = self.get_main_sha();

        let wiki_host = self.client.wiki_host().to_string();
        let latest_revision = fast_import::generate(&mut self.client, self.namespace.as_deref(), since_timestamp, parent_sha.as_deref(), &wiki_host, &self.extension, self.depth, self.verbosity, out)?;

        // Store the latest revision timestamp for future incremental fetches
        if let Some(ts) = latest_revision {
            self.set_latest_revision_timestamp(ts);
        }

        self.imported = true;
        // Note: 'done' is written after all import commands are processed
        Ok(())
    }
Repobility — same analyzer, your code, free for public repos · /scan/
get_latest_commit_timestamp function · rust · L222-L234 (13 LOC)
src/main.rs
    fn get_latest_commit_timestamp(&self) -> Option<i64> {
        let output = ProcessCommand::new("git")
            .args(["config", "dokuwiki.lastRevision"])
            .output()
            .ok()?;

        if !output.status.success() {
            return None;
        }

        let timestamp_str = String::from_utf8_lossy(&output.stdout);
        timestamp_str.trim().parse().ok()
    }
get_main_sha function · rust · L244-L255 (12 LOC)
src/main.rs
    fn get_main_sha(&self) -> Option<String> {
        let output = ProcessCommand::new("git")
            .args(["rev-parse", "refs/dokuwiki/origin/heads/main"])
            .output()
            .ok()?;

        if !output.status.success() {
            return None;
        }

        Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
    }
export function · rust · L256-L301 (46 LOC)
src/main.rs
    fn export<W: Write, R: io::BufRead>(
        &mut self,
        out: &mut W,
        reader: &mut R,
    ) -> Result<()> {
        self.verbosity.info("Exporting to wiki...");

        // Check if remote has changes we haven't fetched
        // This prevents overwriting remote changes (non-fast-forward)
        if let Some(since) = self.get_latest_commit_timestamp() {
            let has_remote_changes = self.client.get_recent_changes(since + 1)
                .map(|changes| !changes.is_empty())
                .unwrap_or(false);

            if has_remote_changes {
                eprintln!("error: failed to push some refs");
                eprintln!("hint: Updates were rejected because the remote contains work that you do not");
                eprintln!("hint: have locally. This is usually caused by another repository pushing to");
                eprintln!("hint: the same ref. If you want to integrate the remote changes, use");
                eprintln!("hint: 'git pull' be
parse_command function · rust · L23-L48 (26 LOC)
src/protocol.rs
pub fn parse_command(line: &str) -> Command {
    let line = line.trim();

    if line.is_empty() {
        return Command::Empty;
    }

    let mut parts = line.splitn(2, ' ');
    let cmd = parts.next().unwrap_or("");
    let arg = parts.next().unwrap_or("").to_string();

    match cmd {
        "capabilities" => Command::Capabilities,
        "list" => Command::List,
        "import" => Command::Import(arg),
        "export" => Command::Export,
        "option" => {
            // Parse "option name value"
            let mut option_parts = arg.splitn(2, ' ');
            let name = option_parts.next().unwrap_or("").to_string();
            let value = option_parts.next().unwrap_or("").to_string();
            Command::Option { name, value }
        }
        _ => Command::Unknown(line.to_string()),
    }
}
from_env function · rust · L20-L34 (15 LOC)
src/verbosity.rs
    pub fn from_env() -> Self {
        let mut level = 0u8;

        // Check tool-specific env var
        if let Ok(v) = env::var("DOKUWIKI_VERBOSE") {
            if let Ok(l) = v.parse::<u8>() {
                // Map DOKUWIKI_VERBOSE=1 to git's verbosity=2 (-v)
                // Map DOKUWIKI_VERBOSE=2 to git's verbosity=3 (-vv)
                level = l + 1;
            }
        }

        VERBOSITY_LEVEL.store(level, Ordering::SeqCst);
        Verbosity
    }
set_level function · rust · L37-L43 (7 LOC)
src/verbosity.rs
    pub fn set_level(&self, level: u8) {
        // Only increase verbosity, don't decrease (env var takes precedence)
        let current = VERBOSITY_LEVEL.load(Ordering::SeqCst);
        if level > current {
            VERBOSITY_LEVEL.store(level, Ordering::SeqCst);
        }
    }
progress function · rust · L65-L75 (11 LOC)
src/verbosity.rs
    pub fn progress(&self, msg: &str, current: usize, total: usize, min_level: u8, max_level: u8) {
        let level = self.level();
        if level >= min_level && level <= max_level {
            eprint!("\r{} {}/{}   ", msg, current, total);
            if current == total {
                eprintln!();
            } else {
                let _ = io::stderr().flush();
            }
        }
    }