From 6c420c37852ce97fe2edea690e743427f1733850 Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Mon, 21 Jul 2025 14:49:14 +0200 Subject: [PATCH 01/26] Add functionality to fetch external commits external commits that reference an issue can now be fetched. They will be marked with 'commitReferencesIssueExternal'. To that end, this commit adds an 'external' field to each commit and a new method to extract commits using the full url instead of just the relative one within the repo. Signed-off-by: Leo Sendelbach --- .../fim/gitwrapper/EventDataProcessor.java | 8 +++-- .../fim/gitwrapper/GitHubCommit.java | 9 +++++ .../fim/gitwrapper/GitHubRepository.java | 36 ++++++++++++++++++- .../fim/gitwrapper/IssueDataProcessor.java | 6 +++- 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java index 40d49a0..f52adf6 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java @@ -86,8 +86,12 @@ public void postDeserialize(EventData.ReferencedEventData result, JsonElement sr } result.commit = repo.getGithubCommit(hash.getAsString()).orElseGet(() -> { - LOG.warning("Found commit unknown to GitHub and local git repo: " + hash); - return null; + LOG.warning("Found commit unknown to GitHub and local git repo: " + hash + " Retry using url..."); + JsonElement url = src.getAsJsonObject().get("commit_url"); + return repo.getGithubCommitUrl(hash.getAsString(), url.getAsString()).orElseGet(() -> { + LOG.warning("Could not find commit: " + hash); + return null; + }); }); } diff --git a/src/de/uni_passau/fim/gitwrapper/GitHubCommit.java b/src/de/uni_passau/fim/gitwrapper/GitHubCommit.java index 16a2429..76d0585 100644 --- a/src/de/uni_passau/fim/gitwrapper/GitHubCommit.java +++ b/src/de/uni_passau/fim/gitwrapper/GitHubCommit.java @@ -26,6 +26,7 @@ public class GitHubCommit extends Commit { private String authorUsername; private String committerUsername; private boolean addedToPullRequest = false; + private boolean isExternal = false; /** * Constructs a new {@link GitHubCommit} with the given id made in the repo. @@ -119,4 +120,12 @@ public boolean isAddedToPullRequest() { void setAddedToPullRequest(boolean added) { this.addedToPullRequest = added; } + + void setExternal(boolean external) { + this.isExternal = true; + } + + boolean getExternal() { + return this.isExternal; + } } diff --git a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java index 335f6cf..fa8f341 100644 --- a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java +++ b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java @@ -352,6 +352,8 @@ public Optional> getIssues(boolean includePullRequests, OffsetDa } else timeLimit = ""; Type finalType = type; + // For debugging, you may add additional parameters to the string. For example, '/issues?creator=sleo&state=all' + // will fetch issues created by user 'sleo' and all related issues and commits. getJSONStringFromPath("/issues?state=all" + timeLimit).map(json -> { List data; try { @@ -367,7 +369,7 @@ public Optional> getIssues(boolean includePullRequests, OffsetDa threadPool.submit(() -> data.parallelStream().forEach(IssueData::freeze)); } catch (JsonSyntaxException e) { - LOG.warning("Encountered invalid JSON: " + json); + LOG.warning("Encountered invalid JSON: " + json + "\n\n" + e.getMessage() + "\n\n" + e); return null; } return data; @@ -1028,6 +1030,38 @@ Optional getGithubCommit(String hash) { }); } + Optional getGithubCommitUrl(String hash, String url) { + if (offline.get()) { + return Optional.of(getGHCommitUnchecked(DummyCommit.DUMMY_COMMIT_ID)); + } else { + try { + Optional res = getJSONStringFromURL(url).map(commitInfo -> + gson.fromJson(commitInfo, new TypeToken() {}.getType())); + checkedHashes.put(hash, res); + if (res.isPresent()) { + res.get().setExternal(true); + } + return res; + } catch (JsonSyntaxException e) { + /* For whatever reason, the JSON String is malformed, perhaps due to ill-encoded characters + * in patches within the files element of the JSON String. + * Due to that, get the JSON String again and remove the content of the files element of the + * JSON String, as it is not needed for further processing. + */ + LOG.info("Malformed JSON String when querying data for commit " + url + ". Neglect files element."); + String jsonStringFromURL = getJSONStringFromURL(url).get(); + jsonStringFromURL = StringUtils.substringBefore(jsonStringFromURL, "\"files\":["); + jsonStringFromURL = jsonStringFromURL + "\"files\":[]}"; + Optional res = Optional.of(gson.fromJson(jsonStringFromURL, new TypeToken() {}.getType())); + checkedHashes.put(hash, res); + if (res.isPresent()) { + res.get().setExternal(true); + } + return res; + } + } + } + /** * Creates a new Commit with the given data, and tries to fill in the missing data from the local Repository * diff --git a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java index b9ced2f..9306895 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java @@ -76,7 +76,11 @@ private List> parseCommits(IssueData issue) { .filter(eventData -> eventData instanceof EventData.ReferencedEventData) // filter out errors from referencing commits .filter(eventData -> ((EventData.ReferencedEventData) eventData).commit != null) - .map(eventData -> new ReferencedLink<>(Collections.singletonList(((EventData.ReferencedEventData) eventData).commit.getId()), eventData.user, eventData.created_at, "commitReferencesIssue")); + .map(eventData -> {if (((GitHubCommit) ((EventData.ReferencedEventData) eventData).commit).getExternal()) + { return new ReferencedLink<>(Collections.singletonList(((EventData.ReferencedEventData) eventData).commit.getId()), eventData.user, eventData.created_at, "commitReferencesIssueExternal") ; + } else { + return new ReferencedLink<>(Collections.singletonList(((EventData.ReferencedEventData) eventData).commit.getId()), eventData.user, eventData.created_at, "commitReferencesIssue"); + }}); // Parse commits from reviews and reviews' comments if (issue.isPullRequest()) { From f198a6e5f5bfe534a750d2201a14b76fd9ebb6d3 Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Mon, 21 Jul 2025 14:52:02 +0200 Subject: [PATCH 02/26] Change 'extractHashtags' to ignore codeblocks references to issues are now only found if they are ouside a code environment (starting and ending with three '`'). This mirrors GitHubs behaviour. Signed-off-by: Leo Sendelbach --- .../uni_passau/fim/gitwrapper/IssueDataProcessor.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java index 9306895..4c3e00e 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java @@ -266,6 +266,15 @@ private List extractHashtags(String text, boolean onlyInSameRepo) { } Pattern hashtagPattern; + // filter out everything in code block + String[] texts = text.split("```"); + text = ""; + for (int i = 0; i < texts.length; i++) { + if (i % 2 == 0) { + text = text + texts[i]; + } + } + if (onlyInSameRepo) { String repoName = repo.getRepoName(); String repoUser = repo.getRepoUser(); @@ -383,6 +392,7 @@ public void postDeserialize(IssueData result, JsonElement src, Gson gson) { Optional>> comments = repo.getComments(lookup); result.setComments(comments.orElse(Collections.emptyList())); } + if (result.getEventsList() == null) { Optional> events = repo.getEvents(lookup); result.setEvents(events.orElse(Collections.emptyList())); From e537267ea4ec29bb6f55be486d347fd7392b767c Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Mon, 21 Jul 2025 14:55:10 +0200 Subject: [PATCH 03/26] Document findings in 'README.md' Add paragraph that documents intended and unintended behaviour for 'referenced' events Signed-off-by: Leo Sendelbach --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 1a7e487..43efc25 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,16 @@ java -Xmx100G -jar "build/libs/GitHubWrapper-1.0-SNAPSHOT.jar" \ - Using the `-repo` parameter, you specify the file path of the repo you want to analyze. Notice that you need to have cloned the repo locally, such that the origin can be derived from this file path. - Using the `-workDir` parameter, you specify the working directory, which usually is the directory which contains the repository directory specified at `-repo`. +### `Referenced` events + +`Referenced` events are events generated in an issue if a commit references that issue in its commit message. The intended behavior is, that the event is present in the issue's event data, and the commit is again present in the related commits of the issue. This does not work if it is not possible to fetch that commit. In this case, the event still exists, but it contains a link to a commit that the api cannot resolve, meaning that no data about the commit can be accessed. This may lead to incorrect data points if the resulting data is automatically processed, for example using the tool `codeface-extraction`. Known causes of this include: + +- a commit was rebased and changed/removed +- an external repository was deleted +- the commit's branch was deleted + +Note that the commit might still be reachable until the automatic garbage collection has removed it from the remote repository. + ### Integration into other projects There is also an option to use the implementation of GitHubWrapper in your code without using the provided `IssueRunner`. From 2f33e8bb4334663ede55c1ef0b35913d7a44973d Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Sat, 9 Aug 2025 13:45:13 +0200 Subject: [PATCH 04/26] Add Section to README.md reformat README and nested if statement Signed-off-by: Leo Sendelbach --- README.md | 26 ++++++++++++------- .../fim/gitwrapper/IssueDataProcessor.java | 12 +++++---- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 43efc25..c326dd0 100644 --- a/README.md +++ b/README.md @@ -43,16 +43,6 @@ java -Xmx100G -jar "build/libs/GitHubWrapper-1.0-SNAPSHOT.jar" \ - Using the `-repo` parameter, you specify the file path of the repo you want to analyze. Notice that you need to have cloned the repo locally, such that the origin can be derived from this file path. - Using the `-workDir` parameter, you specify the working directory, which usually is the directory which contains the repository directory specified at `-repo`. -### `Referenced` events - -`Referenced` events are events generated in an issue if a commit references that issue in its commit message. The intended behavior is, that the event is present in the issue's event data, and the commit is again present in the related commits of the issue. This does not work if it is not possible to fetch that commit. In this case, the event still exists, but it contains a link to a commit that the api cannot resolve, meaning that no data about the commit can be accessed. This may lead to incorrect data points if the resulting data is automatically processed, for example using the tool `codeface-extraction`. Known causes of this include: - -- a commit was rebased and changed/removed -- an external repository was deleted -- the commit's branch was deleted - -Note that the commit might still be reachable until the automatic garbage collection has removed it from the remote repository. - ### Integration into other projects There is also an option to use the implementation of GitHubWrapper in your code without using the provided `IssueRunner`. @@ -101,3 +91,19 @@ repo.getIssues(false).ifPresent(issueData -> issueData.forEach(issue -> { System.out.println(comment.user.username + ": " + comment.body)); })); ``` + +### Further data processing + +The data extracted by this tool can be further processed, for example using the `run-issues.py` skript from the tool [`codeface-extraction`](https://github.com/se-sic/codeface-extraction). This organises and unifies the issue data into a single .list file. It also allows for synchronisation with data from other data extraction tools, such as `codeface`. + +### `Referenced` events + +`Referenced` events are events generated in an issue if a commit references that issue in its commit message. The intended behavior is that the event is present in the issue's event data, and the commit is again present in the related commits of the issue. This does not work if it is not possible to fetch that commit. In this case, the event still exists, but it contains a link to a commit that the api cannot resolve, meaning that no data about the commit can be accessed. +Known causes of this include: + +- a commit was rebased and changed/removed +- an external repository was deleted +- the commit's branch was deleted + +Note that the commit might still be reachable until the automatic garbage collection has removed it from the remote repository. +In itself, this is not problematic. However, when further processing the data using `codeface-extraction`, this may lead to these `referenced` events being present in the final data, even though they should be filtered out as part of the issue processing. diff --git a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java index 4c3e00e..cf87142 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java @@ -76,11 +76,13 @@ private List> parseCommits(IssueData issue) { .filter(eventData -> eventData instanceof EventData.ReferencedEventData) // filter out errors from referencing commits .filter(eventData -> ((EventData.ReferencedEventData) eventData).commit != null) - .map(eventData -> {if (((GitHubCommit) ((EventData.ReferencedEventData) eventData).commit).getExternal()) - { return new ReferencedLink<>(Collections.singletonList(((EventData.ReferencedEventData) eventData).commit.getId()), eventData.user, eventData.created_at, "commitReferencesIssueExternal") ; - } else { - return new ReferencedLink<>(Collections.singletonList(((EventData.ReferencedEventData) eventData).commit.getId()), eventData.user, eventData.created_at, "commitReferencesIssue"); - }}); + .map(eventData -> { + if (((GitHubCommit) ((EventData.ReferencedEventData) eventData).commit).getExternal()) { + return new ReferencedLink<>(Collections.singletonList(((EventData.ReferencedEventData) eventData).commit.getId()), eventData.user, eventData.created_at, "commitReferencesIssueExternal"); + } else { + return new ReferencedLink<>(Collections.singletonList(((EventData.ReferencedEventData) eventData).commit.getId()), eventData.user, eventData.created_at, "commitReferencesIssue"); + } + }); // Parse commits from reviews and reviews' comments if (issue.isPullRequest()) { From 443dcdc049df77a9a3c1bc32f219057224e862b5 Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Tue, 12 Aug 2025 14:41:13 +0200 Subject: [PATCH 05/26] Fix spelling in 'README.md' change BE words for consistency Signed-off-by: Leo Sendelbach --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c326dd0..1a467d1 100644 --- a/README.md +++ b/README.md @@ -94,11 +94,11 @@ repo.getIssues(false).ifPresent(issueData -> issueData.forEach(issue -> { ### Further data processing -The data extracted by this tool can be further processed, for example using the `run-issues.py` skript from the tool [`codeface-extraction`](https://github.com/se-sic/codeface-extraction). This organises and unifies the issue data into a single .list file. It also allows for synchronisation with data from other data extraction tools, such as `codeface`. +The data extracted by this tool can be further processed, for example using the `run-issues.py` script from the tool [`codeface-extraction`](https://github.com/se-sic/codeface-extraction). This organizes and unifies the issue data into a single csv-like .list file. It also allows for synchronization with data from other data extraction tools, such as `codeface`. -### `Referenced` events +### `referenced` events -`Referenced` events are events generated in an issue if a commit references that issue in its commit message. The intended behavior is that the event is present in the issue's event data, and the commit is again present in the related commits of the issue. This does not work if it is not possible to fetch that commit. In this case, the event still exists, but it contains a link to a commit that the api cannot resolve, meaning that no data about the commit can be accessed. +`referenced` events are events generated in an issue if a commit references that issue in its commit message. The intended behavior is that the event is present in the issue's event data, and the commit is again present in the related commits of the issue. This does not work if it is not possible to fetch that commit. In this case, the event still exists, but it contains a link to a commit that the api cannot resolve, meaning that no data about the commit can be accessed. Known causes of this include: - a commit was rebased and changed/removed From 038bbf7182bcab06137b26ae513e11cdfe236768 Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Tue, 26 Aug 2025 11:04:03 +0200 Subject: [PATCH 06/26] Update Copyright headers Signed-off-by: Leo Sendelbach --- src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java | 1 + src/de/uni_passau/fim/gitwrapper/GitHubCommit.java | 1 + src/de/uni_passau/fim/gitwrapper/GitHubRepository.java | 1 + src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java | 1 + 4 files changed, 4 insertions(+) diff --git a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java index f52adf6..bc4b878 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java @@ -1,6 +1,7 @@ /** * Copyright (C) 2016-2018 Florian Heck * Copyright (C) 2019 Thomas Bock + * Copyright (C) 2025 Leo Sendelbach * * This file is part of GitHubWrapper. * diff --git a/src/de/uni_passau/fim/gitwrapper/GitHubCommit.java b/src/de/uni_passau/fim/gitwrapper/GitHubCommit.java index 76d0585..1897aa2 100644 --- a/src/de/uni_passau/fim/gitwrapper/GitHubCommit.java +++ b/src/de/uni_passau/fim/gitwrapper/GitHubCommit.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2019 Thomas Bock + * Copyright (C) 2025 Leo Sendelbach * * This file is part of GitHubWrapper. * diff --git a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java index fa8f341..30c0f72 100644 --- a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java +++ b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java @@ -2,6 +2,7 @@ * Copyright (C) 2016-2020 Florian Heck * Copyright (C) 2018 Claus Hunsen * Copyright (C) 2019-2021 Thomas Bock + * Copyright (C) 2025 Leo Sendelbach * * This file is part of GitHubWrapper. * diff --git a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java index cf87142..e615db1 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java @@ -1,6 +1,7 @@ /** * Copyright (C) 2016-2018 Florian Heck * Copyright (C) 2019-2020 Thomas Bock + * Copyright (C) 2025 Leo Sendelbach * * This file is part of GitHubWrapper. * From 55828ff79ee5b7b2e9d0c62b8858771a81abedd9 Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Tue, 28 Oct 2025 13:50:34 +0100 Subject: [PATCH 07/26] Rename field and setter for external commits Getter previously did not use method parameter. Also renamed and added docs to match existing methods. Signed-off-by: Leo Sendelbach --- .../fim/gitwrapper/GitHubCommit.java | 21 ++++++++++++++----- .../fim/gitwrapper/IssueDataProcessor.java | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/de/uni_passau/fim/gitwrapper/GitHubCommit.java b/src/de/uni_passau/fim/gitwrapper/GitHubCommit.java index 1897aa2..29f8501 100644 --- a/src/de/uni_passau/fim/gitwrapper/GitHubCommit.java +++ b/src/de/uni_passau/fim/gitwrapper/GitHubCommit.java @@ -27,7 +27,7 @@ public class GitHubCommit extends Commit { private String authorUsername; private String committerUsername; private boolean addedToPullRequest = false; - private boolean isExternal = false; + private boolean external = false; /** * Constructs a new {@link GitHubCommit} with the given id made in the repo. @@ -122,11 +122,22 @@ void setAddedToPullRequest(boolean added) { this.addedToPullRequest = added; } - void setExternal(boolean external) { - this.isExternal = true; + /** + * Returns whether this commit is an external commit. + * + * @return whether this commit is an external commit + */ + boolean isExternal() { + return this.external; } - boolean getExternal() { - return this.isExternal; + /** + * Sets whether this commit is an external commit + * + * @param external this commit is an external commit + */ + void setExternal(boolean external) { + this.external = external; } + } diff --git a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java index e615db1..1575c0a 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java @@ -78,7 +78,7 @@ private List> parseCommits(IssueData issue) { // filter out errors from referencing commits .filter(eventData -> ((EventData.ReferencedEventData) eventData).commit != null) .map(eventData -> { - if (((GitHubCommit) ((EventData.ReferencedEventData) eventData).commit).getExternal()) { + if (((GitHubCommit) ((EventData.ReferencedEventData) eventData).commit).isExternal()) { return new ReferencedLink<>(Collections.singletonList(((EventData.ReferencedEventData) eventData).commit.getId()), eventData.user, eventData.created_at, "commitReferencesIssueExternal"); } else { return new ReferencedLink<>(Collections.singletonList(((EventData.ReferencedEventData) eventData).commit.getId()), eventData.user, eventData.created_at, "commitReferencesIssue"); From de8b9b756867276d60fe24b11927927053e9bff8 Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Fri, 31 Oct 2025 15:41:49 +0100 Subject: [PATCH 08/26] Improve performance for extracting hashtags using stringbuilder instead of appending, which would result in copying the string Signed-off-by: Leo Sendelbach --- src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java index 1575c0a..dcc32e9 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java @@ -271,12 +271,13 @@ private List extractHashtags(String text, boolean onlyInSameRepo) { // filter out everything in code block String[] texts = text.split("```"); - text = ""; + StringBuilder sb = new StringBuilder(); for (int i = 0; i < texts.length; i++) { if (i % 2 == 0) { - text = text + texts[i]; + sb.append(texts[i]); } } + text = sb.toString(); if (onlyInSameRepo) { String repoName = repo.getRepoName(); From 526df21fa4806459176d257d51c02fde32ef105a Mon Sep 17 00:00:00 2001 From: shirazJafri Date: Sat, 2 Aug 2025 00:55:03 +0200 Subject: [PATCH 09/26] Add state reason and type to issues data introduce new state reason and type data classes Signed-off-by: Shiraz Jafri --- .../uni_passau/fim/gitwrapper/IssueData.java | 11 +++ .../fim/gitwrapper/IssueDataProcessor.java | 13 +++ .../fim/gitwrapper/StateReason.java | 80 +++++++++++++++++++ .../uni_passau/fim/gitwrapper/TypeData.java | 41 ++++++++++ 4 files changed, 145 insertions(+) create mode 100644 src/de/uni_passau/fim/gitwrapper/StateReason.java create mode 100644 src/de/uni_passau/fim/gitwrapper/TypeData.java diff --git a/src/de/uni_passau/fim/gitwrapper/IssueData.java b/src/de/uni_passau/fim/gitwrapper/IssueData.java index 09539c0..9674968 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueData.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueData.java @@ -39,6 +39,8 @@ public class IssueData implements GitHubRepository.IssueDataCached { UserData user; @Expose(deserialize = false) State state; + @Expose(deserialize = false) StateReason state_reason; + @Expose(deserialize = false) TypeData type; OffsetDateTime created_at; @Nullable OffsetDateTime closed_at; @@ -182,6 +184,15 @@ public State getState() { return state; } + /** + * Gets the reason for the current state of the issue. + * + * @return the state reason + */ + public StateReason getStateReason() { + return state_reason; + } + /** * Gets the date and time the issue was created. * diff --git a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java index dcc32e9..5fd2dcf 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java @@ -392,6 +392,19 @@ public void postDeserialize(IssueData result, JsonElement src, Gson gson) { // fill in missing data result.state = State.getFromString(src.getAsJsonObject().get("state").getAsString()); + JsonElement stateReasonElement = src.getAsJsonObject().get("state_reason"); + String stateReasonValue = (stateReasonElement != null && !stateReasonElement.isJsonNull()) + ? stateReasonElement.getAsString() + : null; + result.state_reason = StateReason.getFromString(stateReasonValue); + + JsonElement typeElement = src.getAsJsonObject().get("type"); + if (typeElement != null && !typeElement.isJsonNull()) { + result.type = gson.fromJson(typeElement, TypeData.class); + } else { + result.type = null; + } + if (result.getCommentsList() == null) { Optional>> comments = repo.getComments(lookup); result.setComments(comments.orElse(Collections.emptyList())); diff --git a/src/de/uni_passau/fim/gitwrapper/StateReason.java b/src/de/uni_passau/fim/gitwrapper/StateReason.java new file mode 100644 index 0000000..0564126 --- /dev/null +++ b/src/de/uni_passau/fim/gitwrapper/StateReason.java @@ -0,0 +1,80 @@ +/** + * Copyright (C) 2025 Shiraz Jafri + * + * This file is part of GitHubWrapper. + * + * GitHubWrapper is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GitHubWrapper is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GitWrapper. If not, see . + */ +package de.uni_passau.fim.gitwrapper; + +/** + * Enumeration of reasons for the state of an Issue or PullRequest. + */ +public enum StateReason { + /** + * Denotes that the Issue or PullRequest was completed. + */ + COMPLETED, + + /** + * Denotes that the Issue or PullRequest was reopened. + */ + REOPENED, + + /** + * Denotes that the Issue or PullRequest was not planned. + */ + NOT_PLANNED, + + /** + * Denotes that the Issue or PullRequest was a duplicate. + */ + DUPLICATE, + + /** + * Denotes that the state reason is not known or not specified. + * This can be used when the state reason is not applicable or not provided. + */ + NONE, + + /** + * Any state reason is included (can be used for unknown reasons). + */ + ANY; + + /** + * Gets the StateReason from a string. + * + * @param string the string to convert + * @return the corresponding StateReason, or ANY if no match is found + */ + public static StateReason getFromString(String string) { + if (string == null) { + return NONE; + } + + switch (string.toLowerCase()) { + case "completed": + return COMPLETED; + case "reopened": + return REOPENED; + case "not_planned": + return NOT_PLANNED; + case "duplicate": + return DUPLICATE; + default: + return ANY; + } + } +} diff --git a/src/de/uni_passau/fim/gitwrapper/TypeData.java b/src/de/uni_passau/fim/gitwrapper/TypeData.java new file mode 100644 index 0000000..50f621e --- /dev/null +++ b/src/de/uni_passau/fim/gitwrapper/TypeData.java @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2025 Shiraz Jafri + * + * This file is part of GitHubWrapper. + * + * GitHubWrapper is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GitHubWrapper is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GitWrapper. If not, see . + */ +package de.uni_passau.fim.gitwrapper; + +/** + * Skeleton object for data about issue types from GitHub's API. + */ +public class TypeData { + String name; + String description; + + /** + * The name of the type. + */ + public String getName() { + return name; + } + + /** + * The description of the type. + */ + public String getDescription() { + return description; + } +} From 560420fa2ebe6f33905541430d9dba56866e4325 Mon Sep 17 00:00:00 2001 From: shirazJafri Date: Sat, 2 Aug 2025 20:31:25 +0200 Subject: [PATCH 10/26] Add event tracking for state changes Add new event data subclass for state changed events Signed-off-by: Shiraz Jafri --- .../uni_passau/fim/gitwrapper/EventData.java | 15 ++++++++++++++ .../fim/gitwrapper/EventDataProcessor.java | 20 +++++++++++++++++++ .../fim/gitwrapper/GitHubRepository.java | 1 + 3 files changed, 36 insertions(+) diff --git a/src/de/uni_passau/fim/gitwrapper/EventData.java b/src/de/uni_passau/fim/gitwrapper/EventData.java index 622fe1e..1199cc6 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventData.java +++ b/src/de/uni_passau/fim/gitwrapper/EventData.java @@ -193,4 +193,19 @@ public UserData getAssigner() { return assigner; } } + + /** + * An Event generated by changing the state of an issue. + */ + public class StateChangedEventData extends EventData { + + StateReason state_reason; + + /** + * The reason for the state change. + */ + public StateReason getStateReason() { + return state_reason; + } + } } diff --git a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java index bc4b878..3e4beec 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java @@ -45,6 +45,8 @@ class EventDataProcessor implements JsonDeserializer, JsonSerializer< map.put("referenced", EventData.ReferencedEventData.class); map.put("merged", EventData.ReferencedEventData.class); map.put("closed", EventData.ReferencedEventData.class); + map.put("closed", EventData.StateChangedEventData.class); + map.put("reopened", EventData.StateChangedEventData.class); map.put("review_requested", EventData.RequestedReviewEventData.class); map.put("review_request_removed", EventData.RequestedReviewEventData.class); map.put("review_dismissed", EventData.DismissedReviewEventData.class); @@ -163,4 +165,22 @@ public void postDeserialize(EventData.AssignedEventData result, JsonElement src, @Override public void postSerialize(JsonElement result, EventData.AssignedEventData src, Gson gson) { } } + + /** + * Processor for state change events. + */ + static class StateChangedEventProcessor implements PostProcessor { + + @Override + public void postDeserialize(EventData.StateChangedEventData result, JsonElement src, Gson gson) { + JsonElement stateReasonElement = src.getAsJsonObject().get("state_reason"); + String stateReasonValue = (stateReasonElement != null && !stateReasonElement.isJsonNull()) + ? stateReasonElement.getAsString() + : null; + result.state_reason = StateReason.getFromString(stateReasonValue); + } + + @Override + public void postSerialize(JsonElement result, EventData.StateChangedEventData src, Gson gson) { } + } } diff --git a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java index 30c0f72..65333d6 100644 --- a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java +++ b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java @@ -242,6 +242,7 @@ public GitHubRepository(String url, File dir, GitWrapper git, List oauth gfb.registerPostProcessor(EventData.LabeledEventData.class, new EventDataProcessor.LabeledEventProcessor()); gfb.registerPostProcessor(EventData.DismissedReviewEventData.class, new EventDataProcessor.DismissedReviewEventProcessor()); gfb.registerPostProcessor(EventData.AssignedEventData.class, new EventDataProcessor.AssignedEventProcessor()); + gfb.registerPostProcessor(EventData.StateChangedEventData.class, new EventDataProcessor.StateChangedEventProcessor()); gfb.registerPostProcessor(ReviewData.ReviewInitialCommentData.class, new ReviewDataProcessor.ReviewInitialCommentDataProcessor(this)); GsonBuilder gb = gfb.createGsonBuilder(); gb.registerTypeAdapter(Commit.class, new CommitProcessor(this, userProcessor)); From 55b63d7753c203944ff4f34d1a04b0b0a994c961 Mon Sep 17 00:00:00 2001 From: shirazJafri Date: Sat, 2 Aug 2025 22:18:07 +0200 Subject: [PATCH 11/26] Add events tracking for issue type changed Add new enum and eventdata subclasses to track changes in issue types Signed-off-by: Shiraz Jafri --- .../uni_passau/fim/gitwrapper/EventData.java | 15 +++++ .../fim/gitwrapper/EventDataProcessor.java | 17 +++++ .../fim/gitwrapper/GitHubRepository.java | 1 + .../fim/gitwrapper/IssueTypeChangeEvent.java | 67 +++++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 src/de/uni_passau/fim/gitwrapper/IssueTypeChangeEvent.java diff --git a/src/de/uni_passau/fim/gitwrapper/EventData.java b/src/de/uni_passau/fim/gitwrapper/EventData.java index 1199cc6..969e91c 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventData.java +++ b/src/de/uni_passau/fim/gitwrapper/EventData.java @@ -208,4 +208,19 @@ public StateReason getStateReason() { return state_reason; } } + + /** + * An Event generated by changing the type of an issue. + */ + public class IssueTypeChangedEventData extends EventData { + + IssueTypeChangeEvent change_type; + + /** + * The type of change that occurred to the issue type. + */ + public IssueTypeChangeEvent getChangeType() { + return change_type; + } + } } diff --git a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java index 3e4beec..ff0fa1a 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java @@ -47,6 +47,9 @@ class EventDataProcessor implements JsonDeserializer, JsonSerializer< map.put("closed", EventData.ReferencedEventData.class); map.put("closed", EventData.StateChangedEventData.class); map.put("reopened", EventData.StateChangedEventData.class); + map.put("issue_type_added", EventData.IssueTypeChangedEventData.class); + map.put("issue_type_changed", EventData.IssueTypeChangedEventData.class); + map.put("issue_type_removed", EventData.IssueTypeChangedEventData.class); map.put("review_requested", EventData.RequestedReviewEventData.class); map.put("review_request_removed", EventData.RequestedReviewEventData.class); map.put("review_dismissed", EventData.DismissedReviewEventData.class); @@ -183,4 +186,18 @@ public void postDeserialize(EventData.StateChangedEventData result, JsonElement @Override public void postSerialize(JsonElement result, EventData.StateChangedEventData src, Gson gson) { } } + + /** + * Processor for issue type change events. + */ + static class IssueTypeChangedEventProcessor implements PostProcessor { + + @Override + public void postDeserialize(EventData.IssueTypeChangedEventData result, JsonElement src, Gson gson) { + } + + @Override + public void postSerialize(JsonElement result, EventData.IssueTypeChangedEventData src, Gson gson) { + } + } } diff --git a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java index 65333d6..ac5d731 100644 --- a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java +++ b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java @@ -243,6 +243,7 @@ public GitHubRepository(String url, File dir, GitWrapper git, List oauth gfb.registerPostProcessor(EventData.DismissedReviewEventData.class, new EventDataProcessor.DismissedReviewEventProcessor()); gfb.registerPostProcessor(EventData.AssignedEventData.class, new EventDataProcessor.AssignedEventProcessor()); gfb.registerPostProcessor(EventData.StateChangedEventData.class, new EventDataProcessor.StateChangedEventProcessor()); + gfb.registerPostProcessor(EventData.IssueTypeChangedEventData.class, new EventDataProcessor.IssueTypeChangedEventProcessor()); gfb.registerPostProcessor(ReviewData.ReviewInitialCommentData.class, new ReviewDataProcessor.ReviewInitialCommentDataProcessor(this)); GsonBuilder gb = gfb.createGsonBuilder(); gb.registerTypeAdapter(Commit.class, new CommitProcessor(this, userProcessor)); diff --git a/src/de/uni_passau/fim/gitwrapper/IssueTypeChangeEvent.java b/src/de/uni_passau/fim/gitwrapper/IssueTypeChangeEvent.java new file mode 100644 index 0000000..8366586 --- /dev/null +++ b/src/de/uni_passau/fim/gitwrapper/IssueTypeChangeEvent.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2025 Shiraz Jafri + * + * This file is part of GitHubWrapper. + * + * GitHubWrapper is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GitHubWrapper is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with GitWrapper. If not, see . + */ +package de.uni_passau.fim.gitwrapper; + +/** + * Represents the type of issue type change event. + */ +public enum IssueTypeChangeEvent { + /** + * An issue type was added to the issue. + */ + ADDED, + + /** + * An issue type was changed for the issue. + */ + CHANGED, + + /** + * An issue type was removed from the issue. + */ + REMOVED, + + /** + * Default value for unknown or unspecified issue type change events. + */ + ANY; + + /** + * Gets the IssueTypeChangeEvent from a string. + * + * @param string the string to convert + * @return the corresponding IssueTypeChangeEvent, or null if no match is found + */ + public static IssueTypeChangeEvent getFromString(String string) { + if (string == null) { + return ANY; + } + + switch (string.toLowerCase()) { + case "issue_type_added": + return ADDED; + case "issue_type_changed": + return CHANGED; + case "issue_type_removed": + return REMOVED; + default: + return null; + } + } +} \ No newline at end of file From a9395e40a1a2c3e452e01278f1977004a078f41a Mon Sep 17 00:00:00 2001 From: shirazJafri Date: Wed, 6 Aug 2025 01:19:46 +0200 Subject: [PATCH 12/26] Change change_type field to not appear in result no longer serializing change_type field in IssueTypeChangedEventData Signed-off-by: Shiraz Jafri --- src/de/uni_passau/fim/gitwrapper/EventData.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/de/uni_passau/fim/gitwrapper/EventData.java b/src/de/uni_passau/fim/gitwrapper/EventData.java index 969e91c..11a8c8b 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventData.java +++ b/src/de/uni_passau/fim/gitwrapper/EventData.java @@ -214,6 +214,7 @@ public StateReason getStateReason() { */ public class IssueTypeChangedEventData extends EventData { + @Expose(serialize = false) IssueTypeChangeEvent change_type; /** From 1d03eb8075fda026f710b0f344e11cf261bdf2ae Mon Sep 17 00:00:00 2001 From: shirazJafri Date: Thu, 7 Aug 2025 01:16:29 +0200 Subject: [PATCH 13/26] Add events for parent and sub issues Introduce new subclasses for parent and subissue events Signed-off-by: Shiraz jarfi --- .../uni_passau/fim/gitwrapper/EventData.java | 12 +++++++ .../fim/gitwrapper/EventDataProcessor.java | 34 +++++++++++++++++++ .../fim/gitwrapper/GitHubRepository.java | 2 ++ 3 files changed, 48 insertions(+) diff --git a/src/de/uni_passau/fim/gitwrapper/EventData.java b/src/de/uni_passau/fim/gitwrapper/EventData.java index 11a8c8b..20db161 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventData.java +++ b/src/de/uni_passau/fim/gitwrapper/EventData.java @@ -224,4 +224,16 @@ public IssueTypeChangeEvent getChangeType() { return change_type; } } + + /** + * An Event generated by changing the parent issue of an issue. + */ + public class ParentIssueChangedEventData extends EventData { + } + + /** + * An Event generated by changing the sub-issue of an issue. + */ + public class SubIssueChangedEventData extends EventData { + } } diff --git a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java index ff0fa1a..6090344 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java @@ -50,6 +50,12 @@ class EventDataProcessor implements JsonDeserializer, JsonSerializer< map.put("issue_type_added", EventData.IssueTypeChangedEventData.class); map.put("issue_type_changed", EventData.IssueTypeChangedEventData.class); map.put("issue_type_removed", EventData.IssueTypeChangedEventData.class); + map.put("parent_issue_added", EventData.ParentIssueChangedEventData.class); + map.put("parent_issue_removed", EventData.ParentIssueChangedEventData.class); + map.put("parent_issue_changed", EventData.ParentIssueChangedEventData.class); + map.put("sub_issue_added", EventData.SubIssueChangedEventData.class); + map.put("sub_issue_removed", EventData.SubIssueChangedEventData.class); + map.put("sub_issue_changed", EventData.SubIssueChangedEventData.class); map.put("review_requested", EventData.RequestedReviewEventData.class); map.put("review_request_removed", EventData.RequestedReviewEventData.class); map.put("review_dismissed", EventData.DismissedReviewEventData.class); @@ -200,4 +206,32 @@ public void postDeserialize(EventData.IssueTypeChangedEventData result, JsonElem public void postSerialize(JsonElement result, EventData.IssueTypeChangedEventData src, Gson gson) { } } + + /** + * Processor for parent issue change events. + */ + static class ParentIssueChangedEventProcessor implements PostProcessor { + + @Override + public void postDeserialize(EventData.ParentIssueChangedEventData result, JsonElement src, Gson gson) { + } + + @Override + public void postSerialize(JsonElement result, EventData.ParentIssueChangedEventData src, Gson gson) { + } + } + + /** + * Processor for sub-issue change events. + */ + static class SubIssueChangedEventProcessor implements PostProcessor { + + @Override + public void postDeserialize(EventData.SubIssueChangedEventData result, JsonElement src, Gson gson) { + } + + @Override + public void postSerialize(JsonElement result, EventData.SubIssueChangedEventData src, Gson gson) { + } + } } diff --git a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java index ac5d731..1d05e5f 100644 --- a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java +++ b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java @@ -244,6 +244,8 @@ public GitHubRepository(String url, File dir, GitWrapper git, List oauth gfb.registerPostProcessor(EventData.AssignedEventData.class, new EventDataProcessor.AssignedEventProcessor()); gfb.registerPostProcessor(EventData.StateChangedEventData.class, new EventDataProcessor.StateChangedEventProcessor()); gfb.registerPostProcessor(EventData.IssueTypeChangedEventData.class, new EventDataProcessor.IssueTypeChangedEventProcessor()); + gfb.registerPostProcessor(EventData.ParentIssueChangedEventData.class, new EventDataProcessor.ParentIssueChangedEventProcessor()); + gfb.registerPostProcessor(EventData.SubIssueChangedEventData.class, new EventDataProcessor.SubIssueChangedEventProcessor()); gfb.registerPostProcessor(ReviewData.ReviewInitialCommentData.class, new ReviewDataProcessor.ReviewInitialCommentDataProcessor(this)); GsonBuilder gb = gfb.createGsonBuilder(); gb.registerTypeAdapter(Commit.class, new CommitProcessor(this, userProcessor)); From ec47c5abfe0d601aa93818ef2b50c7e7a7786801 Mon Sep 17 00:00:00 2001 From: shirazJafri Date: Thu, 7 Aug 2025 02:36:19 +0200 Subject: [PATCH 14/26] Add tracking for connected events Introduce new event data subclass for connected events Signed-off-by: Shiraz Jafri --- src/de/uni_passau/fim/gitwrapper/EventData.java | 6 ++++++ .../fim/gitwrapper/EventDataProcessor.java | 15 +++++++++++++++ .../fim/gitwrapper/GitHubRepository.java | 1 + 3 files changed, 22 insertions(+) diff --git a/src/de/uni_passau/fim/gitwrapper/EventData.java b/src/de/uni_passau/fim/gitwrapper/EventData.java index 20db161..df88aac 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventData.java +++ b/src/de/uni_passau/fim/gitwrapper/EventData.java @@ -236,4 +236,10 @@ public class ParentIssueChangedEventData extends EventData { */ public class SubIssueChangedEventData extends EventData { } + + /** + * An Event generated by connecting to a repository. + */ + public class ConnectedEventData extends EventData { + } } diff --git a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java index 6090344..979e3b8 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java @@ -46,6 +46,7 @@ class EventDataProcessor implements JsonDeserializer, JsonSerializer< map.put("merged", EventData.ReferencedEventData.class); map.put("closed", EventData.ReferencedEventData.class); map.put("closed", EventData.StateChangedEventData.class); + map.put("connected", EventData.ConnectedEventData.class); map.put("reopened", EventData.StateChangedEventData.class); map.put("issue_type_added", EventData.IssueTypeChangedEventData.class); map.put("issue_type_changed", EventData.IssueTypeChangedEventData.class); @@ -234,4 +235,18 @@ public void postDeserialize(EventData.SubIssueChangedEventData result, JsonEleme public void postSerialize(JsonElement result, EventData.SubIssueChangedEventData src, Gson gson) { } } + + /** + * Processor for connected events. + */ + static class ConnectedEventProcessor implements PostProcessor { + + @Override + public void postDeserialize(EventData.ConnectedEventData result, JsonElement src, Gson gson) { + } + + @Override + public void postSerialize(JsonElement result, EventData.ConnectedEventData src, Gson gson) { + } + } } diff --git a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java index 1d05e5f..a5871eb 100644 --- a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java +++ b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java @@ -246,6 +246,7 @@ public GitHubRepository(String url, File dir, GitWrapper git, List oauth gfb.registerPostProcessor(EventData.IssueTypeChangedEventData.class, new EventDataProcessor.IssueTypeChangedEventProcessor()); gfb.registerPostProcessor(EventData.ParentIssueChangedEventData.class, new EventDataProcessor.ParentIssueChangedEventProcessor()); gfb.registerPostProcessor(EventData.SubIssueChangedEventData.class, new EventDataProcessor.SubIssueChangedEventProcessor()); + gfb.registerPostProcessor(EventData.ConnectedEventData.class, new EventDataProcessor.ConnectedEventProcessor()); gfb.registerPostProcessor(ReviewData.ReviewInitialCommentData.class, new ReviewDataProcessor.ReviewInitialCommentDataProcessor(this)); GsonBuilder gb = gfb.createGsonBuilder(); gb.registerTypeAdapter(Commit.class, new CommitProcessor(this, userProcessor)); From 98e4e6dc63eb0d97d66e10013cb9d2f4f77e84a9 Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Tue, 26 Aug 2025 12:48:55 +0200 Subject: [PATCH 15/26] Remove unnecessary map entry key was duplicated with different values, overwriting previous entry Signed-off-by: Leo Sendelbach --- src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java index 979e3b8..20d9553 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java @@ -44,7 +44,6 @@ class EventDataProcessor implements JsonDeserializer, JsonSerializer< map.put("unlabeled", EventData.LabeledEventData.class); map.put("referenced", EventData.ReferencedEventData.class); map.put("merged", EventData.ReferencedEventData.class); - map.put("closed", EventData.ReferencedEventData.class); map.put("closed", EventData.StateChangedEventData.class); map.put("connected", EventData.ConnectedEventData.class); map.put("reopened", EventData.StateChangedEventData.class); From b980f8d52a2a6649f70cae3e4ec6c6840f26442c Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Thu, 25 Sep 2025 12:54:08 +0200 Subject: [PATCH 16/26] Improve SHA-1 extraction from issue comments also add event ids and subissue numbers to data SHA-1 extraction now only recognises hexadecimal strings length 7 and above (previously 5) Issue and Event data are extended by simple fields to hold event ids and subissue numbers Signed-off-by: Leo Sendelbach --- .../uni_passau/fim/gitwrapper/EventData.java | 1 + .../uni_passau/fim/gitwrapper/IssueData.java | 20 ++++++++++ .../fim/gitwrapper/IssueDataProcessor.java | 38 ++++++++++++++++++- 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/de/uni_passau/fim/gitwrapper/EventData.java b/src/de/uni_passau/fim/gitwrapper/EventData.java index df88aac..fe788fa 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventData.java +++ b/src/de/uni_passau/fim/gitwrapper/EventData.java @@ -33,6 +33,7 @@ public abstract class EventData { UserData user; OffsetDateTime created_at; String event; + Long id; /** * The User that created the Event. diff --git a/src/de/uni_passau/fim/gitwrapper/IssueData.java b/src/de/uni_passau/fim/gitwrapper/IssueData.java index 9674968..d7bc769 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueData.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueData.java @@ -53,6 +53,7 @@ public class IssueData implements GitHubRepository.IssueDataCached { private List reviewsList; private List> relatedCommits; List> relatedIssues; + private List subIssues; transient GitHubRepository repo; private transient boolean frozen; @@ -102,6 +103,16 @@ void setRelatedCommits(List> commits) { relatedCommits = commits; } + /** + * Sets a list of sub-issues to this Issue. + * + * @param issues + * the list of issue numbers + */ + void setSubIssues(List issues) { + subIssues = issues; + } + /** * Sets a list of related Issues (rather their numbers) to this Issue * from links containing just issues numbers. @@ -287,6 +298,15 @@ public List> getRelatedCommits() { return relatedCommits; } + /** + * Gets a List of all sub-issues that belong to the Issue. + * + * @return a List of sub-issues in form of a list containing their issue numbers + */ + public List getSubIssues() { + return subIssues; + } + /** * Gets a List of all Issues referenced in the Issue and its Comments. * diff --git a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java index 5fd2dcf..b9a0e04 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java @@ -231,6 +231,28 @@ List> parseIssues(IssueData issue, Gson gson) { .collect(Collectors.toList()); } + /** + * Parse subissues + * + * @param issue + * the Issue to analyze + * @param gson + * the Gson used to deserialize + * @return a list of all Issue numbers of sub-issues + */ + List parseSubIssues(IssueData issue, Gson gson) { + + Optional subIssueJson = repo.getJSONStringFromURL(issueBaseUrl + issue.number + "/sub_issues"); + + if (subIssueJson.isEmpty()) { + return new ArrayList(); + } + ArrayList list = gson.fromJson(subIssueJson.get(), new TypeToken>() {}.getType()); + return list.stream().map(s -> (((IssueData) gson.fromJson(s, new TypeToken() {}.getType())).getNumber())) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + /** * Extracts theoretically valid commit hashes from text. * @@ -242,7 +264,17 @@ private List extractSHA1s(String text) { if (text == null) { return Collections.emptyList(); } - Pattern sha1Pattern = Pattern.compile("([0-9a-f]{5,40})"); + + // filter out everything in code block + String[] texts = text.split("```"); + text = ""; + for (int i = 0; i < texts.length; i++) { + if (i % 2 == 0) { + text = text + texts[i]; + } + } + + Pattern sha1Pattern = Pattern.compile("([0-9a-f]{7,40})"); Matcher matcher = sha1Pattern.matcher(text); List sha1s = new ArrayList<>(); @@ -457,6 +489,10 @@ public void postDeserialize(IssueData result, JsonElement src, Gson gson) { result.setRelatedIssues(parseIssues(result, gson)); } + if (result.getSubIssues() == null) { + result.setSubIssues(parseSubIssues(result, gson)); + } + workingQueue.remove(result.number); } From 6925ad89812eb5e5a3f4c4bcb86e4e90b670417a Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Thu, 25 Sep 2025 13:04:49 +0200 Subject: [PATCH 17/26] Update copyright headers also fix copyright headers in new files Signed-off-by: Leo Sendelbach --- src/de/uni_passau/fim/gitwrapper/EventData.java | 2 ++ src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java | 1 + src/de/uni_passau/fim/gitwrapper/GitHubRepository.java | 1 + src/de/uni_passau/fim/gitwrapper/IssueData.java | 2 ++ src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java | 1 + 5 files changed, 7 insertions(+) diff --git a/src/de/uni_passau/fim/gitwrapper/EventData.java b/src/de/uni_passau/fim/gitwrapper/EventData.java index fe788fa..bb8d84e 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventData.java +++ b/src/de/uni_passau/fim/gitwrapper/EventData.java @@ -1,6 +1,8 @@ /** * Copyright (C) 2016-2018 Florian Heck * Copyright (C) 2019 Thomas Bock + * Copyright (C) 2025 Leo Sendelbach + * Copyright (C) 2025 Shiraz Jafri * * This file is part of GitHubWrapper. * diff --git a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java index 20d9553..238581c 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java @@ -2,6 +2,7 @@ * Copyright (C) 2016-2018 Florian Heck * Copyright (C) 2019 Thomas Bock * Copyright (C) 2025 Leo Sendelbach + * Copyright (C) 2025 Shiraz Jafri * * This file is part of GitHubWrapper. * diff --git a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java index a5871eb..41d3c53 100644 --- a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java +++ b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java @@ -3,6 +3,7 @@ * Copyright (C) 2018 Claus Hunsen * Copyright (C) 2019-2021 Thomas Bock * Copyright (C) 2025 Leo Sendelbach + * Copyright (C) 2025 Shiraz Jafri * * This file is part of GitHubWrapper. * diff --git a/src/de/uni_passau/fim/gitwrapper/IssueData.java b/src/de/uni_passau/fim/gitwrapper/IssueData.java index d7bc769..4f2432d 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueData.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueData.java @@ -1,6 +1,8 @@ /** * Copyright (C) 2016-2018 Florian Heck * Copyright (C) 2019 Thomas Bock + * Copyright (C) 2025 Leo Sendelbach + * Copyright (C) 2025 Shiraz Jafri * * This file is part of GitHubWrapper. * diff --git a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java index b9a0e04..8ab5a41 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java @@ -2,6 +2,7 @@ * Copyright (C) 2016-2018 Florian Heck * Copyright (C) 2019-2020 Thomas Bock * Copyright (C) 2025 Leo Sendelbach + * Copyright (C) 2025 Shiraz Jafri * * This file is part of GitHubWrapper. * From 1b6f8d5e404f06246f6ae557b960c250038b0a05 Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Tue, 14 Oct 2025 14:31:59 +0200 Subject: [PATCH 18/26] Remove superfluous state reasons and events Parernt- and Subissues can not have a 'changed' event None and Any state reasons were not achievable Also removed the state reason field from issue data StateChangedEvents can now have a commit (issues can be closed by a commit) Signed-off-by: Leo Sendelbach --- .../uni_passau/fim/gitwrapper/EventData.java | 21 +++--- .../fim/gitwrapper/EventDataProcessor.java | 58 ++++++++-------- .../fim/gitwrapper/GitHubRepository.java | 4 +- .../uni_passau/fim/gitwrapper/IssueData.java | 10 --- .../fim/gitwrapper/IssueDataProcessor.java | 1 - .../fim/gitwrapper/IssueTypeChangeEvent.java | 67 ------------------- .../fim/gitwrapper/StateReason.java | 19 ++---- 7 files changed, 42 insertions(+), 138 deletions(-) delete mode 100644 src/de/uni_passau/fim/gitwrapper/IssueTypeChangeEvent.java diff --git a/src/de/uni_passau/fim/gitwrapper/EventData.java b/src/de/uni_passau/fim/gitwrapper/EventData.java index bb8d84e..9560691 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventData.java +++ b/src/de/uni_passau/fim/gitwrapper/EventData.java @@ -202,8 +202,17 @@ public UserData getAssigner() { */ public class StateChangedEventData extends EventData { + @Expose(deserialize = false) + Commit commit; StateReason state_reason; + /** + * The commit references. + */ + public Commit getCommit() { + return commit; + } + /** * The reason for the state change. */ @@ -215,17 +224,7 @@ public StateReason getStateReason() { /** * An Event generated by changing the type of an issue. */ - public class IssueTypeChangedEventData extends EventData { - - @Expose(serialize = false) - IssueTypeChangeEvent change_type; - - /** - * The type of change that occurred to the issue type. - */ - public IssueTypeChangeEvent getChangeType() { - return change_type; - } + public class IssueTypeChangedEventData extends EventData { } /** diff --git a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java index 238581c..1b14a09 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java @@ -46,17 +46,15 @@ class EventDataProcessor implements JsonDeserializer, JsonSerializer< map.put("referenced", EventData.ReferencedEventData.class); map.put("merged", EventData.ReferencedEventData.class); map.put("closed", EventData.StateChangedEventData.class); - map.put("connected", EventData.ConnectedEventData.class); map.put("reopened", EventData.StateChangedEventData.class); + map.put("connected", EventData.ConnectedEventData.class); map.put("issue_type_added", EventData.IssueTypeChangedEventData.class); map.put("issue_type_changed", EventData.IssueTypeChangedEventData.class); map.put("issue_type_removed", EventData.IssueTypeChangedEventData.class); map.put("parent_issue_added", EventData.ParentIssueChangedEventData.class); map.put("parent_issue_removed", EventData.ParentIssueChangedEventData.class); - map.put("parent_issue_changed", EventData.ParentIssueChangedEventData.class); map.put("sub_issue_added", EventData.SubIssueChangedEventData.class); map.put("sub_issue_removed", EventData.SubIssueChangedEventData.class); - map.put("sub_issue_changed", EventData.SubIssueChangedEventData.class); map.put("review_requested", EventData.RequestedReviewEventData.class); map.put("review_request_removed", EventData.RequestedReviewEventData.class); map.put("review_dismissed", EventData.DismissedReviewEventData.class); @@ -181,6 +179,18 @@ public void postSerialize(JsonElement result, EventData.AssignedEventData src, G */ static class StateChangedEventProcessor implements PostProcessor { + private GitHubRepository repo; + + /** + * Creates a new EventDataProcessor for the given repo. + * + * @param repo + * the repo + */ + StateChangedEventProcessor(GitHubRepository repo) { + this.repo = repo; + } + @Override public void postDeserialize(EventData.StateChangedEventData result, JsonElement src, Gson gson) { JsonElement stateReasonElement = src.getAsJsonObject().get("state_reason"); @@ -188,6 +198,20 @@ public void postDeserialize(EventData.StateChangedEventData result, JsonElement ? stateReasonElement.getAsString() : null; result.state_reason = StateReason.getFromString(stateReasonValue); + + JsonElement hash = src.getAsJsonObject().get("commit_id"); + if (hash.isJsonNull()) { + return; + } + + result.commit = repo.getGithubCommit(hash.getAsString()).orElseGet(() -> { + LOG.warning("Found commit unknown to GitHub and local git repo: " + hash + " Retry using url..."); + JsonElement url = src.getAsJsonObject().get("commit_url"); + return repo.getGithubCommitUrl(hash.getAsString(), url.getAsString()).orElseGet(() -> { + LOG.warning("Could not find commit: " + hash); + return null; + }); + }); } @Override @@ -208,34 +232,6 @@ public void postSerialize(JsonElement result, EventData.IssueTypeChangedEventDat } } - /** - * Processor for parent issue change events. - */ - static class ParentIssueChangedEventProcessor implements PostProcessor { - - @Override - public void postDeserialize(EventData.ParentIssueChangedEventData result, JsonElement src, Gson gson) { - } - - @Override - public void postSerialize(JsonElement result, EventData.ParentIssueChangedEventData src, Gson gson) { - } - } - - /** - * Processor for sub-issue change events. - */ - static class SubIssueChangedEventProcessor implements PostProcessor { - - @Override - public void postDeserialize(EventData.SubIssueChangedEventData result, JsonElement src, Gson gson) { - } - - @Override - public void postSerialize(JsonElement result, EventData.SubIssueChangedEventData src, Gson gson) { - } - } - /** * Processor for connected events. */ diff --git a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java index 41d3c53..83535f9 100644 --- a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java +++ b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java @@ -243,10 +243,8 @@ public GitHubRepository(String url, File dir, GitWrapper git, List oauth gfb.registerPostProcessor(EventData.LabeledEventData.class, new EventDataProcessor.LabeledEventProcessor()); gfb.registerPostProcessor(EventData.DismissedReviewEventData.class, new EventDataProcessor.DismissedReviewEventProcessor()); gfb.registerPostProcessor(EventData.AssignedEventData.class, new EventDataProcessor.AssignedEventProcessor()); - gfb.registerPostProcessor(EventData.StateChangedEventData.class, new EventDataProcessor.StateChangedEventProcessor()); + gfb.registerPostProcessor(EventData.StateChangedEventData.class, new EventDataProcessor.StateChangedEventProcessor(this)); gfb.registerPostProcessor(EventData.IssueTypeChangedEventData.class, new EventDataProcessor.IssueTypeChangedEventProcessor()); - gfb.registerPostProcessor(EventData.ParentIssueChangedEventData.class, new EventDataProcessor.ParentIssueChangedEventProcessor()); - gfb.registerPostProcessor(EventData.SubIssueChangedEventData.class, new EventDataProcessor.SubIssueChangedEventProcessor()); gfb.registerPostProcessor(EventData.ConnectedEventData.class, new EventDataProcessor.ConnectedEventProcessor()); gfb.registerPostProcessor(ReviewData.ReviewInitialCommentData.class, new ReviewDataProcessor.ReviewInitialCommentDataProcessor(this)); GsonBuilder gb = gfb.createGsonBuilder(); diff --git a/src/de/uni_passau/fim/gitwrapper/IssueData.java b/src/de/uni_passau/fim/gitwrapper/IssueData.java index 4f2432d..3bb67d7 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueData.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueData.java @@ -41,7 +41,6 @@ public class IssueData implements GitHubRepository.IssueDataCached { UserData user; @Expose(deserialize = false) State state; - @Expose(deserialize = false) StateReason state_reason; @Expose(deserialize = false) TypeData type; OffsetDateTime created_at; @Nullable OffsetDateTime closed_at; @@ -197,15 +196,6 @@ public State getState() { return state; } - /** - * Gets the reason for the current state of the issue. - * - * @return the state reason - */ - public StateReason getStateReason() { - return state_reason; - } - /** * Gets the date and time the issue was created. * diff --git a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java index 8ab5a41..37f5a31 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java @@ -429,7 +429,6 @@ public void postDeserialize(IssueData result, JsonElement src, Gson gson) { String stateReasonValue = (stateReasonElement != null && !stateReasonElement.isJsonNull()) ? stateReasonElement.getAsString() : null; - result.state_reason = StateReason.getFromString(stateReasonValue); JsonElement typeElement = src.getAsJsonObject().get("type"); if (typeElement != null && !typeElement.isJsonNull()) { diff --git a/src/de/uni_passau/fim/gitwrapper/IssueTypeChangeEvent.java b/src/de/uni_passau/fim/gitwrapper/IssueTypeChangeEvent.java deleted file mode 100644 index 8366586..0000000 --- a/src/de/uni_passau/fim/gitwrapper/IssueTypeChangeEvent.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (C) 2025 Shiraz Jafri - * - * This file is part of GitHubWrapper. - * - * GitHubWrapper is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * GitHubWrapper is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with GitWrapper. If not, see . - */ -package de.uni_passau.fim.gitwrapper; - -/** - * Represents the type of issue type change event. - */ -public enum IssueTypeChangeEvent { - /** - * An issue type was added to the issue. - */ - ADDED, - - /** - * An issue type was changed for the issue. - */ - CHANGED, - - /** - * An issue type was removed from the issue. - */ - REMOVED, - - /** - * Default value for unknown or unspecified issue type change events. - */ - ANY; - - /** - * Gets the IssueTypeChangeEvent from a string. - * - * @param string the string to convert - * @return the corresponding IssueTypeChangeEvent, or null if no match is found - */ - public static IssueTypeChangeEvent getFromString(String string) { - if (string == null) { - return ANY; - } - - switch (string.toLowerCase()) { - case "issue_type_added": - return ADDED; - case "issue_type_changed": - return CHANGED; - case "issue_type_removed": - return REMOVED; - default: - return null; - } - } -} \ No newline at end of file diff --git a/src/de/uni_passau/fim/gitwrapper/StateReason.java b/src/de/uni_passau/fim/gitwrapper/StateReason.java index 0564126..a3fe417 100644 --- a/src/de/uni_passau/fim/gitwrapper/StateReason.java +++ b/src/de/uni_passau/fim/gitwrapper/StateReason.java @@ -40,28 +40,17 @@ public enum StateReason { /** * Denotes that the Issue or PullRequest was a duplicate. */ - DUPLICATE, - - /** - * Denotes that the state reason is not known or not specified. - * This can be used when the state reason is not applicable or not provided. - */ - NONE, - - /** - * Any state reason is included (can be used for unknown reasons). - */ - ANY; + DUPLICATE; /** * Gets the StateReason from a string. * * @param string the string to convert - * @return the corresponding StateReason, or ANY if no match is found + * @return the corresponding StateReason, the default case being 'COMPLETED' */ public static StateReason getFromString(String string) { if (string == null) { - return NONE; + return COMPLETED; } switch (string.toLowerCase()) { @@ -74,7 +63,7 @@ public static StateReason getFromString(String string) { case "duplicate": return DUPLICATE; default: - return ANY; + return COMPLETED; } } } From efe5fec1fe4051cd8edaf76d020cf30cdd8fe0b3 Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Tue, 28 Oct 2025 14:02:34 +0100 Subject: [PATCH 19/26] Add exception if state reason is not a legal value instead of assigning completed as default case, now throw an error. If new state reasons are added later on githubs side, the occurence of this error should prevent us from not noticing that change. Signed-off-by: Leo Sendelbach --- src/de/uni_passau/fim/gitwrapper/StateReason.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/de/uni_passau/fim/gitwrapper/StateReason.java b/src/de/uni_passau/fim/gitwrapper/StateReason.java index a3fe417..19a2f18 100644 --- a/src/de/uni_passau/fim/gitwrapper/StateReason.java +++ b/src/de/uni_passau/fim/gitwrapper/StateReason.java @@ -63,7 +63,8 @@ public static StateReason getFromString(String string) { case "duplicate": return DUPLICATE; default: - return COMPLETED; + throw new IllegalArgumentException("Found state reason (" + string + ") that was neither 'completed'," + + "'reopened', 'not_planned'. nor 'duplicate'!"); } } } From d3dd80ef283f914b95baab29cf8a56c1b758da39 Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Fri, 31 Oct 2025 16:15:07 +0100 Subject: [PATCH 20/26] Add functionality to detect suggestions suggestions contain a codeblock using the suggestion keyword after opening the block. Add a field to reviewCommentData and result json for this boolean Signed-off-by: Leo Sendelbach --- .../fim/gitwrapper/ReferencedLinkProcessor.java | 3 +++ src/de/uni_passau/fim/gitwrapper/ReviewCommentData.java | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/de/uni_passau/fim/gitwrapper/ReferencedLinkProcessor.java b/src/de/uni_passau/fim/gitwrapper/ReferencedLinkProcessor.java index 955f94e..7ce85cf 100644 --- a/src/de/uni_passau/fim/gitwrapper/ReferencedLinkProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/ReferencedLinkProcessor.java @@ -64,6 +64,7 @@ public ReferencedLink deserialize(JsonElement json, Type typeOfT, JsonDeserializ } if (!json.getAsJsonObject().get("body").isJsonNull()) { commentData.body = json.getAsJsonObject().get("body").getAsString(); + commentData.suggestion = commentData.body.contains("```suggestion") || commentData.body.contains("```suggestion"); } else { commentData.body = ""; } @@ -99,6 +100,7 @@ public ReferencedLink deserialize(JsonElement json, Type typeOfT, JsonDeserializ commentData.commit_id = json.getAsJsonObject().get("commit_id").getAsString(); commentData.original_commit_id = json.getAsJsonObject().get("original_commit_id").getAsString(); commentData.body = json.getAsJsonObject().get("body").getAsString(); + commentData.suggestion = commentData.body.contains("```suggestion") || commentData.body.contains("```suggestion"); result.target = commentData; } else { // normal comment @@ -158,6 +160,7 @@ public void postSerialize(JsonElement result, ReferencedLink src, Gson gson) { result.getAsJsonObject().addProperty("original_position", ((ReviewCommentData) src.getTarget()).getOriginalPosition()); result.getAsJsonObject().addProperty("commit_id", ((ReviewCommentData) src.getTarget()).getCommitId()); result.getAsJsonObject().addProperty("original_commit_id", ((ReviewCommentData) src.getTarget()).getOriginalCommitId()); + result.getAsJsonObject().addProperty("contains_suggestion", ((ReviewCommentData) src.getTarget()).containsSuggestion()); result.getAsJsonObject().remove("target"); break; case "Integer": diff --git a/src/de/uni_passau/fim/gitwrapper/ReviewCommentData.java b/src/de/uni_passau/fim/gitwrapper/ReviewCommentData.java index 950673a..0181a65 100644 --- a/src/de/uni_passau/fim/gitwrapper/ReviewCommentData.java +++ b/src/de/uni_passau/fim/gitwrapper/ReviewCommentData.java @@ -35,6 +35,7 @@ public class ReviewCommentData { @SerializedName(value = "file", alternate = {"path"}) String file; String body; + boolean suggestion = false; public ReviewCommentData() { } @@ -80,4 +81,11 @@ public String getFile() { public String getBody() { return body; } + + /** + * If the comment contains a suggestion + */ + public boolean containsSuggestion() { + return suggestion; + } } From 43714584a118a4f308146e13c5e992b1e06eb90e Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Tue, 4 Nov 2025 12:28:39 +0100 Subject: [PATCH 21/26] Resolve issues pointed out in review Fixes: - case sensitive check for suggestion - minor spelling issue - artifact from state reasons in issue data - debug comment - copyright headers Signed-off-by: Leo Sendelbach --- src/de/uni_passau/fim/gitwrapper/GitHubRepository.java | 4 ++-- src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java | 5 ----- .../uni_passau/fim/gitwrapper/ReferencedLinkProcessor.java | 5 +++-- src/de/uni_passau/fim/gitwrapper/StateReason.java | 3 ++- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java index 83535f9..b97978d 100644 --- a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java +++ b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java @@ -357,8 +357,8 @@ public Optional> getIssues(boolean includePullRequests, OffsetDa } else timeLimit = ""; Type finalType = type; - // For debugging, you may add additional parameters to the string. For example, '/issues?creator=sleo&state=all' - // will fetch issues created by user 'sleo' and all related issues and commits. + // For debugging, you may add additional parameters to the string. For example, '/issues?creator=username&state=all' + // will fetch issues created by the specified and all related issues and commits. getJSONStringFromPath("/issues?state=all" + timeLimit).map(json -> { List data; try { diff --git a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java index 37f5a31..3cae242 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java @@ -425,11 +425,6 @@ public void postDeserialize(IssueData result, JsonElement src, Gson gson) { // fill in missing data result.state = State.getFromString(src.getAsJsonObject().get("state").getAsString()); - JsonElement stateReasonElement = src.getAsJsonObject().get("state_reason"); - String stateReasonValue = (stateReasonElement != null && !stateReasonElement.isJsonNull()) - ? stateReasonElement.getAsString() - : null; - JsonElement typeElement = src.getAsJsonObject().get("type"); if (typeElement != null && !typeElement.isJsonNull()) { result.type = gson.fromJson(typeElement, TypeData.class); diff --git a/src/de/uni_passau/fim/gitwrapper/ReferencedLinkProcessor.java b/src/de/uni_passau/fim/gitwrapper/ReferencedLinkProcessor.java index 7ce85cf..e799ffd 100644 --- a/src/de/uni_passau/fim/gitwrapper/ReferencedLinkProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/ReferencedLinkProcessor.java @@ -1,6 +1,7 @@ /** * Copyright (C) 2018 Florian Heck * Copyright (C) 2019 Thomas Bock + * Copyright (C) 2025 Leo Sendelbach * * This file is part of GitHubWrapper. * @@ -64,7 +65,7 @@ public ReferencedLink deserialize(JsonElement json, Type typeOfT, JsonDeserializ } if (!json.getAsJsonObject().get("body").isJsonNull()) { commentData.body = json.getAsJsonObject().get("body").getAsString(); - commentData.suggestion = commentData.body.contains("```suggestion") || commentData.body.contains("```suggestion"); + commentData.suggestion = commentData.body.toLowerCase().contains("```suggestion"); } else { commentData.body = ""; } @@ -100,7 +101,7 @@ public ReferencedLink deserialize(JsonElement json, Type typeOfT, JsonDeserializ commentData.commit_id = json.getAsJsonObject().get("commit_id").getAsString(); commentData.original_commit_id = json.getAsJsonObject().get("original_commit_id").getAsString(); commentData.body = json.getAsJsonObject().get("body").getAsString(); - commentData.suggestion = commentData.body.contains("```suggestion") || commentData.body.contains("```suggestion"); + commentData.suggestion = commentData.body.toLowerCase().contains("```suggestion"); result.target = commentData; } else { // normal comment diff --git a/src/de/uni_passau/fim/gitwrapper/StateReason.java b/src/de/uni_passau/fim/gitwrapper/StateReason.java index 19a2f18..b5b0692 100644 --- a/src/de/uni_passau/fim/gitwrapper/StateReason.java +++ b/src/de/uni_passau/fim/gitwrapper/StateReason.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2025 Shiraz Jafri + * Copyright (C) 2025 Leo Sendelbach * * This file is part of GitHubWrapper. * @@ -64,7 +65,7 @@ public static StateReason getFromString(String string) { return DUPLICATE; default: throw new IllegalArgumentException("Found state reason (" + string + ") that was neither 'completed'," + - "'reopened', 'not_planned'. nor 'duplicate'!"); + "'reopened', 'not_planned', nor 'duplicate'!"); } } } From e16d7517eb97b4f10034dc1a4fdca8f2cbb210cf Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Tue, 27 Jan 2026 15:22:41 +0100 Subject: [PATCH 22/26] Add additional information to dummy user If no userdata is found except for the username, initialize the dummy with that username Signed-off-by: Leo Sendelbach --- .../uni_passau/fim/gitwrapper/UserDataProcessor.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java index ffd6586..c370726 100644 --- a/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java @@ -77,8 +77,16 @@ public UserData deserialize(JsonElement json, Type typeOfT, JsonDeserializationC private UserData buildAndInsertUser(String username, String url) { Optional jsonData = repo.getJSONStringFromURL(url); if (!jsonData.isPresent()) { - LOG.warning("Could not get information about user '" + username + "'"); - return DUMMY_USER; + if (username == null || username.isEmpty()) { + LOG.warning("Could not get information about unknown user!"); + return DUMMY_USER; + } else { + LOG.warning("Could not get information about user '" + username + "', creating a dummy user entry."); + UserData dummyUser = new UserData(); + dummyUser.username = username; + dummyUser.email = ""; + return dummyUser; + } } JsonElement data = parser.parse(jsonData.get()); UserData user = new UserData(); From 0c0f8ae7140c7f543f475ce31f202093b68caccd Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Tue, 3 Feb 2026 14:48:56 +0100 Subject: [PATCH 23/26] Update copyright header Signed-off-by: Leo Sendelbach --- src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java index c370726..b9c4150 100644 --- a/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java @@ -1,5 +1,6 @@ /** * Copyright (C) 2018 Florian Heck + * Copyright (C) 2026 Leo Sendelbach * * This file is part of GitHubWrapper. * From e0ea7ffb01ad20c4ed2e7a1c7745ab4ae710cead Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Tue, 10 Mar 2026 14:14:54 +0100 Subject: [PATCH 24/26] Add support for locked events This allows us to extract the reason for locking a conversation Signed-off-by: Leo Sendelbach --- src/de/uni_passau/fim/gitwrapper/EventData.java | 17 ++++++++++++++++- .../fim/gitwrapper/EventDataProcessor.java | 3 ++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/de/uni_passau/fim/gitwrapper/EventData.java b/src/de/uni_passau/fim/gitwrapper/EventData.java index 9560691..faa8e92 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventData.java +++ b/src/de/uni_passau/fim/gitwrapper/EventData.java @@ -1,7 +1,7 @@ /** * Copyright (C) 2016-2018 Florian Heck * Copyright (C) 2019 Thomas Bock - * Copyright (C) 2025 Leo Sendelbach + * Copyright (C) 2025-2026 Leo Sendelbach * Copyright (C) 2025 Shiraz Jafri * * This file is part of GitHubWrapper. @@ -244,4 +244,19 @@ public class SubIssueChangedEventData extends EventData { */ public class ConnectedEventData extends EventData { } + + /** + * An Event generated by locking the conversation of an issue or PR. + */ + public class LockedEventData extends EventData { + + String lock_reason; + + /** + * The reason for the state change. + */ + public String getLockReason() { + return lock_reason; + } + } } diff --git a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java index 1b14a09..a3045a4 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/EventDataProcessor.java @@ -1,7 +1,7 @@ /** * Copyright (C) 2016-2018 Florian Heck * Copyright (C) 2019 Thomas Bock - * Copyright (C) 2025 Leo Sendelbach + * Copyright (C) 2025-2026 Leo Sendelbach * Copyright (C) 2025 Shiraz Jafri * * This file is part of GitHubWrapper. @@ -60,6 +60,7 @@ class EventDataProcessor implements JsonDeserializer, JsonSerializer< map.put("review_dismissed", EventData.DismissedReviewEventData.class); map.put("assigned", EventData.AssignedEventData.class); map.put("unassigned", EventData.AssignedEventData.class); + map.put("locked", EventData.LockedEventData.class); } @Override From 555419bfbc826d4e68f0078db7a2915d92f020a7 Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Tue, 10 Mar 2026 17:15:57 +0100 Subject: [PATCH 25/26] Integrate requested changes - update documentation, fix spelling - add new fields to 'freeze' method - use stringbuilder instead of concatenation - fix deserialization of new event type - change state change event to inherit from referenced event Signed-off-by: Leo Sendelbach --- src/de/uni_passau/fim/gitwrapper/EventData.java | 15 +++------------ .../fim/gitwrapper/GitHubRepository.java | 16 ++++++++++++++-- src/de/uni_passau/fim/gitwrapper/IssueData.java | 13 ++++++++++++- .../fim/gitwrapper/IssueDataProcessor.java | 9 +++++---- .../fim/gitwrapper/ReferencedLinkProcessor.java | 3 ++- .../uni_passau/fim/gitwrapper/StateReason.java | 4 ++-- .../fim/gitwrapper/UserDataProcessor.java | 4 +++- 7 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/de/uni_passau/fim/gitwrapper/EventData.java b/src/de/uni_passau/fim/gitwrapper/EventData.java index faa8e92..cd2dba6 100644 --- a/src/de/uni_passau/fim/gitwrapper/EventData.java +++ b/src/de/uni_passau/fim/gitwrapper/EventData.java @@ -200,19 +200,10 @@ public UserData getAssigner() { /** * An Event generated by changing the state of an issue. */ - public class StateChangedEventData extends EventData { + public class StateChangedEventData extends ReferencedEventData { - @Expose(deserialize = false) - Commit commit; StateReason state_reason; - /** - * The commit references. - */ - public Commit getCommit() { - return commit; - } - /** * The reason for the state change. */ @@ -240,7 +231,7 @@ public class SubIssueChangedEventData extends EventData { } /** - * An Event generated by connecting to a repository. + * An Event generated by connecting to an issue or pull request. */ public class ConnectedEventData extends EventData { } @@ -253,7 +244,7 @@ public class LockedEventData extends EventData { String lock_reason; /** - * The reason for the state change. + * The reason for the lock. */ public String getLockReason() { return lock_reason; diff --git a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java index b97978d..7c9951b 100644 --- a/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java +++ b/src/de/uni_passau/fim/gitwrapper/GitHubRepository.java @@ -2,7 +2,7 @@ * Copyright (C) 2016-2020 Florian Heck * Copyright (C) 2018 Claus Hunsen * Copyright (C) 2019-2021 Thomas Bock - * Copyright (C) 2025 Leo Sendelbach + * Copyright (C) 2025-2026 Leo Sendelbach * Copyright (C) 2025 Shiraz Jafri * * This file is part of GitHubWrapper. @@ -358,7 +358,7 @@ public Optional> getIssues(boolean includePullRequests, OffsetDa else timeLimit = ""; Type finalType = type; // For debugging, you may add additional parameters to the string. For example, '/issues?creator=username&state=all' - // will fetch issues created by the specified and all related issues and commits. + // will fetch issues created by the specified user and all related issues and commits. getJSONStringFromPath("/issues?state=all" + timeLimit).map(json -> { List data; try { @@ -1035,6 +1035,18 @@ Optional getGithubCommit(String hash) { }); } + /** + * Gets the corresponding Commit for the given sha1 hash using its URL. If the commit is not known by the local repository, a + * query is sent to GitHub, to confirm its existence there and additional author data is retrieved. (e.g. GitHub + * retains a copy, even if a force push is performed). + * + * @param hash + * the sha1 hash of the Commit + * @param url + * the URL to query for the commit data + * @return optionally a Commit, or an empty Optional, if neither the local repository nor GitHub have a reference + * to a Commit with the given hash + */ Optional getGithubCommitUrl(String hash, String url) { if (offline.get()) { return Optional.of(getGHCommitUnchecked(DummyCommit.DUMMY_COMMIT_ID)); diff --git a/src/de/uni_passau/fim/gitwrapper/IssueData.java b/src/de/uni_passau/fim/gitwrapper/IssueData.java index 3bb67d7..86dcb55 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueData.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueData.java @@ -1,7 +1,7 @@ /** * Copyright (C) 2016-2018 Florian Heck * Copyright (C) 2019 Thomas Bock - * Copyright (C) 2025 Leo Sendelbach + * Copyright (C) 2025-2026 Leo Sendelbach * Copyright (C) 2025 Shiraz Jafri * * This file is part of GitHubWrapper. @@ -165,6 +165,8 @@ void freeze() { // Remove invalid commits before they cause problems .filter(c -> c != null && c.getTarget() != null && c.getTarget().getAuthorTime() != null) .distinct().sorted(compare).collect(Collectors.toList())); + subIssues = Collections.unmodifiableList(subIssues.stream() + .filter(Objects::nonNull).distinct().sorted().collect(Collectors.toList())); frozen = true; } @@ -196,6 +198,15 @@ public State getState() { return state; } + /** + * Gets the type of the issue. + * + * @return the type + */ + public TypeData getType() { + return type; + } + /** * Gets the date and time the issue was created. * diff --git a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java index 3cae242..3d1f6a1 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java @@ -1,7 +1,7 @@ /** * Copyright (C) 2016-2018 Florian Heck * Copyright (C) 2019-2020 Thomas Bock - * Copyright (C) 2025 Leo Sendelbach + * Copyright (C) 2025-2026 Leo Sendelbach * Copyright (C) 2025 Shiraz Jafri * * This file is part of GitHubWrapper. @@ -75,7 +75,7 @@ private List> parseCommits(IssueData issue) { // Parse commits from referenced commits Stream>> referencedCommits = issue.getEventsList().stream() - .filter(eventData -> eventData instanceof EventData.ReferencedEventData) + .filter(eventData -> eventData instanceof EventData.ReferencedEventData || eventData instanceof EventData.StateChangedEventData) // filter out errors from referencing commits .filter(eventData -> ((EventData.ReferencedEventData) eventData).commit != null) .map(eventData -> { @@ -268,12 +268,13 @@ private List extractSHA1s(String text) { // filter out everything in code block String[] texts = text.split("```"); - text = ""; + StringBuilder sb = new StringBuilder(); for (int i = 0; i < texts.length; i++) { if (i % 2 == 0) { - text = text + texts[i]; + sb.append(texts[i]); } } + text = sb.toString(); Pattern sha1Pattern = Pattern.compile("([0-9a-f]{7,40})"); Matcher matcher = sha1Pattern.matcher(text); diff --git a/src/de/uni_passau/fim/gitwrapper/ReferencedLinkProcessor.java b/src/de/uni_passau/fim/gitwrapper/ReferencedLinkProcessor.java index e799ffd..31f6af3 100644 --- a/src/de/uni_passau/fim/gitwrapper/ReferencedLinkProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/ReferencedLinkProcessor.java @@ -1,7 +1,7 @@ /** * Copyright (C) 2018 Florian Heck * Copyright (C) 2019 Thomas Bock - * Copyright (C) 2025 Leo Sendelbach + * Copyright (C) 2025-2026 Leo Sendelbach * * This file is part of GitHubWrapper. * @@ -118,6 +118,7 @@ public ReferencedLink deserialize(JsonElement json, Type typeOfT, JsonDeserializ case "commitAddedToPullRequest": case "commitMentionedInIssue": case "commitReferencesIssue": + case "commitReferencesIssueExternal": result = new ReferencedLink(); result.target = context.deserialize(json.getAsJsonObject().get("commit"), new TypeToken() {}.getType()); diff --git a/src/de/uni_passau/fim/gitwrapper/StateReason.java b/src/de/uni_passau/fim/gitwrapper/StateReason.java index b5b0692..f2c7a0c 100644 --- a/src/de/uni_passau/fim/gitwrapper/StateReason.java +++ b/src/de/uni_passau/fim/gitwrapper/StateReason.java @@ -1,6 +1,6 @@ /** * Copyright (C) 2025 Shiraz Jafri - * Copyright (C) 2025 Leo Sendelbach + * Copyright (C) 2025-2026 Leo Sendelbach * * This file is part of GitHubWrapper. * @@ -64,7 +64,7 @@ public static StateReason getFromString(String string) { case "duplicate": return DUPLICATE; default: - throw new IllegalArgumentException("Found state reason (" + string + ") that was neither 'completed'," + + throw new IllegalArgumentException("Found state reason (" + string + ") that was neither 'completed', " + "'reopened', 'not_planned', nor 'duplicate'!"); } } diff --git a/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java index b9c4150..1ddd5f3 100644 --- a/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java @@ -77,15 +77,18 @@ public UserData deserialize(JsonElement json, Type typeOfT, JsonDeserializationC */ private UserData buildAndInsertUser(String username, String url) { Optional jsonData = repo.getJSONStringFromURL(url); + boolean guess = repo.allowGuessing(); if (!jsonData.isPresent()) { if (username == null || username.isEmpty()) { LOG.warning("Could not get information about unknown user!"); + (guess ? guessedUsersByUsername : strictUsersByUsername).put(username, DUMMY_USER); return DUMMY_USER; } else { LOG.warning("Could not get information about user '" + username + "', creating a dummy user entry."); UserData dummyUser = new UserData(); dummyUser.username = username; dummyUser.email = ""; + (guess ? guessedUsersByUsername : strictUsersByUsername).put(username, dummyUser); return dummyUser; } } @@ -106,7 +109,6 @@ private UserData buildAndInsertUser(String username, String url) { } // if we want to guess for emails, look at user history - boolean guess = repo.allowGuessing(); if (guess) { // get list of recent pushes Optional eventsData = repo.getJSONStringFromURL(data.getAsJsonObject().get("events_url").getAsString().replaceAll("\\{.*}$", "")); From 7730ba60d3d5f090dbb02ef423df3e6ab8cf68fd Mon Sep 17 00:00:00 2001 From: Leo Sendelbach Date: Fri, 13 Mar 2026 11:25:41 +0100 Subject: [PATCH 26/26] Integrate requested changes Remove unnecessary type check, refactor into new helperfunction, and formatting Signed-off-by: Leo Sendelbach --- .../uni_passau/fim/gitwrapper/IssueData.java | 8 ++-- .../fim/gitwrapper/IssueDataProcessor.java | 40 ++++++++++--------- .../fim/gitwrapper/UserDataProcessor.java | 1 - 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/de/uni_passau/fim/gitwrapper/IssueData.java b/src/de/uni_passau/fim/gitwrapper/IssueData.java index 86dcb55..e8134c0 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueData.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueData.java @@ -199,10 +199,10 @@ public State getState() { } /** - * Gets the type of the issue. - * - * @return the type - */ + * Gets the type of the issue. + * + * @return the type + */ public TypeData getType() { return type; } diff --git a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java index 3d1f6a1..857a174 100644 --- a/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/IssueDataProcessor.java @@ -75,7 +75,7 @@ private List> parseCommits(IssueData issue) { // Parse commits from referenced commits Stream>> referencedCommits = issue.getEventsList().stream() - .filter(eventData -> eventData instanceof EventData.ReferencedEventData || eventData instanceof EventData.StateChangedEventData) + .filter(eventData -> eventData instanceof EventData.ReferencedEventData) // filter out errors from referencing commits .filter(eventData -> ((EventData.ReferencedEventData) eventData).commit != null) .map(eventData -> { @@ -266,15 +266,7 @@ private List extractSHA1s(String text) { return Collections.emptyList(); } - // filter out everything in code block - String[] texts = text.split("```"); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < texts.length; i++) { - if (i % 2 == 0) { - sb.append(texts[i]); - } - } - text = sb.toString(); + text = splitCodeBlocks(text); Pattern sha1Pattern = Pattern.compile("([0-9a-f]{7,40})"); Matcher matcher = sha1Pattern.matcher(text); @@ -303,15 +295,7 @@ private List extractHashtags(String text, boolean onlyInSameRepo) { } Pattern hashtagPattern; - // filter out everything in code block - String[] texts = text.split("```"); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < texts.length; i++) { - if (i % 2 == 0) { - sb.append(texts[i]); - } - } - text = sb.toString(); + text = splitCodeBlocks(text); if (onlyInSameRepo) { String repoName = repo.getRepoName(); @@ -352,6 +336,24 @@ private List extractHashtags(String text, boolean onlyInSameRepo) { return hashtags; } + /** + * Splits text into code and non-code blocks and only returns the non-code blocks concatenated. + * + * @param text + * the text to split + * @return the text without code blocks + */ + private String splitCodeBlocks(String text) { + String[] texts = text.split("```"); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < texts.length; i++) { + if (i % 2 == 0) { + sb.append(texts[i]); + } + } + return sb.toString(); + } + @Override public void postSerialize(JsonElement result, IssueData src, Gson gson) { } diff --git a/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java b/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java index 1ddd5f3..4a6bd71 100644 --- a/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java +++ b/src/de/uni_passau/fim/gitwrapper/UserDataProcessor.java @@ -81,7 +81,6 @@ private UserData buildAndInsertUser(String username, String url) { if (!jsonData.isPresent()) { if (username == null || username.isEmpty()) { LOG.warning("Could not get information about unknown user!"); - (guess ? guessedUsersByUsername : strictUsersByUsername).put(username, DUMMY_USER); return DUMMY_USER; } else { LOG.warning("Could not get information about user '" + username + "', creating a dummy user entry.");