Skip to content

Commit d676b25

Browse files
committed
Add support for entering user credentials to access remote private repos
1 parent c460fce commit d676b25

17 files changed

+395
-71
lines changed

.github/workflows/test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ jobs:
4848
./git2cpp -v
4949
5050
- name: Run tests
51+
env:
52+
GIT2CPP_TEST_PRIVATE_TOKEN: ${{ secrets.GIT2CPP_TEST_PRIVATE_TOKEN }}
5153
run: |
5254
pytest -v
5355
@@ -76,6 +78,8 @@ jobs:
7678
run: cmake --build . --parallel 8
7779

7880
- name: Run tests
81+
env:
82+
GIT2CPP_TEST_PRIVATE_TOKEN: ${{ secrets.GIT2CPP_TEST_PRIVATE_TOKEN }}
7983
run: |
8084
pytest -v
8185

CMakeLists.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,12 @@ set(GIT2CPP_SRC
8888
${GIT2CPP_SOURCE_DIR}/utils/ansi_code.hpp
8989
${GIT2CPP_SOURCE_DIR}/utils/common.cpp
9090
${GIT2CPP_SOURCE_DIR}/utils/common.hpp
91+
${GIT2CPP_SOURCE_DIR}/utils/credentials.cpp
92+
${GIT2CPP_SOURCE_DIR}/utils/credentials.hpp
9193
${GIT2CPP_SOURCE_DIR}/utils/git_exception.cpp
9294
${GIT2CPP_SOURCE_DIR}/utils/git_exception.hpp
93-
${GIT2CPP_SOURCE_DIR}/utils/output.cpp
94-
${GIT2CPP_SOURCE_DIR}/utils/output.hpp
95+
${GIT2CPP_SOURCE_DIR}/utils/input_output.cpp
96+
${GIT2CPP_SOURCE_DIR}/utils/input_output.hpp
9597
${GIT2CPP_SOURCE_DIR}/utils/progress.cpp
9698
${GIT2CPP_SOURCE_DIR}/utils/progress.hpp
9799
${GIT2CPP_SOURCE_DIR}/utils/terminal_pager.cpp

src/subcommand/clone_subcommand.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
#include <iostream>
22

33
#include "../subcommand/clone_subcommand.hpp"
4-
#include "../utils/output.hpp"
4+
#include "../utils/credentials.hpp"
5+
#include "../utils/input_output.hpp"
56
#include "../utils/progress.hpp"
67
#include "../wrapper/repository_wrapper.hpp"
78

@@ -42,6 +43,7 @@ void clone_subcommand::run()
4243
checkout_opts.progress_cb = checkout_progress;
4344
checkout_opts.progress_payload = &pd;
4445
clone_opts.checkout_opts = checkout_opts;
46+
clone_opts.fetch_opts.callbacks.credentials = user_credentials;
4547
clone_opts.fetch_opts.callbacks.sideband_progress = sideband_progress;
4648
clone_opts.fetch_opts.callbacks.transfer_progress = fetch_progress;
4749
clone_opts.fetch_opts.callbacks.payload = &pd;

src/subcommand/commit_subcommand.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <unistd.h>
33

44
#include "../subcommand/commit_subcommand.hpp"
5+
#include "../utils/input_output.hpp"
56
#include "../wrapper/index_wrapper.hpp"
67
#include "../wrapper/repository_wrapper.hpp"
78

@@ -24,8 +25,7 @@ void commit_subcommand::run()
2425

2526
if (m_commit_message.empty())
2627
{
27-
std::cout << "Please enter a commit message:" << std::endl;
28-
std::getline(std::cin, m_commit_message);
28+
m_commit_message = prompt_input("Please enter a commit message:\n");
2929
if (m_commit_message.empty())
3030
{
3131
throw std::runtime_error("Aborting, no commit message specified.");

src/subcommand/fetch_subcommand.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
#include <git2/remote.h>
44

55
#include "../subcommand/fetch_subcommand.hpp"
6-
#include "../utils/output.hpp"
6+
#include "../utils/credentials.hpp"
7+
#include "../utils/input_output.hpp"
78
#include "../utils/progress.hpp"
89
#include "../wrapper/repository_wrapper.hpp"
910

@@ -34,6 +35,7 @@ void fetch_subcommand::run()
3435

3536
git_indexer_progress pd = {0};
3637
git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
38+
fetch_opts.callbacks.credentials = user_credentials;
3739
fetch_opts.callbacks.sideband_progress = sideband_progress;
3840
fetch_opts.callbacks.transfer_progress = fetch_progress;
3941
fetch_opts.callbacks.payload = &pd;

src/subcommand/push_subcommand.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <git2/remote.h>
44

55
#include "../subcommand/push_subcommand.hpp"
6+
#include "../utils/credentials.hpp"
67
#include "../utils/progress.hpp"
78
#include "../wrapper/repository_wrapper.hpp"
89

@@ -27,6 +28,7 @@ void push_subcommand::run()
2728
auto remote = repo.find_remote(remote_name);
2829

2930
git_push_options push_opts = GIT_PUSH_OPTIONS_INIT;
31+
push_opts.callbacks.credentials = user_credentials;
3032
push_opts.callbacks.push_transfer_progress = push_transfer_progress;
3133
push_opts.callbacks.push_update_reference = push_update_reference;
3234

src/utils/credentials.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#include <git2/credential.h>
2+
#include <iostream>
3+
4+
#include "credentials.hpp"
5+
#include "input_output.hpp"
6+
7+
// git_credential_acquire_cb
8+
int user_credentials(
9+
git_credential** out,
10+
const char* url,
11+
const char* username_from_url,
12+
unsigned int allowed_types,
13+
void* payload)
14+
{
15+
// Check for cached credentials here, if desired.
16+
// It might be necessary to make this function stateful to avoid repeating unnecessary checks.
17+
18+
*out = nullptr;
19+
20+
if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) {
21+
std::string username = username_from_url ? username_from_url : prompt_input("Username: ");
22+
if (username.empty()) {
23+
giterr_set_str(GIT_ERROR_HTTP, "No username specified");
24+
return GIT_EAUTH;
25+
}
26+
27+
std::string password = prompt_input("Password: ", false);
28+
if (password.empty()) {
29+
giterr_set_str(GIT_ERROR_HTTP, "No password specified");
30+
return GIT_EAUTH;
31+
}
32+
33+
// If successful, this will create and return a git_credential* in the out argument.
34+
return git_credential_userpass_plaintext_new(out, username.c_str(), password.c_str());
35+
}
36+
37+
giterr_set_str(GIT_ERROR_HTTP, "Unexpected credentials request");
38+
return GIT_ERROR;
39+
}

src/utils/credentials.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#pragma once
2+
3+
#include <git2/credential.h>
4+
5+
// Libgit2 callback of type git_credential_acquire_cb to obtain user credentials
6+
// (username and password) to authenticate remote https access.
7+
int user_credentials(
8+
git_credential** out,
9+
const char* url,
10+
const char* username_from_url,
11+
unsigned int allowed_types,
12+
void* payload
13+
);

src/utils/input_output.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#include "ansi_code.hpp"
2+
#include "input_output.hpp"
3+
4+
// OS-specific libraries.
5+
#include <sys/ioctl.h>
6+
7+
cursor_hider::cursor_hider(bool hide /* = true */)
8+
: m_hide(hide)
9+
{
10+
std::cout << (m_hide ? ansi_code::hide_cursor : ansi_code::show_cursor);
11+
}
12+
13+
cursor_hider::~cursor_hider()
14+
{
15+
std::cout << (m_hide ? ansi_code::show_cursor : ansi_code::hide_cursor);
16+
}
17+
18+
19+
alternative_buffer::alternative_buffer()
20+
{
21+
tcgetattr(fileno(stdin), &m_previous_termios);
22+
auto new_termios = m_previous_termios;
23+
// Disable canonical mode (buffered I/O) and echo from stdin to stdout.
24+
new_termios.c_lflag &= (~ICANON & ~ECHO);
25+
tcsetattr(fileno(stdin), TCSANOW, &new_termios);
26+
27+
std::cout << ansi_code::enable_alternative_buffer;
28+
}
29+
30+
alternative_buffer::~alternative_buffer()
31+
{
32+
std::cout << ansi_code::disable_alternative_buffer;
33+
34+
// Restore previous termios settings.
35+
tcsetattr(fileno(stdin), TCSANOW, &m_previous_termios);
36+
}
37+
38+
echo_control::echo_control(bool echo)
39+
: m_echo(echo)
40+
{
41+
if (!m_echo) {
42+
tcgetattr(fileno(stdin), &m_previous_termios);
43+
auto new_termios = m_previous_termios;
44+
new_termios.c_lflag &= ~ECHO;
45+
tcsetattr(fileno(stdin), TCSANOW, &new_termios);
46+
}
47+
}
48+
49+
echo_control::~echo_control()
50+
{
51+
if (!m_echo) {
52+
// Restore previous termios settings.
53+
tcsetattr(fileno(stdin), TCSANOW, &m_previous_termios);
54+
}
55+
}
56+
57+
58+
std::string prompt_input(const std::string_view prompt, bool echo /* = true */)
59+
{
60+
std::cout << prompt;
61+
62+
echo_control ec(echo);
63+
std::string input;
64+
65+
cursor_hider ch(false); // Re-enable cursor if currently hidden.
66+
std::getline(std::cin, input);
67+
68+
if (!echo) {
69+
std::cout << std::endl;
70+
}
71+
72+
// Maybe sanitise input, removing escape codes?
73+
return input;
74+
}

src/utils/input_output.hpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#pragma once
2+
3+
#include <iostream>
4+
#include "common.hpp"
5+
6+
// OS-specific libraries.
7+
#include <termios.h>
8+
9+
// Scope object to hide the cursor. This avoids
10+
// cursor twinkling when rewritting the same line
11+
// too frequently.
12+
// If you are within a cursor_hider context you can
13+
// reenable the cursor using cursor_hider(false).
14+
class cursor_hider : noncopyable_nonmovable
15+
{
16+
public:
17+
cursor_hider(bool hide = true);
18+
19+
~cursor_hider();
20+
21+
private:
22+
bool m_hide;
23+
};
24+
25+
// Scope object to use alternative output buffer for
26+
// fullscreen interactive terminal input/output.
27+
class alternative_buffer : noncopyable_nonmovable
28+
{
29+
public:
30+
alternative_buffer();
31+
32+
~alternative_buffer();
33+
34+
private:
35+
struct termios m_previous_termios;
36+
};
37+
38+
// Scope object to control echo of stdin to stdout.
39+
// This should be disabled when entering passwords for example.
40+
class echo_control : noncopyable_nonmovable
41+
{
42+
public:
43+
echo_control(bool echo);
44+
45+
~echo_control();
46+
47+
private:
48+
bool m_echo;
49+
struct termios m_previous_termios;
50+
};
51+
52+
// Display a prompt on stdout and return newline-terminated input received on
53+
// stdin from the user. The `echo` argument controls whether stdin is echoed
54+
// to stdout, use `false` for passwords.
55+
std::string prompt_input(const std::string_view prompt, bool echo = true);

0 commit comments

Comments
 (0)