git-archaeologist 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
@@ -0,0 +1,112 @@
1
+ import git
2
+ from dataclasses import dataclass
3
+ from datetime import timedelta
4
+
5
+ @dataclass
6
+ class RepositoryStats:
7
+ commits: int
8
+ contributors: set
9
+ files: int
10
+ insertions: int
11
+ deletions: int
12
+ lines: int
13
+ first_commit: object
14
+ latest_commit: object
15
+ lifespan: object
16
+ largest_gap: object
17
+ gap_start: object
18
+ gap_end: object
19
+ most_active_month: object
20
+ most_active_month_commits: int
21
+ history: list
22
+
23
+ def analyze_repo(path):
24
+ repo = git.Repo(path)
25
+ commits = list(repo.iter_commits("--all"))
26
+
27
+ if not commits:
28
+ return RepositoryStats(
29
+ commits=0,
30
+ contributors=set(),
31
+ files=0,
32
+ insertions=0,
33
+ deletions=0,
34
+ lines=0,
35
+ first_commit=None,
36
+ latest_commit=None,
37
+ lifespan=None,
38
+ largest_gap=None,
39
+ gap_start=None,
40
+ gap_end=None,
41
+ most_active_month=None,
42
+ most_active_month_commits=0
43
+ )
44
+
45
+ first_commit = min(commits, key=lambda commit: commit.committed_datetime)
46
+ latest_commit = max(commits, key=lambda commit: commit.committed_datetime)
47
+ lifespan = latest_commit.committed_datetime - first_commit.committed_datetime
48
+ sorted_commits = sorted(commits, key=lambda commit: commit.committed_datetime)
49
+ history = []
50
+
51
+ unique_contrib = set()
52
+ files = set()
53
+ total_insertions = 0
54
+ total_deletions = 0
55
+ total_lines = 0
56
+ monthly_commits = {}
57
+ for commit in commits:
58
+ unique_contrib.add(commit.author.name)
59
+ total_deletions += commit.stats.total['deletions']
60
+ total_insertions += commit.stats.total['insertions']
61
+ total_lines += commit.stats.total["lines"]
62
+
63
+ year = commit.committed_datetime.year
64
+ month = commit.committed_datetime.month
65
+ key = (year, month)
66
+ monthly_commits[key] = monthly_commits.get(key, 0) + 1
67
+ history.append((
68
+ commit.hexsha,
69
+ commit.committed_datetime,
70
+ commit.message,
71
+ commit.author
72
+ ))
73
+ for file in commit.stats.files:
74
+ files.add(file)
75
+ number_files = len(files)
76
+ most_active_month = max(monthly_commits, key=monthly_commits.get)
77
+
78
+ largest_gap = timedelta(0)
79
+ gap_start = None
80
+ gap_end = None
81
+
82
+ for previous, current in zip(sorted_commits[:-1], sorted_commits[1:]):
83
+ gap = current.committed_datetime - previous.committed_datetime
84
+ if gap > largest_gap:
85
+ largest_gap = gap
86
+ gap_start = previous
87
+ gap_end = current
88
+
89
+ return RepositoryStats(
90
+ commits=len(commits),
91
+ contributors=unique_contrib,
92
+ files=number_files,
93
+ insertions=total_insertions,
94
+ deletions=total_deletions,
95
+ lines=total_lines,
96
+ first_commit=first_commit,
97
+ latest_commit=latest_commit,
98
+ lifespan=lifespan,
99
+ largest_gap=largest_gap,
100
+ gap_start=gap_start,
101
+ gap_end=gap_end,
102
+ most_active_month=most_active_month,
103
+ most_active_month_commits=monthly_commits[most_active_month],
104
+ history=history
105
+ )
106
+
107
+
108
+ # CONGRATS YOU HAVE FOUND THE SECOND EASTER EGG!!!!
109
+ # KEEP GOING!!!!!!
110
+ # HOPE YOU LIKE THE PROGRAM
111
+ # MADE BY: OMAR GAMAL ELHALABY
112
+ # RATE IF YOU LIKE, OR LEAVE A COMMENT (IF FOUND ON HACKCLUB)!
@@ -0,0 +1,243 @@
1
+ import argparse
2
+ from pathlib import Path
3
+ import git
4
+ from git.exc import NoSuchPathError, InvalidGitRepositoryError
5
+ from git_archaeologist.analyzer import analyze_repo
6
+ import json
7
+
8
+ def validator(path_str):
9
+ try:
10
+ path = Path(path_str)
11
+ git.Repo(path)
12
+ except NoSuchPathError:
13
+ raise argparse.ArgumentTypeError("ERROR: No such path")
14
+ except InvalidGitRepositoryError:
15
+ raise argparse.ArgumentTypeError("ERROR: git repository not found")
16
+ return path
17
+
18
+
19
+ def print_header():
20
+ print("Git Archaeologist")
21
+ print("===========================\n")
22
+
23
+
24
+ def print_timeline_section(stats):
25
+ print("REPOSITORY TIMELINE")
26
+ print("---------------------------")
27
+ print(f"First Commit: {stats.first_commit.committed_datetime}")
28
+ print(f"Last Commit: {stats.latest_commit.committed_datetime}")
29
+ print(f"Lifespan: {stats.lifespan}")
30
+
31
+
32
+ def print_timeline(stats):
33
+ print_header()
34
+ print_timeline_section(stats)
35
+
36
+
37
+ def print_activity_section(stats):
38
+ print("REPOSITORY ACTIVITY")
39
+ print("---------------------------")
40
+ print(
41
+ f"Most active month: "
42
+ f"{stats.most_active_month[1]}/{stats.most_active_month[0]}"
43
+ )
44
+ print(
45
+ f"Commits in the most active month: "
46
+ f"{stats.most_active_month_commits}"
47
+ )
48
+
49
+ if stats.largest_gap:
50
+ print(f"Longest inactivity: {stats.largest_gap}")
51
+ else:
52
+ print("Longest inactivity: 0 days")
53
+
54
+ if stats.gap_start is None:
55
+ print("From: No Gap!")
56
+ else:
57
+ print(f"From: {stats.gap_start.committed_datetime}")
58
+
59
+ if stats.gap_end is None:
60
+ print("To: No Gap!")
61
+ else:
62
+ print(f"To: {stats.gap_end.committed_datetime}")
63
+
64
+
65
+ def print_activity(stats):
66
+ print_header()
67
+ print_activity_section(stats)
68
+
69
+
70
+ def print_summary_section(stats):
71
+ print("REPOSITORY SUMMARY")
72
+ print("---------------------------")
73
+ print(f"Commits: {stats.commits}")
74
+ print(f"Contributors: {stats.contributors}")
75
+ print(f"Files: {stats.files}")
76
+ print(f"Insertions: {stats.insertions}")
77
+ print(f"Deletions: {stats.deletions}")
78
+ print(f"Lines Changed: {stats.lines}\n")
79
+
80
+ print("CONTRIBUTORS")
81
+ print("---------------------------")
82
+
83
+ for name in stats.contributors:
84
+ print(name)
85
+
86
+
87
+ def print_summary(stats):
88
+ print_header()
89
+ print_summary_section(stats)
90
+
91
+ def print_history(stats):
92
+ print_header()
93
+ print("COMMIT HISTORY")
94
+ print("---------------------------")
95
+ commit_num = 0
96
+
97
+ for commit in stats.history:
98
+ commit_num += 1
99
+ print(f"Commit number: {commit_num}")
100
+ print(f"Commit SHA1 (first 7 digits): {stats.history[commit_num - 1][0][:7]}")
101
+ print(f"Commit Date/Time: {stats.history[commit_num - 1][1]}")
102
+ print(f"Commit message: {stats.history[commit_num - 1][2]}")
103
+ print(f"Commit author(s): {stats.history[commit_num - 1][3]}\n")
104
+
105
+ def print_json(stats):
106
+ dict_stats = vars(stats)
107
+ dict_stats["contributors"] = list(stats.contributors)
108
+ dict_stats["largest_gap"] = str(stats.largest_gap)
109
+ dict_stats["gap_start"] =(
110
+ str(stats.gap_start.committed_datetime)
111
+ if stats.gap_start else None
112
+ )
113
+ dict_stats["gap_end"] = (
114
+ str(stats.gap_end.committed_datetime)
115
+ if stats.gap_end else None
116
+ )
117
+
118
+ dict_stats["first_commit"] = (
119
+ str(stats.first_commit.committed_datetime)
120
+ if stats.first_commit else None
121
+ )
122
+
123
+ dict_stats["latest_commit"] = (
124
+ str(stats.latest_commit.committed_datetime)
125
+ if stats.latest_commit else None
126
+ )
127
+
128
+ dict_stats["lifespan"] = str(stats.lifespan)
129
+ history = []
130
+ commit_number = 0
131
+ for commit_hash, date, message, author in stats.history:
132
+ commit_number += 1
133
+ history.append({
134
+ "number": (commit_number),
135
+ "commit SHA1": commit_hash,
136
+ "date":str(date),
137
+ "message": message.strip(),
138
+ "author(s)": str(author)
139
+ })
140
+ dict_stats["history"] = history
141
+ print(json.dumps(dict_stats, indent=4))
142
+
143
+ def print_all(stats):
144
+ print_header()
145
+
146
+ print("REPOSITORY OVERVIEW")
147
+ print("---------------------------")
148
+ print(f"Commits: {stats.commits}")
149
+ print(f"Contributors: {len(stats.contributors)}")
150
+ print(f"Num. of Files: {stats.files}")
151
+ print(f"Insertions: {stats.insertions}")
152
+ print(f"Deletions: {stats.deletions}")
153
+ print(f"Lines Changed: {stats.lines}\n")
154
+
155
+ print_timeline_section(stats)
156
+ print()
157
+ print_activity_section(stats)
158
+ print()
159
+ print_summary_section(stats)
160
+
161
+
162
+ def main():
163
+ parser = argparse.ArgumentParser(
164
+ description="A git tool to view information regarding repositories"
165
+ )
166
+ output_group = parser.add_mutually_exclusive_group()
167
+
168
+ parser.add_argument(
169
+ "repository",
170
+ type=validator,
171
+ help="Path to repository"
172
+ )
173
+
174
+ parser.add_argument(
175
+ "-v",
176
+ "--version",
177
+ action="version",
178
+ version="1.0.0",
179
+ help="show the program's version",
180
+ )
181
+
182
+ output_group.add_argument(
183
+ "-s",
184
+ "--summary",
185
+ action="store_true",
186
+ help="show a simple summary"
187
+ )
188
+
189
+ output_group.add_argument(
190
+ "-t",
191
+ "--timeline",
192
+ action="store_true",
193
+ help="show a simple timeline for the repository"
194
+ )
195
+
196
+ output_group.add_argument(
197
+ "-a",
198
+ "--activity",
199
+ action="store_true",
200
+ help="show a simple activity dashboard for the repository"
201
+ )
202
+
203
+ output_group.add_argument(
204
+ "--history",
205
+ action="store_true",
206
+ help="show the history of the repository"
207
+ )
208
+
209
+ output_group.add_argument(
210
+ "-j",
211
+ "--json",
212
+ action="store_true",
213
+ help="output repository statistics as JSON"
214
+ )
215
+
216
+ parsed_args = parser.parse_args()
217
+ stats = analyze_repo(parsed_args.repository)
218
+ if stats.commits == 0:
219
+ print_header()
220
+ print("ERROR: Repository has no commits.")
221
+ return
222
+
223
+ if parsed_args.timeline:
224
+ print_timeline(stats)
225
+ elif parsed_args.activity:
226
+ print_activity(stats)
227
+ elif parsed_args.summary:
228
+ print_summary(stats)
229
+ elif parsed_args.history:
230
+ print_history(stats)
231
+ elif parsed_args.json:
232
+ print_json(stats)
233
+ else:
234
+ print_all(stats)
235
+
236
+
237
+ if __name__ == "__main__":
238
+ main()
239
+
240
+
241
+ # HOPE YOU'RE HAPPY WITH THE TOOL!!!!!!!
242
+ # MADE BY: OMAR ELHALABY
243
+ # CONGRATS ON FINDING THE THIRD EASYER EGG!!
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.4
2
+ Name: git-archaeologist
3
+ Version: 1.0.0
4
+ Summary: A CLI tool for analyzing Git repository history
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: GitPython
8
+
9
+ # Git Archaeologist
10
+ A python CLI tool for quickly analyzing and exploring the history of Git repositories.
11
+
12
+ # Problem
13
+ Understanding a Git repository's history can be very time consuming when done manually. Git provides a huge amount of information, but finding useful patterns-such as the repository's lifespan, periods of inactivity, most active months, contributors, and commit history-often requires running multiple commands and manually interpreting the results.
14
+
15
+ __Git Archaeologist__ simplifies this process by collecting important repository statistics and presenting them in a clear human-readable format or a JSON format for developers.
16
+
17
+ # Features
18
+
19
+ - Repository summary
20
+ - Total commits
21
+ - Number of contributors
22
+ - Number of lines changed
23
+ - Total insertions
24
+ - Total deletions
25
+ - Total lines changed
26
+
27
+ - Timeline analysis
28
+ - First commit
29
+ - Latest commit
30
+ - Repo Lifespan
31
+
32
+ - Activity analysis
33
+ - Most active month
34
+ - Number of commits during the most active month
35
+ - Longest period of inactivity
36
+ - Commits marking the begining and end of the longest gap
37
+
38
+ - Commit History
39
+ - Commit SHA1
40
+ - Commit data
41
+ - Commit message
42
+ - Author
43
+
44
+ - JSON output
45
+ - Machine-readable repository statistics
46
+ - Useful for scripts, automation and other apps
47
+
48
+ # Installation
49
+
50
+ Clone the repository:
51
+
52
+ ``` bash
53
+ $ git clone https://github.com/omarelhalaby76-coder/git-archaeologist
54
+ $ cd git-archaeologist
55
+ ```
56
+ Install the dependencies:
57
+ ``` bash
58
+ $ pip install -r requirments.txt
59
+ ```
60
+ Or install the project directly:
61
+ ``` bash
62
+ $ pip install .
63
+ ```
@@ -0,0 +1,8 @@
1
+ git_archaeologist/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ git_archaeologist/analyzer.py,sha256=mxQLEgwnvIUA9FFzBO0wMAn5EAW1Y7rf6YDsBUOKnaQ,3860
3
+ git_archaeologist/cli.py,sha256=_gSGNu4P2L8kGfejFVJRHzu0eQID7Kk_1YyVIysRcTI,6480
4
+ git_archaeologist-1.0.0.dist-info/METADATA,sha256=LlaGaDhhPY71-xmFkcmd5RBQnt34l4N5Mltv7M1ldqQ,1813
5
+ git_archaeologist-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
6
+ git_archaeologist-1.0.0.dist-info/entry_points.txt,sha256=CCaVx5vkVbWYlgHoMmstVpuYDxnESzBshhKuHkOJYac,65
7
+ git_archaeologist-1.0.0.dist-info/top_level.txt,sha256=dUOvLs7-v4cBBpzSJghnkHJWms7Ofwf3Zn__Rn-p07o,18
8
+ git_archaeologist-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ git-archaeologist = git_archaeologist.cli:main
@@ -0,0 +1 @@
1
+ git_archaeologist