GitHub-Crud/lib/GitHub/CrudBeforeNoEncode.pm
#!/usr/bin/perl -I/home/phil/perl/cpan/DataTableText/lib
#-------------------------------------------------------------------------------
# Create, Read, Update, Delete files, commits, issues, and web hooks on GitHub.
# Per: https://developer.github.com/v3/
# Philip R Brenan at gmail dot com, Appa Apps Ltd, 2017-2020
#-------------------------------------------------------------------------------
#podDocumentation
package GitHub::Crud;
use v5.16;
our $VERSION = 20240203;
use warnings FATAL => qw(all);
use strict;
use Carp qw(confess);
use Data::Dump qw(dump);
use Data::Table::Text qw(:all !fileList);
use Digest::SHA1 qw(sha1_hex);
#use Date::Manip;
use Scalar::Util qw(blessed reftype looks_like_number);
use Time::HiRes qw(time);
use Encode qw(encode decode);
use utf8; # To allow utf8 constants for testing
sub url { "https://api.github.com/repos" } # Github repository api url
sub api { "https://api.github.com/" } # Github api url
sub accessFolder { q(/etc/GitHubCrudPersonalAccessToken) }; # Personal access tokens are stored in a file in this folder with the name of the userid of the L<GitHub> repository
my %shas; # L<SHA> digests already seen - used to optimize write and delete
sub specialFileData($) #P Do not encode or decode data with a known file signature
{my ($d) = @_; # String to check
my $h = '';
if ($d and length($d) > 8) # Read file magic number
{for my $e(0..7)
{$h .= sprintf("%x", ord(substr($d, $e, 1)));
}
return 1 if $h =~ m(\A504b)i; # PK Zip
return 1 if $h =~ m(\Ad0cf11e0)i; # OLE files
return 1 if $h =~ m(\Affd8ff)i; # Jpg
return 1 if $h =~ m(\A89504e470d0a1a0a)i; # Png
return 1 if $h =~ m(\A4D546864)i; # Midi
return 1 if $h =~ m(\A49443340)i; # Mp3
return 1 if $d =~ m(\A.PNG); # Png
return 1 if $d =~ m(\ARIFF....WAVEfmt); # Wav
}
0 # Not a special file
}
sub GitHub::Crud::Response::new($$) #P Execute a request against L<GitHub> and decode the response
{my ($gitHub, $request) = @_; # Github, request string
my $R = bless {command=>$request}, "GitHub::Crud::Response"; # Construct the response
my $r = xxx $request, qr(HTTP);
$r =~ s/\r//gs; # Internet line ends
my ($http, @r) = split /\n/, $r;
while(@r > 2 and $http =~ "HTTP" and $http =~ /100/) # Continue messages
{shift @r; $http = shift @r;
}
if ($http and $http =~ "HTTP" and $http =~ /200|201|404|409|422/)
{my $ps = 0; # Parse the response
my @data;
my %can;
for(@r)
{if ($ps == 0)
{if (length == 0)
{$ps = 1;
}
else
{my ($name, $content) = split /\s*:\s*/, $_, 2; # Parse each header
$name =~ s/-/_/gs; # Translate - in names to _
if ($R->can($name))
{$R->$name = $content;
}
else {$can{$name}++} # Update list of new methods required
}
}
else
{push @data, $_;
}
}
if (keys %can) # List of new methods required
{lll "Add the following fields to package GitHub::Crud::Response, continuing ...";
say STDERR " $_=> undef," for(sort keys %can);
}
if (@data) # Save any data
{my $j = join ' ', @data;
my $p = $R->data = bless decodeJson($j), "GitHub::Crud::Response::Data";
if (ref($p) =~ m/hash/is and my $c = $p->content)
{$R->content = decodeBase64($c); # Decode the data
}
}
($R->status) = split / /, $R->Status || $R->status || 200; # Save response status - github returns status == 0 when running as an action so we make it 200
return $gitHub->response = $R; # Return successful response
}
else
{confess "Unexpected response from GitHub:\n$r\n$request\n"; # Confess to failure
}
}
genHash(q(GitHub::Crud::Response), # Attributes describing a response from L<GitHub>.
Accept_Ranges => undef,
access_control_allow_origin => undef,
Access_Control_Allow_Origin => undef,
access_control_expose_headers => undef,
Access_Control_Expose_Headers => undef,
cache_control => undef,
Cache_Control => undef,
Connection => undef,
content_length => undef,
Content_Length => undef,
content_security_policy => undef,
Content_Security_Policy => undef,
content_type => undef,
Content_Type => undef,
content => undef, # The actual content of the file from L<GitHub>.
data => undef, # The data received from L<GitHub>, normally in L<json> format.
date => undef,
Date => undef,
etag => undef,
ETag => undef,
Expires => undef,
last_modified => undef,
Last_Modified => undef,
Link => undef,
Location => undef,
referrer_policy => undef,
Referrer_Policy => undef,
server => undef,
Server => undef,
Source_Age => undef,
Status => undef,
status => undef, # Our version of Status.
strict_transport_security => undef,
Strict_Transport_Security => undef,
vary => undef,
Vary => undef,
Via => undef,
x_accepted_oauth_scopes => undef,
X_Accepted_OAuth_Scopes => undef,
X_Cache_Hits => undef,
X_Cache => undef,
x_content_type_options => undef,
X_Content_Type_Options => undef,
X_Content_Type => undef,
X_Fastly_Request_ID => undef,
x_frame_options => undef,
X_Frame_Options => undef,
X_Geo_Block_List => undef,
x_github_media_type => undef,
X_GitHub_Media_Type => undef,
x_github_request_id => undef,
X_GitHub_Request_Id => undef,
x_oauth_scopes => undef,
X_OAuth_Scopes => undef,
x_ratelimit_limit => undef,
X_RateLimit_Limit => undef,
x_ratelimit_remaining => undef,
X_RateLimit_Remaining => undef,
x_ratelimit_reset => undef,
X_RateLimit_Reset => undef,
x_ratelimit_used => undef,
X_RateLimit_Used => undef,
X_Runtime_rack => undef,
X_Served_By => undef,
X_Timer => undef,
x_xss_protection => undef,
X_XSS_Protection => undef,
x_ratelimit_resource => undef,
x_github_api_version_selected => undef,
);
genHash(q(GitHub::Crud::Response::Data), # Response from a request made to L<GitHub>.
command => undef,
content => undef,
documentation_url => undef,
download_url => undef,
encoding => undef,
git => undef,
git_url => undef,
html => undef,
html_url => undef,
_links => undef,
message => undef,
name => undef,
path => undef,
self => undef,
sha => undef,
size => undef,
type => undef,
url => undef,
);
sub getSha($) #P Compute L<sha> for data after encoding any unicode characters as utf8.
{my ($data) = @_; # String possibly containing non ascii code points
my $length = length($data);
my $blob = 'blob' . " $length\0" . $data;
utf8::encode($blob) unless specialFileData($data); # Utf8 is assumed for things which are not known to be files containing images, sounds or other binary data in a well known format
my $r = eval{sha1_hex($blob)};
confess $@ if $@;
$r
}
if (0) # Test L<sha>
{my $sha = getSha("<h1>Hello World</h1>\n");
my $Sha = "f3e333e80d224c631f2ff51b9b9f7189ad349c15";
unless($sha eq $Sha)
{confess "Wrong SHA: $sha".
"Should be: $Sha";
}
confess "getSha success";
}
sub shaKey($;$) #P Add a L<SHA> key to a L<url>
{my ($gitHub, $fileData) = @_; # Github, optional fileData to specify the file to use if it is not gitFile
filePath($gitHub->repository,
$fileData ? ($fileData->path, $fileData->name) : $gitHub->gitFile)
}
sub saveSha($$) #P Save the L<sha> of a file
{my ($gitHub, $fileData) = @_; # Github, file details returned by list or exists
$shas{$gitHub->shaKey($fileData)} = $fileData->sha;
}
sub copySha($) #P Save the L<sha> of a file just read to a file just about to be written
{my ($gitHub) = @_; # Github
$shas{$gitHub->shaKey} = $gitHub->response->data->sha;
}
sub getExistingSha($) #P Get the L<sha> of a file that already exists
{my ($gitHub) = @_; # Github
my $s = $shas{$gitHub->shaKey}; # Get the L<sha> from the cache
return $s if defined $s; # A special L<sha> of 0 means the file was deleted
my $r = $gitHub->exists; # Get the L<sha> of the file via exists if the file exists
return $r->sha if $r; # L<sha> of existing file
undef # Undef if no such file
}
sub deleteSha($) #P Delete a L<sha> that is no longer valid
{my ($gitHub) = @_; # Github
$shas{$gitHub->shaKey} = undef # Mark the L<sha> as deleted
}
sub qm($) #P Quotemeta extended to include undef
{my ($s) = @_; # String to quote
return '' unless $s;
$s =~ s((\'|\"|\\)) (\\$1)gs;
$s =~ s(\s) (%20)gsr; # Url encode blanks
}
sub patKey($) #P Create an authorization header by locating an appropriate personal access token
{my ($gitHub) = @_; # GitHub
$gitHub->loadPersonalAccessToken unless $gitHub->personalAccessToken; # Load a personal access token if none has been supplied
if (my $pat = $gitHub->personalAccessToken) # User supplied personal access token explicitly
{return "-H \"Authorization: token $pat\""
}
confess "Personal access token required with scope \"public_repo\"". # We must have a personal access token to do anything useful!
" as generated on page:\nhttps://github.com/settings/tokens";
}
sub refOrBranch($$) #P Add a ref or branch keyword
{my ($gitHub, $ref) = @_; # Github, whether to use ref rather than branch
my $b = $gitHub->branch;
return "?ref=$b" if $ref and $b;
return "?branch=$b" if !$ref and $b;
''
}
sub gitHub(%) #P Create a test L<GitHub> object
{my (%options) = @_; # Options
GitHub::Crud::new
(userid => q(philiprbrenan),
repository => q(aaa),
confessOnFailure => 1,
@_);
}
#D1 Constructor # Create a L<GitHub> object with the specified attributes describing the interface with L<github>.
sub new(@) # Create a new L<GitHub> object with attributes as described at: L<GitHub::Crud Definition>.
{my (%attributes) = @_; # Attribute values
my $curl = qx(curl -V); # Check Curl
if ($curl =~ /command not found/)
{confess "Command curl not found"
}
my $g = genHash(__PACKAGE__, # Attributes describing the interface with L<github>.
body => undef, #I The body of an issue.
branch => undef, #I Branch name (you should create this branch first) or omit it for the default branch which is usually 'master'.
confessOnFailure => undef, #I Confess to any failures
failed => undef, # Defined if the last request to L<GitHub> failed else B<undef>.
fileList => undef, # Reference to an array of files produced by L<list|/list>.
gitFile => undef, #I File name on L<GitHub> - this name can contain '/'. This is the file to be read from, written to, copied from, checked for existence or deleted.
gitFolder => undef, #I Folder name on L<GitHub> - this name can contain '/'.
message => undef, #I Optional commit message
nonRecursive => undef, #I Fetch only one level of files with L<list>.
personalAccessToken => undef, #I A personal access token with scope "public_repo" as generated on page: https://github.com/settings/tokens.
personalAccessTokenFolder => accessFolder, #I The folder into which to save personal access tokens. Set to q(/etc/GitHubCrudPersonalAccessToken) by default.
private => undef, #I Whether the repository being created should be private or not.
readData => undef, # Data produced by L<read|/read>.
repository => undef, #I The name of the repository to be worked on minus the userid - you should create this repository first manually.
response => undef, # A reference to L<GitHub>'s response to the latest request.
secret => undef, #I The secret for a web hook - this is created by the creator of the web hook and remembered by L<GitHub>,
title => undef, #I The title of an issue.
webHookUrl => undef, #I The url for a web hook.
userid => undef, #I Userid on L<GitHub> of the repository to be worked on.
);
$g->$_ = $attributes{$_} for sort keys %attributes;
$g
}
#D1 Files # File actions on the contents of L<GitHub> repositories.
sub list($) # List all the files contained in a L<GitHub> repository or all the files below a specified folder in the repository.\mRequired attributes: L<userid|/userid>, L<repository|/repository>.\mOptional attributes: L<gitFolder|/gitFolder>, L<refOrBranch|/refOrBranch>, L<nonRecursive|/nonRecursive>, L<patKey|/patKey>.\mUse the L<gitFolder|/gitFolder> parameter to specify the folder to start the list from, by default, the listing will start at the root folder of your repository.\mUse the L<nonRecursive|/nonRecursive> option if you require only the files in the start folder as otherwise all the folders in the start folder will be listed as well which might take some time.\mIf the list operation is successful, L<failed|/failed> is set to false and L<fileList|/fileList> is set to refer to an array of the file names found.\mIf the list operation fails then L<failed|/failed> is set to true and L<fileList|/fileList> is set to refer to an empty array.\mReturns the list of file names found or empty list if no files were found.
{my ($gitHub) = @_; # GitHub
my $r = sub # Get contents
{my $user = qm $gitHub->userid; $user or confess "userid required";
my $repo = qm $gitHub->repository; $repo or confess "repository required";
my $path = qm $gitHub->gitFolder || '';
my $bran = qm $gitHub->refOrBranch(1);
my $pat = $gitHub->patKey(0);
my $url = url;
my $s = filePath
("curl -si $pat $url", $user, $repo, qq(contents), $path.$bran);
GitHub::Crud::Response::new($gitHub, $s);
}->();
my $failed = $gitHub->failed = $r->status != 200; # Check response code
$failed and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
$gitHub->fileList = [];
if (!$failed and reftype($r->data) =~ m(array)i) # Array of file details
{for(@{$r->data}) # Objectify and save L<sha> digests from file descriptions retrieved by this call
{bless $_, "GitHub::Crud::Response::Data";
saveSha($gitHub, $_);
}
my $path = $gitHub->gitFolder || '';
my @d = map{filePath $path, $_->name} grep {$_->type eq "dir"} @{$r->data};# Folders
my @f = map{filePath $path, $_->name} grep {$_->type eq "file"} @{$r->data};# Files
unless($gitHub->nonRecursive) # Get the contents of sub folders unless otherwise specified
{for my $d(@d)
{my $p = $gitHub->gitFolder = $d;
push @f, $gitHub->list;
}
}
$gitHub->gitFolder = $path; # Restore path supplied by the user
$gitHub->fileList = [@f]; # List of files not directories
}
@{$gitHub->fileList}
}
sub read($;$) # Read data from a file on L<GitHub>.\mRequired attributes: L<userid|/userid>, L<repository|/repository>.\mOptional attributes: L<gitFile|/gitFile> = the file to read, L<refOrBranch|/refOrBranch>, L<patKey|/patKey>.\mIf the read operation is successful, L<failed|/failed> is set to false and L<readData|/readData> is set to the data read from the file.\mIf the read operation fails then L<failed|/failed> is set to true and L<readData|/readData> is set to B<undef>.\mReturns the data read or B<undef> if no file was found.
{my ($gitHub, $File) = @_; # GitHub, file to read if not specified in gitFile
my $user = qm $gitHub->userid; $user or confess "userid required";
my $repo = qm $gitHub->repository; $repo or confess "repository required";
my $file = qm($File//$gitHub->gitFile); $file or confess "gitFile required";
my $bran = qm $gitHub->refOrBranch(1);
my $pat = $gitHub->patKey(0);
my $url = url;
my $s = filePath(qq(curl -si $pat $url),
$user, $repo, qq(contents), $file.$bran);
my $r = GitHub::Crud::Response::new($gitHub, $s); # Get response from GitHub
my $failed = $gitHub->failed = $r->status != 200; # Check response code
$failed and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
if ($failed) # Decode data unless read failed
{$gitHub->readData = undef;
}
else # Decode data
{my $d = decodeBase64($r->data->content);
$gitHub->readData = specialFileData($d) ? $d : decode "UTF8", $d; # Convert to utf unless a known file format
}
$gitHub->readData
}
sub write($$;$) # Write the specified data into a file on L<GitHub>.\mRequired attributes: L<userid|/userid>, L<repository|/repository>, L<patKey|/patKey>. Either specify the target file on: L<GitHub> using the L<gitFile|/gitFile> attribute or supply it as the third parameter. Returns B<true> on success else L<undef>.
{my ($gitHub, $data, $File) = @_; # GitHub object, data to be written, optionally the name of the file on github
unless($data) # No data supplied so delete the file
{if ($File)
{my $file = $gitHub->file;
$gitHub->file = $File;
$gitHub->delete;
$gitHub->file = $file;
}
else
{$gitHub->delete;
}
return 'empty'; # Success
}
my $pat = $gitHub->patKey(1);
my $user = qm $gitHub->userid; $user or confess "userid required";
my $repo = qm $gitHub->repository; $repo or confess "repository required";
my $file = qm($File//$gitHub->gitFile); $file or confess "gitFile required";
my $bran = qm $gitHub->refOrBranch(0) || '?';
my $mess = qm $gitHub->message;
if (!specialFileData($data)) # Send the data as utf8 unless it is a special file
{use Encode 'encode';
$data = encode('UTF-8', $data);
}
my $url = url;
my $save = $gitHub->gitFile; # Save any existing file name as we might need to update it to get the sha if the target file was supplied as a parameter to this sub
$gitHub->gitFile = $File if $File; # Set target file name so we can get its sha
my $s = getSha($data); # Get the L<sha> of the file if the file exists
my $S = $gitHub->getExistingSha; # Get the L<sha> of the file if the file exists
$gitHub->gitFile = $save; # Restore file name
my $sha = $s ? ', "sha": "'. $s .'"' : ''; # L<sha> of existing file or blank string if no existing file
if ($S) # Skip upload if file has not changed. L<sha> of new data
{if ($s eq $S) # Duplicate if the L<sha>s match
{$gitHub->failed = undef;
return "same";
}
}
my $denc = encodeBase64($data) =~ s/\n//gsr;
my $branch = sub # It seems we must put the branch in the json file though the documentation seems to imply it can go in the url or the json
{my $b = $gitHub->branch;
return qq(, "branch" : "$b") if $b;
q()
}->();
my $h = join ' ', map {qq(-H "$_")}
q(Accept: application/vnd.github+json),
q(X-GitHub-Api-Version: 2022-11-28);
my $j = qq({"message": "$mess", "content": "$denc" $sha $branch});
my $t = writeFile(undef, $j); # Write encoded content to temporary file
my $d = qq(-d @).$t;
my $u = filePath($url, $user, $repo, qw(contents), $file.$bran);
my $c = qq(curl -si -X PUT $pat $u $d); # Curl command
lll "AAAA\n", $c;
my $r = GitHub::Crud::Response::new($gitHub, $c); # Execute command to create response
unlink $t; # Cleanup
my $status = $r->status; # Check response code
my $success = $status == 200 ? 'updated' : $status == 201 ? 'created' : undef;# Updated, created
$gitHub->failed = $success ? undef : 1;
!$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
$success # Return true on success
}
sub readBlob($$) # Read a L<blob> from L<GitHub>.\mRequired attributes: L<userid|/userid>, L<repository|/repository>, L<patKey|/patKey>. Returns the content of the L<blob> identified by the specified L<sha>.
{my ($gitHub, $sha) = @_; # GitHub object, data to be written
defined($sha) or confess "sha required";
my $pat = $gitHub->patKey(1);
my $user = qm $gitHub->userid; $user or confess "userid required";
my $repo = qm $gitHub->repository; $repo or confess "repository required";
my $url = url;
my $u = filePath($url, $user, $repo, qw(git blobs), $sha); # Url
my $c = qq(curl -si $pat $u); # Curl command
my $r = GitHub::Crud::Response::new($gitHub, $c); # Execute command to create response
my $status = $r->status; # Check response code
my $success = $status == 200;
$gitHub->failed = $success ? undef : 1;
!$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
$success ? decodeBase64($gitHub->response->data->content) : undef # Return content on success else undef
}
sub writeBlob($$) # Write data into a L<GitHub> as a L<blob> that can be referenced by future commits.\mRequired attributes: L<userid|/userid>, L<repository|/repository>, L<patKey|/patKey>. Returns the L<sha> of the created L<blob> or L<undef> in a failure occurred.
{my ($gitHub, $data) = @_; # GitHub object, data to be written
defined($data) or confess "binary data required";
my $pat = $gitHub->patKey(1);
my $user = qm $gitHub->userid; $user or confess "userid required";
my $repo = qm $gitHub->repository; $repo or confess "repository required";
my $url = url;
my $denc = encodeBase64($data) =~ s/\n//gsr;
my $t = writeTempFile(qq({"content": "$denc", "encoding" : "base64"})); # Write encoded content to temporary file
my $d = qq(-d @).$t;
my $u = filePath($url, $user, $repo, qw(git blobs));
my $c = qq(curl -si -X POST $pat $u $d); # Curl command
my $r = GitHub::Crud::Response::new($gitHub, $c); # Execute command to create response
unlink $t; # Cleanup
my $status = $r->status; # Check response code
my $success = $status == 200 ? 'updated' : $status == 201 ? 'created' : undef;# Updated, created
$gitHub->failed = $success ? undef : 1;
!$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
$success ? $gitHub->response->data->sha : undef # Return sha of blob on success
}
sub copy($$) # Copy a source file from one location to another target location in your L<GitHub> repository, overwriting the target file if it already exists.\mRequired attributes: L<userid|/userid>, L<repository|/repository>, L<patKey|/patKey>, L<gitFile|/gitFile> = the file to be copied.\mOptional attributes: L<refOrBranch|/refOrBranch>.\mIf the write operation is successful, L<failed|/failed> is set to false otherwise it is set to true.\mReturns B<updated> if the write updated the file, B<created> if the write created the file else B<undef> if the write failed.
{my ($gitHub, $target) = @_; # GitHub object, the name of the file to be created
defined($target) or confess "Specify the name of the file to be copied to";
my $r = $gitHub->read; # Read the content of the source file
if (defined $r)
{my $file = $gitHub->gitFile; # Save current source file
my $sha = $gitHub->response->data->sha; # L<sha> of last file read
$gitHub->gitFile = $target; # Set target file as current file
my $R = $gitHub->write($r); # Write content to target file
$gitHub->copySha; # Copy the L<sha> from the file just read
$gitHub->gitFile = $file; # Restore source file
return $R; # Return response from write
}
undef # Failed
}
sub exists($) # Test whether a file exists on L<GitHub> or not and returns an object including the B<sha> and B<size> fields if it does else L<undef>.\mRequired attributes: L<userid|/userid>, L<repository|/repository>, L<gitFile|/gitFile> file to test.\mOptional attributes: L<refOrBranch|/refOrBranch>, L<patKey|/patKey>.
{my ($gitHub) = @_; # GitHub object
my @file = split /\//, $gitHub->gitFile;
confess "gitFile required to name the file to be checked" unless @file;
pop @file;
my $folder = $gitHub->gitFolder;
my $nonRecursive = $gitHub->nonRecursive;
$gitHub->gitFolder = filePath(@file);
$gitHub->nonRecursive = 1;
my $r = $gitHub->list; # Get a file listing
$gitHub->gitFolder = $folder;
$gitHub->nonRecursive = $nonRecursive;
if (!$gitHub->failed and reftype($gitHub->response->data) =~ m(array)i) # Look for requested file in file listing
{for(@{$gitHub->response->data})
{return $_ if $_->path eq $gitHub->gitFile;
}
}
undef
}
sub rename($$) # Rename a source file on L<GitHub> if the target file name is not already in use.\mRequired attributes: L<userid|/userid>, L<repository|/repository>, L<patKey|/patKey>, L<gitFile|/gitFile> = the file to be renamed.\mOptional attributes: L<refOrBranch|/refOrBranch>.\mReturns the new name of the file B<renamed> if the rename was successful else B<undef> if the rename failed.
{my ($gitHub, $target) = @_; # GitHub object, the new name of the file
my $file = $gitHub->gitFile;
$gitHub->gitFile = $target;
return undef if $gitHub->exists;
$gitHub->gitFile = $file;
$gitHub->copy($target);
$gitHub->gitFile = $target;
if ($gitHub->exists)
{$gitHub->gitFile = $file;
return $target if $gitHub->delete;
confess "Failed to delete source file $file";
}
undef
}
sub delete($) # Delete a file from L<GitHub>.\mRequired attributes: L<userid|/userid>, L<repository|/repository>, L<patKey|/patKey>, L<gitFile|/gitFile> = the file to be deleted.\mOptional attributes: L<refOrBranch|/refOrBranch>.\mIf the delete operation is successful, L<failed|/failed> is set to false otherwise it is set to true.\mReturns true if the delete was successful else false.
{my ($gitHub) = @_; # GitHub object
my $pat = $gitHub->patKey(1);
my $user = qm $gitHub->userid; $user or confess "userid required";
my $repo = qm $gitHub->repository; $repo or confess "repository required";
my $file = qm $gitHub->gitFile; $file or confess "file to delete required";
my $bran = qm $gitHub->refOrBranch(0);
my $url = url;
my $s = $gitHub->getExistingSha; # L<sha> of existing file or undef
return 2 unless $s; # File already deleted
my $sha = ' -d \'{"message": "", "sha": "'. $s .'"}\'';
my $u = filePath($url, $user, $repo, qw(contents), $file.$bran.$sha);
my $d = "curl -si -X DELETE $pat $u";
my $r = GitHub::Crud::Response::new($gitHub, $d);
my $success = $r->status == 200; # Check response code
$gitHub->deleteSha if $success; # The L<sha> is no longer valid
$gitHub->failed = $success ? undef : 1;
!$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
$success ? 1 : undef # Return true on success
}
#D1 Repositories # Perform actions on L<github> repositories.
sub getRepository($) # Get the overall details of a repository
{my ($gitHub) = @_; # GitHub object
my $pat = $gitHub->patKey(1);
my $user = qm $gitHub->userid; $user or confess "userid required";
my $repo = qm $gitHub->repository; $repo or confess "repository required";
my $url = url;
my $c = qq(curl -si $pat $url/$user/$repo);
my $r = GitHub::Crud::Response::new($gitHub, $c);
my $success = $r->status == 200; # Check response code
!$success and $gitHub->confessOnFailure and confess dump([$gitHub, $c]); # Confess to any failure if so requested
$r
}
sub listCommits($) # List all the commits in a L<GitHub> repository.\mRequired attributes: L<userid|/userid>, L<repository|/repository>.
{my ($gitHub) = @_; # GitHub object
my $pat = $gitHub->patKey(1);
my $user = qm $gitHub->userid; $user or confess "userid required";
my $repo = qm $gitHub->repository; $repo or confess "repository required";
my $url = url;
my $c = qq(curl -si $pat $url/$user/$repo/branches);
my $r = GitHub::Crud::Response::new($gitHub, $c);
my $success = $r->status == 200; # Check response code
!$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
$r
}
sub listCommitShas($) # Create {commit name => sha} from the results of L</listCommits>.
{my ($commits) = @_; # Commits from L</listCommits>
return undef unless my $data = $commits->data; # Commits array
{map {$$_{name} => $$_{commit}{sha}} @$data} # Commits hash
}
sub writeCommit($$@) # Write all the files in a B<$folder> (or just the the named files) into a L<GitHub> repository in parallel as a commit on the specified branch.\mRequired attributes: L<userid|/userid>, L<repository|/repository>, L<refOrBranch|/refOrBranch>.
{my ($gitHub, $folder, @files) = @_; # GitHub object, file prefix to remove, files to write
-d $folder or confess "No such folder"; # Folder does not exist
my $pat = $gitHub->patKey(1);
my $user = qm $gitHub->userid; $user or confess "userid required";
my $repo = qm $gitHub->repository; $repo or confess "repository required";
my $bran = $gitHub->branch; $bran or confess "branch required";
my $url = url;
my @sha = processFilesInParallel sub # Create blobs for each file
{my ($source) = @_;
my $target = $gitHub->gitFile = swapFilePrefix($source, $folder);
[$source, $target, $gitHub->writeBlob(readBinaryFile($source))]
}, undef, @files ? @files : searchDirectoryTreesForMatchingFiles($folder);
my $tree = sub # Create the tree
{my @t;
for my $f(@sha) # Load files into a tree
{my ($s, $t, $b) = @$f;
push @t, <<END;
{"path" : "$t",
"mode" : "100644",
"type" : "blob",
"sha" : "$b"
}
END
}
my $t = join ",\n", @t; # Assemble tree
my $j = qq({"tree" : [$t]}); # Json describing tree
my $f = writeTempFile($j); # Write Json
my $c = qq(curl -si -X POST $pat -d \@$f $url/$user/$repo/git/trees);
my $r = GitHub::Crud::Response::new($gitHub, $c);
my $success = $r->status == 201; # Check response code
unlink $f; # Cleanup
$success or confess "Unable to create tree: ".dump($r);
$r
}->();
my $parents = sub # Prior commits
{my %c = listCommitShas $gitHub->listCommits;
my $b = $gitHub->branch;
return '' unless my $s = $c{$b};
qq(, "parents" : ["$s"])
}->();
my $commit = sub # Create a commit to hold the tree
{my $s = $tree->data->sha;
my $d = dateTimeStamp;
my $j = <<END;
{ "message" : "Committed by GitHub::Crud on: $d"
, "tree" : "$s"
$parents
}
END
my $f = writeFile(undef, $j); # Write json
my $c = qq(curl -si -X POST $pat -d \@$f $url/$user/$repo/git/commits); # Execute json
my $r = GitHub::Crud::Response::new($gitHub, $c);
my $success = $r->status == 201; # Check response code
unlink $f; # Cleanup
$success or confess "Unable to create commit: ".dump($r);
$r
}->();
my $branch = sub # Update branch - if this fails we will try a force as the next step
{my $s = $commit->data->sha;
my $f = writeFile(undef, <<END);
{
"ref": "refs/heads/$bran",
"sha": "$s"
}
END
my $c = qq(curl -si -X POST $pat -d \@$f $url/$user/$repo/git/refs);
my $r = GitHub::Crud::Response::new($gitHub, $c);
my $success = $r->status == 201; # Check response code
unlink $f; # Cleanup
$r
}->();
my $status = $branch->status; # Creation status
if ($branch->status == 201) {return $branch} # Branch created
elsif ($branch->status == 422) # Update existing branch
{my $branchUpdate = sub
{my $s = $commit->data->sha;
my $f = writeFile(undef, <<END);
{ "sha": "$s",
"force": true
}
END
my $c = qq(curl -si -X PATCH $pat -d \@$f $url/$user/$repo/git/refs/heads/$bran);
my $r = GitHub::Crud::Response::new($gitHub, $c);
my $success = $r->status == 200; # Check response code
unlink $f; # Cleanup
$success or confess "Unable to update branch: ".dump($r);
$r
}->();
return $branchUpdate;
}
confess "Unable to create/update branch: $bran";
}
sub listWebHooks($) # List web hooks associated with your L<GitHub> repository.\mRequired: L<userid|/userid>, L<repository|/repository>, L<patKey|/patKey>. \mIf the list operation is successful, L<failed|/failed> is set to false otherwise it is set to true.\mReturns true if the list operation was successful else false.
{my ($gitHub) = @_; # GitHub object
my $pat = $gitHub->patKey(1);
my $user = qm $gitHub->userid; $user or confess "userid required";
my $repo = qm $gitHub->repository; $repo or confess "repository required";
my $bran = qm $gitHub->refOrBranch(0);
my $url = url;
my $u = filePath($url, $user, $repo, qw(hooks));
my $s = "curl -si $pat $u";
my $r = GitHub::Crud::Response::new($gitHub, $s);
my $success = $r->status =~ m(200|201); # Present or not present
$gitHub->failed = $success ? undef : 1;
!$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
$success ? $gitHub->response->data : undef # Return reference to array of web hooks on success. If there are no web hooks set then the referenced array will be empty.
}
sub createPushWebHook($) # Create a web hook for your L<GitHub> userid.\mRequired: L<userid|/userid>, L<repository|/repository>, L<url|/url>, L<patKey|/patKey>.\mOptional: L<secret|/secret>.\mIf the create operation is successful, L<failed|/failed> is set to false otherwise it is set to true.\mReturns true if the web hook was created successfully else false.
{my ($gitHub) = @_; # GitHub object
my $pat = $gitHub->patKey(1);
my $user = qm $gitHub->userid; $user or confess "userid required";
my $repo = qm $gitHub->repository; $repo or confess "repository required";
my $webUrl = qm $gitHub->webHookUrl; $webUrl or confess "url required";
my $bran = qm $gitHub->refOrBranch(0);
my $secret = $gitHub->secret;
my $sj = $secret ? qq(, "secret": "$secret") : ''; # Secret for Json
my $url = url;
$webUrl =~ m(\Ahttps?://) or confess # Check that we are using a url like thing for the web hook or complain
"Web hook has no scheme, should start with https?:// not:\n$webUrl";
owf(my $tmpFile = temporaryFile(), my $json = <<END); # Write web hook definition
{"name": "web", "active": true, "events": ["push"],
"config": {"url": "$webUrl", "content_type": "json" $sj}
}
END
my $d = q( -d @).$tmpFile;
my $u = filePath($url, $user, $repo, qw(hooks));
my $s = "curl -si -X POST $pat $u $d"; # Create url
my $r = GitHub::Crud::Response::new($gitHub, $s);
my $success = $r->status == 201; # Check response code
unlink $tmpFile; # Cleanup
$gitHub->failed = $success ? undef : 1;
!$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
$success ? 1 : undef # Return true on success
}
sub listRepositories($) # List the repositories accessible to a user on L<GitHub>.\mRequired: L<userid|/userid>.\mReturns details of the repositories.
{my ($gitHub) = @_; # GitHub object
my $pat = $gitHub->patKey(1);
my $user = qm $gitHub->userid; $user or confess "userid required";
my $url = api;
my $u = filePath($url, qw(user repos)); # Request url
my $s = "curl -si $pat $u"; # Create url
my $r = GitHub::Crud::Response::new($gitHub, $s);
my $success = $r->status == 200; # Check response code
$gitHub->failed = $success ? undef : 1;
!$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
$success ? $r->data->@* : undef # Return a list of repositories on success
}
sub createRepository($) # Create a repository on L<GitHub>.\mRequired: L<userid|/userid>, L<repository|/repository>.\mReturns true if the issue was created successfully else false.
{my ($gitHub) = @_; # GitHub object
my $pat = $gitHub->patKey(1);
my $user = qm $gitHub->userid; $user or confess "userid required";
my $repo = qm $gitHub->repository; $repo or confess "repository required";
my $private= $gitHub->private ? q(, "private":true) : q(); # Private or not
my $url = api;
my $json = qq({"name":"$repo", "auto_init":true $private}); # Issue in json
my $tmpFile = writeFile(undef, $json); # Write repo definition
my $d = q( -d @).$tmpFile;
my $u = filePath($url, qw(user repos)); # Request url
my $s = "curl -si -X POST $pat $u $d"; # Create url
my $r = GitHub::Crud::Response::new($gitHub, $s);
my $success = $r->status == 201; # Check response code
unlink $tmpFile; # Cleanup
$gitHub->failed = $success ? undef : 1;
!$success and $gitHub->confessOnFailure and confess dump($gitHub); # Confess to any failure if so requested
$success ? 1 : undef # Return true on success
}
sub createRepositoryFromSavedToken($$;$$) # Create a repository on L<GitHub> using an access token either as supplied or saved in a file using L<savePersonalAccessToken|/savePersonalAccessToken>.\mReturns true if the issue was created successfully else false.
{my ($userid, $repository, $private, $accessFolderOrToken) = @_; # Userid on GitHub, the repository name, true if the repo is private, location of access token.
my $g = GitHub::Crud::new;
$g->userid = $userid;
$g->repository = $repository;
$g->private = $private;
$g->personalAccessTokenFolder = $accessFolderOrToken;
$g->loadPersonalAccessToken;
$g->confessOnFailure = 0;
$g->createRepository;
}
#D1 Issues # Create issues on L<GitHub>.
sub createIssue($) # Create an issue on L<GitHub>.\mRequired: L<userid|/userid>, L<repository|/repository>, L<body|/body>, L<title|/title>.\mIf the operation is successful, L<failed|/failed> is set to false otherwise it is set to true.\mReturns true if the issue was created successfully else false.
{my ($gitHub) = @_; # GitHub object
my $pat = $gitHub->patKey(1);
my $user = qm $gitHub->userid; $user or confess "userid required";
my $repo = qm $gitHub->repository; $repo or confess "repository required";
my $body = $gitHub->body; $body or confess "body required";
my $title = $gitHub->title; $title or confess "title required";
my $bran = qm $gitHub->refOrBranch(0);
my $url = url;
my $json = encodeJson({body=>$body, title=>$title}); # Issue in json
owf(my $tmpFile = temporaryFile(), $json); # Write issue definition
my $d = q( -d @).$tmpFile;
my $u = filePath($url, $user, $repo, qw(issues));
my $s = "curl -si -X POST $pat $u $d"; # Create url
my $r = GitHub::Crud::Response::new($gitHub, $s);
my $success = $r->status == 201; # Check response code
unlink $tmpFile; # Cleanup
$gitHub->failed = $success ? undef : 1;
!$success and $gitHub->confessOnFailure and # Confess to any failure if so requested
confess join "\n", dump($gitHub), $json, $s;
$success ? 1 : undef # Return true on success
}
#D1 Using saved access tokens # Call methods directly using a saved access token rather than first creating a L<GitHub> description object and then calling methods using it. This is often more convenient if you just want to perform one or two actions.
sub createIssueFromSavedToken($$$$;$) # Create an issue on L<GitHub> using an access token as supplied or saved in a file using L<savePersonalAccessToken|/savePersonalAccessToken>.\mReturns true if the issue was created successfully else false.
{my ($userid, $repository, $title, $body, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, issue title, issue body, location of access token.
my $g = GitHub::Crud::new;
$g->userid = $userid;
$g->repository = $repository;
$g->title = $title;
$g->body = $body;
$g->personalAccessTokenFolder = $accessFolderOrToken;
$g->loadPersonalAccessToken;
$g->confessOnFailure = 1;
$g->createIssue;
}
sub writeFileUsingSavedToken($$$$;$) # Write to a file on L<GitHub> using a personal access token as supplied or saved in a file. Return B<1> on success or confess to any failure.
{my ($userid, $repository, $file, $content, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, file name on github, file content, location of access token.
my $g = GitHub::Crud::new;
$g->userid = $userid; $userid or confess "Userid required";
$g->repository = $repository; $repository or confess "Repository required";
$g->gitFile = $file; $file or confess "File required";
$g->personalAccessTokenFolder = $accessFolderOrToken;
$g->loadPersonalAccessToken;
$g->write($content);
}
sub writeFileFromFileUsingSavedToken($$$$;$) # Copy a file to L<GitHub> using a personal access token as supplied or saved in a file. Return B<1> on success or confess to any failure.
{my ($userid, $repository, $file, $localFile, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, file name on github, file content, location of access token.
writeFileUsingSavedToken($userid, $repository, $file,
readBinaryFile($localFile), $accessFolderOrToken);
}
sub readFileUsingSavedToken($$$;$) # Read from a file on L<GitHub> using a personal access token as supplied or saved in a file. Return the content of the file on success or confess to any failure.
{my ($userid, $repository, $file, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, file name on GitHub, location of access token.
my $g = GitHub::Crud::new;
$g->userid = $userid;
$g->repository = $repository;
$g->gitFile = $file;
$g->personalAccessTokenFolder = $accessFolderOrToken;
$g->loadPersonalAccessToken;
$g->read;
}
sub writeFolderUsingSavedToken($$$$;$) # Write all the files in a local folder to a target folder on a named L<GitHub> repository using a personal access token as supplied or saved in a file.
{my ($userid,$repository,$targetFolder,$localFolder,$accessFolderOrToken) = @_;# Userid on GitHub, repository name, target folder on GitHub, local folder name, location of access token.
my $g = GitHub::Crud::new;
$g->userid = $userid;
$g->repository = $repository;
$g->personalAccessTokenFolder = $accessFolderOrToken;
$g->loadPersonalAccessToken;
for my $file(searchDirectoryTreesForMatchingFiles($localFolder))
{$g->gitFile = swapFilePrefix($file, $localFolder, $targetFolder);
$g->write(readBinaryFile($file));
}
}
sub writeCommitUsingSavedToken($$$;$) # Write all the files in a local folder to a named L<GitHub> repository using a personal access token as supplied or saved in a file.
{my ($userid, $repository, $source, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, local folder on GitHub, optionally: location of access token.
my $g = GitHub::Crud::new;
$g->userid = $userid;
$g->repository = $repository;
$g->personalAccessTokenFolder = $accessFolderOrToken;
$g->loadPersonalAccessToken;
$g->branch = 'master';
$g->writeCommit($source);
}
sub deleteFileUsingSavedToken($$$;$) # Delete a file on L<GitHub> using a saved token
{my ($userid, $repository, $target, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, file on GitHub, optional: the folder containing saved access tokens.
my $g = GitHub::Crud::new;
$g->userid = $userid;
$g->repository = $repository;
$g->personalAccessTokenFolder = $accessFolderOrToken;
$g->loadPersonalAccessToken;
$g->gitFile = $target;
$g->delete;
}
sub getRepositoryUsingSavedToken($$;$) # Get repository details from L<GitHub> using a saved token
{my ($userid, $repository, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, optionally: location of access token.
my $g = GitHub::Crud::new;
$g->userid = $userid; $userid or confess "Userid required";
$g->repository = $repository; $repository or confess "Repository required";
$g->personalAccessTokenFolder = $accessFolderOrToken;
$g->loadPersonalAccessToken;
$g->getRepository;
}
sub getRepositoryUpdatedAtUsingSavedToken($$;$) # Get the last time a repository was updated via the 'updated_at' field using a saved token and return the time in number of seconds since the Unix epoch.
{my ($userid, $repository, $accessFolderOrToken) = @_; # Userid on GitHub, repository name, optionally: location of access token.
my $r = &getRepositoryUsingSavedToken(@_); # Get repository details using a saved token
my $u = $r->data->{updated_at};
return Date::Manip::UnixDate($u,'%s');
}
#D1 Actions # Perform an action against the current repository while running as a L<GitHub> action. If such an action requires a security token please supply the token as shown in at the end of: L<https://github.com/philiprbrenan/maze/blob/main/.github/workflows/main.yml>.
sub currentRepo() #P Create a L<GitHub> object for the current repo if we are on L<GitHub> actions
{if (my $r = $ENV{GITHUB_REPOSITORY}) # We are on GitHub
{my ($user, $repo) = split m(/), $r, 2;
my $g = GitHub::Crud::new;
$g->userid = $user;
$g->repository = $repo;
$g->personalAccessToken = $ENV{GITHUB_TOKEN};
$g->confessOnFailure = 1;
if (!$g->personalAccessToken)
{confess "Unable to load github token for repository $r from environment variable: GITHUB_TOKEN\nSee: https://github.com/philiprbrenan/postgres/blob/main/.github/workflows/main.yml";
}
return $g;
}
undef
}
sub createIssueInCurrentRepo($$) # Create an issue in the current L<GitHub> repository if we are running on L<GitHub>.
{my ($title, $body) = @_; # Title of issue, body of issue
if (my $g = currentRepo) # We are on GitHub
{$g->title = $title;
$g->body = $body;
$g->createIssue;
}
}
sub writeFileFromCurrentRun($$) # Write text into a file in the current L<GitHub> repository if we are running on L<GitHub>.
{my ($target, $text) = @_; # The target file name in the repo, the text to write into this file
if (my $g = currentRepo) # We are on GitHub
{$g->gitFile = $target;
$g->write($text);
}
}
sub writeFileFromFileFromCurrentRun($) # Write to a file in the current L<GitHub> repository by copying a local file if we are running on L<GitHub>.
{my ($target) = @_; # File name both locally and in the repo
-e $target or confess "File to upload does not exist:\n$target";
if (my $g = currentRepo) # We are on GitHub
{$g->gitFile = $target;
$g->write(scalar(readFile($target)));
}
}
sub writeBinaryFileFromFileInCurrentRun($$) # Write to a file in the current L<GitHub> repository by copying a local binary file if we are running on L<GitHub>.
{my ($target, $source) = @_; # The target file name in the repo, the current file name in the run
if (my $g = currentRepo) # We are on GitHub
{$g->gitFile = $target;
$g->write(readBinaryFile($source));
}
}
#D1 Access tokens # Load and save access tokens. Some L<GitHub> requests must be signed with an L<OAuth> access token. These methods help you store and reuse such tokens. Access tokens can be created at: L<https://github.com/settings/tokens>.
sub savePersonalAccessToken($) # Save a L<GitHub> personal access token by userid in folder L<personalAccessTokenFolder|/personalAccessTokenFolder>.
{my ($gitHub) = @_; # GitHub object
my $user = qm $gitHub->userid; $user or confess "userid required";
my $pat = $gitHub->personalAccessToken; $pat or confess "personal access token required";
my $dir = $gitHub->personalAccessTokenFolder // accessFolder;
my $file = filePathExt($dir, $user, q(data));
makePath($file);
storeFile($file, {pat=>$pat}); # Store personal access token
-e $file or confess "Unable to store personal access token in file:\n$file"; # Complain if store fails
my $p = retrieveFile $file; # Retrieve access token to check that we wrote it successfully
$pat eq $p->{pat} or # Check file format
confess "File contains the wrong personal access token:\n$file";
}
sub loadPersonalAccessToken($) # Load a personal access token by userid from folder L<personalAccessTokenFolder|/personalAccessTokenFolder>.
{my ($gitHub) = @_; # GitHub object
my $user = qm $gitHub->userid; $user or confess "userid required";
if (length($gitHub->personalAccessTokenFolder//accessFolder) == 43) # Access token supplied directly
{return $gitHub->personalAccessToken = $gitHub->personalAccessTokenFolder;
}
if ($ENV{GITHUB_TOKEN}) # Access token supplied through environment
{return $gitHub->personalAccessToken = $ENV{GITHUB_TOKEN};
}
my $dir = $gitHub->personalAccessTokenFolder // accessFolder;
my $file = filePathExt($dir, $user, q(data));
my $p = retrieveFile $file; # Load personal access token
my $a = $p->{pat} or # Check file format
confess "File does not contain a personal access token:\n$file";
$gitHub->personalAccessToken = $a; # Retrieve token
}
#D0
#-------------------------------------------------------------------------------
# Export - eeee
#-------------------------------------------------------------------------------
use Exporter qw(import);
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
# containingFolder
@ISA = qw(Exporter);
@EXPORT_OK = qw(
createIssueFromSavedToken
createIssueInCurrentRepo
createRepositoryFromSavedToken
deleteFileUsingSavedToken
getRepository
getRepositoryUsingSavedToken
getRepositoryUpdatedAtUsingSavedToken
readFileUsingSavedToken
writeBinaryFileFromFileInCurrentRun
writeCommitUsingSavedToken
writeFileFromCurrentRun
writeFileFromFileUsingSavedToken
writeFileUsingSavedToken
writeFolderUsingSavedToken
);
%EXPORT_TAGS = (all=>[@EXPORT_OK]);
#podDocumentation
=pod
=encoding utf-8
=head1 Name
GitHub::Crud - Create, Read, Update, Delete files, commits, issues, and web hooks on GitHub.
=head1 Synopsis
Create, Read, Update, Delete files, commits, issues, and web hooks on GitHub as
described at:
https://developer.github.com/v3/repos/contents/#update-a-file
=head2 Upload a file from an action
Upload a file created during a github action to the repository for that action:
GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} \
perl -M"GitHub::Crud" -e"GitHub::Crud::writeFileFromFileFromCurrentRun q(output.txt);"
=head2 Upload a folder
Commit a folder to GitHub then read and check some of the uploaded content:
use GitHub::Crud;
use Data::Table::Text qw(:all);
my $f = temporaryFolder; # Folder in which we will create some files to upload in the commit
my $c = dateTimeStamp; # Create some content
my $if = q(/home/phil/.face); # Image file
writeFile(fpe($f, q(data), $_, qw(txt)), $c) for 1..3; # Place content in files in a sub folder
copyBinaryFile $if, my $If = fpe $f, qw(face jpg); # Add an image
my $g = GitHub::Crud::new # Create GitHub
(userid => q(philiprbrenan),
repository => q(aaa),
branch => q(test),
confessOnFailure => 1);
$g->loadPersonalAccessToken; # Load a personal access token
$g->writeCommit($f); # Upload commit - confess to any errors
my $C = $g->read(q(data/1.txt)); # Read data written in commit
my $I = $g->read(q(face.jpg));
my $i = readBinaryFile $if;
confess "Date stamp failed" unless $C eq $c; # Check text
confess "Image failed" unless $i eq $I; # Check image
confess "Write commit succeeded";
=head2 Prerequisites
Please install B<curl> if it is not already present on your computer.
sudo apt-get install curl
=head2 Personal Access Token
You will need to create a personal access token if you wish to gain write
access to your respositories : L<https://github.com/settings/tokens>.
Depending on your security requirements you can either install this token at
the well known location:
/etc/GitHubCrudPersonalAccessToken/<github userid>
or at a location of your choice. If you use a well known location then the
personal access token will be loaded automatically for you, else you will need
to supply it to each call via the L</personalAccessToken> attribute.
The following code will install a personal access token for github userid
B<uuuuuu> in the default location:
use GitHub::Crud qw(:all);
my $g = GitHub::Crud::gitHub
(userid => q(uuuuuu),
personalAccessToken => q(........................................),
# personalAccessTokenFolder => $d,
);
$g->savePersonalAccessToken;
=head1 Description
Create, Read, Update, Delete files, commits, issues, and web hooks on GitHub.
Version 20240202.
The following sections describe the methods in each functional area of this
module. For an alphabetic listing of all methods by name see L<Index|/Index>.
=head1 Constructor
Create a L<GitHub|https://github.com/philiprbrenan> object with the specified attributes describing the interface with L<GitHub|https://github.com/philiprbrenan>.
=head2 newย (%attributes)
Create a new L<GitHub|https://github.com/philiprbrenan> object with attributes as described at: L<GitHub::Crud Definition>.
Parameter Description
1 %attributes Attribute values
B<Example:>
my $f = temporaryFolder; # Folder in which we will create some files to upload in the commit
my $c = dateTimeStamp; # Create some content
my $if = q(/home/phil/.face); # Image file
writeFile(fpe($f, q(data), $_, qw(txt)), $c) for 1..3; # Place content in files in a sub folder
copyBinaryFile $if, my $If = fpe $f, qw(face jpg); # Add an image
my $g = GitHub::Crud::new # Create GitHub # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
(userid => q(philiprbrenan),
repository => q(aaa),
branch => q(test),
confessOnFailure => 1);
$g->loadPersonalAccessToken; # Load a personal access token
$g->writeCommit($f); # Upload commit - confess to any errors
my $C = $g->read(q(data/1.txt)); # Read data written in commit
my $I = $g->read(q(face.jpg));
my $i = readBinaryFile $if;
confess "Date stamp failed" unless $C eq $c; # Check text
confess "Image failed" unless $i eq $I; # Check image
success "Write commit succeeded";
=head1 Files
File actions on the contents of L<GitHub|https://github.com/philiprbrenan> repositories.
=head2 list($gitHub)
List all the files contained in a L<GitHub|https://github.com/philiprbrenan> repository or all the files below a specified folder in the repository.
Required attributes: L<userid|/userid>, L<repository|/repository>.
Optional attributes: L<gitFolder|/gitFolder>, L<refOrBranch|/refOrBranch>, L<nonRecursive|/nonRecursive>, L<patKey|/patKey>.
Use the L<gitFolder|/gitFolder> parameter to specify the folder to start the list from, by default, the listing will start at the root folder of your repository.
Use the L<nonRecursive|/nonRecursive> option if you require only the files in the start folder as otherwise all the folders in the start folder will be listed as well which might take some time.
If the list operation is successful, L<failed|/failed> is set to false and L<fileList|/fileList> is set to refer to an array of the file names found.
If the list operation fails then L<failed|/failed> is set to true and L<fileList|/fileList> is set to refer to an empty array.
Returns the list of file names found or empty list if no files were found.
Parameter Description
1 $gitHub GitHub
B<Example:>
success "list:", gitHub->list; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
# list: alpha.data .github/workflows/test.yaml images/aaa.txt images/aaa/bbb.txt # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
=head2 read($gitHub, $File)
Read data from a file on L<GitHub|https://github.com/philiprbrenan>.
Required attributes: L<userid|/userid>, L<repository|/repository>.
Optional attributes: L<gitFile|/gitFile> = the file to read, L<refOrBranch|/refOrBranch>, L<patKey|/patKey>.
If the read operation is successful, L<failed|/failed> is set to false and L<readData|/readData> is set to the data read from the file.
If the read operation fails then L<failed|/failed> is set to true and L<readData|/readData> is set to B<undef>.
Returns the data read or B<undef> if no file was found.
Parameter Description
1 $gitHub GitHub
2 $File File to read if not specified in gitFile
B<Example:>
my $g = gitHub;
$g->gitFile = my $f = q(z'2 'z"z.data);
my $d = q(๐ฐ๐ฑ๐ฒ);
$g->write($d);
confess "read FAILED" unless $g->read eq $d; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
success "Read passed";
=head2 writeย ย ย ($gitHub, $data, $File)
Write the specified data into a file on L<GitHub|https://github.com/philiprbrenan>.
Required attributes: L<userid|/userid>, L<repository|/repository>, L<patKey|/patKey>. Either specify the target file on: L<GitHub|https://github.com/philiprbrenan> using the L<gitFile|/gitFile> attribute or supply it as the third parameter. Returns B<true> on success else L<undef|https://perldoc.perl.org/functions/undef.html>.
Parameter Description
1 $gitHub GitHub object
2 $data Data to be written
3 $File Optionally the name of the file on github
B<Example:>
my $g = gitHub;
$g->gitFile = "zzz.data";
my $d = dateTimeStamp.q( ๐ฐ๐ฑ๐ฒ);
if (1)
{my $t = time();
$g->write($d); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
lll "First write time: ", time() - $t; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
}
my $r = $g->read;
lll "Write bbb: $r";
if (1)
{my $t = time();
$g->write($d); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
lll "Second write time: ", time() - $t; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
}
confess "write FAILED" unless $g->exists; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
success "Write passed";
=head2 readBlob($gitHub, $sha)
Read a L<blob|https://en.wikipedia.org/wiki/Binary_large_object> from L<GitHub|https://github.com/philiprbrenan>.
Required attributes: L<userid|/userid>, L<repository|/repository>, L<patKey|/patKey>. Returns the content of the L<blob|https://en.wikipedia.org/wiki/Binary_large_object> identified by the specified L<SHA|https://en.wikipedia.org/wiki/SHA-1>.
Parameter Description
1 $gitHub GitHub object
2 $sha Data to be written
B<Example:>
my $g = gitHub;
$g->gitFile = "face.jpg";
my $d = readBinaryFile(q(/home/phil/.face));
my $s = $g->writeBlob($d);
my $S = q(4a2df549febb701ba651aae46e041923e9550cb8);
confess q(Write blob FAILED) unless $s eq $S;
my $D = $g->readBlob($s); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
confess q(Write/Read blob FAILED) unless $d eq $D;
success q(Write/Read blob passed);
=head2 writeBlobย ย ย ($gitHub, $data)
Write data into a L<GitHub|https://github.com/philiprbrenan> as a L<blob|https://en.wikipedia.org/wiki/Binary_large_object> that can be referenced by future commits.
Required attributes: L<userid|/userid>, L<repository|/repository>, L<patKey|/patKey>. Returns the L<SHA|https://en.wikipedia.org/wiki/SHA-1> of the created L<blob|https://en.wikipedia.org/wiki/Binary_large_object> or L<undef|https://perldoc.perl.org/functions/undef.html> in a failure occurred.
Parameter Description
1 $gitHub GitHub object
2 $data Data to be written
B<Example:>
my $g = gitHub;
$g->gitFile = "face.jpg";
my $d = readBinaryFile(q(/home/phil/.face));
my $s = $g->writeBlob($d); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
my $S = q(4a2df549febb701ba651aae46e041923e9550cb8);
confess q(Write blob FAILED) unless $s eq $S;
my $D = $g->readBlob($s);
confess q(Write/Read blob FAILED) unless $d eq $D;
success q(Write/Read blob passed);
=head2 copy($gitHub, $target)
Copy a source file from one location to another target location in your L<GitHub|https://github.com/philiprbrenan> repository, overwriting the target file if it already exists.
Required attributes: L<userid|/userid>, L<repository|/repository>, L<patKey|/patKey>, L<gitFile|/gitFile> = the file to be copied.
Optional attributes: L<refOrBranch|/refOrBranch>.
If the write operation is successful, L<failed|/failed> is set to false otherwise it is set to true.
Returns B<updated> if the write updated the file, B<created> if the write created the file else B<undef> if the write failed.
Parameter Description
1 $gitHub GitHub object
2 $target The name of the file to be created
B<Example:>
my ($f1, $f2) = ("zzz.data", "zzz2.data");
my $g = gitHub;
$g->gitFile = $f2; $g->delete;
$g->gitFile = $f1;
my $d = dateTimeStamp;
my $w = $g->write($d);
my $r = $g->copy($f2); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
lll "Copy created: $r";
$g->gitFile = $f2;
my $D = $g->read;
lll "Read ccc: $D";
confess "copy FAILED" unless $d eq $D; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
success "Copy passed"
=head2 existsย ย ($gitHub)
Test whether a file exists on L<GitHub|https://github.com/philiprbrenan> or not and returns an object including the B<sha> and B<size> fields if it does else L<undef|https://perldoc.perl.org/functions/undef.html>.
Required attributes: L<userid|/userid>, L<repository|/repository>, L<gitFile|/gitFile> file to test.
Optional attributes: L<refOrBranch|/refOrBranch>, L<patKey|/patKey>.
Parameter Description
1 $gitHub GitHub object
B<Example:>
my $g = gitHub;
$g->gitFile = "test4.html";
my $d = dateTimeStamp;
$g->write($d);
confess "exists FAILED" unless $g->read eq $d; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
$g->delete;
confess "exists FAILED" if $g->read eq $d; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
success "Exists passed";
=head2 renameย ย ($gitHub, $target)
Rename a source file on L<GitHub|https://github.com/philiprbrenan> if the target file name is not already in use.
Required attributes: L<userid|/userid>, L<repository|/repository>, L<patKey|/patKey>, L<gitFile|/gitFile> = the file to be renamed.
Optional attributes: L<refOrBranch|/refOrBranch>.
Returns the new name of the file B<renamed> if the rename was successful else B<undef> if the rename failed.
Parameter Description
1 $gitHub GitHub object
2 $target The new name of the file
B<Example:>
my ($f1, $f2) = qw(zzz.data zzz2.data);
my $g = gitHub;
$g->gitFile = $f2; $g->delete;
my $d = dateTimeStamp;
$g->gitFile = $f1;
$g->write($d);
confess "rename FAILED" unless $g->read eq $d; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
$g->rename($f2); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
confess "rename FAILED" if $g->exists; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
$g->gitFile = $f2;
confess "rename FAILED" if $g->read eq $d; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
success "Rename passed";
=head2 deleteย ย ($gitHub)
Delete a file from L<GitHub|https://github.com/philiprbrenan>.
Required attributes: L<userid|/userid>, L<repository|/repository>, L<patKey|/patKey>, L<gitFile|/gitFile> = the file to be deleted.
Optional attributes: L<refOrBranch|/refOrBranch>.
If the delete operation is successful, L<failed|/failed> is set to false otherwise it is set to true.
Returns true if the delete was successful else false.
Parameter Description
1 $gitHub GitHub object
B<Example:>
my $g = gitHub;
my $d = dateTimeStamp;
$g->gitFile = "zzz.data";
$g->write($d);
confess "delete FAILED" unless $g->read eq $d; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
if (1)
{my $t = time();
my $d = $g->delete; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
lll "Delete 1: ", $d;
lll "First delete: ", time() - $t; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
confess "delete FAILED" if $g->exists; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
}
if (1)
{my $t = time();
my $d = $g->delete; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
lll "Delete 1: ", $d;
lll "Second delete: ", time() - $t; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
confess "delete FAILED" if $g->exists; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
}
success "Delete passed";
=head1 Repositories
Perform actions on L<GitHub|https://github.com/philiprbrenan> repositories.
=head2 getRepositoryย ย ย ($gitHub)
Get the overall details of a repository
Parameter Description
1 $gitHub GitHub object
B<Example:>
my $r = gitHub(repository => q(C))->getRepository; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
success "Get repository succeeded";
=head2 listCommitsย ($gitHub)
List all the commits in a L<GitHub|https://github.com/philiprbrenan> repository.
Required attributes: L<userid|/userid>, L<repository|/repository>.
Parameter Description
1 $gitHub GitHub object
B<Example:>
my $c = gitHub->listCommits; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
my %s = listCommitShas $c;
lll "Commits
", dump $c;
lll "Commit shas
", dump \%s;
success "ListCommits passed";
=head2 listCommitShasย ย ($commits)
Create {commit name => sha} from the results of L</listCommits>.
Parameter Description
1 $commits Commits from L</listCommits>
B<Example:>
my $c = gitHub->listCommits;
my %s = listCommitShas $c; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
lll "Commits
", dump $c;
lll "Commit shas
", dump \%s;
success "ListCommits passed";
=head2 writeCommitย ($gitHub, $folder, @files)
Write all the files in a B<$folder> (or just the the named files) into a L<GitHub|https://github.com/philiprbrenan> repository in parallel as a commit on the specified branch.
Required attributes: L<userid|/userid>, L<repository|/repository>, L<refOrBranch|/refOrBranch>.
Parameter Description
1 $gitHub GitHub object
2 $folder File prefix to remove
3 @files Files to write
B<Example:>
my $f = temporaryFolder; # Folder in which we will create some files to upload in the commit
my $c = dateTimeStamp; # Create some content
my $if = q(/home/phil/.face); # Image file
writeFile(fpe($f, q(data), $_, qw(txt)), $c) for 1..3; # Place content in files in a sub folder
copyBinaryFile $if, my $If = fpe $f, qw(face jpg); # Add an image
my $g = GitHub::Crud::new # Create GitHub
(userid => q(philiprbrenan),
repository => q(aaa),
branch => q(test),
confessOnFailure => 1);
$g->loadPersonalAccessToken; # Load a personal access token
$g->writeCommit($f); # Upload commit - confess to any errors # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
my $C = $g->read(q(data/1.txt)); # Read data written in commit
my $I = $g->read(q(face.jpg));
my $i = readBinaryFile $if;
confess "Date stamp failed" unless $C eq $c; # Check text
confess "Image failed" unless $i eq $I; # Check image
success "Write commit succeeded";
=head2 listWebHooks($gitHub)
List web hooks associated with your L<GitHub|https://github.com/philiprbrenan> repository.
Required: L<userid|/userid>, L<repository|/repository>, L<patKey|/patKey>.
If the list operation is successful, L<failed|/failed> is set to false otherwise it is set to true.
Returns true if the list operation was successful else false.
Parameter Description
1 $gitHub GitHub object
B<Example:>
success join ' ', q(Webhooks:), dump(gitHub->listWebHooks); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
=head2 createPushWebHookย ย ย ($gitHub)
Create a web hook for your L<GitHub|https://github.com/philiprbrenan> userid.
Required: L<userid|/userid>, L<repository|/repository>, L<url|/url>, L<patKey|/patKey>.
Optional: L<secret|/secret>.
If the create operation is successful, L<failed|/failed> is set to false otherwise it is set to true.
Returns true if the web hook was created successfully else false.
Parameter Description
1 $gitHub GitHub object
B<Example:>
my $g = gitHub;
my $d = $g->createPushWebHook; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
success join ' ', "Create web hook:", dump($d);
=head2 listRepositories($gitHub)
List the repositories accessible to a user on L<GitHub|https://github.com/philiprbrenan>.
Required: L<userid|/userid>.
Returns details of the repositories.
Parameter Description
1 $gitHub GitHub object
B<Example:>
success "List repositories: ", dump(gitHub()->listRepositories); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
=head2 createRepository($gitHub)
Create a repository on L<GitHub|https://github.com/philiprbrenan>.
Required: L<userid|/userid>, L<repository|/repository>.
Returns true if the issue was created successfully else false.
Parameter Description
1 $gitHub GitHub object
B<Example:>
gitHub(repository => q(ccc))->createRepository; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
success "Create repository succeeded";
=head2 createRepositoryFromSavedTokenย ย ($userid, $repository, $private, $accessFolderOrToken)
Create a repository on L<GitHub|https://github.com/philiprbrenan> using an access token either as supplied or saved in a file using L<savePersonalAccessToken|/savePersonalAccessToken>.
Returns true if the issue was created successfully else false.
Parameter Description
1 $userid Userid on GitHub
2 $repository The repository name
3 $private True if the repo is private
4 $accessFolderOrToken Location of access token.
B<Example:>
createRepositoryFromSavedToken(q(philiprbrenan), q(ddd)); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
success "Create repository succeeded";
=head1 Issues
Create issues on L<GitHub|https://github.com/philiprbrenan>.
=head2 createIssueย ($gitHub)
Create an issue on L<GitHub|https://github.com/philiprbrenan>.
Required: L<userid|/userid>, L<repository|/repository>, L<body|/body>, L<title|/title>.
If the operation is successful, L<failed|/failed> is set to false otherwise it is set to true.
Returns true if the issue was created successfully else false.
Parameter Description
1 $gitHub GitHub object
B<Example:>
gitHub(title=>q(Hello), body=>q(World))->createIssue; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
success "Create issue succeeded";
=head1 Using saved access tokens
Call methods directly using a saved access token rather than first creating a L<GitHub|https://github.com/philiprbrenan> description object and then calling methods using it. This is often more convenient if you just want to perform one or two actions.
=head2 createIssueFromSavedTokenย ย ย ($userid, $repository, $title, $body, $accessFolderOrToken)
Create an issue on L<GitHub|https://github.com/philiprbrenan> using an access token as supplied or saved in a file using L<savePersonalAccessToken|/savePersonalAccessToken>.
Returns true if the issue was created successfully else false.
Parameter Description
1 $userid Userid on GitHub
2 $repository Repository name
3 $title Issue title
4 $body Issue body
5 $accessFolderOrToken Location of access token.
B<Example:>
&createIssueFromSavedToken(qw(philiprbrenan ddd hello World)); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
success "Create issue succeeded";
=head2 writeFileUsingSavedToken($userid, $repository, $file, $content, $accessFolderOrToken)
Write to a file on L<GitHub|https://github.com/philiprbrenan> using a personal access token as supplied or saved in a file. Return B<1> on success or confess to any failure.
Parameter Description
1 $userid Userid on GitHub
2 $repository Repository name
3 $file File name on github
4 $content File content
5 $accessFolderOrToken Location of access token.
B<Example:>
my $s = q(HelloWorld);
&writeFileUsingSavedToken(qw(philiprbrenan ddd hello.txt), $s); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
my $S = gitHub(repository=>q(ddd), gitFile=>q(hello.txt))->read;
confess "Write file using saved token FAILED" unless $s eq $S;
success "Write file using saved token succeeded";
=head2 writeFileFromFileUsingSavedToken($userid, $repository, $file, $localFile, $accessFolderOrToken)
Copy a file to L<GitHub|https://github.com/philiprbrenan> using a personal access token as supplied or saved in a file. Return B<1> on success or confess to any failure.
Parameter Description
1 $userid Userid on GitHub
2 $repository Repository name
3 $file File name on github
4 $localFile File content
5 $accessFolderOrToken Location of access token.
B<Example:>
my $f = writeFile(undef, my $s = "World
");
&writeFileFromFileUsingSavedToken(qw(philiprbrenan ddd hello.txt), $f); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
my $S = gitHub(repository=>q(ddd), gitFile=>q(hello.txt))->read;
confess "Write file from file using saved token FAILED" unless $s eq $S;
success "Write file from file using saved token succeeded"
=head2 readFileUsingSavedTokenย ($userid, $repository, $file, $accessFolderOrToken)
Read from a file on L<GitHub|https://github.com/philiprbrenan> using a personal access token as supplied or saved in a file. Return the content of the file on success or confess to any failure.
Parameter Description
1 $userid Userid on GitHub
2 $repository Repository name
3 $file File name on GitHub
4 $accessFolderOrToken Location of access token.
B<Example:>
my $s = q(Hello to the World);
&writeFileUsingSavedToken(qw(philiprbrenan ddd hello.txt), $s);
my $S = &readFileUsingSavedToken (qw(philiprbrenan ddd hello.txt)); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
confess "Read file using saved token FAILED" unless $s eq $S;
success "Read file using saved token succeeded"
=head2 writeFolderUsingSavedTokenย ย ($userid, $repository, $targetFolder, $localFolder, $accessFolderOrToken)
Write all the files in a local folder to a target folder on a named L<GitHub|https://github.com/philiprbrenan> repository using a personal access token as supplied or saved in a file.
Parameter Description
1 $userid Userid on GitHub
2 $repository Repository name
3 $targetFolder Target folder on GitHub
4 $localFolder Local folder name
5 $accessFolderOrToken Location of access token.
B<Example:>
writeCommitUsingSavedToken("philiprbrenan", "test", "/home/phil/files/");
writeFolderUsingSavedToken("philiprbrenan", "test", "files", "/home/phil/files/"); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
=head2 writeCommitUsingSavedTokenย ย ($userid, $repository, $source, $accessFolderOrToken)
Write all the files in a local folder to a named L<GitHub|https://github.com/philiprbrenan> repository using a personal access token as supplied or saved in a file.
Parameter Description
1 $userid Userid on GitHub
2 $repository Repository name
3 $source Local folder on GitHub
4 $accessFolderOrToken Optionally: location of access token.
B<Example:>
writeCommitUsingSavedToken("philiprbrenan", "test", "/home/phil/files/"); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
writeFolderUsingSavedToken("philiprbrenan", "test", "files", "/home/phil/files/");
=head2 deleteFileUsingSavedTokenย ย ย ($userid, $repository, $target, $accessFolderOrToken)
Delete a file on L<GitHub|https://github.com/philiprbrenan> using a saved token
Parameter Description
1 $userid Userid on GitHub
2 $repository Repository name
3 $target File on GitHub
4 $accessFolderOrToken Optional: the folder containing saved access tokens.
B<Example:>
deleteFileUsingSavedToken("philiprbrenan", "test", "aaa.data"); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
=head2 getRepositoryUsingSavedToken($userid, $repository, $accessFolderOrToken)
Get repository details from L<GitHub|https://github.com/philiprbrenan> using a saved token
Parameter Description
1 $userid Userid on GitHub
2 $repository Repository name
3 $accessFolderOrToken Optionally: location of access token.
B<Example:>
my $r = getRepositoryUsingSavedToken(q(philiprbrenan), q(aaa)); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
success "Get repository using saved access token succeeded";
=head2 getRepositoryUpdatedAtUsingSavedTokenย ย ย ($userid, $repository, $accessFolderOrToken)
Get the last time a repository was updated via the 'updated_at' field using a saved token and return the time in number of seconds since the Unix epoch.
Parameter Description
1 $userid Userid on GitHub
2 $repository Repository name
3 $accessFolderOrToken Optionally: location of access token.
B<Example:>
my $u = getRepositoryUpdatedAtUsingSavedToken(q(philiprbrenan), q(aaa)); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
success "Get repository updated_at field succeeded";
=head1 Actions
Perform an action against the current repository while running as a L<GitHub|https://github.com/philiprbrenan> action. If such an action requires a security token please supply the token as shown in at the end of: L<https://github.com/philiprbrenan/maze/blob/main/.github/workflows/main.yml>.
=head2 createIssueInCurrentRepo($title, $body)
Create an issue in the current L<GitHub|https://github.com/philiprbrenan> repository if we are running on L<GitHub|https://github.com/philiprbrenan>.
Parameter Description
1 $title Title of issue
2 $body Body of issue
B<Example:>
createIssueInCurrentRepo("Hello World", "Need to run Hello World"); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
writeFileFromCurrentRun("output.text", "Hello World");
writeFileFromFileFromCurrentRun("output.txt");
writeBinaryFileFromFileInCurrentRun("image.jpg", "out/image.jpg");
=head2 writeFileFromCurrentRunย ($target, $text)
Write text into a file in the current L<GitHub|https://github.com/philiprbrenan> repository if we are running on L<GitHub|https://github.com/philiprbrenan>.
Parameter Description
1 $target The target file name in the repo
2 $text The text to write into this file
B<Example:>
createIssueInCurrentRepo("Hello World", "Need to run Hello World");
writeFileFromCurrentRun("output.text", "Hello World"); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
writeFileFromFileFromCurrentRun("output.txt");
writeBinaryFileFromFileInCurrentRun("image.jpg", "out/image.jpg");
=head2 writeFileFromFileFromCurrentRunย ($target)
Write to a file in the current L<GitHub|https://github.com/philiprbrenan> repository by copying a local file if we are running on L<GitHub|https://github.com/philiprbrenan>.
Parameter Description
1 $target File name both locally and in the repo
B<Example:>
createIssueInCurrentRepo("Hello World", "Need to run Hello World");
writeFileFromCurrentRun("output.text", "Hello World");
writeFileFromFileFromCurrentRun("output.txt"); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
writeBinaryFileFromFileInCurrentRun("image.jpg", "out/image.jpg");
=head2 writeBinaryFileFromFileInCurrentRunย ($target, $source)
Write to a file in the current L<GitHub|https://github.com/philiprbrenan> repository by copying a local binary file if we are running on L<GitHub|https://github.com/philiprbrenan>.
Parameter Description
1 $target The target file name in the repo
2 $source The current file name in the run
B<Example:>
createIssueInCurrentRepo("Hello World", "Need to run Hello World");
writeFileFromCurrentRun("output.text", "Hello World");
writeFileFromFileFromCurrentRun("output.txt");
writeBinaryFileFromFileInCurrentRun("image.jpg", "out/image.jpg"); # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
=head1 Access tokens
Load and save access tokens. Some L<GitHub|https://github.com/philiprbrenan> requests must be signed with an L<Oauth|https://en.wikipedia.org/wiki/OAuth> access token. These methods help you store and reuse such tokens. Access tokens can be created at: L<https://github.com/settings/tokens>.
=head2 savePersonalAccessTokenย ($gitHub)
Save a L<GitHub|https://github.com/philiprbrenan> personal access token by userid in folder L<personalAccessTokenFolder|/personalAccessTokenFolder>.
Parameter Description
1 $gitHub GitHub object
B<Example:>
my $d = temporaryFolder;
my $t = join '', 1..20;
my $g = gitHub
(userid => q(philiprbrenan),
personalAccessToken => $t,
personalAccessTokenFolder => $d,
);
$g->savePersonalAccessToken; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
my $T = $g->loadPersonalAccessToken;
confess "Load/Save token FAILED" unless $t eq $T;
success "Load/Save token succeeded"
=head2 loadPersonalAccessTokenย ($gitHub)
Load a personal access token by userid from folder L<personalAccessTokenFolder|/personalAccessTokenFolder>.
Parameter Description
1 $gitHub GitHub object
B<Example:>
my $d = temporaryFolder;
my $t = join '', 1..20;
my $g = gitHub
(userid => q(philiprbrenan),
personalAccessToken => $t,
personalAccessTokenFolder => $d,
);
$g->savePersonalAccessToken;
my $T = $g->loadPersonalAccessToken; # ๐๐
๐ฎ๐บ๐ฝ๐น๐ฒ
confess "Load/Save token FAILED" unless $t eq $T;
success "Load/Save token succeeded"
=head1 Hash Definitions
=head2 GitHub::Crud Definition
Attributes describing the interface with L<GitHub|https://github.com/philiprbrenan>.
=head3 Input fields
=head4 body
The body of an issue.
=head4 branch
Branch name (you should create this branch first) or omit it for the default branch which is usually 'master'.
=head4 confessOnFailure
Confess to any failures
=head4 gitFile
File name on L<GitHub|https://github.com/philiprbrenan> - this name can contain '/'. This is the file to be read from, written to, copied from, checked for existence or deleted.
=head4 gitFolder
Folder name on L<GitHub|https://github.com/philiprbrenan> - this name can contain '/'.
=head4 message
Optional commit message
=head4 nonRecursive
Fetch only one level of files with L<list|https://en.wikipedia.org/wiki/Linked_list>.
=head4 personalAccessToken
A personal access token with scope "public_repo" as generated on page: https://github.com/settings/tokens.
=head4 personalAccessTokenFolder
The folder into which to save personal access tokens. Set to q(/etc/GitHubCrudPersonalAccessToken) by default.
=head4 private
Whether the repository being created should be private or not.
=head4 repository
The name of the repository to be worked on minus the userid - you should create this repository first manually.
=head4 secret
The secret for a web hook - this is created by the creator of the web hook and remembered by L<GitHub|https://github.com/philiprbrenan>,
=head4 title
The title of an issue.
=head4 userid
Userid on L<GitHub|https://github.com/philiprbrenan> of the repository to be worked on.
=head4 webHookUrl
The url for a web hook.
=head3 Output fields
=head4 failed
Defined if the last request to L<GitHub|https://github.com/philiprbrenan> failed else B<undef>.
=head4 fileList
Reference to an array of files produced by L<list|/list>.
=head4 readData
Data produced by L<read|/read>.
=head4 response
A reference to L<GitHub|https://github.com/philiprbrenan>'s response to the latest request.
=head2 GitHub::Crud::Response Definition
Attributes describing a response from L<GitHub|https://github.com/philiprbrenan>.
=head3 Output fields
=head4 content
The actual content of the file from L<GitHub|https://github.com/philiprbrenan>.
=head4 data
The data received from L<GitHub|https://github.com/philiprbrenan>, normally in L<Json|https://en.wikipedia.org/wiki/JSON> format.
=head4 status
Our version of Status.
=head1 Private Methods
=head2 specialFileDataย ($d)
Do not encode or decode data with a known file signature
Parameter Description
1 $d String to check
=head2 currentRepoย ()
Create a L<GitHub|https://github.com/philiprbrenan> object for the current repo if we are on L<GitHub|https://github.com/philiprbrenan> actions
=head1 Index
1 L<copy|/copy> - Copy a source file from one location to another target location in your L<GitHub|https://github.com/philiprbrenan> repository, overwriting the target file if it already exists.
2 L<createIssue|/createIssue> - Create an issue on L<GitHub|https://github.com/philiprbrenan>.
3 L<createIssueFromSavedToken|/createIssueFromSavedToken> - Create an issue on L<GitHub|https://github.com/philiprbrenan> using an access token as supplied or saved in a file using L<savePersonalAccessToken|/savePersonalAccessToken>.
4 L<createIssueInCurrentRepo|/createIssueInCurrentRepo> - Create an issue in the current L<GitHub|https://github.com/philiprbrenan> repository if we are running on L<GitHub|https://github.com/philiprbrenan>.
5 L<createPushWebHook|/createPushWebHook> - Create a web hook for your L<GitHub|https://github.com/philiprbrenan> userid.
6 L<createRepository|/createRepository> - Create a repository on L<GitHub|https://github.com/philiprbrenan>.
7 L<createRepositoryFromSavedToken|/createRepositoryFromSavedToken> - Create a repository on L<GitHub|https://github.com/philiprbrenan> using an access token either as supplied or saved in a file using L<savePersonalAccessToken|/savePersonalAccessToken>.
8 L<currentRepo|/currentRepo> - Create a L<GitHub|https://github.com/philiprbrenan> object for the current repo if we are on L<GitHub|https://github.com/philiprbrenan> actions
9 L<delete|/delete> - Delete a file from L<GitHub|https://github.com/philiprbrenan>.
10 L<deleteFileUsingSavedToken|/deleteFileUsingSavedToken> - Delete a file on L<GitHub|https://github.com/philiprbrenan> using a saved token
11 L<exists|/exists> - Test whether a file exists on L<GitHub|https://github.com/philiprbrenan> or not and returns an object including the B<sha> and B<size> fields if it does else L<undef|https://perldoc.perl.org/functions/undef.html>.
12 L<getRepository|/getRepository> - Get the overall details of a repository
13 L<getRepositoryUpdatedAtUsingSavedToken|/getRepositoryUpdatedAtUsingSavedToken> - Get the last time a repository was updated via the 'updated_at' field using a saved token and return the time in number of seconds since the Unix epoch.
14 L<getRepositoryUsingSavedToken|/getRepositoryUsingSavedToken> - Get repository details from L<GitHub|https://github.com/philiprbrenan> using a saved token
15 L<list|/list> - List all the files contained in a L<GitHub|https://github.com/philiprbrenan> repository or all the files below a specified folder in the repository.
16 L<listCommits|/listCommits> - List all the commits in a L<GitHub|https://github.com/philiprbrenan> repository.
17 L<listCommitShas|/listCommitShas> - Create {commit name => sha} from the results of L</listCommits>.
18 L<listRepositories|/listRepositories> - List the repositories accessible to a user on L<GitHub|https://github.com/philiprbrenan>.
19 L<listWebHooks|/listWebHooks> - List web hooks associated with your L<GitHub|https://github.com/philiprbrenan> repository.
20 L<loadPersonalAccessToken|/loadPersonalAccessToken> - Load a personal access token by userid from folder L<personalAccessTokenFolder|/personalAccessTokenFolder>.
21 L<new|/new> - Create a new L<GitHub|https://github.com/philiprbrenan> object with attributes as described at: L<GitHub::Crud Definition>.
22 L<read|/read> - Read data from a file on L<GitHub|https://github.com/philiprbrenan>.
23 L<readBlob|/readBlob> - Read a L<blob|https://en.wikipedia.org/wiki/Binary_large_object> from L<GitHub|https://github.com/philiprbrenan>.
24 L<readFileUsingSavedToken|/readFileUsingSavedToken> - Read from a file on L<GitHub|https://github.com/philiprbrenan> using a personal access token as supplied or saved in a file.
25 L<rename|/rename> - Rename a source file on L<GitHub|https://github.com/philiprbrenan> if the target file name is not already in use.
26 L<savePersonalAccessToken|/savePersonalAccessToken> - Save a L<GitHub|https://github.com/philiprbrenan> personal access token by userid in folder L<personalAccessTokenFolder|/personalAccessTokenFolder>.
27 L<specialFileData|/specialFileData> - Do not encode or decode data with a known file signature
28 L<write|/write> - Write the specified data into a file on L<GitHub|https://github.com/philiprbrenan>.
29 L<writeBinaryFileFromFileInCurrentRun|/writeBinaryFileFromFileInCurrentRun> - Write to a file in the current L<GitHub|https://github.com/philiprbrenan> repository by copying a local binary file if we are running on L<GitHub|https://github.com/philiprbrenan>.
30 L<writeBlob|/writeBlob> - Write data into a L<GitHub|https://github.com/philiprbrenan> as a L<blob|https://en.wikipedia.org/wiki/Binary_large_object> that can be referenced by future commits.
31 L<writeCommit|/writeCommit> - Write all the files in a B<$folder> (or just the the named files) into a L<GitHub|https://github.com/philiprbrenan> repository in parallel as a commit on the specified branch.
32 L<writeCommitUsingSavedToken|/writeCommitUsingSavedToken> - Write all the files in a local folder to a named L<GitHub|https://github.com/philiprbrenan> repository using a personal access token as supplied or saved in a file.
33 L<writeFileFromCurrentRun|/writeFileFromCurrentRun> - Write text into a file in the current L<GitHub|https://github.com/philiprbrenan> repository if we are running on L<GitHub|https://github.com/philiprbrenan>.
34 L<writeFileFromFileFromCurrentRun|/writeFileFromFileFromCurrentRun> - Write to a file in the current L<GitHub|https://github.com/philiprbrenan> repository by copying a local file if we are running on L<GitHub|https://github.com/philiprbrenan>.
35 L<writeFileFromFileUsingSavedToken|/writeFileFromFileUsingSavedToken> - Copy a file to L<GitHub|https://github.com/philiprbrenan> using a personal access token as supplied or saved in a file.
36 L<writeFileUsingSavedToken|/writeFileUsingSavedToken> - Write to a file on L<GitHub|https://github.com/philiprbrenan> using a personal access token as supplied or saved in a file.
37 L<writeFolderUsingSavedToken|/writeFolderUsingSavedToken> - Write all the files in a local folder to a target folder on a named L<GitHub|https://github.com/philiprbrenan> repository using a personal access token as supplied or saved in a file.
=head1 Installation
This module is written in 100% Pure Perl and, thus, it is easy to read,
comprehend, use, modify and install via B<cpan>:
sudo cpan install GitHub::Crud
=head1 Author
L<philiprbrenan@gmail.com|mailto:philiprbrenan@gmail.com>
L<http://www.appaapps.com|http://www.appaapps.com>
=head1 Copyright
Copyright (c) 2016-2023 Philip R Brenan.
This module is free software. It may be used, redistributed and/or modified
under the same terms as Perl itself.
=cut
# Tests and documentation
sub test
{my $p = __PACKAGE__;
binmode($_, ":utf8") for *STDOUT, *STDERR;
return if eval "eof(${p}::DATA)";
my $s = eval "join('', <${p}::DATA>)";
$@ and die $@;
eval $s;
$@ and die $@;
1
}
test unless caller;
1;
#podDocumentation
__DATA__
use Test::More tests => 1;
ok 1;
sub success(@) # Write a success message and exit
{say STDERR join ' ', @_;
# exit;
}
if (0) { #Tread
my $g = gitHub;
$g->gitFile = my $f = q(z'2 'z"z.data);
my $d = q(๐ฐ๐ฑ๐ฒ);
$g->write($d);
confess "read FAILED" unless $g->read eq $d;
success "Read passed";
}
if (0) { #Twrite # The second write should be faster because its L<sha> is known from the read
my $g = gitHub;
$g->gitFile = "zzz.data";
my $d = dateTimeStamp.q( ๐ฐ๐ฑ๐ฒ);
if (1)
{my $t = time();
$g->write($d);
lll "First write time: ", time() - $t;
}
my $r = $g->read;
lll "Write bbb: $r";
if (1)
{my $t = time();
$g->write($d);
lll "Second write time: ", time() - $t;
}
confess "write FAILED" unless $g->exists;
success "Write passed";
}
if (0) { # Write an image
my $g = gitHub;
my $f = "face.jpg";
$g->write(readBinaryFile($f), $f);
confess "write FAILED" unless $g->exists;
success "Write passed";
}
if (0) { # Write to and then read from a branch other than master - the branch must already exist in the repo
my $g = gitHub;
$g->gitFile = "zzz.data";
$g->branch = "aaa";
$g->delete;
confess "delete branch FAILED" if $g->exists;
my $d = dateTimeStamp;
$g->write($d);
confess "write branch FAILED" unless $g->read eq $d;
success "Write branch passed";
}
if (0) { # Write and read a zip file
my $g = gitHub;
my $f = temporaryFolder;
writeFile(fpe($f, $_, q(txt)), $_) for 1..3;
my $z = fpe($f, qw(z zip));
say STDERR qx(cd $f; zip $z *);
$g->gitFile = "z.zip";
$g->write(readBinaryFile($z), q(z.zip));
my $F = temporaryFolder;
my $Z = fpe($F, qw(z zip));
writeBinaryFile($Z, $g->read);
my $r = qx(cd $F; unzip $Z);
$r =~ m(extracting: 3.txt) or confess "Failed to unzip";
success "Wrote/read a zip file";
}
if (0) { #TreadBlob #TwriteBlob # Write image as blob and read it back
my $g = gitHub;
$g->gitFile = "face.jpg";
my $d = readBinaryFile(q(/home/phil/.face));
my $s = $g->writeBlob($d);
my $S = q(4a2df549febb701ba651aae46e041923e9550cb8);
confess q(Write blob FAILED) unless $s eq $S;
my $D = $g->readBlob($s);
confess q(Write/Read blob FAILED) unless $d eq $D;
success q(Write/Read blob passed);
}
if (0) { #Tcopy
my ($f1, $f2) = ("zzz.data", "zzz2.data");
my $g = gitHub;
$g->gitFile = $f2; $g->delete;
$g->gitFile = $f1;
my $d = dateTimeStamp;
my $w = $g->write($d);
my $r = $g->copy($f2);
lll "Copy created: $r";
$g->gitFile = $f2;
my $D = $g->read;
lll "Read ccc: $D";
confess "copy FAILED" unless $d eq $D;
success "Copy passed"
}
if (0) { #Texists
my $g = gitHub;
$g->gitFile = "test4.html";
my $d = dateTimeStamp;
$g->write($d);
confess "exists FAILED" unless $g->read eq $d;
$g->delete;
confess "exists FAILED" if $g->read eq $d;
success "Exists passed";
}
if (0) { #Trename
my ($f1, $f2) = qw(zzz.data zzz2.data);
my $g = gitHub;
$g->gitFile = $f2; $g->delete;
my $d = dateTimeStamp;
$g->gitFile = $f1;
$g->write($d);
confess "rename FAILED" unless $g->read eq $d;
$g->rename($f2);
confess "rename FAILED" if $g->exists;
$g->gitFile = $f2;
confess "rename FAILED" if $g->read eq $d;
success "Rename passed";
}
if (0) { #Tdelete # The second delete should be faster because the fact that the file has been deleted is held in the L<sha> cache
my $g = gitHub;
my $d = dateTimeStamp;
$g->gitFile = "zzz.data";
$g->write($d);
confess "delete FAILED" unless $g->read eq $d;
if (1)
{my $t = time();
my $d = $g->delete;
lll "Delete 1: ", $d;
lll "First delete: ", time() - $t;
confess "delete FAILED" if $g->exists;
}
if (1)
{my $t = time();
my $d = $g->delete;
lll "Delete 1: ", $d;
lll "Second delete: ", time() - $t;
confess "delete FAILED" if $g->exists;
}
success "Delete passed";
}
if (0)
{my $g = gitHub;
$g->gitFile = "testFromAppaApps.html";
my $d = join '-', 1..9;
success
"Write : ", dump($g->write($d)),
"\nRead 1: ", dump($g->read),
"\nDelete: ", dump($g->delete),
"\nRead 2: ", dump($g->read);
}
if (0) { #TlistCommits #TlistCommitShas # List all commits
my $c = gitHub->listCommits;
my %s = listCommitShas $c;
lll "Commits\n", dump $c;
lll "Commit shas\n", dump \%s;
success "ListCommits passed";
}
if (0) { #Tlist
success "list:", gitHub->list;
# list: alpha.data .github/workflows/test.yaml images/aaa.txt images/aaa/bbb.txt
}
if (0) { #TwriteCommit #Tnew
my $f = temporaryFolder; # Folder in which we will create some files to upload in the commit
my $c = dateTimeStamp; # Create some content
my $if = q(/home/phil/.face); # Image file
writeFile(fpe($f, q(data), $_, qw(txt)), $c) for 1..3; # Place content in files in a sub folder
copyBinaryFile $if, my $If = fpe $f, qw(face jpg); # Add an image
my $g = GitHub::Crud::new # Create GitHub
(userid => q(philiprbrenan),
repository => q(aaa),
branch => q(test),
confessOnFailure => 1);
$g->loadPersonalAccessToken; # Load a personal access token
$g->writeCommit($f); # Upload commit - confess to any errors
my $C = $g->read(q(data/1.txt)); # Read data written in commit
my $I = $g->read(q(face.jpg));
my $i = readBinaryFile $if;
confess "Date stamp failed" unless $C eq $c; # Check text
confess "Image failed" unless $i eq $I; # Check image
success "Write commit succeeded";
}
if (0) { #TcreatePushWebHook
my $g = gitHub;
my $d = $g->createPushWebHook;
success join ' ', "Create web hook:", dump($d);
}
if (0) { #TlistRepositories
success "List repositories: ", dump(gitHub()->listRepositories);
}
if (0) { #TgetRepository
my $r = gitHub(repository => q(C))->getRepository;
success "Get repository succeeded";
}
if (0) { #TgetRepositoryUsingSavedToken
my $r = getRepositoryUsingSavedToken(q(philiprbrenan), q(aaa));
success "Get repository using saved access token succeeded";
}
if (0) { #TgetRepositoryUpdatedAtUsingSavedToken
my $u = getRepositoryUpdatedAtUsingSavedToken(q(philiprbrenan), q(aaa));
success "Get repository updated_at field succeeded";
}
if (0) { #TcreateRepository
gitHub(repository => q(ccc))->createRepository;
success "Create repository succeeded";
}
if (0) { #TlistWebHooks
success join ' ', q(Webhooks:), dump(gitHub->listWebHooks);
}
if (0) { #TcreateIssue
gitHub(title=>q(Hello), body=>q(World))->createIssue;
success "Create issue succeeded";
}
if (0) { #TcreateIssueFromSavedToken
&createIssueFromSavedToken(qw(philiprbrenan ddd hello World));
success "Create issue succeeded";
}
if (0) { #TcreateRepositoryFromSavedToken
createRepositoryFromSavedToken(q(philiprbrenan), q(ddd));
success "Create repository succeeded";
}
if (0) { #TwriteFileUsingSavedToken
my $s = q(HelloWorld);
&writeFileUsingSavedToken(qw(philiprbrenan ddd hello.txt), $s);
my $S = gitHub(repository=>q(ddd), gitFile=>q(hello.txt))->read;
confess "Write file using saved token FAILED" unless $s eq $S;
success "Write file using saved token succeeded";
}
if (0) { #TwriteFileFromFileUsingSavedToken
my $f = writeFile(undef, my $s = "World\n");
&writeFileFromFileUsingSavedToken(qw(philiprbrenan ddd hello.txt), $f);
my $S = gitHub(repository=>q(ddd), gitFile=>q(hello.txt))->read;
confess "Write file from file using saved token FAILED" unless $s eq $S;
success "Write file from file using saved token succeeded"
}
if (0) { # Write an empty string to a file to delete it
my $g = gitHub
(userid=>q(philiprbrenan), repository=>q(ddd), gitFile=>q(hello.txt));
my $s = "hello";
my $f = $g->write($s);
my $S = $g->read;
confess "Write.read failed" unless $s eq $S;
$g->write;
$g->exists and confess "File still exists";
success "Delete file on empty write succeeded"
}
if (0) { #TreadFileUsingSavedToken
my $s = q(Hello to the World);
&writeFileUsingSavedToken(qw(philiprbrenan ddd hello.txt), $s);
my $S = &readFileUsingSavedToken (qw(philiprbrenan ddd hello.txt));
confess "Read file using saved token FAILED" unless $s eq $S;
success "Read file using saved token succeeded"
}
if (0) { #TsavePersonalAccessToken #TloadPersonalAccessToken
my $d = temporaryFolder;
my $t = join '', 1..20;
my $g = gitHub
(userid => q(philiprbrenan),
personalAccessToken => $t,
personalAccessTokenFolder => $d,
);
$g->savePersonalAccessToken;
my $T = $g->loadPersonalAccessToken;
confess "Load/Save token FAILED" unless $t eq $T;
success "Load/Save token succeeded"
}
if (0) { #TcreateIssueInCurrentRepo #TwriteFileFromCurrentRun #TwriteFileFromFileFromCurrentRun #TwriteBinaryFileFromFileInCurrentRun
createIssueInCurrentRepo("Hello World", "Need to run Hello World");
writeFileFromCurrentRun("output.text", "Hello World");
writeFileFromFileFromCurrentRun("output.txt");
writeBinaryFileFromFileInCurrentRun("image.jpg", "out/image.jpg");
}
if (0) { #TdeleteFileUsingSavedToken
deleteFileUsingSavedToken("philiprbrenan", "test", "aaa.data");
}
if (0) { #TwriteCommitUsingSavedToken #TwriteFolderUsingSavedToken
writeCommitUsingSavedToken("philiprbrenan", "test", "/home/phil/files/");
writeFolderUsingSavedToken("philiprbrenan", "test", "files", "/home/phil/files/");
}