Skip to content

Commit c73bbee

Browse files
committed
Add a "clone" command for git clone'ing repos
libgit sucks hard, so let's just fork and call 'git clone', using sd_event to make things nice and async. In the case where the git repository already exists, let's transparently turn "git clone" into "git pull", in order to make updates simpler. There's probably bugs here. I need better (any) unit tests for the event loop portion of this...
1 parent 1fb0d47 commit c73bbee

File tree

13 files changed

+339
-36
lines changed

13 files changed

+339
-36
lines changed

e2e/clone

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/bin/bash
2+
3+
# shellcheck disable=SC1090
4+
source "${0%/*}/common"
5+
6+
# should clone
7+
if ! auracle clone auracle-git; then
8+
echo "FAIL: expected 0 exit status, got non-zero" >&2
9+
exit 1
10+
fi
11+
12+
# should do nothing (update)
13+
if ! auracle clone auracle-git; then
14+
echo "FAIL: expected 0 exit status, got non-zero" >&2
15+
exit 1
16+
fi
17+
18+
assert_file_exists "$TEST_TMPDIR/auracle-git/PKGBUILD"
19+
assert_directory_exists "$TEST_TMPDIR/auracle-git/.git"

e2e/common

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/bin/bash
2+
3+
find_build_directory() {
4+
local build_dirs=(*/.ninja_log)
5+
6+
if [[ ! -e ${build_dirs[0]} ]]; then
7+
echo "error: No build directory found. Have you run 'meson build' yet?" >&2
8+
return 1
9+
elif (( ${#build_dirs[*]} > 1 )); then
10+
echo "error: Multiple build directories found. Unable to proceed." >&2
11+
return 1
12+
fi
13+
14+
printf '%s\n' "${build_dirs[0]%/*}"
15+
}
16+
17+
assert_directory_exists() {
18+
if [[ ! -d $1 ]]; then
19+
printf 'FAIL: expected "%s", but not found\n' "$1" >&2
20+
exit 1
21+
fi
22+
}
23+
24+
assert_file_exists() {
25+
if [[ ! -f $1 ]]; then
26+
printf 'FAIL: expected "%s", but not found\n' "$1" >&2
27+
exit 1
28+
fi
29+
}
30+
31+
auracle() {
32+
"$AURACLE_BIN" -C "$TEST_TMPDIR" "$@" 2>/dev/null
33+
}
34+
35+
BUILD_DIR=$(find_build_directory) || exit 1
36+
AURACLE_BIN=$BUILD_DIR/auracle
37+
38+
TEST_TMPDIR=$(mktemp -d)
39+
trap 'rm -rf "$TEST_TMPDIR"' EXIT
40+

e2e/download

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
3+
# shellcheck disable=SC1090
4+
source "${0%/*}/common"
5+
6+
if ! auracle download pkgfile-git; then
7+
echo "FAIL: expected 0 exit status, got non-zero" >&2
8+
exit 1
9+
fi
10+
assert_file_exists "$TEST_TMPDIR/pkgfile-git/PKGBUILD"
11+
12+
if ! auracle download -r auracle-git; then
13+
echo "FAIL: expected 0 exit status, got non-zero" >&2
14+
exit 1
15+
fi
16+
assert_file_exists "$TEST_TMPDIR/auracle-git/PKGBUILD"
17+
assert_file_exists "$TEST_TMPDIR/nlohmann-json/PKGBUILD"

e2e/rawquery

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
3+
# shellcheck disable=SC1090
4+
source "${0%/*}/common"
5+
6+
if ! out=$(auracle rawsearch aura | jq .resultcount 2>&1) && (( out > 0 )); then
7+
echo "FAIL: expected valid JSON, but got: $out" >&2
8+
exit 1
9+
fi
10+
11+
if ! out=$(auracle rawsearch auracle-git | jq .resultcount 2>&1) || (( out != 1 )); then
12+
echo "FAIL: expected valid JSON with resultcount of 1, but got: $out" >&2
13+
exit 1
14+
fi
15+
16+
if ! out=$(auracle rawsearch --searchby maintainer falconindy | jq -r '.results[].Name') ||
17+
[[ $out != *auracle-git* ]]; then
18+
echo "FAIL: expected valid JSON with at least 'auracle-git', but got: $out" >&2
19+
exit 1
20+
fi

extra/bash_completion

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ _auracle() {
4343
fi
4444

4545
local -A VERBS=(
46-
[AUR_PACKAGES]='buildorder download pkgbuild info rawinfo'
46+
[AUR_PACKAGES]='buildorder clone download pkgbuild info rawinfo'
4747
[LOCAL_PACKAGES]='sync'
4848
[NONE]='search rawsearch'
4949
)

man/auracle.1.pod

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ input to this operation.
102102
Pass one to many arguments to download packages. Use the B<--recurse> flag
103103
to download dependencies of packages.
104104

105+
=item B<clone> I<PACKAGES>...
106+
107+
Pass one to many arguments to clone package git repositories. Use the
108+
B<--recurse> flag to clone dependencies of packages. If the git repository
109+
already exists, the repository will instead be updated via a fast-forward only
110+
git pull .
111+
105112
=item B<buildorder> I<PACKAGES>...
106113

107114
Pass one to many arguments to print a build order for the given packages.

src/aur/aur.cc

Lines changed: 124 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
#include <fcntl.h>
44
#include <string.h>
5-
65
#include <unistd.h>
6+
77
#include <chrono>
8+
#include <filesystem>
89
#include <string_view>
910

11+
namespace fs = std::filesystem;
12+
1013
namespace aur {
1114

1215
namespace {
@@ -58,7 +61,7 @@ class ResponseHandler {
5861

5962
static size_t BodyCallback(char* ptr, size_t size, size_t nmemb,
6063
void* userdata) {
61-
auto* handler = static_cast<ResponseHandler*>(userdata);
64+
auto handler = static_cast<ResponseHandler*>(userdata);
6265

6366
handler->body.append(ptr, size * nmemb);
6467

@@ -67,7 +70,7 @@ class ResponseHandler {
6770

6871
static size_t HeaderCallback(char* buffer, size_t size, size_t nitems,
6972
void* userdata) {
70-
auto* handler = static_cast<ResponseHandler*>(userdata);
73+
auto handler = static_cast<ResponseHandler*>(userdata);
7174

7275
// Remove 2 bytes to ignore trailing \r\n
7376
ci_string_view header(buffer, size * nitems - 2);
@@ -122,8 +125,7 @@ class RpcResponseHandler : public ResponseHandler {
122125
public:
123126
using CallbackType = Aur::RpcResponseCallback;
124127

125-
RpcResponseHandler(Aur::RpcResponseCallback callback)
126-
: callback_(std::move(callback)) {}
128+
RpcResponseHandler(CallbackType callback) : callback_(std::move(callback)) {}
127129

128130
private:
129131
int Run(const std::string& error) const override {
@@ -146,8 +148,7 @@ class RawResponseHandler : public ResponseHandler {
146148
public:
147149
using CallbackType = Aur::RawResponseCallback;
148150

149-
RawResponseHandler(Aur::RawResponseCallback callback)
150-
: callback_(std::move(callback)) {}
151+
RawResponseHandler(CallbackType callback) : callback_(std::move(callback)) {}
151152

152153
private:
153154
int Run(const std::string& error) const override {
@@ -161,6 +162,33 @@ class RawResponseHandler : public ResponseHandler {
161162
const CallbackType callback_;
162163
};
163164

165+
class CloneResponseHandler : public ResponseHandler {
166+
public:
167+
using CallbackType = Aur::CloneResponseCallback;
168+
169+
CloneResponseHandler(Aur* aur, CallbackType callback)
170+
: aur_(aur), callback_(std::move(callback)) {}
171+
172+
Aur* aur() const { return aur_; }
173+
174+
void SetOperation(std::string operation) {
175+
operation_ = std::move(operation);
176+
}
177+
178+
private:
179+
int Run(const std::string& error) const override {
180+
if (!error.empty()) {
181+
return callback_(error);
182+
}
183+
184+
return callback_(CloneResponse{std::move(operation_)});
185+
}
186+
187+
Aur* aur_;
188+
const CallbackType callback_;
189+
std::string operation_;
190+
};
191+
164192
} // namespace
165193

166194
Aur::Aur(const std::string& baseurl) : baseurl_(baseurl) {
@@ -175,6 +203,10 @@ Aur::Aur(const std::string& baseurl) : baseurl_(baseurl) {
175203
curl_multi_setopt(curl_, CURLMOPT_TIMERFUNCTION, &Aur::TimerCallback);
176204
curl_multi_setopt(curl_, CURLMOPT_TIMERDATA, this);
177205

206+
sigset_t ss;
207+
sigaddset(&ss, SIGCHLD);
208+
sigprocmask(SIG_BLOCK, &ss, &saved_ss_);
209+
178210
sd_event_default(&event_);
179211
}
180212

@@ -183,12 +215,14 @@ Aur::~Aur() {
183215
curl_global_cleanup();
184216

185217
sd_event_unref(event_);
218+
219+
sigprocmask(SIG_SETMASK, &saved_ss_, nullptr);
186220
}
187221

188222
// static
189223
int Aur::SocketCallback(CURLM* curl, curl_socket_t s, int action,
190224
void* userdata, void*) {
191-
Aur* aur = static_cast<Aur*>(userdata);
225+
auto aur = static_cast<Aur*>(userdata);
192226

193227
auto iter = aur->active_io_.find(s);
194228
sd_event_source* io = iter != aur->active_io_.end() ? iter->second : nullptr;
@@ -255,7 +289,7 @@ int Aur::SocketCallback(CURLM* curl, curl_socket_t s, int action,
255289

256290
// static
257291
int Aur::OnIO(sd_event_source* s, int fd, uint32_t revents, void* userdata) {
258-
Aur* aur = static_cast<Aur*>(userdata);
292+
auto aur = static_cast<Aur*>(userdata);
259293
int action, k = 0;
260294

261295
// Throwing an exception here would indicate a bug in Aur::SocketCallback.
@@ -275,27 +309,25 @@ int Aur::OnIO(sd_event_source* s, int fd, uint32_t revents, void* userdata) {
275309
return -EINVAL;
276310
}
277311

278-
aur->ProcessDoneEvents();
279-
return 0;
312+
return aur->ProcessDoneEvents();
280313
}
281314

282315
// static
283316
int Aur::OnTimer(sd_event_source* s, uint64_t usec, void* userdata) {
284-
Aur* aur = static_cast<Aur*>(userdata);
317+
auto aur = static_cast<Aur*>(userdata);
285318
int k = 0;
286319

287320
if (curl_multi_socket_action(aur->curl_, CURL_SOCKET_TIMEOUT, 0, &k) !=
288321
CURLM_OK) {
289322
return -EINVAL;
290323
}
291324

292-
aur->ProcessDoneEvents();
293-
return 0;
325+
return aur->ProcessDoneEvents();
294326
}
295327

296328
// static
297329
int Aur::TimerCallback(CURLM* curl, long timeout_ms, void* userdata) {
298-
Aur* aur = static_cast<Aur*>(userdata);
330+
auto aur = static_cast<Aur*>(userdata);
299331

300332
if (timeout_ms < 0) {
301333
if (aur->timer_) {
@@ -332,7 +364,7 @@ int Aur::TimerCallback(CURLM* curl, long timeout_ms, void* userdata) {
332364

333365
void Aur::StartRequest(CURL* curl) {
334366
curl_multi_add_handle(curl_, curl);
335-
active_requests_.insert(curl);
367+
active_requests_.Add(curl);
336368
}
337369

338370
int Aur::FinishRequest(CURL* curl, CURLcode result, bool dispatch_callback) {
@@ -354,7 +386,7 @@ int Aur::FinishRequest(CURL* curl, CURLcode result, bool dispatch_callback) {
354386

355387
auto r = dispatch_callback ? handler->RunCallback(error) : 0;
356388

357-
active_requests_.erase(curl);
389+
active_requests_.Remove(curl);
358390
curl_multi_remove_handle(curl_, curl);
359391
curl_easy_cleanup(curl);
360392

@@ -383,16 +415,11 @@ int Aur::ProcessDoneEvents() {
383415
return 0;
384416
}
385417

386-
void Aur::Cancel() {
387-
while (!active_requests_.empty()) {
388-
FinishRequest(*active_requests_.begin(), CURLE_ABORTED_BY_CALLBACK,
389-
/* dispatch_callback = */ false);
390-
}
391-
}
392-
393418
int Aur::Wait() {
394-
while (!active_requests_.empty()) {
395-
sd_event_run(event_, 0);
419+
while (!active_requests_.IsEmpty()) {
420+
if (sd_event_run(event_, 0) < 0) {
421+
break;
422+
}
396423
}
397424

398425
return 0;
@@ -473,6 +500,77 @@ void Aur::QueueRequest(
473500
}
474501
}
475502

503+
// static
504+
int Aur::OnCloneExit(sd_event_source* s, const siginfo_t* si, void* userdata) {
505+
auto handler = static_cast<CloneResponseHandler*>(userdata);
506+
507+
handler->aur()->active_requests_.Remove(s);
508+
sd_event_source_unref(s);
509+
510+
std::string error;
511+
if (si->si_status != 0) {
512+
error.assign("TODO: useful error message for non-zero exit status: " +
513+
std::to_string(si->si_status));
514+
}
515+
516+
return handler->RunCallback(error);
517+
}
518+
519+
void Aur::QueueCloneRequest(const CloneRequest& request,
520+
const CloneResponseCallback& callback) {
521+
auto response_handler = new CloneResponseHandler(this, callback);
522+
523+
const bool update = fs::exists(fs::path(request.reponame()) / ".git");
524+
if (update) {
525+
response_handler->SetOperation("update");
526+
} else {
527+
response_handler->SetOperation("clone");
528+
}
529+
530+
int pid = fork();
531+
if (pid < 0) {
532+
response_handler->RunCallback(std::string(strerror(errno)));
533+
return;
534+
}
535+
536+
if (pid == 0) {
537+
auto url = request.Build(baseurl_)[0];
538+
539+
const char* cmd[] = {
540+
NULL, // git
541+
NULL, // if pulling, -C
542+
NULL, // if pulling, arg to -C
543+
NULL, // 'clone' or 'pull'
544+
NULL, // --quiet
545+
NULL, // --ff-only (if pulling), URL if cloning
546+
NULL,
547+
};
548+
int idx = 0;
549+
550+
cmd[idx++] = "git";
551+
if (update) {
552+
cmd[idx++] = "-C";
553+
cmd[idx++] = request.reponame().c_str();
554+
cmd[idx++] = "pull";
555+
cmd[idx++] = "--quiet";
556+
cmd[idx++] = "--ff-only";
557+
} else {
558+
cmd[idx++] = "clone";
559+
cmd[idx++] = "--quiet";
560+
cmd[idx++] = url.c_str();
561+
}
562+
563+
execvp(cmd[0], const_cast<char* const*>(cmd));
564+
_exit(127);
565+
}
566+
567+
sd_event_source* child;
568+
sd_event_add_child(event_, &child, pid, WEXITED, &Aur::OnCloneExit,
569+
response_handler);
570+
571+
active_requests_.Add(child);
572+
}
573+
476574
void Aur::QueueRawRpcRequest(const RpcRequest& request,
477575
const RawResponseCallback& callback) {
478576
QueueRequest<RawRpcRequestTraits>(request, callback);

0 commit comments

Comments
 (0)