@anxin233/gitviz 1.0.3 → 1.1.0

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.
package/dist/cli.cjs CHANGED
@@ -11470,15 +11470,63 @@ var GitParser = class {
11470
11470
  return false;
11471
11471
  }
11472
11472
  }
11473
- async parseCommits(limit = 1e3) {
11474
- const log = await this.git.log({
11473
+ async getBranches() {
11474
+ try {
11475
+ const branchSummary = await this.git.branch(["-a"]);
11476
+ const branches = [];
11477
+ const seenBranches = /* @__PURE__ */ new Set();
11478
+ for (const branchName of branchSummary.all) {
11479
+ if (branchName.includes("remotes/origin/HEAD"))
11480
+ continue;
11481
+ const cleanName = branchName.replace("remotes/origin/", "").replace(/^\*\s*/, "");
11482
+ if (seenBranches.has(cleanName))
11483
+ continue;
11484
+ seenBranches.add(cleanName);
11485
+ try {
11486
+ const log = await this.git.log({ maxCount: 1, [branchName]: null });
11487
+ if (log.latest) {
11488
+ const commitCount = await this.git.raw(["rev-list", "--count", branchName]);
11489
+ branches.push({
11490
+ name: cleanName,
11491
+ commits: parseInt(commitCount.trim()),
11492
+ lastCommit: new Date(log.latest.date)
11493
+ });
11494
+ }
11495
+ } catch {
11496
+ }
11497
+ }
11498
+ return branches;
11499
+ } catch {
11500
+ return [];
11501
+ }
11502
+ }
11503
+ async parseCommits(limit = 1e3, branch) {
11504
+ const logOptions = {
11475
11505
  maxCount: limit,
11476
11506
  "--numstat": null,
11477
11507
  "--pretty": "format:%H|%an|%ae|%ai|%s"
11478
- });
11508
+ };
11509
+ if (branch) {
11510
+ logOptions[branch] = null;
11511
+ }
11512
+ const log = await this.git.log(logOptions);
11479
11513
  const commits = [];
11480
11514
  for (const commit of log.all) {
11481
- const diffSummary = await this.git.diffSummary([`${commit.hash}^`, commit.hash]);
11515
+ let diffSummary;
11516
+ try {
11517
+ diffSummary = await this.git.diffSummary([`${commit.hash}^`, commit.hash]);
11518
+ } catch {
11519
+ try {
11520
+ diffSummary = await this.git.diffSummary(["4b825dc642cb6eb9a060e54bf8d69288fbee4904", commit.hash]);
11521
+ } catch {
11522
+ diffSummary = {
11523
+ files: [],
11524
+ insertions: 0,
11525
+ deletions: 0,
11526
+ changed: 0
11527
+ };
11528
+ }
11529
+ }
11482
11530
  commits.push({
11483
11531
  hash: commit.hash,
11484
11532
  author: commit.author_name || "Unknown",
@@ -11487,7 +11535,8 @@ var GitParser = class {
11487
11535
  message: commit.message,
11488
11536
  files: diffSummary.files.map((f) => f.file),
11489
11537
  insertions: diffSummary.insertions,
11490
- deletions: diffSummary.deletions
11538
+ deletions: diffSummary.deletions,
11539
+ branch
11491
11540
  });
11492
11541
  }
11493
11542
  return commits;
@@ -11561,7 +11610,8 @@ var Analyzer = class {
11561
11610
  return {
11562
11611
  timeline: this.generateTimelineData(analysis.commits),
11563
11612
  contributors: this.generateContributorData(analysis.contributors),
11564
- heatmap: this.generateHeatmapData(analysis.files)
11613
+ heatmap: this.generateHeatmapData(analysis.files),
11614
+ branches: this.generateBranchData(analysis.branches || [])
11565
11615
  };
11566
11616
  }
11567
11617
  generateTimelineData(commits) {
@@ -11597,6 +11647,13 @@ var Analyzer = class {
11597
11647
  heat: f.changes / maxChanges
11598
11648
  })).sort((a, b) => b.changes - a.changes).slice(0, 50);
11599
11649
  }
11650
+ generateBranchData(branches) {
11651
+ return branches.map((b) => ({
11652
+ name: b.name,
11653
+ commits: b.commits,
11654
+ lastCommit: b.lastCommit.toISOString().split("T")[0]
11655
+ })).sort((a, b) => b.commits - a.commits);
11656
+ }
11600
11657
  };
11601
11658
 
11602
11659
  // src/visualizers/html-generator.ts
@@ -11624,6 +11681,12 @@ function generateHTML(data, repoName) {
11624
11681
  padding: 3rem;
11625
11682
  box-shadow: 0 20px 60px rgba(0,0,0,0.3);
11626
11683
  }
11684
+ .header {
11685
+ display: flex;
11686
+ justify-content: space-between;
11687
+ align-items: center;
11688
+ margin-bottom: 2rem;
11689
+ }
11627
11690
  h1 {
11628
11691
  font-size: 3rem;
11629
11692
  margin-bottom: 0.5rem;
@@ -11635,7 +11698,33 @@ function generateHTML(data, repoName) {
11635
11698
  .subtitle {
11636
11699
  color: #666;
11637
11700
  font-size: 1.2rem;
11638
- margin-bottom: 3rem;
11701
+ margin-bottom: 1rem;
11702
+ }
11703
+ .controls {
11704
+ display: flex;
11705
+ gap: 1rem;
11706
+ align-items: center;
11707
+ }
11708
+ .lang-switch {
11709
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
11710
+ color: white;
11711
+ border: none;
11712
+ padding: 0.5rem 1.5rem;
11713
+ border-radius: 8px;
11714
+ cursor: pointer;
11715
+ font-size: 1rem;
11716
+ transition: transform 0.2s;
11717
+ }
11718
+ .lang-switch:hover {
11719
+ transform: translateY(-2px);
11720
+ }
11721
+ .branch-selector {
11722
+ padding: 0.5rem 1rem;
11723
+ border: 2px solid #667eea;
11724
+ border-radius: 8px;
11725
+ font-size: 1rem;
11726
+ cursor: pointer;
11727
+ background: white;
11639
11728
  }
11640
11729
  .section {
11641
11730
  margin-bottom: 4rem;
@@ -11664,6 +11753,7 @@ function generateHTML(data, repoName) {
11664
11753
  pointer-events: none;
11665
11754
  opacity: 0;
11666
11755
  transition: opacity 0.3s;
11756
+ z-index: 1000;
11667
11757
  }
11668
11758
  .stats {
11669
11759
  display: grid;
@@ -11687,40 +11777,106 @@ function generateHTML(data, repoName) {
11687
11777
  font-size: 1rem;
11688
11778
  opacity: 0.9;
11689
11779
  }
11780
+ .branch-comparison {
11781
+ display: grid;
11782
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
11783
+ gap: 1rem;
11784
+ margin-bottom: 2rem;
11785
+ }
11786
+ .branch-card {
11787
+ background: white;
11788
+ border: 2px solid #667eea;
11789
+ border-radius: 12px;
11790
+ padding: 1.5rem;
11791
+ cursor: pointer;
11792
+ transition: all 0.3s;
11793
+ }
11794
+ .branch-card:hover {
11795
+ transform: translateY(-4px);
11796
+ box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
11797
+ }
11798
+ .branch-card.active {
11799
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
11800
+ color: white;
11801
+ }
11802
+ .branch-name {
11803
+ font-size: 1.2rem;
11804
+ font-weight: bold;
11805
+ margin-bottom: 0.5rem;
11806
+ }
11807
+ .branch-info {
11808
+ font-size: 0.9rem;
11809
+ opacity: 0.8;
11810
+ }
11811
+ .hidden {
11812
+ display: none;
11813
+ }
11690
11814
  </style>
11691
11815
  </head>
11692
11816
  <body>
11693
11817
  <div class="container">
11694
- <h1>\u{1F4CA} GitViz</h1>
11695
- <p class="subtitle">Repository: ${repoName}</p>
11818
+ <div class="header">
11819
+ <div>
11820
+ <h1>\u{1F4CA} GitViz</h1>
11821
+ <p class="subtitle" data-i18n="subtitle">Repository: ${repoName}</p>
11822
+ </div>
11823
+ <div class="controls">
11824
+ <button class="lang-switch" onclick="toggleLanguage()">
11825
+ <span id="lang-text">\u4E2D\u6587</span>
11826
+ </button>
11827
+ </div>
11828
+ </div>
11696
11829
 
11697
11830
  <div class="stats">
11698
11831
  <div class="stat-card">
11699
11832
  <div class="stat-value">${data.timeline.reduce((sum, d) => sum + d.commits, 0)}</div>
11700
- <div class="stat-label">Total Commits</div>
11833
+ <div class="stat-label" data-i18n="totalCommits">Total Commits</div>
11701
11834
  </div>
11702
11835
  <div class="stat-card">
11703
11836
  <div class="stat-value">${data.contributors.length}</div>
11704
- <div class="stat-label">Contributors</div>
11837
+ <div class="stat-label" data-i18n="contributors">Contributors</div>
11705
11838
  </div>
11706
11839
  <div class="stat-card">
11707
11840
  <div class="stat-value">${data.heatmap.length}</div>
11708
- <div class="stat-label">Files Changed</div>
11841
+ <div class="stat-label" data-i18n="filesChanged">Files Changed</div>
11842
+ </div>
11843
+ ${data.branches && data.branches.length > 0 ? `
11844
+ <div class="stat-card">
11845
+ <div class="stat-value">${data.branches.length}</div>
11846
+ <div class="stat-label" data-i18n="branches">Branches</div>
11709
11847
  </div>
11848
+ ` : ""}
11710
11849
  </div>
11711
11850
 
11851
+ ${data.branches && data.branches.length > 1 ? `
11712
11852
  <div class="section">
11713
- <h2>\u{1F4C8} Commit Timeline</h2>
11853
+ <h2 data-i18n="branchComparison">\u{1F33F} Branch Comparison</h2>
11854
+ <div class="branch-comparison" id="branchComparison">
11855
+ ${data.branches.map((branch, idx) => `
11856
+ <div class="branch-card ${idx === 0 ? "active" : ""}" onclick="selectBranch('${branch.name}', this)">
11857
+ <div class="branch-name">${branch.name}</div>
11858
+ <div class="branch-info">
11859
+ <div><span data-i18n="commits">Commits</span>: ${branch.commits}</div>
11860
+ <div><span data-i18n="lastCommit">Last Commit</span>: ${branch.lastCommit}</div>
11861
+ </div>
11862
+ </div>
11863
+ `).join("")}
11864
+ </div>
11865
+ </div>
11866
+ ` : ""}
11867
+
11868
+ <div class="section">
11869
+ <h2 data-i18n="commitTimeline">\u{1F4C8} Commit Timeline</h2>
11714
11870
  <div class="chart" id="timeline"></div>
11715
11871
  </div>
11716
11872
 
11717
11873
  <div class="section">
11718
- <h2>\u{1F465} Top Contributors</h2>
11874
+ <h2 data-i18n="topContributors">\u{1F465} Top Contributors</h2>
11719
11875
  <div class="chart" id="contributors"></div>
11720
11876
  </div>
11721
11877
 
11722
11878
  <div class="section">
11723
- <h2>\u{1F525} File Change Heatmap</h2>
11879
+ <h2 data-i18n="fileHeatmap">\u{1F525} File Change Heatmap</h2>
11724
11880
  <div class="chart" id="heatmap"></div>
11725
11881
  </div>
11726
11882
  </div>
@@ -11729,6 +11885,74 @@ function generateHTML(data, repoName) {
11729
11885
 
11730
11886
  <script>
11731
11887
  const data = ${JSON.stringify(data)};
11888
+ let currentLang = 'en';
11889
+ let selectedBranch = null;
11890
+
11891
+ // \u56FD\u9645\u5316\u6587\u672C
11892
+ const i18n = {
11893
+ en: {
11894
+ subtitle: 'Repository: ${repoName}',
11895
+ totalCommits: 'Total Commits',
11896
+ contributors: 'Contributors',
11897
+ filesChanged: 'Files Changed',
11898
+ branches: 'Branches',
11899
+ branchComparison: '\u{1F33F} Branch Comparison',
11900
+ commits: 'Commits',
11901
+ lastCommit: 'Last Commit',
11902
+ commitTimeline: '\u{1F4C8} Commit Timeline',
11903
+ topContributors: '\u{1F465} Top Contributors',
11904
+ fileHeatmap: '\u{1F525} File Change Heatmap',
11905
+ date: 'Date',
11906
+ changes: 'Changes',
11907
+ insertions: 'Insertions',
11908
+ deletions: 'Deletions',
11909
+ lines: 'Lines'
11910
+ },
11911
+ zh: {
11912
+ subtitle: '\u4ED3\u5E93: ${repoName}',
11913
+ totalCommits: '\u603B\u63D0\u4EA4\u6570',
11914
+ contributors: '\u8D21\u732E\u8005',
11915
+ filesChanged: '\u6587\u4EF6\u53D8\u66F4',
11916
+ branches: '\u5206\u652F\u6570',
11917
+ branchComparison: '\u{1F33F} \u5206\u652F\u5BF9\u6BD4',
11918
+ commits: '\u63D0\u4EA4\u6570',
11919
+ lastCommit: '\u6700\u540E\u63D0\u4EA4',
11920
+ commitTimeline: '\u{1F4C8} \u63D0\u4EA4\u65F6\u95F4\u7EBF',
11921
+ topContributors: '\u{1F465} \u9876\u7EA7\u8D21\u732E\u8005',
11922
+ fileHeatmap: '\u{1F525} \u6587\u4EF6\u53D8\u66F4\u70ED\u529B\u56FE',
11923
+ date: '\u65E5\u671F',
11924
+ changes: '\u53D8\u66F4',
11925
+ insertions: '\u65B0\u589E',
11926
+ deletions: '\u5220\u9664',
11927
+ lines: '\u4EE3\u7801\u884C'
11928
+ }
11929
+ };
11930
+
11931
+ function toggleLanguage() {
11932
+ currentLang = currentLang === 'en' ? 'zh' : 'en';
11933
+ document.getElementById('lang-text').textContent = currentLang === 'en' ? '\u4E2D\u6587' : 'English';
11934
+
11935
+ // \u66F4\u65B0\u6240\u6709\u5E26 data-i18n \u5C5E\u6027\u7684\u5143\u7D20
11936
+ document.querySelectorAll('[data-i18n]').forEach(el => {
11937
+ const key = el.getAttribute('data-i18n');
11938
+ if (i18n[currentLang][key]) {
11939
+ el.textContent = i18n[currentLang][key];
11940
+ }
11941
+ });
11942
+ }
11943
+
11944
+ function selectBranch(branchName, element) {
11945
+ selectedBranch = branchName;
11946
+
11947
+ // \u66F4\u65B0\u9009\u4E2D\u72B6\u6001
11948
+ document.querySelectorAll('.branch-card').forEach(card => {
11949
+ card.classList.remove('active');
11950
+ });
11951
+ element.classList.add('active');
11952
+
11953
+ // \u8FD9\u91CC\u53EF\u4EE5\u6DFB\u52A0\u6839\u636E\u5206\u652F\u8FC7\u6EE4\u6570\u636E\u7684\u903B\u8F91
11954
+ console.log('Selected branch:', branchName);
11955
+ }
11732
11956
 
11733
11957
  // Timeline Chart
11734
11958
  {
@@ -11771,9 +11995,11 @@ function generateHTML(data, repoName) {
11771
11995
  .attr("width", x.bandwidth())
11772
11996
  .attr("height", d => height - y(d.commits))
11773
11997
  .on("mouseover", function(event, d) {
11774
- d3.select("#tooltip")
11998
+ const tooltip = d3.select("#tooltip");
11999
+ const lang = currentLang;
12000
+ tooltip
11775
12001
  .style("opacity", 1)
11776
- .html(\`<strong>\${d.date}</strong><br/>Commits: \${d.commits}<br/>+\${d.insertions} -\${d.deletions}\`)
12002
+ .html(\`<strong>\${d.date}</strong><br/>\${i18n[lang].commits}: \${d.commits}<br/>+\${d.insertions} -\${d.deletions}\`)
11777
12003
  .style("left", (event.pageX + 10) + "px")
11778
12004
  .style("top", (event.pageY - 10) + "px");
11779
12005
  })
@@ -11826,9 +12052,11 @@ function generateHTML(data, repoName) {
11826
12052
  .attr("width", x.bandwidth())
11827
12053
  .attr("height", d => height - y(d.commits))
11828
12054
  .on("mouseover", function(event, d) {
11829
- d3.select("#tooltip")
12055
+ const tooltip = d3.select("#tooltip");
12056
+ const lang = currentLang;
12057
+ tooltip
11830
12058
  .style("opacity", 1)
11831
- .html(\`<strong>\${d.name}</strong><br/>Commits: \${d.commits}<br/>Lines: \${d.lines}\`)
12059
+ .html(\`<strong>\${d.name}</strong><br/>\${i18n[lang].commits}: \${d.commits}<br/>\${i18n[lang].lines}: \${d.lines}\`)
11832
12060
  .style("left", (event.pageX + 10) + "px")
11833
12061
  .style("top", (event.pageY - 10) + "px");
11834
12062
  })
@@ -11867,9 +12095,11 @@ function generateHTML(data, repoName) {
11867
12095
  .attr("height", y.bandwidth())
11868
12096
  .attr("fill", d => colorScale(d.heat))
11869
12097
  .on("mouseover", function(event, d) {
11870
- d3.select("#tooltip")
12098
+ const tooltip = d3.select("#tooltip");
12099
+ const lang = currentLang;
12100
+ tooltip
11871
12101
  .style("opacity", 1)
11872
- .html(\`<strong>\${d.file}</strong><br/>Changes: \${d.changes}\`)
12102
+ .html(\`<strong>\${d.file}</strong><br/>\${i18n[lang].changes}: \${d.changes}\`)
11873
12103
  .style("left", (event.pageX + 10) + "px")
11874
12104
  .style("top", (event.pageY - 10) + "px");
11875
12105
  })
@@ -11918,9 +12148,12 @@ program2.command("analyze").description("Analyze a Git repository and generate v
11918
12148
  spinner.fail(source_default.red("Error: No commits found"));
11919
12149
  process.exit(1);
11920
12150
  }
12151
+ spinner.text = "Getting branch information...";
12152
+ const branches = await parser4.getBranches();
11921
12153
  spinner.text = `Analyzing ${commits.length} commits...`;
11922
12154
  const analyzer = new Analyzer();
11923
12155
  const analysis = analyzer.analyze(commits);
12156
+ analysis.branches = branches;
11924
12157
  spinner.text = "Generating visualization data...";
11925
12158
  const vizData = analyzer.generateVisualizationData(analysis);
11926
12159
  spinner.text = "Creating HTML report...";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anxin233/gitviz",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "🎨 Beautiful, interactive Git repository visualizations - Transform your Git history into stunning visual stories",
5
5
  "type": "module",
6
6
  "bin": {
package/dist/cli/index.js DELETED
@@ -1,67 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import chalk from 'chalk';
4
- import ora from 'ora';
5
- import { writeFileSync } from 'fs';
6
- import { join } from 'path';
7
- import { GitParser } from '../core/git-parser.js';
8
- import { Analyzer } from '../core/analyzer.js';
9
- import { generateHTML } from '../visualizers/html-generator.js';
10
- const program = new Command();
11
- program
12
- .name('gitviz')
13
- .description('🎨 Beautiful, interactive Git repository visualizations')
14
- .version('1.0.0');
15
- program
16
- .command('analyze')
17
- .description('Analyze a Git repository and generate visualizations')
18
- .option('-p, --path <path>', 'Path to Git repository', '.')
19
- .option('-o, --output <file>', 'Output HTML file', 'gitviz-report.html')
20
- .option('-l, --limit <number>', 'Limit number of commits to analyze', '1000')
21
- .action(async (options) => {
22
- const spinner = ora('Initializing GitViz...').start();
23
- try {
24
- const parser = new GitParser(options.path);
25
- spinner.text = 'Checking if directory is a Git repository...';
26
- const isRepo = await parser.isGitRepository();
27
- if (!isRepo) {
28
- spinner.fail(chalk.red('Error: Not a Git repository'));
29
- process.exit(1);
30
- }
31
- spinner.text = 'Parsing Git commits...';
32
- const commits = await parser.parseCommits(parseInt(options.limit));
33
- if (commits.length === 0) {
34
- spinner.fail(chalk.red('Error: No commits found'));
35
- process.exit(1);
36
- }
37
- spinner.text = `Analyzing ${commits.length} commits...`;
38
- const analyzer = new Analyzer();
39
- const analysis = analyzer.analyze(commits);
40
- spinner.text = 'Generating visualization data...';
41
- const vizData = analyzer.generateVisualizationData(analysis);
42
- spinner.text = 'Creating HTML report...';
43
- const repoName = options.path === '.' ? 'Current Repository' : options.path;
44
- const html = generateHTML(vizData, repoName);
45
- const outputPath = join(process.cwd(), options.output);
46
- writeFileSync(outputPath, html, 'utf-8');
47
- spinner.succeed(chalk.green('✨ Visualization generated successfully!'));
48
- console.log('\n' + chalk.bold('📊 Repository Statistics:'));
49
- console.log(chalk.cyan(` Total Commits: ${analysis.totalCommits}`));
50
- console.log(chalk.cyan(` Contributors: ${analysis.totalContributors}`));
51
- console.log(chalk.cyan(` Files Changed: ${analysis.files.size}`));
52
- console.log(chalk.cyan(` Date Range: ${analysis.dateRange.start.toLocaleDateString()} - ${analysis.dateRange.end.toLocaleDateString()}`));
53
- console.log('\n' + chalk.bold(`📁 Output: ${outputPath}`));
54
- console.log(chalk.gray(`\nOpen the file in your browser to view the interactive visualization.\n`));
55
- }
56
- catch (error) {
57
- spinner.fail(chalk.red('Error: ' + error.message));
58
- process.exit(1);
59
- }
60
- });
61
- program
62
- .command('quick')
63
- .description('Quick analysis of current directory')
64
- .action(async () => {
65
- program.parse(['node', 'gitviz', 'analyze']);
66
- });
67
- program.parse();