diff --git a/gitnet/__init__.py b/gitnet/__init__.py
index 28bb8a1..6f69f6a 100644
--- a/gitnet/__init__.py
+++ b/gitnet/__init__.py
@@ -14,7 +14,7 @@
# If not, see .
# *********************************************************************************************
-from .get_log import get_log
+from .get_log import get_log, new_get_log, get_log_v3
from .exceptions import RepositoryError, ParseError, InputError
from .log import Log
from .commit_log import CommitLog
diff --git a/gitnet/get_log.py b/gitnet/get_log.py
index 25ad71c..b2f36ee 100644
--- a/gitnet/get_log.py
+++ b/gitnet/get_log.py
@@ -20,6 +20,7 @@
from gitnet.exceptions import RepositoryError, ParseError, InputError
from gitnet.commit_log import CommitLog
import subprocess as sub
+import line_profiler
def retrieve_commits(path, mode="stat"):
@@ -214,7 +215,6 @@ def parse_commits(commit_str):
warnings.warn("Parser was unable to identify {}. Identity string <{}> not recognized".format(line,id))
return collection
-
def get_log(path, mode="stat", commit_source="local git"):
"""
A function for gathering data from a local Git repository.
@@ -244,3 +244,324 @@ def get_log(path, mode="stat", commit_source="local git"):
source=commit_source,
path=path,
key_type=detect_key)
+
+
+# New implementations
+def new_get_log(path, mode="stat", commit_source="local git"):
+ if commit_source == "local git":
+ detect_key = "hash"
+ else:
+ detect_key = "unknown"
+ return CommitLog(dofd=new_helper(path, mode),
+ source=commit_source,
+ path=path,
+ key_type=detect_key)
+
+
+def new_helper(path, mode="stat"):
+ print("Attempting local git log retrieval...")
+ # Log command modes, referenced by "mode" input.
+ log_commands = {"basic": ["git", "log"], "raw": ["git", "log", "--raw"], "stat": ["git", "log", "--stat"]}
+ if mode not in log_commands.keys():
+ raise InputError("{} is not a valid retrieval mode.".format(mode))
+
+ # Save the current directory. Navigate to new directory. Retrieve logs. Return to original directory.
+ work_dir = os.getcwd()
+ os.chdir(path)
+ proc = sub.Popen(log_commands[mode], stdout=sub.PIPE)
+
+ # Iterate through the lines and place them in the dictionary
+ collection = {}
+ sha = ""
+ for line in generate(proc.stdout):
+ string = line.decode('utf-8')
+
+ # Identify and parse the string
+ if string != "\n":
+ id = new_identify(string)
+ # Commit Hash?
+ if id == "hash":
+ sha = string[7:14]
+ collection[sha] = {}
+ collection[sha]["hash"] = string[7:]
+ collection[sha]["mode"] = mode
+ # Author?
+ elif id == "author":
+ collection[sha]["author"] = string.split("<")[0][8:-1]
+ collection[sha]["email"] = string.split("<")[1][:-1]
+ # Date?
+ elif id == "date":
+ collection[sha]["date"] = string[8:]
+ # Message?
+ elif id == "message":
+ if "message" in collection[sha].keys():
+ collection[sha]["message"] += " " + string[4:]
+ else:
+ collection[sha]["message"] = string[4:]
+ # File change record?
+ elif id == "change":
+ if "changes" in collection[sha].keys():
+ collection[sha]["changes"].append(string[1:])
+ collection[sha]["files"].append(string.split("|")[0].replace(" ", ""))
+ else:
+ collection[sha]["changes"] = [string[1:]]
+ collection[sha]["files"] = [string.split("|")[0].replace(" ", "")]
+ elif id == "summary":
+ collection[sha]["summary"] = string[1:]
+ # Filter numbers
+ temp = string.split(",")
+ for s in temp:
+ num = int("".join(list(filter(str.isdigit, s))))
+ if "file" in s and "change" in s:
+ collection[sha]["fedits"] = num
+ if "insert" in s:
+ collection[sha]["inserts"] = num
+ if "delet" in s:
+ collection[sha]["deletes"] = num
+ elif id == "merge":
+ collection[sha]["merge"] = string[6:]
+ elif id == "multiple" or id == "none":
+ if "errors" in collection[sha].keys():
+ collection[sha]["errors"].append(string)
+ else:
+ collection[sha]["errors"] = [string]
+ else:
+ warnings.warn("Parser was unable to identify {}. Identity string <{}> not recognized".format(line, id))
+ os.chdir(work_dir)
+
+ # If the retrieval was unsuccessful, raise an error.
+ if len(collection) == 0:
+ print("Raising error.")
+ if "true" in str(sub.Popen("git rev-parse --is-inside-work-tree", stdout=sub.PIPE).stdout):
+ raise RepositoryError("{} is not a Git repository.".format(path))
+ else:
+ raise RepositoryError("{} has no commits.".format(path))
+ # If the retrieval was successful, print a summary."
+ print("Got {} log records from: {}".format(len(collection), path))
+
+ return collection
+
+
+def new_identify(s):
+ """
+ A helper function for `parse_commits()`. It takes a string and attempts to identify it as an entry
+ field from a Git commit log.
+
+ **Parameters** :
+
+ > *s* : `string`
+
+ >> One line of standard git log output (in basic, raw, or stat mode).
+
+ **Return** :
+
+ > A string identifying the type of data received.
+
+ **Examples** :
+
+ > `identify("commit 5be676481b4051af62f21eb2c8601b3f6bafb195") => "hash"`
+ > `identify("Author: JBWBecker ") => "author"`
+ > `identify("__init__.py | 2 ++") => "change"`
+
+ """
+ # identify checks whether the string matches an expected format. All matches are saved in a list.
+ matches = []
+ if s[:6] == "commit" and len(s) == 48:
+ matches.append("hash")
+ if s[:7] == "Author:":
+ matches.append("author")
+ if s[:5] == "Date:":
+ matches.append("date")
+ if s[:4] == " ":
+ matches.append("message")
+ if (s[0] == " " and s[:4] != " " and "|" in s) or (s[0] == ":" and type(int(s[1:8])) == int):
+ matches.append("change")
+ if s[0] == " " and s[:4] != " " and (("insertion" in s and "(+)" in s) or ("deletion" in s and "(-)" in s)):
+ matches.append("summary")
+ if s[:6] == "Merge:":
+ matches.append("merge")
+ # If only one match was found, produce that string. Otherwise, produce "other" and raise a Warning.
+ if len(matches) == 1:
+ return matches[0]
+ elif len(matches) > 1:
+ warnings.warn("Unexpected parsing behaviour. <{}> matched multiple input patterns ({}) during parsing,"
+ " so was identified as 'other'.".format(s,matches))
+ return "multiple"
+ else:
+ warnings.warn("Unexpected parsing behaviour. <{}> did not match any input patterns during parsing,"
+ " so was identified as 'other'.".format(s))
+ return "none"
+
+
+def generate(stdout):
+ line = stdout.readline()
+ while line != b"":
+ if len(line) > 1:
+ yield line
+ line = stdout.readline()
+
+
+# Version 3
+def get_log_v3(path, mode="stat", commit_source="local git"):
+ if commit_source == "local git":
+ detect_key = "hash"
+ else:
+ detect_key = "unknown"
+ return CommitLog(dofd=helper_v3(path, mode),
+ source=commit_source,
+ path=path,
+ key_type=detect_key)
+
+
+def helper_v3(path, mode="stat"):
+ print("Attempting local git log retrieval...")
+ # Log command modes, referenced by "mode" input.
+ log_commands = {"basic": ["git", "log"], "raw": ["git", "log", "--raw"], "stat": ["git", "log", "--stat"]}
+ if mode not in log_commands.keys():
+ raise InputError("{} is not a valid retrieval mode.".format(mode))
+
+ # Save the current directory. Navigate to new directory. Retrieve logs. Return to original directory.
+ work_dir = os.getcwd()
+ os.chdir(path)
+ proc = sub.Popen(log_commands[mode], stdout=sub.PIPE)
+ stdout = generate(proc.stdout)
+
+ # Iterate through the lines and place them in the dictionary
+ collection = {}
+ sha = ""
+ for line in stdout:
+ string = line.decode('utf-8')
+
+ # Identify and parse the string
+ if string != "\n":
+ id = identify_v3(string)
+ # Commit Hash?
+ if id == "hash":
+ sha = string[7:14]
+ collection[sha] = {}
+ collection[sha]["hash"] = string[7:]
+ collection[sha]["mode"] = mode
+
+ # File change record?
+ elif id == "change":
+ if "changes" in collection[sha].keys():
+ collection[sha]["changes"].append(string[1:])
+ collection[sha]["files"].append(string.split("|")[0].replace(" ", ""))
+ else:
+ collection[sha]["changes"] = [string[1:]]
+ collection[sha]["files"] = [string.split("|")[0].replace(" ", "")]
+
+ # Message?
+ elif id == "message":
+ if "message" in collection[sha].keys():
+ collection[sha]["message"] += " " + string[4:]
+ else:
+ collection[sha]["message"] = string[4:]
+
+ # Author?
+ elif id == "author":
+ collection[sha]["author"] = string.split("<")[0][8:-1]
+ collection[sha]["email"] = string.split("<")[1][:-1]
+
+ # Date?
+ elif id == "date":
+ collection[sha]["date"] = string[8:]
+
+ # Summary?
+ elif id == "summary":
+ collection[sha]["summary"] = string[1:]
+ # Filter numbers
+ temp = string.split(",")
+ for s in temp:
+ num = int("".join(list(filter(str.isdigit, s))))
+ if "file" in s and "change" in s:
+ collection[sha]["fedits"] = num
+ if "insert" in s:
+ collection[sha]["inserts"] = num
+ if "delet" in s:
+ collection[sha]["deletes"] = num
+
+ # Merge?
+ elif id == "merge":
+ collection[sha]["merge"] = string[6:]
+
+ # Error?
+ elif id == "multiple" or id == "none":
+ if "errors" in collection[sha].keys():
+ collection[sha]["errors"].append(string)
+ else:
+ collection[sha]["errors"] = [string]
+ else:
+ warnings.warn("Parser was unable to identify {}. Identity string <{}> not recognized".format(line, id))
+ os.chdir(work_dir)
+
+ # If the retrieval was unsuccessful, raise an error.
+ if len(collection) == 0:
+ print("Raising error.")
+ if "true" in str(sub.Popen("git rev-parse --is-inside-work-tree", stdout=sub.PIPE).stdout):
+ raise RepositoryError("{} is not a Git repository.".format(path))
+ else:
+ raise RepositoryError("{} has no commits.".format(path))
+ # If the retrieval was successful, print a summary."
+ print("Got {} log records from: {}".format(len(collection), path))
+
+ return collection
+
+
+def identify_v3(s):
+ """
+ A helper function for `parse_commits()`. It takes a string and attempts to identify it as an entry
+ field from a Git commit log.
+
+ **Parameters** :
+
+ > *s* : `string`
+
+ >> One line of standard git log output (in basic, raw, or stat mode).
+
+ **Return** :
+
+ > A string identifying the type of data received.
+
+ **Examples** :
+
+ > `identify("commit 5be676481b4051af62f21eb2c8601b3f6bafb195") => "hash"`
+ > `identify("Author: JBWBecker ") => "author"`
+ > `identify("__init__.py | 2 ++") => "change"`
+
+ """
+ # identify checks whether the string matches an expected format. All matches are saved in a list.
+ matches = []
+
+ # Change?
+ if (s[0] == " " and s[:4] != " " and "|" in s) or (s[0] == ":" and type(int(s[1:8])) == int):
+ matches.append("change")
+ # Message?
+ elif s[:4] == " ":
+ matches.append("message")
+ # Hash?
+ elif s[:6] == "commit" and len(s) == 48:
+ matches.append("hash")
+ # Author?
+ elif s[:7] == "Author:":
+ matches.append("author")
+ # Date?
+ elif s[:5] == "Date:":
+ matches.append("date")
+ # Summary?
+ elif s[0] == " " and s[:4] != " " and (("insertion" in s and "(+)" in s) or ("deletion" in s and "(-)" in s)):
+ matches.append("summary")
+ # Merge?
+ elif s[:6] == "Merge:":
+ matches.append("merge")
+ # If only one match was found, produce that string. Otherwise, produce "other" and raise a Warning.
+ if len(matches) == 1:
+ return matches[0]
+ elif len(matches) > 1:
+ warnings.warn("Unexpected parsing behaviour. <{}> matched multiple input patterns ({}) during parsing,"
+ " so was identified as 'other'.".format(s,matches))
+ return "multiple"
+ else:
+ warnings.warn("Unexpected parsing behaviour. <{}> did not match any input patterns during parsing,"
+ " so was identified as 'other'.".format(s))
+ return "none"
\ No newline at end of file