xruben136x / SZZ_unisannio

Implementation of SZZ Algorithm
Mozilla Public License 2.0
4 stars 0 forks source link

Issue Tracker Version #13

Closed nik200122 closed 10 months ago

nik200122 commented 10 months ago

This issue has been opened to develop a second version of the szz algorithm, using the issue tracker.

OMANDA:

import git from github import Github, GithubException, UnknownObjectException import re import subprocess

git diff between two commit

def get_diff(repo_path, commit_A, commit_B): repo = git.Repo(repo_path) diff = repo.git.diff(commit_A, commit_B, '-U0', '--histogram') return diff

get issue's status

def get_issue_status(repo, issue_number): try: issue = repo.get_issue(number=issue_number) return issue.state except UnknownObjectException: print(f"Issue #{issue_number} not found.") return None

get issue's number

def get_issue_numbers(commit_message): pattern = re.compile(r'#(\d+)') return [int(match.group(1)) for match in pattern.finditer(commit_message)]

retrieve of all bug fix's commits

def get_commits_fixing_issues(repo, bug_fix_commit): fixing_commits = {}

for issue_number in get_issue_numbers(bug_fix_commit.message):
    issue_status = get_issue_status(repo, issue_number)

    if issue_status == 'closed':
        issue = repo.get_issue(number=issue_number)

        # Aggiungi la verifica per evitare errori 
        try:
            comments = issue.get_comments()
            commit_sha_list = re.findall(r'([a-f0-9]{40})', " ".join(comment.body for comment in comments))
        except GithubException as e:
            print(f"Error getting comments for issue #{issue_number}: {e}")
            continue

        fixing_commits[issue_number] = []

        # Aggiungi la verifica per evitare errori 
        for commit_sha in commit_sha_list:
            try:
                commit = repo.get_commit(commit_sha)
                fixing_commits[issue_number].append((commit_sha, commit.author))
            except GithubException as e:
                print(f"Error getting commit information for SHA {commit_sha}: {e}")

return fixing_commits

get remote repository

def get_github_repo(repository_path): repo = git.Repo(repository_path) remote_url = repo.remotes.origin.url owner, repo_name = re.search(r'://(\w+)(?:.git)?$', remote_url).groups()

return owner, repo_name

get the dictionary where the key is the file path and the value is a list of numbers of the changed lines

def generate_changes_dict(diff_output): file_path_pattern = re.compile(r'^+++ b/(.*)$') line_number_pattern = re.compile(r'^@@ -(\d+)(,(\d+))? +(\d+)(,(\d+))? @@')

result_dict = {}
current_file_path = None
numbers_list = []

diff_lines = diff_output.split('\n')

for line in diff_lines:
    file_path_match = file_path_pattern.match(line)
    line_number_match = line_number_pattern.match(line)

    if file_path_match:
        if current_file_path and numbers_list:
            result_dict[current_file_path] = numbers_list
            numbers_list = []

        current_file_path = file_path_match.group(1)
    elif line_number_match:
        start_line = int(line_number_match.group(1))
        num_lines = 1 if line_number_match.group(3) is None else int(line_number_match.group(3))

        # Aggiungi le linee modificate solo se non sono commenti
        if not match_comment(line):
            numbers_list.extend(range(start_line, start_line + num_lines))

if current_file_path and numbers_list:
    result_dict[current_file_path] = numbers_list

return result_dict

def match_comment(line): comment_pattern = re.compile(r'^\s(#|//|<!--|/*)|(?:.?--!>|.?*/)\s$')

return comment_pattern.match(line[1:])  # Ignora il primo carattere perchè le linee iniziano per '-'

def get_candidate_commits(blame_result, file_path, changes_dict):

Definisci il pattern delle espressioni regolari

pattern = re.compile(r'([a-f0-9]+)\s+(\d+)\s+(\d+)?(?:\s+(\d+))?\nauthor\s+([^\n]+)')

# Inizializza il set di commit
commit_set = set()

# Trova tutte le corrispondenze nel testo di output
matches = pattern.findall(blame_result)

# Estrai le informazioni desiderate
for match in matches:
    commit_hash, first_number, second_number, third_number, author = match

    # Controlla se il secondo numero è nella lista associata al percorso del file
    if int(second_number) in changes_dict.get(file_path, []):
        # Aggiungi le informazioni richieste al set
        commit_set.add((commit_hash, author))

# Restituisci il set di commit
return commit_set

def get_all_candidate_commits(repo, parent_commit, changes_dict): all_candidate_commits = set()

for file_path, line_numbers in changes_dict.items():
    blame_result = repo.git.blame(parent_commit, file_path, "--line-porcelain")
    candidate_commits = get_candidate_commits(blame_result, file_path, changes_dict)
    all_candidate_commits = all_candidate_commits.union(candidate_commits)

return all_candidate_commits

retrieve commit from the repo

repository_url = r'C:\Users\Navigator\Desktop\IS\tensorflow' github_token = "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" repo = git.Repo(repository_url)

Ottieni il proprietario e il nome del repository da un percorso locale

owner, repo_name = get_github_repo(repository_url)

Inizializza l'oggetto Github con il token di accesso

github_repo = Github(github_token).get_repo(f"{owner}/{repo_name}")

retrieve bug fix commit

bug_fix_commits = []

for issue in github_repo.get_issues(state='closed'): print(f"Issue #{issue.number}")

# Ottieni la data di apertura dell'issue
issue_opened_at = issue.created_at
print(f"Issue opened at: {issue_opened_at}")

# Ottieni i commit associati all'issue, limitati a quelli effettuati prima della data di apertura dell'issue
commits = github_repo.get_commits(since=issue_opened_at, until=issue.closed_at)

# Itera su ciascun commit associato all'issue
for commit in commits:
    commit_date = commit.commit.author.date

    # Stampa solo i commit effettuati prima della data di apertura dell'issue
    if commit_date < issue_opened_at:
        print(f"\nCommit: {commit.sha}")
        parent_commit = commit.parents[0]
        diff = get_diff(repository_url, commit.commit, parent_commit)
        changes_dic = generate_changes_dict(diff)
        print(changes_dic)

        all_candidate_commits = get_all_candidate_commits(repo, parent_commit, changes_dic)

        # print commit fix and candidate commits
        print(bug_fix_commits)
        print("Candidate commits: ")
        print(all_candidate_commits)

Ho questo particolare programma che mi permette di implementare l'algoritmo SZZ su una serie di commit identificati in un issue tracker, analizzato tramite API di GitHub. Sapresti darmi un'idea su come implementare un programma analogo, estraendo dunque i commit dall'issue tracker, non tramite API di GitHub ma bensì immettendo un file .json, rappresentativo delle informazioni dell'issue tracker, fornito dall'utente?

RISPOSTA: Certamente! Se desideri implementare un programma simile per estrarre i commit da un file JSON invece di utilizzare le API di GitHub, puoi seguire un'approccio simile a quello che hai già implementato, ma dovrai leggere le informazioni direttamente dal file JSON fornito dall'utente.

Ecco un esempio di come potresti iniziare ad adattare il tuo programma:

Lettura del file JSON: Invece di utilizzare le API di GitHub per ottenere la lista delle issues e altri dettagli, dovrai leggere il contenuto dal file JSON fornito dall'utente. Supponiamo che il file JSON abbia una struttura simile a questa:

json Copy code [ { "issue_number": 1, "created_at": "2023-01-01T12:00:00Z", "closed_at": "2023-01-10T12:00:00Z" }, { "issue_number": 2, "created_at": "2023-02-01T12:00:00Z", "closed_at": "2023-02-15T12:00:00Z" }, // ... altri oggetti issue ] Puoi leggere questo file JSON nel tuo programma Python usando il modulo json:

python Copy code import json

with open('path/to/your/issues.json') as json_file: issues_data = json.load(json_file) Iterazione sulle issues e sui commit associati: Dopo aver letto il file JSON, puoi iterare sulle issues e recuperare i commit associati a ciascuna issue. Puoi mantenere la struttura del codice che hai già implementato, ma sostituisci le chiamate alle API di GitHub con la lettura delle informazioni dalla struttura dati del tuo file JSON.

python Copy code for issue_data in issues_data: issue_number = issue_data['issue_number'] issue_opened_at = issue_data['created_at'] issue_closed_at = issue_data['closed_at']

# Ottenere i commit associati all'issue, limitati a quelli effettuati prima della data di apertura dell'issue
# ... continua con il resto del tuo codice ...

Altro: Puoi mantenere il resto del tuo codice senza dover apportare molte modifiche. Tuttavia, dovrai assicurarti che le informazioni lette dal file JSON corrispondano alle informazioni che il tuo codice si aspetta.

Ricorda di gestire eventuali errori durante la lettura del file JSON e di garantire che la struttura del file sia conforme alle aspettative del tuo programma. Inoltre, tieni presente che il formato del file JSON e la struttura delle informazioni delle issues possono variare in base alle esigenze specifiche del tuo progetto.

DOMANDA: Come Integro al codice:

RISPOSTA: Certamente, posso fornirti un esempio semplificato di come potresti adattare il tuo programma per leggere le informazioni da un file JSON. Assumerò una struttura di file JSON simile a quella menzionata precedentemente:

python Copy code import git import re import json from github import GithubException, UnknownObjectException

Funzione per ottenere il diff tra due commit

def get_diff(repo_path, commit_A, commit_B): repo = git.Repo(repo_path) diff = repo.git.diff(commit_A, commit_B, '-U0', '--histogram') return diff

Funzione per ottenere lo stato di un'issue

def get_issue_status(issues_data, issue_number): for issue in issues_data: if issue['issue_number'] == issue_number: return 'closed' if 'closed_at' in issue else 'open' return None

Funzione per ottenere i numeri delle issue da un messaggio di commit

def get_issue_numbers(commit_message): pattern = re.compile(r'#(\d+)') return [int(match.group(1)) for match in pattern.finditer(commit_message)]

Funzione per ottenere i commit che risolvono le issue

def get_commits_fixing_issues(issues_data, bug_fix_commit): fixing_commits = {}

for issue_number in get_issue_numbers(bug_fix_commit.message):
    issue_status = get_issue_status(issues_data, issue_number)

    if issue_status == 'closed':
        # Altre operazioni come ottenere i commenti, ecc.
        fixing_commits[issue_number] = []  # Aggiungi le informazioni desiderate

return fixing_commits

Funzione per ottenere i cambiamenti in un commit

def generate_changes_dict(diff_output):

... (la tua implementazione rimane invariata) ...

pass

Funzione per estrarre i commit candidati

def get_all_candidate_commits(issues_data, parent_commit, changes_dict): all_candidate_commits = set()

for issue_data in issues_data:
    for file_path, line_numbers in changes_dict.items():
        # Simula il risultato del blame con un set fittizio di commit
        # Questo è solo un esempio, dovrai adattarlo alla tua logica specifica
        commit_set = {("hash1", "author1"), ("hash2", "author2")}
        all_candidate_commits = all_candidate_commits.union(commit_set)

return all_candidate_commits

Carica le informazioni sulle issue dal file JSON

with open('path/to/your/issues.json') as json_file: issues_data = json.load(json_file)

Supponi che tu abbia già il bug_fix_commit da qualche parte

bug_fix_commit = ... # Sostituisci con il tuo modo di ottenere il commit di fix

... (il resto del tuo codice rimane invariato) ...

Aggiorna le chiamate alle funzioni per passare le issues_data invece di utilizzare le API di GitHub

fixing_commits = get_commits_fixing_issues(issues_data, bug_fix_commit)

Itera sulle fixing_commits e gestisci i commit candidati

for issue_number, commits_info in fixing_commits.items(): for commit_info in commits_info: commit_sha, commit_author = commit_info

... (il resto del tuo codice rimane invariato) ...

    # Aggiorna le chiamate alle funzioni per passare le issues_data invece di utilizzare le API di GitHub
    all_candidate_commits = get_all_candidate_commits(issues_data, parent_commit, changes_dic)
    print(f"Bug Fix Commit: {bug_fix_commit}")
    print(f"Candidate Commits: {all_candidate_commits}")

Questo esempio fornisce solo una struttura di base per l'adattamento del tuo programma all'uso di un file JSON invece delle API di GitHub. Assicurati di personalizzare il codice in base alle tue specifiche esigenze e alla struttura effettiva del file JSON che intendi utilizzare.

Domanda: come prendo la data del commit dall'oggetto commit in python? Risposta: Per ottenere la data del commit da un oggetto commit in Python utilizzando la libreria GitPython, puoi accedere all'attributo committed_date dell'oggetto commit. Ecco un esempio: import git

Sostituisci 'path_to_repo' con il percorso al tuo repository

path_to_repo = '/percorso/al/tuo/repository'

repo = git.Repo(path_to_repo)

Itera tra tutti i commit nel repository

for commit in repo.iter_commits():

Ottieni la data del commit come timestamp

commit_date_timestamp = commit.committed_date

# Converti il timestamp in un formato leggibile, ad esempio ISO 8601
commit_date = commit.authored_datetime.isoformat()

print(f"Commit Hash: {commit.hexsha}")
print(f"Commit Date: {commit_date}")
print("Message:", commit.message)
print()

In questo esempio, commit.committed_date restituisce la data del commit come timestamp. Puoi utilizzare commit.authored_datetime per ottenere la data di autore anziché la data di commit se necessario.

Assicurati di installare la libreria GitPython nel tuo ambiente Python se non l'hai già fatto, puoi farlo tramite:

bash Copy code pip install gitpython Adatta il codice in base alle tue esigenze specifiche.

Domanda: Come mai nel codice che mi hai fornito utilizzi due funzioni diverse per estrarre la data? C'è qualche differenza tra le due? Se sì spiegami dettagliatamente la differenza Risposta: Mi scuso per la confusione. La mia risposta potrebbe aver generato ambiguità. La scelta tra committed_date e authored_datetime dipende dalle tue esigenze specifiche riguardo alla data del commit.

committed_date: Restituisce la data in cui il commit è stato effettivamente registrato nel repository (data di commit). authored_datetime: Restituisce la data in cui il commit è stato inizialmente creato (data di autore). A seconda del contesto del tuo progetto e di cosa intendi con "data del commit", puoi scegliere tra questi due attributi. Nel mio esempio, ho fornito entrambe le opzioni per illustrare come accedere a entrambe le informazioni. Puoi eliminare quella che non è necessaria per il tuo caso d'uso. Se desideri solo la data di commit, puoi usare committed_date:

commit_date_timestamp = commit.committed_date commit_date = commit.authored_datetime.isoformat() Se desideri la data di autore, puoi usare authored_datetime:

commit_date_timestamp = commit.authored_date commit_date = commit.authored_datetime.isoformat() Scegli l'opzione che meglio si adatta alle tue esigenze specifiche per la gestione delle date del commit nel tuo progetto.

Domanda: Ho estratto questa data da un file JSON:2020-12-04T09:59:39Z. Vorrei convertirla in timestamp, come faccio? Risposta: Puoi convertire una data nel formato specificato (ISO 8601 con il fuso orario "Z" per UTC) in un timestamp Unix utilizzando il modulo datetime di Python. Ecco come farlo:

from datetime import datetime

data_iso8601 = "2020-12-04T09:59:39Z"

Converti la stringa ISO 8601 in un oggetto datetime

data_datetime = datetime.fromisoformat(data_iso8601.replace('Z', '+00:00'))

Estrai il timestamp Unix

timestamp_unix = int(data_datetime.timestamp())

print("Data in formato ISO 8601:", data_iso8601) print("Timestamp Unix:", timestamp_unix) In questo esempio:

fromisoformat converte la stringa ISO 8601 in un oggetto datetime. Il metodo timestamp() restituisce il timestamp Unix come un numero intero. Assicurati di gestire correttamente il fuso orario, poiché la "Z" nella tua data indica il fuso orario UTC. Nel caso in cui le date possano essere in diversi fusi orari, puoi utilizzare la libreria pytz per gestire in modo più robusto i fusi orari.

from datetime import datetime import pytz

data_iso8601 = "2020-12-04T09:59:39Z"

Converti la stringa ISO 8601 in un oggetto datetime con fuso orario UTC

data_datetime = datetime.fromisoformat(data_iso8601.replace('Z', '+00:00')).replace(tzinfo=pytz.UTC)

Estrai il timestamp Unix

timestamp_unix = int(data_datetime.timestamp())

print("Data in formato ISO 8601:", data_iso8601) print("Timestamp Unix:", timestamp_unix) In questo secondo esempio, la libreria pytz è utilizzata per gestire il fuso orario in modo più esplicito.