Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
*~
build/
build-*/
.cache/
.opencode/
/git-wip
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

- Use c++23 best practices. Use CamelCase for classes, use snake_case for method names, variable names, etc. Use #pragma once in headers. Use m_ prefix for member variables.
- use manual arg parsing, use spdlog for debug logging (set `WIP_DEBUG=1` to see debug), use libgit2 for git functionality
- build with `make`, test with `make test`
- build with `make BUILD=build-agent`, test with `make BUILD=build-agent test` — use `BUILD=build-agent` to isolate agent builds from user's default `build/` directory
- manage/install dependencies with `dependencies.sh` script
- unit tests go into `test/unit/test_*.cpp`
- CLI integration tests go into `test/cli/test_*.sh` — source `test/cli/lib.sh`, must be executable
Expand Down Expand Up @@ -229,6 +229,7 @@ test/cli/
test_status.sh # status command tests
test_status2.sh # status after work-branch advance
test_save_file.sh # save with explicit file arguments
test_log.sh # log command tests
CMakeLists.txt # registers each test_*.sh with ctest
```

Expand Down
2 changes: 1 addition & 1 deletion nix/package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ EOF

cmakeFlags = [
"-DCMAKE_BUILD_TYPE=Release"
"-DBUILD_TESTING=OFF"
"-DBUILD_TESTING=OFF"
];

buildPhase = ''
Expand Down
20 changes: 17 additions & 3 deletions src/cmd_log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ int LogCmd::run(int argc, char *argv[]) {
// -----------------------------------------------------------------------
// 1. Parse arguments
// -----------------------------------------------------------------------
bool pretty = false;
bool stat = false;
bool pretty = false;
bool stat = false;
bool reflog_mode = false;
size_t limit = 0;
std::vector<std::string> files;

std::vector<std::string> args;
Expand All @@ -35,13 +36,25 @@ int LogCmd::run(int argc, char *argv[]) {
} else if (a == "--reflog" || a == "-r") {
reflog_mode = true;
} else if (a == "--help" || a == "-h") {
std::println("Usage: git-wip log [--pretty|-p] [--stat|-s] [--reflog|-r] [-- <file>...]\n");
std::println("Usage: git-wip log [-<limit>] [--pretty|-p] [--stat|-s] [--reflog|-r] [-- <file>...]\n");
// - #
std::println(" -<limit> # number of entries to display");
std::println(" -p, --pretty # use pretty oneline log (default full log)");
std::println(" -s, --stat # show file changes in log");
std::println(" -r, --reflog # invoke reflog (shows historical entries)");
std::println(" <file>... # filter on changes to specific file(s)\n");
return 0;
} else if (a[0] == '-' && std::all_of(a.begin()+1, a.end(), [](char ch) { return std::isdigit(ch); })) {

long value = 0;
auto [ptr,ec] = std::from_chars(a.data()+1, a.data() + a.size(), value);
if (ec == std::errc{} && ptr == a.data() + a.size() && value > 0) {
limit = value;
} else {
spdlog::error("could not parse log limit from '{}'", a);
return 1;
}

} else if (!a.empty() && a[0] == '-') {
spdlog::error("git-wip log: unknown option '{}'", a);
return 1;
Expand Down Expand Up @@ -133,6 +146,7 @@ int LogCmd::run(int argc, char *argv[]) {
spdlog::debug("log: stop={}", stop_arg);

std::string cmd = "git log";
if (limit) cmd += fmt::format(" -{}", limit);
if (pretty) cmd += " --graph" + pretty_fmt;
if (stat) cmd += " --stat";
for (const auto &f : files) cmd += " -- " + f;
Expand Down
2 changes: 1 addition & 1 deletion test/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
set(LEGACY_TEST_TREE "${CMAKE_CURRENT_BINARY_DIR}/test-artifacts")
set(GIT_WIP_BIN "$<TARGET_FILE:git-wip>")

foreach(TEST_NAME IN ITEMS test_legacy test_spaces test_status test_status2 test_status_ref test_save_file test_save_subdir test_list test_delete test_help)
foreach(TEST_NAME IN ITEMS test_legacy test_spaces test_status test_status2 test_status_ref test_save_file test_save_subdir test_list test_delete test_help test_log)
add_test(
NAME "cli/${TEST_NAME}"
COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}.sh"
Expand Down
125 changes: 125 additions & 0 deletions test/cli/test_log.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env bash
source "$(dirname "$0")/lib.sh"

create_test_repo
RUN git config user.email "test@example.com"
RUN git config user.name "Test User"

RUN "echo v1 >file.txt"
RUN git add file.txt
RUN git commit -m initial

# -------------------------------------------------------------------------
# no wip branch yet — log should report error

_RUN "$GIT_WIP" log
EXP_grep "has no WIP commits"

# -------------------------------------------------------------------------
# create 3 wip commits with distinct file changes

RUN "echo v2 >file.txt"
RUN "$GIT_WIP" save "\"WIP one\""

RUN "echo v3 >file.txt"
RUN "$GIT_WIP" save "\"WIP two\""

RUN "echo v4 >file.txt"
RUN "$GIT_WIP" save "\"WIP three\""

# -------------------------------------------------------------------------
# log (no flags) — default full log format

RUN "$GIT_WIP" log
EXP_grep "^commit "
EXP_grep "WIP three"
EXP_grep "WIP two"
EXP_grep "WIP one"

# -------------------------------------------------------------------------
# log -p / --pretty — compact oneline graph format

RUN "$GIT_WIP" log -p
EXP_grep "WIP three"
EXP_grep "WIP two"
EXP_grep "WIP one"
# pretty format includes color codes and relative time
EXP_grep "ago"

RUN "$GIT_WIP" log --pretty
EXP_grep "WIP three"
EXP_grep "ago"

# -------------------------------------------------------------------------
# log -s / --stat — shows file changes

RUN "$GIT_WIP" log -s
EXP_grep "^commit "
EXP_grep "WIP three"
EXP_grep "file.txt"
EXP_grep "changed"

RUN "$GIT_WIP" log --stat
EXP_grep "file.txt"
EXP_grep "changed"

# -------------------------------------------------------------------------
# log -p -s — both pretty and stat

RUN "$GIT_WIP" log -p -s
EXP_grep "WIP three"
EXP_grep "file.txt"
EXP_grep "changed"

# -------------------------------------------------------------------------
# log -<limit> — limit number of entries

RUN "$GIT_WIP" log -1
EXP_grep "WIP three"
EXP_grep -v "WIP two"
EXP_grep -v "WIP one"

RUN "$GIT_WIP" log -2
EXP_grep "WIP three"
EXP_grep "WIP two"
EXP_grep -v "WIP one"

# -------------------------------------------------------------------------
# log --reflog / -r — shows reflog entries

RUN "$GIT_WIP" log -r
EXP_grep "refs/wip/master"
EXP_grep "git-wip:"

RUN "$GIT_WIP" log --reflog
EXP_grep "refs/wip/master"

# -------------------------------------------------------------------------
# log after work branch advances — wip log resets to new base

RUN git add file.txt
RUN git commit -m "\"real commit\""

RUN "echo v5 >file.txt"
RUN "$GIT_WIP" save "\"WIP after commit\""

# now log should show only the new wip commit, not the old ones
RUN "$GIT_WIP" log
EXP_grep "WIP after commit"
EXP_grep -v "WIP three"
EXP_grep -v "WIP two"
EXP_grep -v "WIP one"

# -------------------------------------------------------------------------
# log -h / --help — shows usage

RUN "$GIT_WIP" log -h
EXP_grep "Usage:"
EXP_grep "pretty"
EXP_grep "stat"
EXP_grep "reflog"

RUN "$GIT_WIP" log --help
EXP_grep "Usage:"

echo "OK: $TEST_NAME"
Loading