mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
179 lines
5.8 KiB
Go
179 lines
5.8 KiB
Go
package git
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/apex/log"
|
|
"github.com/blacktop/ipsw/internal/download"
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/plumbing/object"
|
|
"github.com/shurcooL/githubv4"
|
|
)
|
|
|
|
// GetFunctionRegex returns a regex pattern to match functions based on file extension
|
|
func GetFunctionRegex(funcName, filePath string) *regexp.Regexp {
|
|
switch strings.ToLower(filepath.Ext(filePath)) {
|
|
case ".go":
|
|
// Go function pattern - anchored to the 'func' keyword
|
|
return regexp.MustCompile(fmt.Sprintf(`(?m)^func\s+(\(\s*\w+\s+[^)]+\)\s+)?%s\s*\([^)]*\)[^{]*{[\s\S]*?^}`, funcName))
|
|
case ".c", ".cpp", ".cc", ".cxx", ".h", ".hpp", ".hxx":
|
|
// C/C++ function pattern - anchored to the function name
|
|
return regexp.MustCompile(fmt.Sprintf(`(?m)^[^\n;{}]*%s\s*\([^)]*\)\s*(?:{[\s\S]*?^}|;)`, funcName))
|
|
case ".js", ".ts":
|
|
// JavaScript/TypeScript pattern - anchored to various function forms
|
|
return regexp.MustCompile(fmt.Sprintf(`(?m)^(?:function\s+%s|const\s+%s\s*=\s*function|const\s+%s\s*=\s*\([^)]*\)\s*=>|let\s+%s\s*=\s*function|var\s+%s\s*=\s*function|%s\s*:\s*function)\s*\([^)]*\)[^{]*{[\s\S]*?^}`,
|
|
funcName, funcName, funcName, funcName, funcName, funcName))
|
|
case ".py":
|
|
// Python function pattern - anchored to the def keyword
|
|
return regexp.MustCompile(fmt.Sprintf(`(?m)^def\s+%s\s*\([^)]*\)[^:]*:[\s\S]*?^(?:\S|$)`, funcName))
|
|
case ".java", ".kt", ".swift":
|
|
// Java/Kotlin/Swift pattern - anchored to function with proper indentation
|
|
return regexp.MustCompile(fmt.Sprintf(`(?m)^[^\n;{}]*\s+%s\s*\([^)]*\)[^{]*{[\s\S]*?^}`, funcName))
|
|
case ".rb":
|
|
// Ruby pattern - anchored to the def keyword
|
|
return regexp.MustCompile(fmt.Sprintf(`(?m)^def\s+%s(?:\s*\([^)]*\)|\s+[^(\n]*|\s*)\n[\s\S]*?^end`, funcName))
|
|
case ".php":
|
|
// PHP pattern - anchored to the function keyword
|
|
return regexp.MustCompile(fmt.Sprintf(`(?m)^function\s+%s\s*\([^)]*\)[^{]*{[\s\S]*?^}`, funcName))
|
|
default:
|
|
// Generic pattern - anchored to function name with word boundary
|
|
return regexp.MustCompile(fmt.Sprintf(`(?m)^\b%s\b\s*\([^)]*\)[^{]*{[\s\S]*?^}`, funcName))
|
|
}
|
|
}
|
|
|
|
type GitFunctionChange struct {
|
|
Commit *download.Commit
|
|
Content string
|
|
}
|
|
|
|
// GetFunctionChanges returns a history of changes to a specific function in a file
|
|
func GetFunctionChanges(repo *git.Repository, funcName, filePath string) ([]GitFunctionChange, error) {
|
|
var changes []GitFunctionChange
|
|
|
|
// Get commit history
|
|
iter, err := repo.Log(&git.LogOptions{})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get commit history: %v", err)
|
|
}
|
|
defer iter.Close()
|
|
|
|
var commits []*object.Commit
|
|
if err := iter.ForEach(func(c *object.Commit) error {
|
|
commits = append(commits, c)
|
|
return nil
|
|
}); err != nil {
|
|
log.Debugf("Some commits could not be processed: %v", err)
|
|
if len(commits) == 0 {
|
|
return nil, fmt.Errorf("failed to iterate commits: %v", err)
|
|
}
|
|
// Continue with the commits we were able to get
|
|
}
|
|
|
|
if len(commits) == 0 {
|
|
return nil, fmt.Errorf("no commits found in repository")
|
|
}
|
|
|
|
// Create pattern based on file type
|
|
functionRegex := GetFunctionRegex(funcName, filePath)
|
|
if functionRegex == nil {
|
|
return nil, fmt.Errorf("could not generate function regex for file type: %s", filepath.Ext(filePath))
|
|
}
|
|
|
|
// Collect all function versions
|
|
type funcVersion struct {
|
|
commit *object.Commit
|
|
content string
|
|
}
|
|
var funcVersions []funcVersion
|
|
|
|
for _, commit := range commits {
|
|
// Get file content at this commit
|
|
file, err := commit.File(filePath)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
content, err := file.Contents()
|
|
if err != nil {
|
|
log.Warnf("Failed to get contents of %s at commit %s: %v", filePath, commit.Hash, err)
|
|
continue
|
|
}
|
|
|
|
// Extract the function content
|
|
matches := functionRegex.FindStringSubmatch(content)
|
|
var currentContent string
|
|
if len(matches) > 0 {
|
|
currentContent = matches[0]
|
|
} else {
|
|
currentContent = ""
|
|
}
|
|
|
|
// Add to our versions list (even if empty, to track deletions)
|
|
funcVersions = append(funcVersions, funcVersion{
|
|
commit: commit,
|
|
content: currentContent,
|
|
})
|
|
}
|
|
|
|
if len(funcVersions) == 0 {
|
|
_, err = commits[0].File(filePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("file '%s' not found in commit history", filePath)
|
|
}
|
|
return nil, fmt.Errorf("function '%s' not found in file '%s' across history", funcName, filePath)
|
|
}
|
|
|
|
// Build the changes list, comparing adjacent versions
|
|
var functionFoundInHistory bool
|
|
for i := range funcVersions {
|
|
current := funcVersions[i]
|
|
previousContent := ""
|
|
if i+1 < len(funcVersions) {
|
|
previousContent = funcVersions[i+1].content
|
|
}
|
|
|
|
if current.content != "" {
|
|
functionFoundInHistory = true
|
|
}
|
|
|
|
isChange := i < len(funcVersions)-1 && previousContent != current.content && current.content != ""
|
|
|
|
if isChange {
|
|
headline := strings.SplitN(current.commit.Message, "\n", 2)[0]
|
|
body := ""
|
|
if strings.Contains(current.commit.Message, "\n") {
|
|
body = strings.SplitN(current.commit.Message, "\n", 2)[1]
|
|
}
|
|
commitInfo := &download.Commit{
|
|
OID: githubv4.GitObjectID(current.commit.Hash.String()),
|
|
Author: struct {
|
|
Name githubv4.String `json:"name,omitempty"`
|
|
Email githubv4.String `json:"email,omitempty"`
|
|
Date githubv4.GitTimestamp `json:"date"`
|
|
}{
|
|
Name: githubv4.String(current.commit.Author.Name),
|
|
Email: githubv4.String(current.commit.Author.Email),
|
|
Date: githubv4.GitTimestamp{Time: current.commit.Author.When},
|
|
},
|
|
Message: githubv4.String(current.commit.Message),
|
|
MsgHeadline: githubv4.String(headline),
|
|
MsgBody: githubv4.String(body),
|
|
}
|
|
|
|
changes = append(changes, GitFunctionChange{
|
|
Commit: commitInfo,
|
|
Content: current.content,
|
|
})
|
|
}
|
|
}
|
|
|
|
if !functionFoundInHistory && len(changes) == 0 {
|
|
return nil, fmt.Errorf("function '%s' not found in file '%s' across history", funcName, filePath)
|
|
}
|
|
|
|
return changes, nil
|
|
}
|