diff --git a/.gitignore b/.gitignore
index b911be2..0c20846 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,4 +10,6 @@ sasjsresults/
mc_*
# ignore .env files as they can contain sasjs access tokens
-*.env*
\ No newline at end of file
+*.env*
+
+~
diff --git a/all.sas b/all.sas
index 064d032..398c61f 100644
--- a/all.sas
+++ b/all.sas
@@ -781,6 +781,43 @@ or %index(&pgm,/tests/testteardown)
/* send them out without spaces or quote markers */
%do;%unquote(%upcase(&fmt))%end;
%mend mf_getfmtname;/**
+ @file
+ @brief Retrieves the current branch from a local GIT repo
+ @details In a local git repository, the current branch is always available in
+ the `.git/HEAD` file in a format like this: `ref: refs/heads/master`
+
+ This macro simply reads the file and returns the last word (eg `master`).
+
+ Example usage:
+
+ %let gitdir=%sysfunc(pathname(work))/core;
+ %let repo=https://github.com/sasjs/core;
+ %put source clone rc=%sysfunc(GITFN_CLONE(&repo,&gitdir));
+
+ %put The current branch is %mf_getgitbranch(&gitdir);
+
+ @param [in] gitdir The directory containing the GIT repository
+
+
SAS Macros
+ @li mf_readfile.sas
+
+ Related Macros
+ @li mp_gitadd.sas
+ @li mp_gitlog.sas
+ @li mp_gitreleaseinfo.sas
+ @li mp_gitstatus.sas
+
+ @version 9.2
+ @author Allan Bowe
+**/
+
+%macro mf_getgitbranch(gitdir
+)/*/STORE SOURCE*/;
+
+ %scan(%mf_readfile(&gitdir/.git/HEAD),-1)
+
+%mend mf_getgitbranch;
+/**
@file
@brief retrieves a key value pair from a control dataset
@details By default, control dataset is work.mp_setkeyvalue. Usage:
@@ -1902,6 +1939,69 @@ Usage:
)/*/STORE SOURCE*/;
%mf_getattrn(&libds,NLOBS)
%mend mf_nobs;/**
+ @file
+ @brief Reads the first line of a file using pure macro
+ @details Reads the first line of a file and returns it. Future versions may
+ read each line into a macro variable array.
+
+ Generally, reading data into macro variables is not great as certain
+ nonprintable characters (such as CR, LF) may be dropped in the conversion.
+
+ Usage:
+
+ %mf_writefile(&sasjswork/myfile.txt,l1=some content,l2=more content)
+
+ %put %mf_readfile(&sasjswork/myfile.txt);
+
+
+ @param [in] fpath Full path to file to be read
+
+ Related Macros
+ @li mf_deletefile.sas
+ @li mf_writefile.sas
+ @li mf_readfile.test.sas
+
+ @version 9.2
+ @author Allan Bowe
+**/
+/** @cond */
+
+%macro mf_readfile(fpath
+)/*/STORE SOURCE*/;
+%local fref rc fid fcontent;
+
+/* check file exists */
+%if %sysfunc(filename(fref,&fpath)) ne 0 %then %do;
+ %put &=fref &=fpath;
+ %put %str(ERR)OR: %sysfunc(sysmsg());
+ %return;
+%end;
+
+%let fid=%sysfunc(fopen(&fref,I));
+
+%if &fid=0 %then %do;
+ %put %str(ERR)OR: %sysfunc(sysmsg());
+ %return;
+%end;
+
+%if %sysfunc(fread(&fid)) = 0 %then %do;
+ %let rc=%sysfunc(fget(&fid,fcontent,65534));
+ &fcontent
+%end;
+
+/*
+%do %while(%sysfunc(fread(&fid)) = 0);
+ %let rc=%sysfunc(fget(&fid,fcontent,65534));
+ &fcontent
+%end;
+*/
+
+%let rc=%sysfunc(fclose(&fid));
+%let rc=%sysfunc(filename(&fref));
+
+%mend mf_readfile;
+/** @endcond */
+/**
@file mf_trimstr.sas
@brief Removes character(s) from the end, if they exist
@details If the designated characters exist at the end of the string, they
@@ -8212,6 +8312,105 @@ data _null_;
run;
%mend mp_gitadd;
+/**
+ @file
+ @brief Creates a dataset with the commit history of a local repository
+ @details Returns the commit history from a local repository. The name of the
+ branch is also returned.
+
+ More details here:
+https://documentation.sas.com/doc/ko/pgmsascdc/v_033/lefunctionsref/n1qo5miyvry1nen111js203hlwrh.htm
+
+ Usage:
+
+ %let gitdir=%sysfunc(pathname(work))/core;
+ %let repo=https://github.com/sasjs/core;
+ %put source clone rc=%sysfunc(GITFN_CLONE(&repo,&dir));
+
+ %mp_gitlog(&gitdir,outds=work.mp_gitlog)
+
+ @param [in] gitdir The directory containing the GIT repository
+ @param [in] filter= (BRANCHONLY) To return only the commits for the current
+ branch, use BRANCHONLY (the default). Anything else will return the entire
+ commit history.
+ @param [out] outds= (work.mp_gitlog) The output dataset to create.
+ All vars are $128 except `message` which is $4000.
+ @li author returns the author who submitted the commit.
+ @li children_ids returns a list of the children commit IDs
+ @li committer returns the name of the committer.
+ @li committer_email returns the email of the committer.
+ @li email returns the email of the commit author.
+ @li id returns the commit ID of the commit object.
+ @li in_current_branch returns "TRUE" or "FALSE" to indicate if the commit is
+ in the current branch.
+ @li message returns the commit message.
+ @li parent_ids returns a list of the parent commit IDs.
+ @li stash returns "TRUE" or "FALSE" to indicate if the commit is a stash
+ commit.
+ @li time returns the time of the commit as numeric string
+ @li commit_time_num time of the commit as numeric SAS datetime
+ @li commit_time_str the commit_time_num variable cast as string
+
+ @param [in] mdebug= (0) Set to 1 to enable DEBUG messages
+
+ SAS Macros
+ @li mf_getgitbranch.sas
+
+ Related Files
+ @li mp_gitadd.sas
+ @li mp_gitreleaseinfo.sas
+ @li mp_gitstatus.sas
+
+**/
+
+%macro mp_gitlog(gitdir,outds=work.mp_gitlog,mdebug=0,filter=BRANCHONLY);
+
+%local varlist i var;
+%let varlist=author children_ids committer committer_email email id
+ in_current_branch parent_ids stash time ;
+
+data &outds;
+ LENGTH gitdir branch $ 1024 message $4000 &varlist $128 commit_time_num 8.
+ commit_time_str $32;
+ call missing (of _all_);
+ branch="%mf_getgitbranch(&gitdir)";
+ gitdir=symget('gitdir');
+ rc=git_status_free(trim(gitdir));
+ if rc=-1 then do;
+ put "The libgit2 library is unavailable and no Git operations can be used.";
+ put "See: https://stackoverflow.com/questions/74082874";
+ stop;
+ end;
+ else if rc=-2 then do;
+ put "The libgit2 library is available, but the status function failed.";
+ put "See the log for details.";
+ stop;
+ end;
+ entries=git_commit_log(trim(gitdir));
+ do n=1 to entries;
+
+ %do i=1 %to %sysfunc(countw(&varlist message));
+ %let var=%scan(&varlist message,&i,%str( ));
+ rc=git_commit_get(n,trim(gitdir),"&var",&var);
+ %end;
+ /* convert unix time to SAS time - https://4gl.uk/corelink0 */
+ /* Number of seconds between 01JAN1960 and 01JAN1970: 315619200 */
+ format commit_time_num datetime19.;
+ commit_time_num=sum(input(cats(time),best.),315619200);
+ commit_time_str=put(commit_time_num,datetime19.);
+ %if &mdebug=1 %then %do;
+ putlog (_all_)(=);
+ %end;
+ if "&filter"="BRANCHONLY" then do;
+ if cats(in_current_branch)='TRUE' then output;
+ end;
+ else output;
+ end;
+ rc=git_commit_free(trim(gitdir));
+ keep gitdir branch &varlist message time commit_time_num commit_time_str;
+run;
+
+%mend mp_gitlog;
/**
@file
@brief Pulls latest release info from a GIT repository
@@ -8348,7 +8547,7 @@ data &outds;
putlog (_all_)(=);
%end;
end;
- rc=git_status_free(gitdir);
+ rc=git_status_free(trim(gitdir));
drop rc cnt;
run;
diff --git a/base/mf_getgitbranch.sas b/base/mf_getgitbranch.sas
new file mode 100644
index 0000000..b46b8c3
--- /dev/null
+++ b/base/mf_getgitbranch.sas
@@ -0,0 +1,37 @@
+/**
+ @file
+ @brief Retrieves the current branch from a local GIT repo
+ @details In a local git repository, the current branch is always available in
+ the `.git/HEAD` file in a format like this: `ref: refs/heads/master`
+
+ This macro simply reads the file and returns the last word (eg `master`).
+
+ Example usage:
+
+ %let gitdir=%sysfunc(pathname(work))/core;
+ %let repo=https://github.com/sasjs/core;
+ %put source clone rc=%sysfunc(GITFN_CLONE(&repo,&gitdir));
+
+ %put The current branch is %mf_getgitbranch(&gitdir);
+
+ @param [in] gitdir The directory containing the GIT repository
+
+ SAS Macros
+ @li mf_readfile.sas
+
+ Related Macros
+ @li mp_gitadd.sas
+ @li mp_gitlog.sas
+ @li mp_gitreleaseinfo.sas
+ @li mp_gitstatus.sas
+
+ @version 9.2
+ @author Allan Bowe
+**/
+
+%macro mf_getgitbranch(gitdir
+)/*/STORE SOURCE*/;
+
+ %scan(%mf_readfile(&gitdir/.git/HEAD),-1)
+
+%mend mf_getgitbranch;
diff --git a/base/mf_readfile.sas b/base/mf_readfile.sas
new file mode 100644
index 0000000..fa7fd97
--- /dev/null
+++ b/base/mf_readfile.sas
@@ -0,0 +1,63 @@
+/**
+ @file
+ @brief Reads the first line of a file using pure macro
+ @details Reads the first line of a file and returns it. Future versions may
+ read each line into a macro variable array.
+
+ Generally, reading data into macro variables is not great as certain
+ nonprintable characters (such as CR, LF) may be dropped in the conversion.
+
+ Usage:
+
+ %mf_writefile(&sasjswork/myfile.txt,l1=some content,l2=more content)
+
+ %put %mf_readfile(&sasjswork/myfile.txt);
+
+
+ @param [in] fpath Full path to file to be read
+
+ Related Macros
+ @li mf_deletefile.sas
+ @li mf_writefile.sas
+ @li mf_readfile.test.sas
+
+ @version 9.2
+ @author Allan Bowe
+**/
+/** @cond */
+
+%macro mf_readfile(fpath
+)/*/STORE SOURCE*/;
+%local fref rc fid fcontent;
+
+/* check file exists */
+%if %sysfunc(filename(fref,&fpath)) ne 0 %then %do;
+ %put &=fref &=fpath;
+ %put %str(ERR)OR: %sysfunc(sysmsg());
+ %return;
+%end;
+
+%let fid=%sysfunc(fopen(&fref,I));
+
+%if &fid=0 %then %do;
+ %put %str(ERR)OR: %sysfunc(sysmsg());
+ %return;
+%end;
+
+%if %sysfunc(fread(&fid)) = 0 %then %do;
+ %let rc=%sysfunc(fget(&fid,fcontent,65534));
+ &fcontent
+%end;
+
+/*
+%do %while(%sysfunc(fread(&fid)) = 0);
+ %let rc=%sysfunc(fget(&fid,fcontent,65534));
+ &fcontent
+%end;
+*/
+
+%let rc=%sysfunc(fclose(&fid));
+%let rc=%sysfunc(filename(&fref));
+
+%mend mf_readfile;
+/** @endcond */
diff --git a/base/mp_gitlog.sas b/base/mp_gitlog.sas
new file mode 100644
index 0000000..830fc18
--- /dev/null
+++ b/base/mp_gitlog.sas
@@ -0,0 +1,99 @@
+/**
+ @file
+ @brief Creates a dataset with the commit history of a local repository
+ @details Returns the commit history from a local repository. The name of the
+ branch is also returned.
+
+ More details here:
+https://documentation.sas.com/doc/ko/pgmsascdc/v_033/lefunctionsref/n1qo5miyvry1nen111js203hlwrh.htm
+
+ Usage:
+
+ %let gitdir=%sysfunc(pathname(work))/core;
+ %let repo=https://github.com/sasjs/core;
+ %put source clone rc=%sysfunc(GITFN_CLONE(&repo,&dir));
+
+ %mp_gitlog(&gitdir,outds=work.mp_gitlog)
+
+ @param [in] gitdir The directory containing the GIT repository
+ @param [in] filter= (BRANCHONLY) To return only the commits for the current
+ branch, use BRANCHONLY (the default). Anything else will return the entire
+ commit history.
+ @param [out] outds= (work.mp_gitlog) The output dataset to create.
+ All vars are $128 except `message` which is $4000.
+ @li author returns the author who submitted the commit.
+ @li children_ids returns a list of the children commit IDs
+ @li committer returns the name of the committer.
+ @li committer_email returns the email of the committer.
+ @li email returns the email of the commit author.
+ @li id returns the commit ID of the commit object.
+ @li in_current_branch returns "TRUE" or "FALSE" to indicate if the commit is
+ in the current branch.
+ @li message returns the commit message.
+ @li parent_ids returns a list of the parent commit IDs.
+ @li stash returns "TRUE" or "FALSE" to indicate if the commit is a stash
+ commit.
+ @li time returns the time of the commit as numeric string
+ @li commit_time_num time of the commit as numeric SAS datetime
+ @li commit_time_str the commit_time_num variable cast as string
+
+ @param [in] mdebug= (0) Set to 1 to enable DEBUG messages
+
+ SAS Macros
+ @li mf_getgitbranch.sas
+
+ Related Files
+ @li mp_gitadd.sas
+ @li mp_gitreleaseinfo.sas
+ @li mp_gitstatus.sas
+
+**/
+
+%macro mp_gitlog(gitdir,outds=work.mp_gitlog,mdebug=0,filter=BRANCHONLY);
+
+%local varlist i var;
+%let varlist=author children_ids committer committer_email email id
+ in_current_branch parent_ids stash time ;
+
+data &outds;
+ LENGTH gitdir branch $ 1024 message $4000 &varlist $128 commit_time_num 8.
+ commit_time_str $32;
+ call missing (of _all_);
+ branch="%mf_getgitbranch(&gitdir)";
+ gitdir=symget('gitdir');
+ rc=git_status_free(trim(gitdir));
+ if rc=-1 then do;
+ put "The libgit2 library is unavailable and no Git operations can be used.";
+ put "See: https://stackoverflow.com/questions/74082874";
+ stop;
+ end;
+ else if rc=-2 then do;
+ put "The libgit2 library is available, but the status function failed.";
+ put "See the log for details.";
+ stop;
+ end;
+ entries=git_commit_log(trim(gitdir));
+ do n=1 to entries;
+
+ %do i=1 %to %sysfunc(countw(&varlist message));
+ %let var=%scan(&varlist message,&i,%str( ));
+ rc=git_commit_get(n,trim(gitdir),"&var",&var);
+ %end;
+ /* convert unix time to SAS time - https://4gl.uk/corelink0 */
+ /* Number of seconds between 01JAN1960 and 01JAN1970: 315619200 */
+ format commit_time_num datetime19.;
+ commit_time_num=sum(input(cats(time),best.),315619200);
+ commit_time_str=put(commit_time_num,datetime19.);
+ %if &mdebug=1 %then %do;
+ putlog (_all_)(=);
+ %end;
+ if "&filter"="BRANCHONLY" then do;
+ if cats(in_current_branch)='TRUE' then output;
+ end;
+ else output;
+ end;
+ rc=git_commit_free(trim(gitdir));
+ keep gitdir branch &varlist message time commit_time_num commit_time_str;
+run;
+
+%mend mp_gitlog;
diff --git a/base/mp_gitstatus.sas b/base/mp_gitstatus.sas
index b27d52d..0428483 100644
--- a/base/mp_gitstatus.sas
+++ b/base/mp_gitstatus.sas
@@ -60,7 +60,7 @@ data &outds;
putlog (_all_)(=);
%end;
end;
- rc=git_status_free(gitdir);
+ rc=git_status_free(trim(gitdir));
drop rc cnt;
run;
diff --git a/tests/base/mf_getgitbranch.test.sas b/tests/base/mf_getgitbranch.test.sas
new file mode 100644
index 0000000..d1225c9
--- /dev/null
+++ b/tests/base/mf_getgitbranch.test.sas
@@ -0,0 +1,20 @@
+/**
+ @file
+ @brief Testing mf_getgitbranch.sas macro
+
+ SAS Macros
+ @li mf_getgitbranch.sas
+ @li mp_assert.sas
+
+**/
+
+/* grab core repo */
+%let gitdir=%sysfunc(pathname(work))/core;
+%let repo=https://github.com/sasjs/core;
+%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&gitdir));
+
+%mp_assert(
+ iftrue=(%mf_getgitbranch(&gitdir)=main),
+ desc=Checking correct branch was obtained,
+ outds=work.test_results
+)
diff --git a/tests/base/mf_readfile.test.sas b/tests/base/mf_readfile.test.sas
new file mode 100644
index 0000000..6a16908
--- /dev/null
+++ b/tests/base/mf_readfile.test.sas
@@ -0,0 +1,40 @@
+/**
+ @file
+ @brief Testing mf_readfile.sas macro
+
+ SAS Macros
+ @li mf_readfile.sas
+ @li mf_writefile.sas
+ @li mp_assert.sas
+ @li mp_assertscope.sas
+
+**/
+
+%let f=&sasjswork/myfile.txt;
+
+%mf_writefile(&f,l1=some content,l2=more content)
+data _null_;
+ infile "&f";
+ input;
+ putlog _infile_;
+run;
+
+%mp_assert(
+ iftrue=(&syscc=0),
+ desc=Check code ran without errors,
+ outds=work.test_results
+)
+
+/* test for scope leakage */
+%global result;
+%mp_assertscope(SNAPSHOT)
+%put %mf_readfile(&f);
+%mp_assertscope(COMPARE)
+
+/* test result */
+%mp_assert(
+ iftrue=(%mf_readfile(&f)=some content),
+ desc=Checking first line was ingested successfully,
+ outds=work.test_results
+)
+
diff --git a/tests/base/mp_gitlog.test.sas b/tests/base/mp_gitlog.test.sas
new file mode 100644
index 0000000..ad6a40c
--- /dev/null
+++ b/tests/base/mp_gitlog.test.sas
@@ -0,0 +1,32 @@
+/**
+ @file
+ @brief Testing mp_gitlog.sas macro
+
+ SAS Macros
+ @li mf_nobs.sas
+ @li mp_gitlog.sas
+ @li mp_assert.sas
+ @li mp_assertscope.sas
+
+**/
+
+/* grab core repo */
+%let gitdir=%sysfunc(pathname(work))/core;
+%let repo=https://github.com/sasjs/core;
+%put source clone rc=%sysfunc(GITFN_CLONE(&repo,&gitdir));
+
+%mp_assertscope(SNAPSHOT)
+%mp_gitlog(&gitdir,outds=work.test1)
+%mp_assertscope(COMPARE)
+
+%mp_assert(
+ iftrue=(&syscc=0),
+ desc=Regular test works,
+ outds=work.test_results
+)
+
+%mp_assert(
+ iftrue=(%mf_nobs(work.test1)>1000),
+ desc=output has gt 1000 rows,
+ outds=work.test_results
+)