diff --git a/src/cfgnet/analyze/analyzer.py b/src/cfgnet/analyze/analyzer.py index 4bb8ddd..fc48254 100644 --- a/src/cfgnet/analyze/analyzer.py +++ b/src/cfgnet/analyze/analyzer.py @@ -17,17 +17,19 @@ import logging import time -from typing import Optional, Set +from typing import Optional, Set, List from cfgnet.vcs.git import Git from cfgnet.vcs.git_history import GitHistory from cfgnet.network.network import Network, NetworkConfiguration from cfgnet.analyze.csv_writer import CSVWriter +from cfgnet.utility.statistics import CommitStatistics class Analyzer: def __init__(self, cfg: NetworkConfiguration): self.cfg: NetworkConfiguration = cfg self.conflicts_cvs_path: Optional[str] = None + self.stats_csv_path: Optional[str] = None self.time_last_progress_print: float = 0 self._setup_dirs() @@ -45,9 +47,17 @@ def _setup_dirs(self) -> None: self.conflicts_csv_path = os.path.join( analysis_dir, f"conflicts_{self.cfg.project_name()}.csv" ) + self.commit_stats_file = os.path.join( + analysis_dir, f"commit_stats_{self.cfg.project_name()}.csv" + ) + + self.option_stats_file = os.path.join( + analysis_dir, f"option_stats_{self.cfg.project_name()}.csv" + ) - if os.path.exists(self.conflicts_csv_path): - os.remove(self.conflicts_csv_path) + for path in [self.conflicts_csv_path, self.commit_stats_file]: + if os.path.exists(path): + os.remove(path) def _print_progress(self, num_commit: int, final: bool = False) -> None: """Print the progress of th analysis.""" @@ -71,13 +81,25 @@ def analyze_commit_history(self) -> None: commit_hash_pre_analysis = repo.get_current_commit_hash() conflicts: Set = set() + commit_stats: List[CommitStatistics] = [] history = GitHistory(repo) commit = history.restore_initial_commit() try: ref_network = Network.init_network(cfg=self.cfg) + initial_stats = CommitStatistics() + stats = CommitStatistics.calc_stats( + commit=commit, + commit_number=history.commit_index, + network=ref_network, + conflicts=conflicts, + prev=initial_stats, + ) + commit_stats.append(stats) + while history.has_next_commit(): commit = history.next_commit() + commit_number = history.commit_index detected_conflicts, ref_network = ref_network.validate( commit.hexsha @@ -85,7 +107,16 @@ def analyze_commit_history(self) -> None: conflicts.update(detected_conflicts) - self._print_progress(num_commit=history.commit_index + 1) + stats = CommitStatistics.calc_stats( + commit=commit, + commit_number=commit_number, + network=ref_network, + conflicts=detected_conflicts, + prev=stats, + ) + commit_stats.append(stats) + + self._print_progress(num_commit=commit_number + 1) if commit.hexsha == commit_hash_pre_analysis: break @@ -111,6 +142,13 @@ def analyze_commit_history(self) -> None: csv_path=self.conflicts_csv_path, conflicts=conflicts ) + CommitStatistics.write_stats_to_csv( + self.commit_stats_file, commit_stats + ) + CommitStatistics.write_options_to_csv( + self.option_stats_file, commit_stats + ) + self._print_progress( num_commit=history.commit_index + 1, final=True ) diff --git a/src/cfgnet/conflicts/conflict.py b/src/cfgnet/conflicts/conflict.py index f4b6239..5b4ce39 100644 --- a/src/cfgnet/conflicts/conflict.py +++ b/src/cfgnet/conflicts/conflict.py @@ -19,7 +19,7 @@ import hashlib import logging -from typing import Any, List, Optional, Set +from typing import Any, Iterable, Optional, Set from cfgnet.linker.link import Link from cfgnet.network.nodes import ArtifactNode, OptionNode, ValueNode @@ -64,7 +64,7 @@ def is_involved(self, node: Any) -> bool: """ @staticmethod - def count_total(conflicts: List[Conflict]) -> int: + def count_total(conflicts: Iterable[Conflict]) -> int: """Total conflict count across a list.""" return sum([conflict.count() for conflict in conflicts]) diff --git a/src/cfgnet/utility/statistics.py b/src/cfgnet/utility/statistics.py new file mode 100644 index 0000000..da22788 --- /dev/null +++ b/src/cfgnet/utility/statistics.py @@ -0,0 +1,233 @@ +# This file is part of the CfgNet module. +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program 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 General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +import csv +import re +from collections import OrderedDict +from typing import List, Iterable + +from cfgnet.network.network import Network +from cfgnet.network.nodes import OptionNode, ArtifactNode, ValueNode +from cfgnet.conflicts.conflict import Conflict +from cfgnet.vcs.git import Commit + + +class CommitStatistics: + def __init__(self, commit=None): + self.commit_hash = None + self.commit_number = 0 + self.total_num_artifact_nodes = 0 + self.num_configuration_files_changed = 0 + self.config_files_changed = set() + self.total_option_nodes = 0 + self.total_value_nodes = 0 + self.num_value_nodes_added = 0 + self.num_value_nodes_removed = 0 + self.num_value_nodes_changed = 0 + self.total_links = 0 + self.links_added = 0 + self.links_removed = 0 + self.conflicts_detected = 0 + + self.docker_nodes = {} + self.nodejs_nodes = {} + self.maven_nodes = {} + self.travis_nodes = {} + self.docker_compose_nodes = {} + + self._value_node_ids = set() + self._value_node_parent_ids = set() + self._links = set() + self._conflicts = set() + + if commit: + self.commit_hash = commit.hexsha + + @staticmethod + # pylint: disable=protected-access + def calc_stats( + commit: Commit, + commit_number: int, + network: Network, + conflicts: Iterable[Conflict], + prev: "CommitStatistics", + ) -> "CommitStatistics": + stats = CommitStatistics(commit=commit) + + # commit data + stats.commit_number = commit_number + stats.commit_hash = commit.hexsha + + # artifact data + artifact_nodes = network.get_nodes(ArtifactNode) + stats.total_num_artifact_nodes = len(artifact_nodes) + total_config_files = {node.rel_file_path for node in artifact_nodes} + files_changed = set(commit.stats.files.keys()) + stats.config_files_changed = total_config_files.intersection( + files_changed + ) + stats.num_configuration_files_changed = len(stats.config_files_changed) + + # option data + option_nodes = network.get_nodes(OptionNode) + stats.total_option_nodes = len(option_nodes) + + # value data + value_nodes = network.get_nodes(ValueNode) + stats.total_value_nodes = len(value_nodes) + stats._value_node_ids = {node.id for node in value_nodes} + stats._value_node_parent_ids = {node.parent.id for node in value_nodes} + + stats.num_value_nodes_added = len( + stats._value_node_ids.difference(prev._value_node_ids) + ) + stats.num_value_nodes_removed = len( + prev._value_node_ids.difference(stats._value_node_ids) + ) + + stats.num_value_nodes_changed = 0 + value_parents_in_common = stats._value_node_parent_ids.intersection( + prev._value_node_parent_ids + ) + for node in value_parents_in_common: + + def same_parent(node_id, parent=node): + return node_id.startswith(parent) + + value_prev = list(filter(same_parent, prev._value_node_ids))[0] + value_new = list(filter(same_parent, stats._value_node_ids))[0] + if value_prev != value_new: + stats.num_value_nodes_changed += 1 + stats.num_value_nodes_added -= 1 + stats.num_value_nodes_removed -= 1 + + # link data + stats._links = network.links + stats.total_links = len(stats._links) + stats.links_added = len(stats._links.difference(prev._links)) + stats.links_removed = len(prev._links.difference(stats._links)) + + # conflict data + stats.conflicts_detected = len(list(conflicts)) + + # nodes data + stats.docker_nodes = CommitStatistics.parse_options( + network, r"Dockerfile" + ) + stats.nodejs_nodes = CommitStatistics.parse_options( + network, r"package.json" + ) + stats.maven_nodes = CommitStatistics.parse_options(network, r"pom.xml") + stats.docker_compose_nodes = CommitStatistics.parse_options( + network, r"docker-compose(.\w+)?.yml" + ) + stats.travis_nodes = CommitStatistics.parse_options( + network, r".travis.yml" + ) + + return stats + + @staticmethod + def write_stats_to_csv( + file_path: str, commit_data: List["CommitStatistics"] + ) -> None: + with open(file_path, "w+", encoding="utf-8") as stats_csv: + writer = csv.DictWriter( + stats_csv, + fieldnames=CommitStatistics.commit_stats_fieldnames(), + ) + writer.writeheader() + for stats in commit_data: + writer.writerow(stats.data_dict()) + + @staticmethod + def write_options_to_csv( + nodes_file_path: str, commit_data: List["CommitStatistics"] + ) -> None: + with open(nodes_file_path, "w+", encoding="utf-8") as stats_csv: + writer = csv.DictWriter( + stats_csv, fieldnames=CommitStatistics.nodes_fieldnames() + ) + writer.writeheader() + for stats in commit_data: + writer.writerow(stats.option_dict()) + + @staticmethod + def commit_stats_fieldnames(): + return list(CommitStatistics().data_dict().keys()) + + @staticmethod + def nodes_fieldnames(): + return list(CommitStatistics().option_dict().keys()) + + def data_dict(self): + data = OrderedDict( + { + "commit_number": self.commit_number, + "commit_hash": self.commit_hash, + "total_num_artifact_nodes": self.total_num_artifact_nodes, + "num_configuration_files_changed": self.num_configuration_files_changed, + "config_files_changed": sorted(self.config_files_changed), + "total_option_nodes": self.total_option_nodes, + "total_value_nodes": self.total_value_nodes, + "num_value_nodes_changed": self.num_value_nodes_changed, + "num_value_nodes_added": self.num_value_nodes_added, + "num_value_nodes_removed": self.num_value_nodes_removed, + "total_links": self.total_links, + "links_added": self.links_added, + "links_removed": self.links_removed, + "conflicts_detected": self.conflicts_detected, + } + ) + return data + + def option_dict(self): + data = OrderedDict( + { + "docker_nodes": self.docker_nodes, + "docker_compose_nodes": self.docker_compose_nodes, + "maven_nodes": self.maven_nodes, + "nodejs_nodes": self.nodejs_nodes, + "travis_nodes": self.travis_nodes, + } + ) + return data + + @staticmethod + def parse_options(network: Network, file_name: str) -> dict: + artifacts = list( + filter( + lambda x: isinstance(x, ArtifactNode) + and re.compile(file_name).search(x.id), + network.get_nodes(node_type=ArtifactNode), + ) + ) + + data = {} + + for artifact in artifacts: + artifact_data = {} + options = filter( + lambda x: x.prevalue_node, + artifact.get_nodes(node_type=OptionNode), + ) + for option in options: + parts = option.id.split("::::") + option_name = "::::".join(parts[2:]) + artifact_data[f"{option_name}"] = option.children[0].name + + data[artifact.rel_file_path] = artifact_data + + return data diff --git a/tests/cfgnet/analyze/test_analyzer.py b/tests/cfgnet/analyze/test_analyzer.py index 4655d12..5c45182 100644 --- a/tests/cfgnet/analyze/test_analyzer.py +++ b/tests/cfgnet/analyze/test_analyzer.py @@ -55,10 +55,19 @@ def test_analyze(get_config): conflicts_csv_path = os.path.join( analysis_dir, f"conflicts_{project_name}.csv" ) + stats_csv_path = os.path.join( + analysis_dir, f"commit_stats_{project_name}.csv" + ) assert os.path.exists(conflicts_csv_path) - with open(conflicts_csv_path, "r", encoding="utf-8") as csv_stats_file: + with open(conflicts_csv_path, "r", encoding="utf-8") as csv_conflicts_file: + reader = csv.DictReader(csv_conflicts_file) + rows = list(reader) + + assert len(rows) == 2 + + with open(stats_csv_path, "r", encoding="utf-8") as csv_stats_file: reader = csv.DictReader(csv_stats_file) rows = list(reader) diff --git a/tests/cfgnet/analyze/test_commit_stats.py b/tests/cfgnet/analyze/test_commit_stats.py new file mode 100644 index 0000000..89804f1 --- /dev/null +++ b/tests/cfgnet/analyze/test_commit_stats.py @@ -0,0 +1,175 @@ +# This file is part of the CfgNet module. +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program 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 General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . + +import os +import csv +import pytest + +from cfgnet.network.network_configuration import NetworkConfiguration +from cfgnet.analyze.analyzer import Analyzer +from tests.utility.temporary_repository import TemporaryRepository + + +TOTAL_OPTIONS = ["8", "17", "17", "16", "17", "17", "21", "21"] +TOTAL_VALUES = ["6", "13", "13", "12", "13", "13", "16", "16"] +NUM_VALUE_NODES_CHANGED = ["0", "0", "1", "0", "0", "3", "0", "1"] +NUM_VALUE_NODES_ADDED = ["6", "7", "0", "0", "1", "0", "3", "0"] +NUM_VALUE_NODES_REMOVED = ["0", "0", "0", "1", "0", "0", "0", "0"] +NUM_CONFIG_FILES_CHANGED = ["1", "1", "1", "1", "1", "2", "1", "1"] +TOTAL_LINKS = ["0", "0", "0", "0", "0", "0", "1", "0"] +LINKS_ADDED = ["0", "0", "0", "0", "0", "0", "1", "0"] +LINKS_REMOVED = ["0", "0", "0", "0", "0", "0", "0", "1"] +CONFLICTS_DETECTED = ["0", "0", "0", "0", "0", "0", "0", "1"] + + +@pytest.fixture(name="get_repo") +def get_repo_(): + repo = TemporaryRepository("tests/test_repos/commit_stats") + return repo + + +@pytest.fixture(name="get_csv_path") +def get_config_(get_repo): + network_configuration = NetworkConfiguration( + project_root_abs=os.path.abspath(get_repo.root), + enable_static_blacklist=False, + enable_dynamic_blacklist=False, + enable_internal_links=False, + ) + + analyzer = Analyzer(network_configuration) + analyzer.analyze_commit_history() + + project_name = network_configuration.project_name() + data_dir = os.path.join( + network_configuration.project_root_abs, + network_configuration.cfgnet_path_rel, + ) + analysis_dir = os.path.join(data_dir, "analysis") + stats_csv_path = os.path.join( + analysis_dir, f"commit_stats_{project_name}.csv" + ) + + return stats_csv_path + + +def test_number_of_commits(get_csv_path): + with open(get_csv_path, "r", encoding="utf-8") as csv_stats_file: + reader = csv.DictReader(csv_stats_file) + rows = list(reader) + + assert len(rows) == 8 + + +def test_total_option_number(get_csv_path): + with open(get_csv_path, "r", encoding="utf-8") as csv_stats_file: + reader = csv.DictReader(csv_stats_file) + rows = list(reader) + + for i in range(len(rows)): + assert rows[i]["total_option_nodes"] == TOTAL_OPTIONS[i] + + +def test_num_config_file_changed(get_csv_path): + with open(get_csv_path, "r", encoding="utf-8") as csv_stats_file: + reader = csv.DictReader(csv_stats_file) + rows = list(reader) + + for i in range(len(rows)): + assert ( + rows[i]["num_configuration_files_changed"] + == NUM_CONFIG_FILES_CHANGED[i] + ) + + +def test_total_value_nodes(get_csv_path): + with open(get_csv_path, "r", encoding="utf-8") as csv_stats_file: + reader = csv.DictReader(csv_stats_file) + rows = list(reader) + + for i in range(len(rows)): + print("added: ", i) + assert rows[i]["total_value_nodes"] == TOTAL_VALUES[i] + + +def test_num_value_nodes_added(get_csv_path): + with open(get_csv_path, "r", encoding="utf-8") as csv_stats_file: + reader = csv.DictReader(csv_stats_file) + rows = list(reader) + + for i in range(len(rows)): + print("added: ", i) + assert rows[i]["num_value_nodes_added"] == NUM_VALUE_NODES_ADDED[i] + + +def test_num_value_nodes_removed(get_csv_path): + with open(get_csv_path, "r", encoding="utf-8") as csv_stats_file: + reader = csv.DictReader(csv_stats_file) + rows = list(reader) + + for i in range(len(rows)): + print("removed: ", i) + assert ( + rows[i]["num_value_nodes_removed"] + == NUM_VALUE_NODES_REMOVED[i] + ) + + +def test_num_value_nodes_changed(get_csv_path): + with open(get_csv_path, "r", encoding="utf-8") as csv_stats_file: + reader = csv.DictReader(csv_stats_file) + rows = list(reader) + + for i in range(len(rows)): + assert ( + rows[i]["num_value_nodes_changed"] + == NUM_VALUE_NODES_CHANGED[i] + ) + + +def test_total_links(get_csv_path): + with open(get_csv_path, "r", encoding="utf-8") as csv_stats_file: + reader = csv.DictReader(csv_stats_file) + rows = list(reader) + + for i in range(len(rows)): + assert rows[i]["total_links"] == TOTAL_LINKS[i] + + +def test_links_added(get_csv_path): + with open(get_csv_path, "r", encoding="utf-8") as csv_stats_file: + reader = csv.DictReader(csv_stats_file) + rows = list(reader) + + for i in range(len(rows)): + assert rows[i]["links_added"] == LINKS_ADDED[i] + + +def test_links_removed(get_csv_path): + with open(get_csv_path, "r", encoding="utf-8") as csv_stats_file: + reader = csv.DictReader(csv_stats_file) + rows = list(reader) + + for i in range(len(rows)): + assert rows[i]["links_removed"] == LINKS_REMOVED[i] + + +def test_conflicts_detected(get_csv_path): + with open(get_csv_path, "r", encoding="utf-8") as csv_stats_file: + reader = csv.DictReader(csv_stats_file) + rows = list(reader) + + for i in range(len(rows)): + assert rows[i]["conflicts_detected"] == CONFLICTS_DETECTED[i] diff --git a/tests/test_repos/commit_stats/0001-Add-package.json.patch b/tests/test_repos/commit_stats/0001-Add-package.json.patch new file mode 100644 index 0000000..dccfb3c --- /dev/null +++ b/tests/test_repos/commit_stats/0001-Add-package.json.patch @@ -0,0 +1,32 @@ +From de20da557315fcb57bd33711080943f8ea663823 Mon Sep 17 00:00:00 2001 +From: simisimon +Date: Tue, 15 Mar 2022 16:57:55 +0100 +Subject: [PATCH 1/6] Add package.json + +--- + package.json | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + create mode 100644 package.json + +diff --git a/package.json b/package.json +new file mode 100644 +index 0000000..82ca76b +--- /dev/null ++++ b/package.json +@@ -0,0 +1,12 @@ ++{ ++ "name": "node-js-sample", ++ "version": "0.2.0", ++ "description": "Example Project", ++ "main": "index.js", ++ "scripts": { ++ "start": "node index.js" ++ }, ++ "dependencies": { ++ "express": "^4.13.3" ++ } ++} +\ No newline at end of file +-- +2.25.1 + diff --git a/tests/test_repos/commit_stats/0002-Add-Dockerfile.patch b/tests/test_repos/commit_stats/0002-Add-Dockerfile.patch new file mode 100644 index 0000000..993f187 --- /dev/null +++ b/tests/test_repos/commit_stats/0002-Add-Dockerfile.patch @@ -0,0 +1,24 @@ +From 61a1f60756dc1f94484d61f2433284ec662b6f81 Mon Sep 17 00:00:00 2001 +From: simisimon +Date: Tue, 15 Mar 2022 16:58:35 +0100 +Subject: [PATCH 2/6] Add Dockerfile + +--- + Dockerfile | 5 +++++ + 1 file changed, 5 insertions(+) + create mode 100644 Dockerfile + +diff --git a/Dockerfile b/Dockerfile +new file mode 100644 +index 0000000..4aa00ce +--- /dev/null ++++ b/Dockerfile +@@ -0,0 +1,5 @@ ++FROM java:8 as builder ++ ++EXPOSE 1234 ++ ++ADD --chown=1 /foo.jar bar.jar +-- +2.25.1 + diff --git a/tests/test_repos/commit_stats/0003-Change-one-option-in-Dockerfile.patch b/tests/test_repos/commit_stats/0003-Change-one-option-in-Dockerfile.patch new file mode 100644 index 0000000..269900f --- /dev/null +++ b/tests/test_repos/commit_stats/0003-Change-one-option-in-Dockerfile.patch @@ -0,0 +1,23 @@ +From e93b9f8fd22a8a1b312f11a594b75f340933fcb8 Mon Sep 17 00:00:00 2001 +From: simisimon +Date: Tue, 15 Mar 2022 16:59:02 +0100 +Subject: [PATCH 3/6] Change one option in Dockerfile + +--- + Dockerfile | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/Dockerfile b/Dockerfile +index 4aa00ce..ddf7643 100644 +--- a/Dockerfile ++++ b/Dockerfile +@@ -1,5 +1,5 @@ + FROM java:8 as builder + +-EXPOSE 1234 ++EXPOSE 8000 + + ADD --chown=1 /foo.jar bar.jar +-- +2.25.1 + diff --git a/tests/test_repos/commit_stats/0004-Delete-one-option-in-Dockerfile.patch b/tests/test_repos/commit_stats/0004-Delete-one-option-in-Dockerfile.patch new file mode 100644 index 0000000..40b0e2d --- /dev/null +++ b/tests/test_repos/commit_stats/0004-Delete-one-option-in-Dockerfile.patch @@ -0,0 +1,22 @@ +From fc6d5186a621c2032341e3e20ea36f1d0834d3b8 Mon Sep 17 00:00:00 2001 +From: simisimon +Date: Tue, 15 Mar 2022 16:59:16 +0100 +Subject: [PATCH 4/6] Delete one option in Dockerfile + +--- + Dockerfile | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/Dockerfile b/Dockerfile +index ddf7643..c9cf346 100644 +--- a/Dockerfile ++++ b/Dockerfile +@@ -1,5 +1,3 @@ + FROM java:8 as builder + +-EXPOSE 8000 +- + ADD --chown=1 /foo.jar bar.jar +-- +2.25.1 + diff --git a/tests/test_repos/commit_stats/0005-Add-one-option-in-Dockerfile.patch b/tests/test_repos/commit_stats/0005-Add-one-option-in-Dockerfile.patch new file mode 100644 index 0000000..353d306 --- /dev/null +++ b/tests/test_repos/commit_stats/0005-Add-one-option-in-Dockerfile.patch @@ -0,0 +1,22 @@ +From 5b99ba743cf4db9a7fd6d66ec0ab77dd1198831a Mon Sep 17 00:00:00 2001 +From: simisimon +Date: Tue, 15 Mar 2022 16:59:28 +0100 +Subject: [PATCH 5/6] Add one option in Dockerfile + +--- + Dockerfile | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/Dockerfile b/Dockerfile +index c9cf346..ddf7643 100644 +--- a/Dockerfile ++++ b/Dockerfile +@@ -1,3 +1,5 @@ + FROM java:8 as builder + ++EXPOSE 8000 ++ + ADD --chown=1 /foo.jar bar.jar +-- +2.25.1 + diff --git a/tests/test_repos/commit_stats/0006-Change-three-options.patch b/tests/test_repos/commit_stats/0006-Change-three-options.patch new file mode 100644 index 0000000..12c895d --- /dev/null +++ b/tests/test_repos/commit_stats/0006-Change-three-options.patch @@ -0,0 +1,44 @@ +From 9c119704b16a5d1865d64736ec57cef5e9e5bbf3 Mon Sep 17 00:00:00 2001 +From: simisimon +Date: Tue, 15 Mar 2022 16:59:58 +0100 +Subject: [PATCH 6/6] Change three options + +--- + Dockerfile | 2 +- + package.json | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/Dockerfile b/Dockerfile +index ddf7643..edab55f 100644 +--- a/Dockerfile ++++ b/Dockerfile +@@ -1,5 +1,5 @@ + FROM java:8 as builder + +-EXPOSE 8000 ++EXPOSE 7777 + + ADD --chown=1 /foo.jar bar.jar +diff --git a/package.json b/package.json +index 82ca76b..1fd5ffa 100644 +--- a/package.json ++++ b/package.json +@@ -1,12 +1,12 @@ + { + "name": "node-js-sample", +- "version": "0.2.0", ++ "version": "0.5.0", + "description": "Example Project", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { +- "express": "^4.13.3" ++ "express": "5.1.3" + } + } +\ No newline at end of file +-- +2.25.1 + diff --git a/tests/test_repos/commit_stats/0007-Add-pom.xml.patch b/tests/test_repos/commit_stats/0007-Add-pom.xml.patch new file mode 100644 index 0000000..481d318 --- /dev/null +++ b/tests/test_repos/commit_stats/0007-Add-pom.xml.patch @@ -0,0 +1,24 @@ +From edcfba50b5b62689a361127a42b273951d7eb239 Mon Sep 17 00:00:00 2001 +From: simisimon +Date: Fri, 18 Mar 2022 13:59:24 +0100 +Subject: [PATCH 7/8] Add pom.xml + +--- + pom.xml | 4 ++++ + 1 file changed, 4 insertions(+) + create mode 100644 pom.xml + +diff --git a/pom.xml b/pom.xml +new file mode 100644 +index 0000000..f9e682f +--- /dev/null ++++ b/pom.xml +@@ -0,0 +1,4 @@ ++ ++ 0.2.0 ++ 7777 ++ +\ No newline at end of file +-- +2.25.1 + diff --git a/tests/test_repos/commit_stats/0008-Destroy-one-link-between-Docker-and-Maven.patch b/tests/test_repos/commit_stats/0008-Destroy-one-link-between-Docker-and-Maven.patch new file mode 100644 index 0000000..d3e8f6a --- /dev/null +++ b/tests/test_repos/commit_stats/0008-Destroy-one-link-between-Docker-and-Maven.patch @@ -0,0 +1,23 @@ +From 9771f0dfc5f805cab98692410e229cc8f1000795 Mon Sep 17 00:00:00 2001 +From: simisimon +Date: Fri, 18 Mar 2022 14:00:10 +0100 +Subject: [PATCH 8/8] Destroy one link between Docker and Maven + +--- + pom.xml | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/pom.xml b/pom.xml +index f9e682f..9a4f924 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -1,4 +1,4 @@ + + 0.2.0 +- 7777 ++ 9000 + +\ No newline at end of file +-- +2.25.1 +