Skip to content
Open
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
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ jobs:
./git2cpp -v

- name: Run tests
env:
GIT2CPP_TEST_PRIVATE_TOKEN: ${{ secrets.GIT2CPP_TEST_PRIVATE_TOKEN }}
run: |
pytest -v

Expand Down Expand Up @@ -76,6 +78,8 @@ jobs:
run: cmake --build . --parallel 8

- name: Run tests
env:
GIT2CPP_TEST_PRIVATE_TOKEN: ${{ secrets.GIT2CPP_TEST_PRIVATE_TOKEN }}
run: |
pytest -v

Expand Down
6 changes: 4 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,12 @@ set(GIT2CPP_SRC
${GIT2CPP_SOURCE_DIR}/utils/ansi_code.hpp
${GIT2CPP_SOURCE_DIR}/utils/common.cpp
${GIT2CPP_SOURCE_DIR}/utils/common.hpp
${GIT2CPP_SOURCE_DIR}/utils/credentials.cpp
${GIT2CPP_SOURCE_DIR}/utils/credentials.hpp
${GIT2CPP_SOURCE_DIR}/utils/git_exception.cpp
${GIT2CPP_SOURCE_DIR}/utils/git_exception.hpp
${GIT2CPP_SOURCE_DIR}/utils/output.cpp
${GIT2CPP_SOURCE_DIR}/utils/output.hpp
${GIT2CPP_SOURCE_DIR}/utils/input_output.cpp
${GIT2CPP_SOURCE_DIR}/utils/input_output.hpp
${GIT2CPP_SOURCE_DIR}/utils/progress.cpp
${GIT2CPP_SOURCE_DIR}/utils/progress.hpp
${GIT2CPP_SOURCE_DIR}/utils/terminal_pager.cpp
Expand Down
4 changes: 3 additions & 1 deletion src/subcommand/clone_subcommand.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#include <iostream>

#include "../subcommand/clone_subcommand.hpp"
#include "../utils/output.hpp"
#include "../utils/credentials.hpp"
#include "../utils/input_output.hpp"
#include "../utils/progress.hpp"
#include "../wrapper/repository_wrapper.hpp"

Expand Down Expand Up @@ -42,6 +43,7 @@ void clone_subcommand::run()
checkout_opts.progress_cb = checkout_progress;
checkout_opts.progress_payload = &pd;
clone_opts.checkout_opts = checkout_opts;
clone_opts.fetch_opts.callbacks.credentials = user_credentials;
clone_opts.fetch_opts.callbacks.sideband_progress = sideband_progress;
clone_opts.fetch_opts.callbacks.transfer_progress = fetch_progress;
clone_opts.fetch_opts.callbacks.payload = &pd;
Expand Down
4 changes: 2 additions & 2 deletions src/subcommand/commit_subcommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <unistd.h>

#include "../subcommand/commit_subcommand.hpp"
#include "../utils/input_output.hpp"
#include "../wrapper/index_wrapper.hpp"
#include "../wrapper/repository_wrapper.hpp"

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

if (m_commit_message.empty())
{
std::cout << "Please enter a commit message:" << std::endl;
std::getline(std::cin, m_commit_message);
m_commit_message = prompt_input("Please enter a commit message:\n");
if (m_commit_message.empty())
{
throw std::runtime_error("Aborting, no commit message specified.");
Expand Down
4 changes: 3 additions & 1 deletion src/subcommand/fetch_subcommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
#include <git2/remote.h>

#include "../subcommand/fetch_subcommand.hpp"
#include "../utils/output.hpp"
#include "../utils/credentials.hpp"
#include "../utils/input_output.hpp"
#include "../utils/progress.hpp"
#include "../wrapper/repository_wrapper.hpp"

Expand Down Expand Up @@ -34,6 +35,7 @@ void fetch_subcommand::run()

git_indexer_progress pd = {0};
git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
fetch_opts.callbacks.credentials = user_credentials;
fetch_opts.callbacks.sideband_progress = sideband_progress;
fetch_opts.callbacks.transfer_progress = fetch_progress;
fetch_opts.callbacks.payload = &pd;
Expand Down
2 changes: 2 additions & 0 deletions src/subcommand/push_subcommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <git2/remote.h>

#include "../subcommand/push_subcommand.hpp"
#include "../utils/credentials.hpp"
#include "../utils/progress.hpp"
#include "../wrapper/repository_wrapper.hpp"

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

git_push_options push_opts = GIT_PUSH_OPTIONS_INIT;
push_opts.callbacks.credentials = user_credentials;
push_opts.callbacks.push_transfer_progress = push_transfer_progress;
push_opts.callbacks.push_update_reference = push_update_reference;

Expand Down
39 changes: 39 additions & 0 deletions src/utils/credentials.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include <git2/credential.h>
#include <iostream>

#include "credentials.hpp"
#include "input_output.hpp"

// git_credential_acquire_cb
int user_credentials(
git_credential** out,
const char* url,
const char* username_from_url,
unsigned int allowed_types,
void* payload)
{
// Check for cached credentials here, if desired.
// It might be necessary to make this function stateful to avoid repeating unnecessary checks.

*out = nullptr;

if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) {
std::string username = username_from_url ? username_from_url : prompt_input("Username: ");
if (username.empty()) {
giterr_set_str(GIT_ERROR_HTTP, "No username specified");
return GIT_EAUTH;
}

std::string password = prompt_input("Password: ", false);
if (password.empty()) {
giterr_set_str(GIT_ERROR_HTTP, "No password specified");
return GIT_EAUTH;
}

// If successful, this will create and return a git_credential* in the out argument.
return git_credential_userpass_plaintext_new(out, username.c_str(), password.c_str());
}

giterr_set_str(GIT_ERROR_HTTP, "Unexpected credentials request");
return GIT_ERROR;
}
13 changes: 13 additions & 0 deletions src/utils/credentials.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include <git2/credential.h>

// Libgit2 callback of type git_credential_acquire_cb to obtain user credentials
// (username and password) to authenticate remote https access.
int user_credentials(
git_credential** out,
const char* url,
const char* username_from_url,
unsigned int allowed_types,
void* payload
);
74 changes: 74 additions & 0 deletions src/utils/input_output.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include "ansi_code.hpp"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have renamed this from output.cpp to input_output.cpp as it is now a mixture of input and output functions and scope classes (and the header file too).

#include "input_output.hpp"

// OS-specific libraries.
#include <sys/ioctl.h>

cursor_hider::cursor_hider(bool hide /* = true */)
: m_hide(hide)
{
std::cout << (m_hide ? ansi_code::hide_cursor : ansi_code::show_cursor);
}

cursor_hider::~cursor_hider()
{
std::cout << (m_hide ? ansi_code::show_cursor : ansi_code::hide_cursor);
}


alternative_buffer::alternative_buffer()
{
tcgetattr(fileno(stdin), &m_previous_termios);
auto new_termios = m_previous_termios;
// Disable canonical mode (buffered I/O) and echo from stdin to stdout.
new_termios.c_lflag &= (~ICANON & ~ECHO);
tcsetattr(fileno(stdin), TCSANOW, &new_termios);

std::cout << ansi_code::enable_alternative_buffer;
}

alternative_buffer::~alternative_buffer()
{
std::cout << ansi_code::disable_alternative_buffer;

// Restore previous termios settings.
tcsetattr(fileno(stdin), TCSANOW, &m_previous_termios);
}

echo_control::echo_control(bool echo)
: m_echo(echo)
{
if (!m_echo) {
tcgetattr(fileno(stdin), &m_previous_termios);
auto new_termios = m_previous_termios;
new_termios.c_lflag &= ~ECHO;
tcsetattr(fileno(stdin), TCSANOW, &new_termios);
}
}

echo_control::~echo_control()
{
if (!m_echo) {
// Restore previous termios settings.
tcsetattr(fileno(stdin), TCSANOW, &m_previous_termios);
}
}


std::string prompt_input(const std::string_view prompt, bool echo /* = true */)
{
std::cout << prompt;

echo_control ec(echo);
std::string input;

cursor_hider ch(false); // Re-enable cursor if currently hidden.
std::getline(std::cin, input);

if (!echo) {
std::cout << std::endl;
}

// Maybe sanitise input, removing escape codes?
return input;
}
55 changes: 55 additions & 0 deletions src/utils/input_output.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#pragma once

#include <iostream>
#include "common.hpp"

// OS-specific libraries.
#include <termios.h>

// Scope object to hide the cursor. This avoids
// cursor twinkling when rewritting the same line
// too frequently.
// If you are within a cursor_hider context you can
// reenable the cursor using cursor_hider(false).
class cursor_hider : noncopyable_nonmovable
{
public:
cursor_hider(bool hide = true);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows nested scope objects to disable the cursor when doing a long git clone, but re-enable it for the username and password entry.


~cursor_hider();

private:
bool m_hide;
};

// Scope object to use alternative output buffer for
// fullscreen interactive terminal input/output.
class alternative_buffer : noncopyable_nonmovable
{
public:
alternative_buffer();

~alternative_buffer();

private:
struct termios m_previous_termios;
};

// Scope object to control echo of stdin to stdout.
// This should be disabled when entering passwords for example.
class echo_control : noncopyable_nonmovable
{
public:
echo_control(bool echo);

~echo_control();

private:
bool m_echo;
struct termios m_previous_termios;
};

// Display a prompt on stdout and return newline-terminated input received on
// stdin from the user. The `echo` argument controls whether stdin is echoed
// to stdout, use `false` for passwords.
std::string prompt_input(const std::string_view prompt, bool echo = true);
23 changes: 0 additions & 23 deletions src/utils/output.cpp

This file was deleted.

37 changes: 0 additions & 37 deletions src/utils/output.hpp

This file was deleted.

2 changes: 1 addition & 1 deletion src/utils/terminal_pager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#include <termcolor/termcolor.hpp>

#include "ansi_code.hpp"
#include "output.hpp"
#include "input_output.hpp"
#include "terminal_pager.hpp"
#include "common.hpp"

Expand Down
18 changes: 18 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,21 @@ def commit_env_config(monkeypatch):
subprocess.run(["export", f"{key}='{value}'"], check=True)
else:
monkeypatch.setenv(key, value)


@pytest.fixture(scope="session")
def private_test_repo():
# Fixture containing everything needed to access private github repo.
# GIT2CPP_TEST_PRIVATE_TOKEN is the fine-grained Personal Access Token for private test repo.
# If this is not available as an environment variable, tests that use this fixture are skipped.
token = os.getenv('GIT2CPP_TEST_PRIVATE_TOKEN')
if token is None:
pytest.skip("No token for private test repo GIT2CPP_TEST_PRIVATE_TOKEN")
repo_name = "git2cpp-test-private"
org_name = "QuantStack"
return {
"repo_name": repo_name,
"http_url": f"http://github.com/{org_name}/{repo_name}",
"https_url": f"https://github.com/{org_name}/{repo_name}",
"token": token
}
Loading
Loading