Spaces:
Running
Running
feat: create git repository explorer app with diff, tree, and README viewing capabilities
Browse files- .gradio/certificate.pem +31 -0
- __pycache__/git_diff_utils.cpython-313.pyc +0 -0
- __pycache__/git_readme_utils.cpython-313.pyc +0 -0
- __pycache__/git_tree_utils.cpython-313.pyc +0 -0
- app.py +187 -0
- git_diff_utils.py +101 -0
- git_readme_utils.py +141 -0
- git_tree_utils.py +137 -0
- requirements.txt +2 -0
.gradio/certificate.pem
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
-----BEGIN CERTIFICATE-----
|
2 |
+
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
|
31 |
+
-----END CERTIFICATE-----
|
__pycache__/git_diff_utils.cpython-313.pyc
ADDED
Binary file (5.26 kB). View file
|
|
__pycache__/git_readme_utils.cpython-313.pyc
ADDED
Binary file (7.32 kB). View file
|
|
__pycache__/git_tree_utils.cpython-313.pyc
ADDED
Binary file (6.36 kB). View file
|
|
app.py
ADDED
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import sys
|
3 |
+
from git_diff_utils import get_latest_commit_diff
|
4 |
+
from git_tree_utils import get_repo_tree_structure, print_tree
|
5 |
+
from git_readme_utils import get_repo_readme
|
6 |
+
|
7 |
+
def get_diff(repo_source, commit_depth=1):
|
8 |
+
"""
|
9 |
+
Get the diff of a specific commit from a git repository.
|
10 |
+
|
11 |
+
Args:
|
12 |
+
repo_source (str): Path to local repository or URL to remote repository
|
13 |
+
commit_depth (int, optional): How many commits back to check (default=1 for latest commit)
|
14 |
+
|
15 |
+
Returns:
|
16 |
+
str: The git diff output formatted as text
|
17 |
+
"""
|
18 |
+
try:
|
19 |
+
commit_depth = int(commit_depth)
|
20 |
+
if commit_depth < 1:
|
21 |
+
return "Error: commit_depth must be a positive integer"
|
22 |
+
except ValueError:
|
23 |
+
return "Error: commit_depth must be an integer"
|
24 |
+
|
25 |
+
print(f"Getting diff for repository: {repo_source} (commit depth: {commit_depth})")
|
26 |
+
diff = get_latest_commit_diff(repo_source, commit_depth)
|
27 |
+
return diff
|
28 |
+
|
29 |
+
def get_tree(repo_source, commit_depth=1):
|
30 |
+
"""
|
31 |
+
Get the tree structure of a git repository at a specific commit.
|
32 |
+
|
33 |
+
Args:
|
34 |
+
repo_source (str): Path to local repository or URL to remote repository
|
35 |
+
commit_depth (int, optional): How many commits back to check (default=1 for latest commit)
|
36 |
+
|
37 |
+
Returns:
|
38 |
+
str: The formatted tree structure as text
|
39 |
+
"""
|
40 |
+
try:
|
41 |
+
commit_depth = int(commit_depth)
|
42 |
+
if commit_depth < 1:
|
43 |
+
return "Error: commit_depth must be a positive integer"
|
44 |
+
except ValueError:
|
45 |
+
return "Error: commit_depth must be an integer"
|
46 |
+
|
47 |
+
print(f"Getting tree structure for repository: {repo_source} (commit depth: {commit_depth})")
|
48 |
+
tree = get_repo_tree_structure(repo_source, commit_depth)
|
49 |
+
|
50 |
+
if isinstance(tree, dict):
|
51 |
+
# Capture the output of print_tree to a string
|
52 |
+
import io
|
53 |
+
from contextlib import redirect_stdout
|
54 |
+
|
55 |
+
f = io.StringIO()
|
56 |
+
with redirect_stdout(f):
|
57 |
+
print("Repository Structure:")
|
58 |
+
print_tree(tree)
|
59 |
+
output = f.getvalue()
|
60 |
+
return output
|
61 |
+
else:
|
62 |
+
return tree # Error message
|
63 |
+
|
64 |
+
def get_readme(repo_source, commit_depth=1):
|
65 |
+
"""
|
66 |
+
Get the README content from a git repository at a specific commit.
|
67 |
+
|
68 |
+
Args:
|
69 |
+
repo_source (str): Path to local repository or URL to remote repository
|
70 |
+
commit_depth (int, optional): How many commits back to check (default=1 for latest commit)
|
71 |
+
|
72 |
+
Returns:
|
73 |
+
str: The content of the README file or an error message
|
74 |
+
"""
|
75 |
+
try:
|
76 |
+
commit_depth = int(commit_depth)
|
77 |
+
if commit_depth < 1:
|
78 |
+
return "Error: commit_depth must be a positive integer"
|
79 |
+
except ValueError:
|
80 |
+
return "Error: commit_depth must be an integer"
|
81 |
+
|
82 |
+
print(f"Getting README for repository: {repo_source} (commit depth: {commit_depth})")
|
83 |
+
result = get_repo_readme(repo_source, commit_depth)
|
84 |
+
|
85 |
+
if isinstance(result, tuple):
|
86 |
+
readme_file, readme_content = result
|
87 |
+
return f"README File: {readme_file}\n\n{readme_content}"
|
88 |
+
else:
|
89 |
+
return result # Error message
|
90 |
+
|
91 |
+
# Create the Gradio interface
|
92 |
+
with gr.Blocks(title="Git Repository Explorer") as demo:
|
93 |
+
gr.Markdown("# Git Repository Explorer")
|
94 |
+
|
95 |
+
# Feature selection
|
96 |
+
feature = gr.Radio(
|
97 |
+
choices=["View Commit Diff", "View Repository Structure", "View README"],
|
98 |
+
value="View Commit Diff",
|
99 |
+
label="Select Feature"
|
100 |
+
)
|
101 |
+
|
102 |
+
# Common inputs for all features
|
103 |
+
with gr.Row():
|
104 |
+
with gr.Column():
|
105 |
+
repo_input = gr.Textbox(
|
106 |
+
label="Repository Path or URL",
|
107 |
+
placeholder="Enter local path or remote URL (e.g., https://github.com/username/repo.git)",
|
108 |
+
lines=1
|
109 |
+
)
|
110 |
+
|
111 |
+
depth_input = gr.Number(
|
112 |
+
label="Commit Depth",
|
113 |
+
value=1,
|
114 |
+
minimum=1,
|
115 |
+
step=1,
|
116 |
+
info="How many commits back to check (1 = latest commit)"
|
117 |
+
)
|
118 |
+
|
119 |
+
submit_btn = gr.Button("Execute", variant="primary")
|
120 |
+
|
121 |
+
# Output area
|
122 |
+
output = gr.TextArea(
|
123 |
+
label="Output",
|
124 |
+
lines=20,
|
125 |
+
max_lines=50,
|
126 |
+
show_copy_button=True
|
127 |
+
)
|
128 |
+
|
129 |
+
# Function to handle feature selection and execution
|
130 |
+
def process_request(feature_selection, repo_source, commit_depth):
|
131 |
+
if feature_selection == "View Commit Diff":
|
132 |
+
return get_diff(repo_source, commit_depth)
|
133 |
+
elif feature_selection == "View Repository Structure":
|
134 |
+
return get_tree(repo_source, commit_depth)
|
135 |
+
elif feature_selection == "View README":
|
136 |
+
return get_readme(repo_source, commit_depth)
|
137 |
+
return "Please select a feature"
|
138 |
+
|
139 |
+
# Update button text based on feature selection
|
140 |
+
def update_button_text(feature_selection):
|
141 |
+
if feature_selection == "View Commit Diff":
|
142 |
+
return "Get Diff"
|
143 |
+
elif feature_selection == "View Repository Structure":
|
144 |
+
return "Get Tree Structure"
|
145 |
+
elif feature_selection == "View README":
|
146 |
+
return "Get README"
|
147 |
+
return "Execute"
|
148 |
+
|
149 |
+
# Update output label based on feature selection
|
150 |
+
def update_output_label(feature_selection):
|
151 |
+
if feature_selection == "View Commit Diff":
|
152 |
+
return "Diff Output"
|
153 |
+
elif feature_selection == "View Repository Structure":
|
154 |
+
return "Repository Structure"
|
155 |
+
elif feature_selection == "View README":
|
156 |
+
return "README Content"
|
157 |
+
return "Output"
|
158 |
+
|
159 |
+
# Set up events
|
160 |
+
feature.change(fn=update_button_text, inputs=feature, outputs=submit_btn)
|
161 |
+
feature.change(fn=update_output_label, inputs=feature, outputs=output)
|
162 |
+
|
163 |
+
submit_btn.click(
|
164 |
+
fn=process_request,
|
165 |
+
inputs=[feature, repo_input, depth_input],
|
166 |
+
outputs=output
|
167 |
+
)
|
168 |
+
|
169 |
+
gr.Markdown("""
|
170 |
+
### Instructions:
|
171 |
+
1. Select the feature you want to use
|
172 |
+
2. Enter a git repository path or URL
|
173 |
+
3. Specify how many commits back to check (default: 1 for the latest commit)
|
174 |
+
4. Click the execute button
|
175 |
+
|
176 |
+
This tool can handle both local repositories and remote URLs automatically.
|
177 |
+
|
178 |
+
#### Features:
|
179 |
+
- **View Commit Diff**: Shows the differences made in a specific commit
|
180 |
+
- **View Repository Structure**: Displays the tree structure of the repository at a specific commit
|
181 |
+
- **View README**: Shows the content of the README file if present in the repository
|
182 |
+
""")
|
183 |
+
|
184 |
+
# Launch the app with MCP server enabled
|
185 |
+
if __name__ == "__main__":
|
186 |
+
#demo.launch(share=True, mcp_server=True)
|
187 |
+
demo.launch(share=False, server_name="0.0.0.0", server_port=7860, mcp_server=True)
|
git_diff_utils.py
ADDED
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import subprocess
|
2 |
+
import tempfile
|
3 |
+
import shutil
|
4 |
+
import os
|
5 |
+
from typing import Optional, Union, Dict, List
|
6 |
+
|
7 |
+
|
8 |
+
def get_latest_commit_diff(repo_source: str, commit_depth: int = 1) -> Union[str, Dict[str, List[str]]]:
|
9 |
+
"""
|
10 |
+
Returns the differences made in a specified commit of a git repository.
|
11 |
+
|
12 |
+
Args:
|
13 |
+
repo_source: Path to the git repository or URL to a remote repository.
|
14 |
+
Function automatically detects if it's a local path or remote URL.
|
15 |
+
commit_depth: How many commits back to check (default=1 for the latest commit).
|
16 |
+
For example, 2 would return the diff for the second-to-last commit.
|
17 |
+
|
18 |
+
Returns:
|
19 |
+
A string containing the git diff output or a dictionary with structured diff information
|
20 |
+
"""
|
21 |
+
temp_dir = None
|
22 |
+
try:
|
23 |
+
# Determine if repo_source is a remote URL or local path
|
24 |
+
is_remote = repo_source.startswith(('http://', 'https://', 'git://', 'ssh://')) or repo_source.endswith('.git')
|
25 |
+
|
26 |
+
# If it's a remote repository, clone it to a temporary directory
|
27 |
+
if is_remote:
|
28 |
+
temp_dir = tempfile.mkdtemp()
|
29 |
+
print(f"Cloning remote repository: {repo_source}")
|
30 |
+
clone_cmd = ["git", "clone", repo_source, temp_dir]
|
31 |
+
subprocess.check_output(clone_cmd)
|
32 |
+
repo_path = temp_dir
|
33 |
+
else:
|
34 |
+
repo_path = repo_source
|
35 |
+
|
36 |
+
# Validate commit_depth parameter
|
37 |
+
if commit_depth < 1:
|
38 |
+
return "Error: commit_depth must be a positive integer"
|
39 |
+
|
40 |
+
# Get the commit hash for the specified depth
|
41 |
+
if commit_depth == 1:
|
42 |
+
# For the latest commit
|
43 |
+
commit_ref = "HEAD"
|
44 |
+
else:
|
45 |
+
# For earlier commits: HEAD~1, HEAD~2, etc. (0-indexed in git, so we subtract 1)
|
46 |
+
commit_ref = f"HEAD~{commit_depth-1}"
|
47 |
+
|
48 |
+
# Get the commit hash
|
49 |
+
commit_cmd = ["git", "-C", repo_path, "rev-parse", commit_ref]
|
50 |
+
try:
|
51 |
+
commit_hash = subprocess.check_output(commit_cmd).decode('utf-8').strip()
|
52 |
+
except subprocess.CalledProcessError:
|
53 |
+
return f"Error: Could not find commit at depth {commit_depth}"
|
54 |
+
|
55 |
+
# Get the diff from the commit
|
56 |
+
diff_cmd = ["git", "-C", repo_path, "show", "--name-status", commit_hash]
|
57 |
+
diff_output = subprocess.check_output(diff_cmd).decode('utf-8')
|
58 |
+
|
59 |
+
# For more detailed content diff including the changes
|
60 |
+
content_diff_cmd = ["git", "-C", repo_path, "show", commit_hash]
|
61 |
+
content_diff = subprocess.check_output(content_diff_cmd).decode('utf-8')
|
62 |
+
|
63 |
+
return content_diff
|
64 |
+
|
65 |
+
except subprocess.CalledProcessError as e:
|
66 |
+
return f"Error executing git command: {str(e)}"
|
67 |
+
except Exception as e:
|
68 |
+
return f"Unexpected error: {str(e)}"
|
69 |
+
finally:
|
70 |
+
# Clean up temporary directory if it was created
|
71 |
+
if temp_dir and os.path.exists(temp_dir):
|
72 |
+
shutil.rmtree(temp_dir)
|
73 |
+
|
74 |
+
|
75 |
+
# Example usage
|
76 |
+
if __name__ == "__main__":
|
77 |
+
import sys
|
78 |
+
|
79 |
+
if len(sys.argv) < 2:
|
80 |
+
print("Usage: python main.py <repo_path_or_url> [commit_depth]")
|
81 |
+
print(" repo_path_or_url: Path to local repository or URL to remote repository")
|
82 |
+
print(" commit_depth: Optional - How many commits back to check (default=1 for latest commit)")
|
83 |
+
sys.exit(1)
|
84 |
+
|
85 |
+
repo_source = sys.argv[1]
|
86 |
+
commit_depth = 1 # Default to latest commit
|
87 |
+
|
88 |
+
# Check if commit_depth was provided
|
89 |
+
if len(sys.argv) > 2:
|
90 |
+
try:
|
91 |
+
commit_depth = int(sys.argv[2])
|
92 |
+
if commit_depth < 1:
|
93 |
+
print("Error: commit_depth must be a positive integer")
|
94 |
+
sys.exit(1)
|
95 |
+
except ValueError:
|
96 |
+
print("Error: commit_depth must be an integer")
|
97 |
+
sys.exit(1)
|
98 |
+
|
99 |
+
print(f"Getting diff for repository: {repo_source} (commit depth: {commit_depth})")
|
100 |
+
diff = get_latest_commit_diff(repo_source, commit_depth)
|
101 |
+
print(diff)
|
git_readme_utils.py
ADDED
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import subprocess
|
2 |
+
import tempfile
|
3 |
+
import shutil
|
4 |
+
import os
|
5 |
+
from typing import Optional, Union, Dict, List, Tuple
|
6 |
+
|
7 |
+
|
8 |
+
def get_repo_readme(repo_source: str, commit_depth: int = 1) -> Union[Tuple[str, str], str]:
|
9 |
+
"""
|
10 |
+
Returns the README content of a git repository at a specific commit.
|
11 |
+
|
12 |
+
Args:
|
13 |
+
repo_source: Path to the git repository or URL to a remote repository.
|
14 |
+
Function automatically detects if it's a local path or remote URL.
|
15 |
+
commit_depth: How many commits back to check (default=1 for the latest commit).
|
16 |
+
For example, 2 would return the README from the second-to-last commit.
|
17 |
+
|
18 |
+
Returns:
|
19 |
+
A tuple containing (readme_filename, readme_content) if README is found,
|
20 |
+
or an error string if something fails or no README is found
|
21 |
+
"""
|
22 |
+
temp_dir = None
|
23 |
+
try:
|
24 |
+
# Determine if repo_source is a remote URL or local path
|
25 |
+
is_remote = repo_source.startswith(('http://', 'https://', 'git://', 'ssh://')) or repo_source.endswith('.git')
|
26 |
+
|
27 |
+
# If it's a remote repository, clone it to a temporary directory
|
28 |
+
if is_remote:
|
29 |
+
temp_dir = tempfile.mkdtemp()
|
30 |
+
print(f"Cloning remote repository: {repo_source}")
|
31 |
+
clone_cmd = ["git", "clone", repo_source, temp_dir]
|
32 |
+
subprocess.check_output(clone_cmd)
|
33 |
+
repo_path = temp_dir
|
34 |
+
else:
|
35 |
+
repo_path = repo_source
|
36 |
+
|
37 |
+
# Validate commit_depth parameter
|
38 |
+
if commit_depth < 1:
|
39 |
+
return "Error: commit_depth must be a positive integer"
|
40 |
+
|
41 |
+
# Get the commit hash for the specified depth
|
42 |
+
if commit_depth == 1:
|
43 |
+
# For the latest commit
|
44 |
+
commit_ref = "HEAD"
|
45 |
+
else:
|
46 |
+
# For earlier commits: HEAD~1, HEAD~2, etc. (0-indexed in git, so we subtract 1)
|
47 |
+
commit_ref = f"HEAD~{commit_depth-1}"
|
48 |
+
|
49 |
+
# Get the commit hash
|
50 |
+
commit_cmd = ["git", "-C", repo_path, "rev-parse", commit_ref]
|
51 |
+
try:
|
52 |
+
commit_hash = subprocess.check_output(commit_cmd).decode('utf-8').strip()
|
53 |
+
except subprocess.CalledProcessError:
|
54 |
+
return f"Error: Could not find commit at depth {commit_depth}"
|
55 |
+
|
56 |
+
# Common README file patterns to check
|
57 |
+
readme_patterns = [
|
58 |
+
"README.md",
|
59 |
+
"README.txt",
|
60 |
+
"README",
|
61 |
+
"readme.md",
|
62 |
+
"Readme.md",
|
63 |
+
"ReadMe.md",
|
64 |
+
"readme.txt",
|
65 |
+
"README.markdown",
|
66 |
+
"readme.markdown"
|
67 |
+
]
|
68 |
+
|
69 |
+
# Check for the existence of README files
|
70 |
+
ls_files_cmd = ["git", "-C", repo_path, "ls-tree", "--name-only", commit_hash]
|
71 |
+
files = subprocess.check_output(ls_files_cmd).decode('utf-8').strip().split('\n')
|
72 |
+
|
73 |
+
readme_file = None
|
74 |
+
for pattern in readme_patterns:
|
75 |
+
if pattern in files:
|
76 |
+
readme_file = pattern
|
77 |
+
break
|
78 |
+
|
79 |
+
# Also check in the root directory
|
80 |
+
if not readme_file:
|
81 |
+
for pattern in readme_patterns:
|
82 |
+
if any(f == pattern or f.endswith(f"/{pattern}") for f in files):
|
83 |
+
readme_file = next((f for f in files if f == pattern or f.endswith(f"/{pattern}")), None)
|
84 |
+
break
|
85 |
+
|
86 |
+
if not readme_file:
|
87 |
+
return "No README file found in the repository"
|
88 |
+
|
89 |
+
# Get the content of the README file at the specified commit
|
90 |
+
show_cmd = ["git", "-C", repo_path, "show", f"{commit_hash}:{readme_file}"]
|
91 |
+
try:
|
92 |
+
readme_content = subprocess.check_output(show_cmd).decode('utf-8')
|
93 |
+
return (readme_file, readme_content)
|
94 |
+
except subprocess.CalledProcessError:
|
95 |
+
return f"Error: Could not read {readme_file} at commit {commit_hash}"
|
96 |
+
|
97 |
+
except subprocess.CalledProcessError as e:
|
98 |
+
return f"Error executing git command: {str(e)}"
|
99 |
+
except Exception as e:
|
100 |
+
return f"Unexpected error: {str(e)}"
|
101 |
+
finally:
|
102 |
+
# Clean up temporary directory if it was created
|
103 |
+
if temp_dir and os.path.exists(temp_dir):
|
104 |
+
shutil.rmtree(temp_dir)
|
105 |
+
|
106 |
+
|
107 |
+
# Example usage
|
108 |
+
if __name__ == "__main__":
|
109 |
+
import sys
|
110 |
+
|
111 |
+
if len(sys.argv) < 2:
|
112 |
+
print("Usage: python git_readme_utils.py <repo_path_or_url> [commit_depth]")
|
113 |
+
print(" repo_path_or_url: Path to local repository or URL to remote repository")
|
114 |
+
print(" commit_depth: Optional - How many commits back to check (default=1 for latest commit)")
|
115 |
+
sys.exit(1)
|
116 |
+
|
117 |
+
repo_source = sys.argv[1]
|
118 |
+
commit_depth = 1 # Default to latest commit
|
119 |
+
|
120 |
+
# Check if commit_depth was provided
|
121 |
+
if len(sys.argv) > 2:
|
122 |
+
try:
|
123 |
+
commit_depth = int(sys.argv[2])
|
124 |
+
if commit_depth < 1:
|
125 |
+
print("Error: commit_depth must be a positive integer")
|
126 |
+
sys.exit(1)
|
127 |
+
except ValueError:
|
128 |
+
print("Error: commit_depth must be an integer")
|
129 |
+
sys.exit(1)
|
130 |
+
|
131 |
+
print(f"Getting README for repository: {repo_source} (commit depth: {commit_depth})")
|
132 |
+
result = get_repo_readme(repo_source, commit_depth)
|
133 |
+
|
134 |
+
if isinstance(result, tuple):
|
135 |
+
readme_file, readme_content = result
|
136 |
+
print(f"\nFound README file: {readme_file}\n")
|
137 |
+
print("=" * 80)
|
138 |
+
print(readme_content)
|
139 |
+
print("=" * 80)
|
140 |
+
else:
|
141 |
+
print(result) # Error message
|
git_tree_utils.py
ADDED
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import subprocess
|
2 |
+
import tempfile
|
3 |
+
import shutil
|
4 |
+
import os
|
5 |
+
from typing import Optional, Union, Dict, List, Any
|
6 |
+
|
7 |
+
|
8 |
+
def get_repo_tree_structure(repo_source: str, commit_depth: int = 1) -> Union[Dict[str, Any], str]:
|
9 |
+
"""
|
10 |
+
Returns the tree structure of a git repository at a specific commit.
|
11 |
+
|
12 |
+
Args:
|
13 |
+
repo_source: Path to the git repository or URL to a remote repository.
|
14 |
+
Function automatically detects if it's a local path or remote URL.
|
15 |
+
commit_depth: How many commits back to check (default=1 for the latest commit).
|
16 |
+
For example, 2 would return the tree for the second-to-last commit.
|
17 |
+
|
18 |
+
Returns:
|
19 |
+
A dictionary representing the repository structure tree or error string if something fails
|
20 |
+
"""
|
21 |
+
temp_dir = None
|
22 |
+
try:
|
23 |
+
# Determine if repo_source is a remote URL or local path
|
24 |
+
is_remote = repo_source.startswith(('http://', 'https://', 'git://', 'ssh://')) or repo_source.endswith('.git')
|
25 |
+
|
26 |
+
# If it's a remote repository, clone it to a temporary directory
|
27 |
+
if is_remote:
|
28 |
+
temp_dir = tempfile.mkdtemp()
|
29 |
+
print(f"Cloning remote repository: {repo_source}")
|
30 |
+
clone_cmd = ["git", "clone", repo_source, temp_dir]
|
31 |
+
subprocess.check_output(clone_cmd)
|
32 |
+
repo_path = temp_dir
|
33 |
+
else:
|
34 |
+
repo_path = repo_source
|
35 |
+
|
36 |
+
# Validate commit_depth parameter
|
37 |
+
if commit_depth < 1:
|
38 |
+
return "Error: commit_depth must be a positive integer"
|
39 |
+
|
40 |
+
# Get the commit hash for the specified depth
|
41 |
+
if commit_depth == 1:
|
42 |
+
# For the latest commit
|
43 |
+
commit_ref = "HEAD"
|
44 |
+
else:
|
45 |
+
# For earlier commits: HEAD~1, HEAD~2, etc. (0-indexed in git, so we subtract 1)
|
46 |
+
commit_ref = f"HEAD~{commit_depth-1}"
|
47 |
+
|
48 |
+
# Get the commit hash
|
49 |
+
commit_cmd = ["git", "-C", repo_path, "rev-parse", commit_ref]
|
50 |
+
try:
|
51 |
+
commit_hash = subprocess.check_output(commit_cmd).decode('utf-8').strip()
|
52 |
+
except subprocess.CalledProcessError:
|
53 |
+
return f"Error: Could not find commit at depth {commit_depth}"
|
54 |
+
|
55 |
+
# Use git ls-tree recursively to get the repository structure
|
56 |
+
ls_tree_cmd = ["git", "-C", repo_path, "ls-tree", "-r", "--name-only", commit_hash]
|
57 |
+
file_list = subprocess.check_output(ls_tree_cmd).decode('utf-8').strip().split('\n')
|
58 |
+
|
59 |
+
# Build a tree structure from the file paths
|
60 |
+
root = {}
|
61 |
+
for file_path in file_list:
|
62 |
+
if not file_path: # Skip empty paths
|
63 |
+
continue
|
64 |
+
|
65 |
+
parts = file_path.split('/')
|
66 |
+
current = root
|
67 |
+
|
68 |
+
# Navigate through path components, creating nested dictionaries as needed
|
69 |
+
for i, part in enumerate(parts):
|
70 |
+
if i == len(parts) - 1: # It's a file (last part of path)
|
71 |
+
current[part] = None # Files have None value
|
72 |
+
else: # It's a directory
|
73 |
+
if part not in current:
|
74 |
+
current[part] = {} # Create directory dict if it doesn't exist
|
75 |
+
current = current[part] # Move into the directory
|
76 |
+
|
77 |
+
return root
|
78 |
+
|
79 |
+
except subprocess.CalledProcessError as e:
|
80 |
+
return f"Error executing git command: {str(e)}"
|
81 |
+
except Exception as e:
|
82 |
+
return f"Unexpected error: {str(e)}"
|
83 |
+
finally:
|
84 |
+
# Clean up temporary directory if it was created
|
85 |
+
if temp_dir and os.path.exists(temp_dir):
|
86 |
+
shutil.rmtree(temp_dir)
|
87 |
+
|
88 |
+
|
89 |
+
# Helper function to print the tree structure in a more readable format
|
90 |
+
def print_tree(tree, indent=""):
|
91 |
+
"""
|
92 |
+
Print the repository tree structure in a readable format
|
93 |
+
|
94 |
+
Args:
|
95 |
+
tree: Dictionary representing the repository structure
|
96 |
+
indent: Current indentation level string (used in recursion)
|
97 |
+
"""
|
98 |
+
for key, value in sorted(tree.items()):
|
99 |
+
if value is None: # It's a file
|
100 |
+
print(f"{indent}βββ {key}")
|
101 |
+
else: # It's a directory
|
102 |
+
print(f"{indent}βββ {key}/")
|
103 |
+
print_tree(value, indent + "β ")
|
104 |
+
|
105 |
+
|
106 |
+
# Example usage
|
107 |
+
if __name__ == "__main__":
|
108 |
+
import sys
|
109 |
+
|
110 |
+
if len(sys.argv) < 2:
|
111 |
+
print("Usage: python git_tree_utils.py <repo_path_or_url> [commit_depth]")
|
112 |
+
print(" repo_path_or_url: Path to local repository or URL to remote repository")
|
113 |
+
print(" commit_depth: Optional - How many commits back to check (default=1 for latest commit)")
|
114 |
+
sys.exit(1)
|
115 |
+
|
116 |
+
repo_source = sys.argv[1]
|
117 |
+
commit_depth = 1 # Default to latest commit
|
118 |
+
|
119 |
+
# Check if commit_depth was provided
|
120 |
+
if len(sys.argv) > 2:
|
121 |
+
try:
|
122 |
+
commit_depth = int(sys.argv[2])
|
123 |
+
if commit_depth < 1:
|
124 |
+
print("Error: commit_depth must be a positive integer")
|
125 |
+
sys.exit(1)
|
126 |
+
except ValueError:
|
127 |
+
print("Error: commit_depth must be an integer")
|
128 |
+
sys.exit(1)
|
129 |
+
|
130 |
+
print(f"Getting repository structure for: {repo_source} (commit depth: {commit_depth})")
|
131 |
+
tree = get_repo_tree_structure(repo_source, commit_depth)
|
132 |
+
|
133 |
+
if isinstance(tree, dict):
|
134 |
+
print("\nRepository Structure:")
|
135 |
+
print_tree(tree)
|
136 |
+
else:
|
137 |
+
print(tree) # Error message
|
requirements.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
gradio>=4.0.0
|
2 |
+
GitPython
|