posthog_cli/utils/
git.rs

1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::io::Read;
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct GitInfo {
9    pub repo_name: Option<String>,
10    pub branch: String,
11    pub commit_id: String,
12}
13
14pub fn get_git_info(dir: Option<PathBuf>) -> Result<Option<GitInfo>> {
15    let git_dir = match find_git_dir(dir) {
16        Some(dir) => dir,
17        None => return Ok(None),
18    };
19
20    let repo_name = get_repo_name(&git_dir);
21    let branch = get_current_branch(&git_dir).context("Failed to determine current branch")?;
22    let commit = get_head_commit(&git_dir, &branch).context("Failed to determine commit ID")?;
23
24    Ok(Some(GitInfo {
25        repo_name,
26        branch,
27        commit_id: commit,
28    }))
29}
30
31fn find_git_dir(dir: Option<PathBuf>) -> Option<PathBuf> {
32    let mut current_dir = dir.unwrap_or(std::env::current_dir().ok()?);
33
34    loop {
35        let git_dir = current_dir.join(".git");
36        if git_dir.is_dir() {
37            return Some(git_dir);
38        }
39
40        if !current_dir.pop() {
41            return None;
42        }
43    }
44}
45
46fn get_repo_name(git_dir: &Path) -> Option<String> {
47    // Try grab it from the configured remote, otherwise just use the directory name
48    let config_path = git_dir.join("config");
49    if config_path.exists() {
50        let config_content = match fs::read_to_string(&config_path) {
51            Ok(content) => content,
52            Err(_) => return None,
53        };
54
55        for line in config_content.lines() {
56            let line = line.trim();
57            if line.starts_with("url = ") && line.ends_with(".git") {
58                let url = line.trim_start_matches("url = ");
59                if let Some(repo_name) = url.split('/').last() {
60                    let clean_name = repo_name.trim_end_matches(".git");
61                    return Some(clean_name.to_string());
62                }
63            }
64        }
65    }
66
67    if let Some(parent) = git_dir.parent() {
68        if let Some(name) = parent.file_name() {
69            return Some(name.to_string_lossy().to_string());
70        }
71    }
72
73    None
74}
75
76fn get_current_branch(git_dir: &Path) -> Result<String> {
77    // First try to read from HEAD file
78    let head_path = git_dir.join("HEAD");
79    let mut head_content = String::new();
80    fs::File::open(&head_path)
81        .with_context(|| format!("Failed to open HEAD file at {:?}", head_path))?
82        .read_to_string(&mut head_content)
83        .context("Failed to read HEAD file")?;
84
85    // Parse HEAD content
86    if head_content.starts_with("ref: refs/heads/") {
87        Ok(head_content
88            .trim_start_matches("ref: refs/heads/")
89            .trim()
90            .to_string())
91    } else if head_content.trim().len() == 40 || head_content.trim().len() == 64 {
92        Ok("HEAD-detached".to_string())
93    } else {
94        anyhow::bail!("Unrecognized HEAD format")
95    }
96}
97
98fn get_head_commit(git_dir: &Path, branch: &str) -> Result<String> {
99    if branch == "HEAD-detached" {
100        // For detached HEAD, read directly from HEAD
101        let head_path = git_dir.join("HEAD");
102        let mut head_content = String::new();
103        fs::File::open(&head_path)
104            .with_context(|| format!("Failed to open HEAD file at {:?}", head_path))?
105            .read_to_string(&mut head_content)
106            .context("Failed to read HEAD file")?;
107
108        return Ok(head_content.trim().to_string());
109    }
110
111    // Try to read the commit from the branch reference
112    let ref_path = git_dir.join("refs/heads").join(branch);
113    if ref_path.exists() {
114        let mut commit_id = String::new();
115        fs::File::open(&ref_path)
116            .with_context(|| format!("Failed to open branch reference at {:?}", ref_path))?
117            .read_to_string(&mut commit_id)
118            .context("Failed to read branch reference file")?;
119
120        return Ok(commit_id.trim().to_string());
121    }
122
123    anyhow::bail!("Could not determine commit ID")
124}