better-git-of-theseus 0.4.2__py3-none-any.whl → 0.4.5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: better-git-of-theseus
3
- Version: 0.4.2
3
+ Version: 0.4.5
4
4
  Summary: Plot stats on Git repositories with interactive Plotly charts
5
5
  Home-page: https://github.com/onewesong/better-git-of-theseus
6
6
  Author: Erik Bernhardsson
@@ -1,15 +1,15 @@
1
- better_git_of_theseus-0.4.2.dist-info/licenses/LICENSE,sha256=yNNDAWUe1WLKnuUcRp9X95C-yP2lfGl69m97Ftw-DUw,11345
1
+ better_git_of_theseus-0.4.5.dist-info/licenses/LICENSE,sha256=yNNDAWUe1WLKnuUcRp9X95C-yP2lfGl69m97Ftw-DUw,11345
2
2
  git_of_theseus/__init__.py,sha256=LeG5tCOgvZMmKOjmO_HRg54sWF2K3-lTBf8H_vHMFio,273
3
3
  git_of_theseus/analyze.py,sha256=78E1G2FdSS9VZd0jKSnO5gpXwzNCjtzkSAxSzadYM3A,21547
4
- git_of_theseus/app.py,sha256=8aS72pMg4CyEqdYhcsz1QZSxYw8wh_iQLvb-2LOK25A,5192
5
- git_of_theseus/cmd.py,sha256=4W8C0tb-9Uejq4WRjGyYKXmZgE4HQBk7KFIKrozY4Og,639
4
+ git_of_theseus/app.py,sha256=6GuBWC3WaN0VkwHAmKM_6ZNalf7NcXgXYq3ZP6XMDvY,8463
5
+ git_of_theseus/cmd.py,sha256=kvi3sgC0ICj7PvXuzCQsijU5swS3JkfAtIT5yZRNbFo,550
6
6
  git_of_theseus/line_plot.py,sha256=LegoVy0VEFT4sM5fYCES-I_2H9UaerCopDI3J2dyHeU,3117
7
- git_of_theseus/plotly_plots.py,sha256=c_9rJo3qlOy-TdHPsvuDH-6hVVO0_xYq-DmOnmgqOCE,7414
7
+ git_of_theseus/plotly_plots.py,sha256=lbLA4E0SE7Hl4TSxHcAWjdLee_yWLzURX0qAaPmZxus,8576
8
8
  git_of_theseus/stack_plot.py,sha256=q4-YlW3PyiwbIBFeHBA3dsdR1I_XKUQD74hAuSfhIR4,3150
9
9
  git_of_theseus/survival_plot.py,sha256=NEITAa0pMD9uJVsPd7JA71ucavnG1RxgC-F6Jk-K5bE,4868
10
10
  git_of_theseus/utils.py,sha256=Xw2udch9ixSgFInGhIC4_RJ_9IB3E8MmV1dmznavCWc,1026
11
- better_git_of_theseus-0.4.2.dist-info/METADATA,sha256=ZqXuZz94OODNr50giDYHD5Iihw5D_38undkBRXIeHDI,3633
12
- better_git_of_theseus-0.4.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
13
- better_git_of_theseus-0.4.2.dist-info/entry_points.txt,sha256=dEBL6oCDAozY13Y_qxS_6-qkyCA7R2TpjoLH6QJR72g,66
14
- better_git_of_theseus-0.4.2.dist-info/top_level.txt,sha256=2kpp8WgiBzqVLxua_mBS00Nj4cUORaRbJi121THJ_0o,15
15
- better_git_of_theseus-0.4.2.dist-info/RECORD,,
11
+ better_git_of_theseus-0.4.5.dist-info/METADATA,sha256=tB4xIc-SpbNWz2ZKN1Kq8kV_Pn9ahcnw6KGj1HdyBwQ,3633
12
+ better_git_of_theseus-0.4.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
13
+ better_git_of_theseus-0.4.5.dist-info/entry_points.txt,sha256=dEBL6oCDAozY13Y_qxS_6-qkyCA7R2TpjoLH6QJR72g,66
14
+ better_git_of_theseus-0.4.5.dist-info/top_level.txt,sha256=2kpp8WgiBzqVLxua_mBS00Nj4cUORaRbJi121THJ_0o,15
15
+ better_git_of_theseus-0.4.5.dist-info/RECORD,,
git_of_theseus/app.py CHANGED
@@ -4,13 +4,26 @@ import tempfile
4
4
  import shutil
5
5
  try:
6
6
  from git_of_theseus.analyze import analyze
7
- from git_of_theseus.plotly_plots import plotly_stack_plot, plotly_line_plot, plotly_survival_plot
7
+ from git_of_theseus.plotly_plots import plotly_stack_plot, plotly_line_plot, plotly_survival_plot, plotly_bar_plot
8
8
  except ImportError:
9
9
  from analyze import analyze
10
- from plotly_plots import plotly_stack_plot, plotly_line_plot, plotly_survival_plot
10
+ from plotly_plots import plotly_stack_plot, plotly_line_plot, plotly_survival_plot, plotly_bar_plot
11
11
 
12
12
  st.set_page_config(page_title="Git of Theseus Dash", layout="wide")
13
13
 
14
+ # GitHub Link in Sidebar
15
+ st.sidebar.markdown(
16
+ """
17
+ <div style="display: flex; align-items: center; margin-bottom: 20px;">
18
+ <img src="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" width="30" style="margin-right: 10px;">
19
+ <a href="https://github.com/onewesong/better-git-of-theseus" target="_blank" style="text-decoration: none; color: inherit; font-weight: bold;">
20
+ better-git-of-theseus
21
+ </a>
22
+ </div>
23
+ """,
24
+ unsafe_allow_html=True
25
+ )
26
+
14
27
  st.title("📊 Git of Theseus - Repository Analysis")
15
28
 
16
29
  import sys
@@ -18,12 +31,49 @@ import sys
18
31
  # Sidebar Configuration
19
32
  st.sidebar.header("Configuration")
20
33
 
34
+ with st.sidebar.expander("📖 How to use", expanded=False):
35
+ st.markdown("""
36
+ **Better Git of Theseus** is a tool to analyze the evolution of Git repositories.
37
+
38
+ ### Plots Explained:
39
+ - **Stack Plot**: Shows code growth over time, broken down by cohort (when code was added).
40
+ - **Line Plot**: Shows trends across different dimensions (Author, Extension, etc.).
41
+ - **Distribution**: Shows the **current** distribution (Who contributed most, which file types are dominant).
42
+ - **Survival Plot**: Estimates how long a line of code typically lasts before being modified or deleted.
43
+
44
+ ### Tips:
45
+ - **Cohort Format**: `%Y` (Yearly) and `%Y-%m` (Monthly) are recommended.
46
+ - **Mailmap**: Use a `.mailmap` file in the repo root to resolve duplicate author names.
47
+ """)
48
+
21
49
  default_repo = "."
22
50
  if len(sys.argv) > 1:
23
51
  default_repo = sys.argv[1]
24
52
 
25
- repo_path = st.sidebar.text_input("Git Repository Path", value=default_repo)
26
- branch = st.sidebar.text_input("Branch", value="master")
53
+ repo_path = default_repo
54
+ # Path display removed as per user request
55
+
56
+ # Fetch branches for the selectbox
57
+ try:
58
+ import git
59
+ repo = git.Repo(repo_path)
60
+ # Get local branches
61
+ branches = [h.name for h in repo.heads]
62
+
63
+ # Try to determine the best default branch (active one, or master/main)
64
+ try:
65
+ current_active = repo.active_branch.name
66
+ except:
67
+ current_active = "master"
68
+
69
+ if current_active in branches:
70
+ branches.remove(current_active)
71
+
72
+ options = [current_active] + sorted(branches)
73
+ branch = st.sidebar.selectbox("Branch", options=options)
74
+ except Exception as e:
75
+ # Fallback if git repo access fails
76
+ branch = st.sidebar.text_input("Branch", value="master")
27
77
 
28
78
  with st.sidebar.expander("Analysis Parameters"):
29
79
  cohortfm = st.text_input(
@@ -35,9 +85,23 @@ with st.sidebar.expander("Analysis Parameters"):
35
85
  "- `%Y-W%W`: Week (e.g., 2023-W01)\n"
36
86
  "- `%Y-%m-%d`: Day"
37
87
  )
38
- interval = st.number_input("Interval (seconds)", value=7 * 24 * 60 * 60)
39
- procs = st.number_input("Processes", value=2, min_value=1)
40
- ignore = st.text_area("Ignore (comma separated)").split(",")
88
+ interval = st.number_input(
89
+ "Analysis Interval (seconds)",
90
+ value=7 * 24 * 60 * 60,
91
+ help="The time step between data points. Default is 604800s (7 days). Larger values are faster; smaller values result in smoother curves."
92
+ )
93
+ st.caption(f"Current resolution: {interval / 86400:.1f} days")
94
+
95
+ procs = st.number_input(
96
+ "Parallel Processes",
97
+ value=2,
98
+ min_value=1,
99
+ help="Number of concurrent processes. Increase to speed up analysis on multi-core CPUs, but note it increases RAM usage."
100
+ )
101
+ ignore = st.text_area(
102
+ "Ignore Patterns",
103
+ help="Glob patterns to ignore (comma separated), e.g.: 'tests/**, *.md'"
104
+ ).split(",")
41
105
  ignore = [i.strip() for i in ignore if i.strip()]
42
106
 
43
107
  @st.cache_data(show_spinner=False)
@@ -71,7 +135,7 @@ if st.sidebar.button("🚀 Run Analysis") or (len(sys.argv) > 1 and st.session_s
71
135
  # Main View
72
136
  if st.session_state.analysis_results:
73
137
  results = st.session_state.analysis_results
74
- tab1, tab2, tab3 = st.tabs(["Stack Plot", "Line Plot", "Survival Plot"])
138
+ tab1, tab2, tab3, tab4 = st.tabs(["Stack Plot", "Line Plot", "Distribution", "Survival Plot"])
75
139
 
76
140
  with tab1:
77
141
  st.header("Stack Plot")
@@ -115,6 +179,22 @@ if st.session_state.analysis_results:
115
179
  st.warning(f"Data for {data_source_label_line} not found.")
116
180
 
117
181
  with tab3:
182
+ st.header("Latest Distribution")
183
+ col1, col2 = st.columns([1, 3])
184
+ with col1:
185
+ data_source_label_bar = st.selectbox("Data Source", list(source_map.keys()), key="bar_source")
186
+ data_key_bar = source_map[data_source_label_bar]
187
+ max_n_bar = st.slider("Max Series", 5, 100, 30, key="bar_max_n")
188
+ with col2:
189
+ project_name = os.path.basename(os.path.abspath(repo_path))
190
+ data_bar = results.get(data_key_bar)
191
+ if data_bar:
192
+ fig = plotly_bar_plot(data_bar, max_n=max_n_bar, title=f"{project_name} - {data_source_label_bar}")
193
+ st.plotly_chart(fig, width="stretch")
194
+ else:
195
+ st.warning(f"Data for {data_source_label_bar} not found.")
196
+
197
+ with tab4:
118
198
  st.header("Survival Plot")
119
199
  col1, col2 = st.columns([1, 3])
120
200
  with col1:
git_of_theseus/cmd.py CHANGED
@@ -7,9 +7,8 @@ def main():
7
7
  cmd_dir = os.path.dirname(os.path.abspath(__file__))
8
8
  app_path = os.path.join(cmd_dir, "app.py")
9
9
 
10
- # The first argument is the repo path, default to current directory
11
- repo_path = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
12
- repo_path = os.path.abspath(repo_path)
10
+ # Always use the current working directory
11
+ repo_path = os.path.abspath(os.getcwd())
13
12
 
14
13
  # Run streamlit
15
14
  # We pass the repo_path as an argument to the streamlit script
@@ -240,4 +240,37 @@ def plotly_survival_plot(commit_history, exp_fit=False, years=5, title=None):
240
240
  )
241
241
 
242
242
 
243
+ return fig
244
+
245
+ def plotly_bar_plot(data, max_n=20, title=None):
246
+ ts, y, labels = _process_stack_line_data(data, max_n, normalize=False)
247
+
248
+ # Get latest data point (current state)
249
+ latest_values = [row[-1] for row in y]
250
+
251
+ # Sort by value for better bar chart presentation
252
+ # (Though _process_stack_line_data already does some sorting, we want descending order)
253
+ indices = sorted(range(len(labels)), key=lambda i: latest_values[i], reverse=True)
254
+
255
+ sorted_labels = [labels[i] for i in indices]
256
+ sorted_values = [latest_values[i] for i in indices]
257
+
258
+ # Generate colors
259
+ colors = px.colors.qualitative.Plotly
260
+ if len(sorted_labels) > len(colors):
261
+ colors = px.colors.qualitative.Dark24
262
+
263
+ fig = go.Figure(go.Bar(
264
+ x=sorted_labels,
265
+ y=sorted_values,
266
+ marker_color=[colors[i % len(colors)] for i in range(len(sorted_labels))]
267
+ ))
268
+
269
+ fig.update_layout(
270
+ title=dict(text=f"{title} (Current Distribution)" if title else "Current Distribution", x=0.5),
271
+ yaxis=dict(title="Lines of Code"),
272
+ xaxis=dict(title=""),
273
+ margin=dict(l=20, r=20, t=50, b=100),
274
+ )
275
+
243
276
  return fig