log command
The log command is used to view the commit history of a repository. It displays:
- The commit hash (SHA-1 identifier)
- The author of the commit
- The commit timestamp
- The commit message
Command Implementation
- Check if
.mygit/HEADexists:
./index.php
if (!file_exists(".mygit/HEAD")) {
echo "Error: No commits yet\n";
return;
}
- Ensures that the repository has at least one commit.
- If
.mygit/HEADdoesn’t exist, no commits have been made, so it prints an error and exits.
- Determine the latest commit hash:
./index.php
$head_content = trim(file_get_contents(".mygit/HEAD"));
$curr_hash = null;
if (str_starts_with($head_content, "ref: ")) {
$ref = substr($head_content, 5);
if (!file_exists(".mygit/" . $ref)) {
echo "Error: No commits yet\n";
return;
}
$curr_hash = trim(file_get_contents(".mygit/" . $ref));
} else {
$curr_hash = $head_content;
}
- Reads .mygit/HEAD, which stores either:
- A reference (
ref: refs/heads/main), pointing to a branch’s latest commit. - A direct commit hash (in a detached HEAD state).
- A reference (
- If it’s a reference, we retrieve the latest commit hash from
.mygit/refs/heads/main. - Otherwise, we assume that
.mygit/HEADcontains a commit hash.
- Iterate through the commit history:
./index.php
while ($curr_hash) {
- The loop iterates over commit objects, starting from
$curr_hash(latest commit) and following parent commits.
- Retrieve and uncompress the commit object:
./index.php
$dir = ".mygit/objects/" . substr($curr_hash, 0, 2);
$path = $dir . '/' . substr($curr_hash, 2);
if (!file_exists($path)) {
echo "Error: Commit object not found\n";
break;
}
$compressed = file_get_contents($path);
$data = gzuncompress($compressed);
$parts = explode("\0", $data, 2);
if (count($parts) < 2) {
echo "Error: Invalid commit object format\n";
break;
}
- we construct the path to the commit object by using the first two characters of the hash as the directory name and the rest as file path.
- Reads and uncompresses the commit object.
- Splits the content at the
\0null byte (used to separate the header and commit content). - If the commit object format is invalid, it prints an error and exits.
-
Extract commit metadata:
- Extract the content:
./index.php$content = $parts[1];
$lines = explode("\n", $content);-
$parts[1]contains everything after the\0, which the actual commit metadata and message. -
Then we split the content into lines.
- Initialize variables:
./index.php$commit_data = [];
$message = "";
$reading_message = false;-
$commit_datawill store key-value pairs of commit metadata (tree, parent, author). -
$messagewill store the commit message. -
$reading_messageis a flag to track when we start reading the message.
- Process each line:
./index.phpforeach ($lines as $line) {
if (empty(trim($line))) continue;-
We skip empty lines to avoid unnecessary processing.
- Handle the commit message:
./index.phpif (str_starts_with($line, "message ")) {
$message = substr($line, 8);
$reading_message = true;
} else if ($reading_message) {
$message .= "\n" . $line;
} else {
$parts = explode(" ", $line, 2);
if (count($parts) == 2) {
$commit_data[$parts[0]] = $parts[1];
}
}- If the line starts with "message ", it marks the beginning of the commit message.
- We use
substr($line, 8)to remove "message " and stores the actual message. - If
$reading_messageis already true, additional lines are appended to$message(we use it to handle multi-line commit messages). - Else if a line isn’t part of the message, we store it as a key-value pair after separating it into parts.
Example of what's stored in our variables after processing:
example$commit_data = [
"tree" => "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s",
"parent" => "7a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s",
"author" => "John Doe john@example.com 1707301245"
];
$message = "Fixed a major bug\nthat caused a crash.";
This part code:
./index.php
$content = $parts[1];
$lines = explode("\n", $content);
$commit_data = [];
$message = "";
$reading_message = false;
foreach ($lines as $line) {
if (empty(trim($line))) continue;
if (str_starts_with($line, "message ")) {
$message = substr($line, 8);
$reading_message = true;
} else if ($reading_message) {
$message .= "\n" . $line;
} else {
$parts = explode(" ", $line, 2);
if (count($parts) == 2) {
$commit_data[$parts[0]] = $parts[1];
}
}
}
- Print commit data:
./index.php
echo "\033[33mcommit " . $curr_hash . "\033[0m\n";
if (isset($commit_data["author"])) {
$author_data = explode(" ", $commit_data["author"]);
$timestamp = end($author_data);
$author_name = implode(" ", array_slice($author_data, 0, -1));
$date = date("Y-m-d H:i:s", (int) $timestamp) . "\n";
}
echo "\033[36mAuthor: " . $author_name . "\033[0m\n";
echo "Date: " . $date;
echo "\n " . $message . "\n\n";
- Prints the commit hash in yellow (
\033[33m). - We extracts the author's name and timestamp.
- Then we convert the timestamp into a human-readable format (
YYYY-MM-DD HH:MM:SS). - Prints the author’s name in cyan (
\033[36m). - Prints the commit message.
- Move to the parent commit:
./index.php
$curr_hash = $commit_data["parent"] ?? null;
- Sets
$curr_hashto the parent commit hash. - If no parent exists, it stops our while loop.
Full code for this section:
./index.php
function command_log()
{
if (!file_exists(".mygit/HEAD")) {
echo "Error: No commits yet\n";
return;
}
$head_content = trim(file_get_contents(".mygit/HEAD"));
$curr_hash = null;
if (str_starts_with($head_content, "ref: ")) {
$ref = substr($head_content, 5);
if (!file_exists(".mygit/" . $ref)) {
echo "Error: No commits yet\n";
return;
}
$curr_hash = trim(file_get_contents(".mygit/" . $ref));
} else {
$curr_hash = $head_content;
}
while ($curr_hash) {
$dir = ".mygit/objects/" . substr($curr_hash, 0, 2);
$path = $dir . '/' . substr($curr_hash, 2);
if (!file_exists($path)) {
echo "Error: Commit object not found\n";
break;
}
$compressed = file_get_contents($path);
$data = gzuncompress($compressed);
$parts = explode("\0", $data, 2);
if (count($parts) < 2) {
echo "Error: Invalid commit object format\n";
break;
}
$content = $parts[1];
$lines = explode("\n", $content);
$commit_data = [];
$message = "";
$reading_message = false;
foreach ($lines as $line) {
if (empty(trim($line)))
continue;
if (str_starts_with($line, "message ")) {
$message = substr($line, 8);
$reading_message = true;
} else if ($reading_message) {
$message .= "\n" . $line;
} else {
$parts = explode(" ", $line, 2);
if (count($parts) == 2) {
$commit_data[$parts[0]] = $parts[1];
}
}
}
echo "\033[33mcommit " . $curr_hash . "\033[0m\n";
if (isset($commit_data["author"])) {
$author_data = explode(" ", $commit_data["author"]);
$timestamp = end($author_data);
$author_name = implode(" ", array_slice($author_data, 0, -1));
$date = date("Y-m-d H:i:s", (int) $timestamp) . "\n";
}
echo "\033[36mAuthor: " . $author_name . "\033[0m\n";
echo "Date: " . $date;
echo "\n " . $message . "\n\n";
$curr_hash = $commit_data["parent"] ?? null;
}
}