Git operations can be performed programmatically with PHP using the libgit2 library. Here are some common operations.
Cloning a Repository
$repo = git_clone("https://github.com/nanodano/reference", "/tmp/reference");
Opening a repository
$repo = git_repository_open("/tmp/reference");
Git Blame
This will blame the README.md file from HEAD
$options = git_blame_options_new();
$blame = git_blame_file($repo, "README.md", $options);
$obj = git_revparse_single($repo, "HEAD:README.md");
$id = git_object_id($obj);
$blob = git_blob_lookup($repo, $id);
$raw = git_blob_rawcontent($blob);
$i = 0;
$lines = explode("\n", $raw);
foreach ($lines as $data) {
$hunk = git_blame_get_hunk_byline($blame, $i+1);
if (!$hunk) {
continue;
}
$sig = sprintf("%s <%s>", $hunk['final_signature']['name'], $hunk['final_signature']['email']);
printf("%s ( %-30s, %4d) %s\n", substr($hunk['final_commit_id'], 10),
$sig,
$i+1,
$data
);
$i++;
}
Git Diff
$tree = git_tree_lookup($repo, "e14ccb8e18d632d78ce2f0aeb06597a03f42b237");
$diff = git_diff_tree_to_workdir($repo, $tree, git_diff_options_init());
$p = array();
git_diff_print($diff, GIT_DIFF_FORMAT_PATCH, function($diff_delta, $diff_hunk, $diff_line, $payload){
if ($diff_line['origin'] == "-" || $diff_line['origin'] == "+") {
echo $diff_line['origin'];
}
echo $diff_line['content'];
}, $p);
Git Patch
$tree = git_tree_lookup($repo, "e14ccb8e18d632d78ce2f0aeb06597a03f42b237");
$diff = git_diff_tree_to_workdir($repo, $tree, git_diff_options_init());
$payload = array();
$patch = git_patch_from_diff($diff, 1);
git_patch_print($patch, function($diff_delta, $diff_hunk, $diff_line, $payload){
if ($diff_line['origin'] == "-" || $diff_line['origin'] == "+") {
echo $diff_line['origin'];
}
echo $diff_line['content'];
}, $payload);
Git Push
$payload = array();
$repo = git_repository_open(".");
$remote = git_remote_load($repo, "origin");
$remote_callbacks = [
"credentials" => function($url, $username_from_url, $allowed_types, &$payload) {
// Build with LibSSH2 to use ssh protocol.
if ($allowed_types & GIT_CREDTYPE_USERPASS_PLAINTEXT) {
return git_cred_userpass_plaintext_new("nanodano", getenv("GITHUB_TOKEN"));
} else {
error_log("not supported allowed types");
}
}];
git_remote_set_callbacks($remote, $remote_callbacks);
if (git_remote_connect($remote, GIT_DIRECTION_PUSH)) {
$push = git_push_new($remote);
git_push_add_refspec($push, "refs/heads/master:refs/heads/master");
git_push_finish($push);
git_push_unpack_ok($push);
git_push_status_foreach($push, function($ref, $name, &$payload){
var_dump($ref, $name, $payload);
}, $payload);
git_push_update_tips($push);
git_remote_disconnect($remote);
}
Reference Lookup
$ref = git_reference_dwim($repo, "HEAD");
$obj = git_reference_peel($ref, GIT_OBJ_ANY);
echo git_object_id($obj);
Reference Log
$reflog = git_reflog_read($repo, "HEAD");
$count = git_reflog_entrycount($reflog);
for ($i = 0; $i < $count; $i++) {
$entry = git_reflog_entry_byindex($reflog, $i);
var_dump(git_reflog_entry_committer($entry));
}
Git Status
$list = git_status_list_new($repo, array());
$payload = array();
printf("# Changes to be committed:\n");
printf("# (use "git reset HEAD <file>..." to unstage)\n");
printf("#\n");
$cnt = git_status_list_entrycount($list);
for ($i = 0; $i < $cnt; $i++) {
$entry = git_status_byindex($list, $i);
$flags = $entry['status'];
$stat = getStat($flags);
if (is_array($entry['head_to_index'])) {
printf("# %15s %s\n", $stat, $entry['head_to_index']['new_file']['path']);
}
}
printf("#\n");
printf("# Changes not staged for commit:\n");
printf("# (use "git add <file>..." to update what will be committed)\n");
printf("# (use "git checkout -- <file>..." to discard changes in working directory)\n");
printf("#\n");
for ($i = 0; $i < $cnt; $i++) {
$entry = git_status_byindex($list, $i);
$flags = $entry['status'];
$stat = getStat($flags);
if (is_array($entry['index_to_workdir'])) {
printf("# %15s %s\n", $stat, $entry['index_to_workdir']['new_file']['path']);
}
}
printf("#\n");
function getStat($flags)
{
$stat = "";
if ($flags & GIT_STATUS_IGNORED) {
return;
}
if ($flags == GIT_STATUS_CURRENT) {
return;
}
if ($flags & GIT_STATUS_INDEX_NEW){
$stat = "new file:";
}
if ($flags & GIT_STATUS_WT_NEW) {
$stat = "untracked:";
}
if ($flags & GIT_STATUS_INDEX_MODIFIED ||$flags & GIT_STATUS_WT_MODIFIED) {
$stat = "modified:";
}
if ($flags & GIT_STATUS_INDEX_DELETED || $flags & GIT_STATUS_WT_DELETED) {
$stat = "deleted:";
}
if ($flags & GIT_STATUS_INDEX_RENAMED || $flags & GIT_STATUS_WT_RENAMED) {
$stat = "renamed:";
}
if ($flags & GIT_STATUS_INDEX_TYPECHANGE || $flags & GIT_STATUS_WT_TYPECHANGE) {
$stat = "typechange:";
}
return $stat;
}