mirror of
https://github.com/Murmele/Gittyup.git
synced 2024-10-05 22:47:16 +03:00
commit
ff418c49eb
208
src/dialogs/AmendDialog.cpp
Normal file
208
src/dialogs/AmendDialog.cpp
Normal file
@ -0,0 +1,208 @@
|
||||
#include "AmendDialog.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <QCheckBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QDateTimeEdit>
|
||||
#include <QRadioButton>
|
||||
#include <QGroupBox>
|
||||
|
||||
enum Row { Author = 0, Committer, CommitMessageLabel, CommitMessage, Buttons };
|
||||
|
||||
class DateSelectionGroupWidget : public QGroupBox {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DateSelectionGroupWidget(QWidget *parent = nullptr)
|
||||
: QGroupBox(tr("Datetime source"), parent) {
|
||||
QHBoxLayout *l = new QHBoxLayout();
|
||||
|
||||
current = new QRadioButton(tr("Current"), this);
|
||||
current->setObjectName("Current");
|
||||
manual = new QRadioButton(tr("Manual"), this);
|
||||
manual->setObjectName("Manual");
|
||||
original = new QRadioButton(tr("Original"), this);
|
||||
original->setObjectName("Original");
|
||||
|
||||
current->setChecked(true);
|
||||
|
||||
connect(current, &QRadioButton::clicked,
|
||||
[this]() { emit typeChanged(type()); });
|
||||
connect(manual, &QRadioButton::clicked,
|
||||
[this]() { emit typeChanged(type()); });
|
||||
connect(original, &QRadioButton::clicked,
|
||||
[this]() { emit typeChanged(type()); });
|
||||
|
||||
l->addWidget(current);
|
||||
l->addWidget(manual);
|
||||
l->addWidget(original);
|
||||
setLayout(l);
|
||||
}
|
||||
ContributorInfo::SelectedDateTimeType type() {
|
||||
if (original->isChecked()) {
|
||||
return ContributorInfo::SelectedDateTimeType::Original;
|
||||
} else if (manual->isChecked()) {
|
||||
return ContributorInfo::SelectedDateTimeType::Manual;
|
||||
}
|
||||
return ContributorInfo::SelectedDateTimeType::Current;
|
||||
}
|
||||
|
||||
signals:
|
||||
void typeChanged(ContributorInfo::SelectedDateTimeType);
|
||||
|
||||
private:
|
||||
QRadioButton *current;
|
||||
QRadioButton *manual;
|
||||
QRadioButton *original;
|
||||
};
|
||||
|
||||
class InfoBox : public QGroupBox {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum LocalRow { Name = 0, Email, DateType, CommitDate };
|
||||
|
||||
InfoBox(const QString &title, const git::Signature &signature,
|
||||
QWidget *parent = nullptr)
|
||||
: QGroupBox(title + ":", parent), m_signature(signature) {
|
||||
|
||||
setObjectName(title);
|
||||
|
||||
auto *l = new QVBoxLayout();
|
||||
|
||||
auto *lName = new QLabel(tr("Name:"), this);
|
||||
m_name = new QLineEdit(signature.name(), this);
|
||||
m_name->setObjectName("Name");
|
||||
auto *hName = new QHBoxLayout();
|
||||
hName->addWidget(lName);
|
||||
hName->addWidget(m_name);
|
||||
|
||||
auto *lEmail = new QLabel(tr("Email:"), this);
|
||||
m_email = new QLineEdit(signature.email(), this);
|
||||
m_email->setObjectName("Email");
|
||||
auto *hEmail = new QHBoxLayout();
|
||||
hEmail->addWidget(lEmail);
|
||||
hEmail->addWidget(m_email);
|
||||
|
||||
m_commitDateType = new DateSelectionGroupWidget(this);
|
||||
m_commitDateType->setObjectName(title + "CommitDateType");
|
||||
m_lCommitDate = new QLabel(tr("Commit date:"), this);
|
||||
m_commitDate = new QDateTimeEdit(signature.date().toLocalTime(), this);
|
||||
m_commitDate->setObjectName(title + "CommitDate");
|
||||
QSizePolicy sp(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
m_commitDate->setSizePolicy(sp);
|
||||
auto *hDate = new QHBoxLayout();
|
||||
hDate->addWidget(m_lCommitDate);
|
||||
hDate->addWidget(m_commitDate);
|
||||
|
||||
l->addLayout(hName);
|
||||
l->addLayout(hEmail);
|
||||
l->addWidget(m_commitDateType);
|
||||
l->addLayout(hDate);
|
||||
|
||||
setLayout(l);
|
||||
|
||||
dateTimeTypeChanged(m_commitDateType->type());
|
||||
|
||||
connect(m_commitDateType, &DateSelectionGroupWidget::typeChanged, this,
|
||||
&InfoBox::dateTimeTypeChanged);
|
||||
}
|
||||
|
||||
ContributorInfo getInfo() const {
|
||||
ContributorInfo ci;
|
||||
ci.name = name();
|
||||
ci.email = email();
|
||||
ci.commitDate = commitDate();
|
||||
ci.commitDateType = commitDateType();
|
||||
|
||||
return ci;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void dateTimeTypeChanged(const ContributorInfo::SelectedDateTimeType type) {
|
||||
const auto enabled = type == ContributorInfo::SelectedDateTimeType::Manual;
|
||||
m_lCommitDate->setVisible(enabled);
|
||||
m_commitDate->setVisible(enabled);
|
||||
}
|
||||
|
||||
private:
|
||||
QString name() const { return m_name->text(); }
|
||||
|
||||
QString email() const { return m_email->text(); }
|
||||
|
||||
QDateTime commitDate() const {
|
||||
if (commitDateType() == ContributorInfo::SelectedDateTimeType::Original) {
|
||||
return m_signature.date().toLocalTime();
|
||||
} else {
|
||||
return m_commitDate->dateTime();
|
||||
}
|
||||
}
|
||||
|
||||
ContributorInfo::SelectedDateTimeType commitDateType() const {
|
||||
return m_commitDateType->type();
|
||||
}
|
||||
|
||||
QLineEdit *m_name;
|
||||
QLineEdit *m_email;
|
||||
QDateTimeEdit *m_commitDate;
|
||||
QLabel *m_lCommitDate;
|
||||
DateSelectionGroupWidget *m_commitDateType;
|
||||
|
||||
git::Signature m_signature;
|
||||
};
|
||||
|
||||
AmendDialog::AmendDialog(const git::Signature &author,
|
||||
const git::Signature &committer,
|
||||
const QString &commitMessage, QWidget *parent)
|
||||
: QDialog(parent) {
|
||||
|
||||
auto *l = new QGridLayout();
|
||||
|
||||
// author
|
||||
// committer
|
||||
// message
|
||||
|
||||
m_authorInfo = new InfoBox(tr("Author"), author, this);
|
||||
l->addWidget(m_authorInfo, Row::Author, 0, 1, 2);
|
||||
|
||||
m_committerInfo = new InfoBox(tr("Committer"), committer, this);
|
||||
l->addWidget(m_committerInfo, Row::Committer, 0, 1, 2);
|
||||
|
||||
auto *lMessage = new QLabel(tr("Commit Message:"), this);
|
||||
m_commitMessage = new QTextEdit(commitMessage, this);
|
||||
m_commitMessage->setObjectName("Textlabel Commit Message");
|
||||
l->addWidget(lMessage, Row::CommitMessageLabel, 0);
|
||||
l->addWidget(m_commitMessage, Row::CommitMessage, 0, 1, 2);
|
||||
|
||||
auto *ok = new QPushButton(tr("Amend"), this);
|
||||
auto *cancel = new QPushButton(tr("Cancel"), this);
|
||||
|
||||
connect(ok, &QPushButton::clicked, this, &QDialog::accept);
|
||||
connect(cancel, &QPushButton::clicked, this, &QDialog::reject);
|
||||
|
||||
auto *hl = new QHBoxLayout();
|
||||
hl->addWidget(cancel);
|
||||
hl->addWidget(ok);
|
||||
|
||||
l->addLayout(hl, Buttons, 1);
|
||||
|
||||
setLayout(l);
|
||||
}
|
||||
|
||||
AmendInfo AmendDialog::getInfo() const {
|
||||
AmendInfo ai;
|
||||
ai.authorInfo = m_authorInfo->getInfo();
|
||||
ai.committerInfo = m_committerInfo->getInfo();
|
||||
ai.commitMessage = commitMessage();
|
||||
|
||||
return ai;
|
||||
}
|
||||
|
||||
QString AmendDialog::commitMessage() const {
|
||||
return m_commitMessage->toPlainText();
|
||||
}
|
||||
|
||||
#include "AmendDialog.moc"
|
41
src/dialogs/AmendDialog.h
Normal file
41
src/dialogs/AmendDialog.h
Normal file
@ -0,0 +1,41 @@
|
||||
#include <QDialog>
|
||||
#include <QDateTime>
|
||||
#include "git/Signature.h"
|
||||
|
||||
class QLineEdit;
|
||||
class QTextEdit;
|
||||
class QCheckBox;
|
||||
class QDateTimeEdit;
|
||||
class DateSelectionGroupWidget;
|
||||
class QLabel;
|
||||
class InfoBox;
|
||||
class AmendDialog;
|
||||
|
||||
struct ContributorInfo {
|
||||
enum class SelectedDateTimeType { Current, Manual, Original };
|
||||
QString name;
|
||||
QString email;
|
||||
QDateTime commitDate;
|
||||
SelectedDateTimeType commitDateType;
|
||||
};
|
||||
|
||||
struct AmendInfo {
|
||||
ContributorInfo authorInfo;
|
||||
ContributorInfo committerInfo;
|
||||
QString commitMessage;
|
||||
};
|
||||
|
||||
class AmendDialog : public QDialog {
|
||||
public:
|
||||
AmendDialog(const git::Signature &author, const git::Signature &committer,
|
||||
const QString &commitMessage, QWidget *parent = nullptr);
|
||||
|
||||
AmendInfo getInfo() const;
|
||||
|
||||
private:
|
||||
QString commitMessage() const;
|
||||
|
||||
InfoBox *m_authorInfo;
|
||||
InfoBox *m_committerInfo;
|
||||
QTextEdit *m_commitMessage;
|
||||
};
|
@ -3,6 +3,7 @@ add_library(
|
||||
AboutDialog.cpp
|
||||
AccountDialog.cpp
|
||||
AddRemoteDialog.cpp
|
||||
AmendDialog.cpp
|
||||
BranchDelegate.cpp
|
||||
BranchTableModel.cpp
|
||||
CheckoutDialog.cpp
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "git2/diff.h"
|
||||
#include "git2/refs.h"
|
||||
#include "git2/revert.h"
|
||||
#include "git2/commit.h"
|
||||
#include <QDateTime>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
@ -240,6 +241,16 @@ bool Commit::revert() const {
|
||||
return !error;
|
||||
}
|
||||
|
||||
bool Commit::amend(const Signature &author, const Signature &committer,
|
||||
const QString &commitMessage, const Tree &tree) const {
|
||||
Repository repo = this->repo();
|
||||
git_oid oid;
|
||||
int error = git_commit_amend(&oid, *this, "HEAD", &*author, &*committer, NULL,
|
||||
commitMessage.toUtf8(), tree);
|
||||
emit repo.notifier()->referenceUpdated(repo.head());
|
||||
return !error;
|
||||
}
|
||||
|
||||
bool Commit::reset(git_reset_t type, const QStringList &paths) const {
|
||||
QVector<char *> rawPaths;
|
||||
QVector<QByteArray> storage;
|
||||
|
@ -65,6 +65,9 @@ public:
|
||||
// Revert this commit in the index and workdir.
|
||||
bool revert() const;
|
||||
|
||||
bool amend(const Signature &author, const Signature &committer,
|
||||
const QString &commitMessage, const Tree &tree) const;
|
||||
|
||||
// Reset HEAD to this commit.
|
||||
bool reset(git_reset_t type = GIT_RESET_MIXED,
|
||||
const QStringList &paths = QStringList()) const;
|
||||
|
@ -188,6 +188,15 @@ Config Repository::appConfig() const {
|
||||
|
||||
bool Repository::isBare() const { return git_repository_is_bare(d->repo); }
|
||||
|
||||
Signature Repository::signature(const QString &name, const QString &email) {
|
||||
return Signature(name, email);
|
||||
}
|
||||
|
||||
Signature Repository::signature(const QString &name, const QString &email,
|
||||
const QDateTime &date) {
|
||||
return Signature(name, email, date);
|
||||
}
|
||||
|
||||
Signature Repository::defaultSignature(bool *fake, const QString &overrideUser,
|
||||
const QString &overrideEmail) const {
|
||||
QString name, email;
|
||||
@ -538,6 +547,24 @@ Commit Repository::lookupCommit(const Id &id) const {
|
||||
return Commit(commit);
|
||||
}
|
||||
|
||||
bool Repository::amend(const git::Commit &commitToAmend,
|
||||
const git::Signature &author,
|
||||
const git::Signature &committer,
|
||||
const QString &commitMessage) {
|
||||
|
||||
// Write the index tree.
|
||||
Index idx = index();
|
||||
if (!idx.isValid())
|
||||
return false;
|
||||
|
||||
// Add new staged files to the amended commit
|
||||
Tree tree = idx.writeTree();
|
||||
if (!tree.isValid())
|
||||
return false;
|
||||
|
||||
return commitToAmend.amend(author, committer, commitMessage, tree);
|
||||
}
|
||||
|
||||
Commit Repository::commit(const QString &message,
|
||||
const AnnotatedCommit &mergeHead, bool *fakeSignature,
|
||||
const QString &overrideUser,
|
||||
|
@ -92,6 +92,10 @@ public:
|
||||
const QString &overrideUser = QString(),
|
||||
const QString &overrideEmail = QString()) const;
|
||||
|
||||
Signature signature(const QString &name, const QString &email);
|
||||
Signature signature(const QString &name, const QString &email,
|
||||
const QDateTime &date);
|
||||
|
||||
// ignore
|
||||
bool isIgnored(const QString &path) const;
|
||||
|
||||
@ -151,6 +155,9 @@ public:
|
||||
const QString &message,
|
||||
const AnnotatedCommit &mergeHead = AnnotatedCommit());
|
||||
|
||||
bool amend(const Commit &commitToAmend, const Signature &author,
|
||||
const Signature &committer, const QString &commitMessage);
|
||||
|
||||
QList<Commit> starredCommits() const;
|
||||
bool isCommitStarred(const Id &commit) const;
|
||||
void setCommitStarred(const Id &commit, bool starred);
|
||||
|
@ -17,6 +17,23 @@ Signature::Signature(git_signature *signature, bool owned)
|
||||
: d(
|
||||
signature, owned ? git_signature_free : [](git_signature *) {}) {}
|
||||
|
||||
Signature::Signature(const QString &name, const QString &email) {
|
||||
git_signature *signature = nullptr;
|
||||
|
||||
git_signature_now(&signature, name.toUtf8(), email.toUtf8());
|
||||
d = QSharedPointer<git_signature>(signature, git_signature_free);
|
||||
}
|
||||
|
||||
Signature::Signature(const QString &name, const QString &email,
|
||||
const QDateTime &date) {
|
||||
git_signature *signature = nullptr;
|
||||
|
||||
auto offset = date.offsetFromUtc() / 60;
|
||||
git_signature_new(&signature, name.toUtf8(), email.toUtf8(),
|
||||
date.toSecsSinceEpoch(), offset);
|
||||
d = QSharedPointer<git_signature>(signature, git_signature_free);
|
||||
}
|
||||
|
||||
Signature::operator const git_signature *() const { return d.data(); }
|
||||
|
||||
QString Signature::name() const { return d->name; }
|
||||
|
@ -35,6 +35,8 @@ public:
|
||||
|
||||
private:
|
||||
Signature(git_signature *signature = nullptr, bool owned = false);
|
||||
Signature(const QString &name, const QString &email);
|
||||
Signature(const QString &name, const QString &email, const QDateTime &date);
|
||||
operator const git_signature *() const;
|
||||
|
||||
QSharedPointer<git_signature> d;
|
||||
|
@ -48,8 +48,6 @@ const QString kPathspecFmt = "pathspec:%1";
|
||||
// FIXME: Use 'core.abbrev' config instead?
|
||||
const int kShortIdSize = 7;
|
||||
|
||||
enum Role { DiffRole = Qt::UserRole, CommitRole, GraphRole, GraphColorRole };
|
||||
|
||||
enum GraphSegment {
|
||||
Dot,
|
||||
Top,
|
||||
@ -344,7 +342,7 @@ public:
|
||||
|
||||
return mStatus.isFinished() ? QVariant() : mProgress;
|
||||
|
||||
case DiffRole: {
|
||||
case CommitList::Role::DiffRole: {
|
||||
if (status)
|
||||
return QVariant::fromValue(this->status());
|
||||
|
||||
@ -354,10 +352,10 @@ public:
|
||||
return QVariant::fromValue(diff);
|
||||
}
|
||||
|
||||
case CommitRole:
|
||||
case CommitList::Role::CommitRole:
|
||||
return status ? QVariant() : QVariant::fromValue(row.commit);
|
||||
|
||||
case GraphRole: {
|
||||
case CommitList::Role::GraphRole: {
|
||||
QVariantList columns;
|
||||
foreach (const Column &column, row.columns) {
|
||||
QVariantList segments;
|
||||
@ -369,7 +367,7 @@ public:
|
||||
return columns;
|
||||
}
|
||||
|
||||
case GraphColorRole: {
|
||||
case CommitList::Role::GraphColorRole: {
|
||||
QVariantList columns;
|
||||
foreach (const Column &column, row.columns) {
|
||||
QVariantList segments;
|
||||
@ -571,7 +569,7 @@ public:
|
||||
QVariant data(const QModelIndex &index,
|
||||
int role = Qt::DisplayRole) const override {
|
||||
switch (role) {
|
||||
case DiffRole: {
|
||||
case CommitList::Role::DiffRole: {
|
||||
git::Commit commit = mCommits.at(index.row());
|
||||
bool ignoreWhitespace = Settings::instance()->isWhitespaceIgnored();
|
||||
git::Diff diff = commit.diff(git::Commit(), -1, ignoreWhitespace);
|
||||
@ -579,7 +577,7 @@ public:
|
||||
return QVariant::fromValue(diff);
|
||||
}
|
||||
|
||||
case CommitRole:
|
||||
case CommitList::Role::CommitRole:
|
||||
return QVariant::fromValue(mCommits.at(index.row()));
|
||||
}
|
||||
|
||||
@ -650,8 +648,9 @@ public:
|
||||
|
||||
// Draw graph.
|
||||
painter->save();
|
||||
QVariantList columns = index.data(GraphRole).toList();
|
||||
QVariantList colorColumns = index.data(GraphColorRole).toList();
|
||||
QVariantList columns = index.data(CommitList::Role::GraphRole).toList();
|
||||
QVariantList colorColumns =
|
||||
index.data(CommitList::Role::GraphColorRole).toList();
|
||||
for (int i = 0; i < columns.size(); ++i) {
|
||||
int x = rect.x();
|
||||
int y = rect.y();
|
||||
@ -759,7 +758,8 @@ public:
|
||||
rect.setWidth(rect.width() - constants.hMargin);
|
||||
|
||||
// Draw content.
|
||||
git::Commit commit = index.data(CommitRole).value<git::Commit>();
|
||||
git::Commit commit =
|
||||
index.data(CommitList::Role::CommitRole).value<git::Commit>();
|
||||
if (commit.isValid()) {
|
||||
const QFontMetrics &fm = opt.fontMetrics;
|
||||
QRect star = rect;
|
||||
|
@ -24,6 +24,8 @@ class CommitList : public QListView {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Role { DiffRole = Qt::UserRole, CommitRole, GraphRole, GraphColorRole };
|
||||
|
||||
CommitList(Index *index, QWidget *parent = nullptr);
|
||||
|
||||
// Get the status diff item.
|
||||
|
@ -353,8 +353,8 @@ public:
|
||||
committer.join(", "));
|
||||
|
||||
// Set date range.
|
||||
QDate lastDate = last.committer().date().date();
|
||||
QDate firstDate = first.committer().date().date();
|
||||
QDate lastDate = last.committer().date().toLocalTime().date();
|
||||
QDate firstDate = first.committer().date().toLocalTime().date();
|
||||
QString lastDateStr = lastDate.toString(Qt::DefaultLocaleShortDate);
|
||||
QString firstDateStr = firstDate.toString(Qt::DefaultLocaleShortDate);
|
||||
QString dateStr = (lastDate == firstDate)
|
||||
@ -391,7 +391,7 @@ public:
|
||||
git::Commit commit = commits.first();
|
||||
git::Signature author = commit.author();
|
||||
git::Signature committer = commit.committer();
|
||||
QDateTime date = commit.committer().date();
|
||||
QDateTime date = commit.committer().date().toLocalTime();
|
||||
mHash->setText(brightText(tr("Id:")) + " " + commit.shortId());
|
||||
mAuthorCommitterDate->setDate(
|
||||
brightText(date.toString(Qt::DefaultLocaleLongDate)));
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "ToolBar.h"
|
||||
#include "app/Application.h"
|
||||
#include "conf/Settings.h"
|
||||
#include "dialogs/AmendDialog.h"
|
||||
#include "dialogs/CheckoutDialog.h"
|
||||
#include "dialogs/CommitDialog.h"
|
||||
#include "dialogs/DeleteBranchDialog.h"
|
||||
@ -40,6 +41,7 @@
|
||||
#include "git/Signature.h"
|
||||
#include "git/TagRef.h"
|
||||
#include "git/Tree.h"
|
||||
#include "git/Signature.h"
|
||||
#include "git2/merge.h"
|
||||
#include "host/Accounts.h"
|
||||
#include "index/Index.h"
|
||||
@ -1877,25 +1879,7 @@ void RepoView::amendCommit() {
|
||||
if (!commit.isValid())
|
||||
return;
|
||||
|
||||
QList<git::Commit> parents = commit.parents();
|
||||
switch (parents.size()) {
|
||||
case 0:
|
||||
// Return to the unborn HEAD state.
|
||||
// FIXME: Prompt to reset?
|
||||
head.remove(true);
|
||||
mDetails->setCommitMessage(commit.message());
|
||||
refresh();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Reset to parent commit.
|
||||
promptToReset(parents.first(), GIT_RESET_SOFT, commit);
|
||||
break;
|
||||
|
||||
default:
|
||||
// FIXME: Return to merging state?
|
||||
break;
|
||||
}
|
||||
promptToAmend(commit);
|
||||
}
|
||||
|
||||
void RepoView::promptToCheckout() {
|
||||
@ -2159,18 +2143,54 @@ void RepoView::promptToDeleteTag(const git::Reference &ref) {
|
||||
dialog->open();
|
||||
}
|
||||
|
||||
void RepoView::promptToReset(const git::Commit &commit, git_reset_t type,
|
||||
const git::Commit &commitToAmend) {
|
||||
void RepoView::promptToAmend(const git::Commit &commit) {
|
||||
auto *d = new AmendDialog(commit.author(), commit.committer(),
|
||||
commit.message(), this);
|
||||
d->setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(d, &QDialog::accepted, [this, d, commit]() {
|
||||
auto info = d->getInfo();
|
||||
|
||||
git::Signature author = getSignature(info.authorInfo);
|
||||
git::Signature committer = getSignature(info.committerInfo);
|
||||
|
||||
amend(commit, author, committer, info.commitMessage);
|
||||
});
|
||||
|
||||
d->show();
|
||||
}
|
||||
|
||||
void RepoView::amend(const git::Commit &commit, const git::Signature &author,
|
||||
const git::Signature &committer,
|
||||
const QString &commitMessage) {
|
||||
git::Reference head = mRepo.head();
|
||||
Q_ASSERT(head.isValid());
|
||||
|
||||
QString title = tr("Amend");
|
||||
|
||||
if (!mRepo.amend(commit, author, committer, commitMessage)) {
|
||||
error(addLogEntry(tr("Amending commit %1").arg(commit.link()), title),
|
||||
tr("amend"), head.name());
|
||||
} else {
|
||||
head = mRepo.head();
|
||||
Q_ASSERT(head.isValid());
|
||||
|
||||
QString text =
|
||||
tr("%1 to %2", "update ref").arg(head.name(), head.target().link());
|
||||
addLogEntry(text, title);
|
||||
}
|
||||
}
|
||||
|
||||
void RepoView::promptToReset(const git::Commit &commit, git_reset_t type) {
|
||||
git::Branch head = mRepo.head();
|
||||
if (!head.isValid()) {
|
||||
QString title = commitToAmend ? tr("Amend") : tr("Reset");
|
||||
QString title = tr("Reset");
|
||||
LogEntry *entry = addLogEntry(tr("<i>no branch</i>"), title);
|
||||
entry->addEntry(LogEntry::Error, tr("You are not currently on a branch."));
|
||||
return;
|
||||
}
|
||||
|
||||
QString id = commitToAmend ? commitToAmend.shortId() : commit.shortId();
|
||||
QString title = commitToAmend ? tr("Amend") : tr("Reset");
|
||||
QString id = commit.shortId();
|
||||
QString title = tr("Reset");
|
||||
switch (type) {
|
||||
case GIT_RESET_SOFT:
|
||||
title += " Soft";
|
||||
@ -2184,10 +2204,8 @@ void RepoView::promptToReset(const git::Commit &commit, git_reset_t type,
|
||||
}
|
||||
title += "?";
|
||||
|
||||
QString text = commitToAmend
|
||||
? tr("Are you sure you want to amend '%1'?").arg(id)
|
||||
: tr("Are you sure you want to reset '%1' to '%2'?")
|
||||
.arg(head.name(), id);
|
||||
QString text =
|
||||
tr("Are you sure you want to reset '%1' to '%2'?").arg(head.name(), id);
|
||||
QMessageBox *dialog = new QMessageBox(QMessageBox::Warning, title, text,
|
||||
QMessageBox::Cancel, this);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
@ -2211,16 +2229,10 @@ void RepoView::promptToReset(const git::Commit &commit, git_reset_t type,
|
||||
|
||||
dialog->setInformativeText(info);
|
||||
|
||||
QString buttonText = commitToAmend ? tr("Amend") : tr("Reset");
|
||||
QString buttonText = tr("Reset");
|
||||
QPushButton *accept = dialog->addButton(buttonText, QMessageBox::AcceptRole);
|
||||
connect(accept, &QPushButton::clicked, this,
|
||||
[this, commit, type, commitToAmend] {
|
||||
reset(commit, type, commitToAmend);
|
||||
|
||||
// Pre-populate the commit message editor.
|
||||
if (commitToAmend)
|
||||
mDetails->setCommitMessage(commitToAmend.message());
|
||||
});
|
||||
[this, commit, type] { reset(commit, type); });
|
||||
|
||||
dialog->open();
|
||||
}
|
||||
@ -2901,6 +2913,13 @@ bool RepoView::checkForConflicts(LogEntry *parent, const QString &action) {
|
||||
return true;
|
||||
}
|
||||
|
||||
git::Signature RepoView::getSignature(const ContributorInfo &info) {
|
||||
if (info.commitDateType != ContributorInfo::SelectedDateTimeType::Current)
|
||||
return mRepo.signature(info.name, info.email, info.commitDate);
|
||||
|
||||
return mRepo.signature(info.name, info.email);
|
||||
}
|
||||
|
||||
bool RepoView::match(QObject *search, QObject *parent) {
|
||||
QObjectList children = parent->children();
|
||||
for (auto child : children) {
|
||||
|
@ -41,6 +41,7 @@ class PathspecWidget;
|
||||
class ReferenceWidget;
|
||||
class RemoteCallbacks;
|
||||
class ToolBar;
|
||||
class ContributorInfo;
|
||||
|
||||
namespace git {
|
||||
class Result;
|
||||
@ -262,9 +263,12 @@ public:
|
||||
void promptToAddTag(const git::Commit &commit);
|
||||
void promptToDeleteTag(const git::Reference &ref);
|
||||
|
||||
void promptToAmend(const git::Commit &commit);
|
||||
void amend(const git::Commit &commit, const git::Signature &author,
|
||||
const git::Signature &committer, const QString &commitMessage);
|
||||
|
||||
// reset
|
||||
void promptToReset(const git::Commit &commit, git_reset_t type,
|
||||
const git::Commit &commitToAmend = git::Commit());
|
||||
void promptToReset(const git::Commit &commit, git_reset_t type);
|
||||
void reset(const git::Commit &commit, git_reset_t type,
|
||||
const git::Commit &commitToAmend = git::Commit());
|
||||
|
||||
@ -392,6 +396,8 @@ private:
|
||||
|
||||
bool checkForConflicts(LogEntry *parent, const QString &action);
|
||||
|
||||
git::Signature getSignature(const ContributorInfo &info);
|
||||
|
||||
git::Repository mRepo;
|
||||
|
||||
Index *mIndex;
|
||||
|
@ -101,4 +101,5 @@ test(NAME rebase)
|
||||
test(NAME TreeView)
|
||||
test(NAME Submodule)
|
||||
test(NAME referencelist)
|
||||
test(NAME amend)
|
||||
test(NAME SshConfig)
|
||||
|
368
test/amend.cpp
Normal file
368
test/amend.cpp
Normal file
@ -0,0 +1,368 @@
|
||||
#include "Test.h"
|
||||
|
||||
#include "git/Signature.h"
|
||||
#include "git/Reference.h"
|
||||
#include "git/Tree.h"
|
||||
#include "ui/MainWindow.h"
|
||||
#include "ui/DoubleTreeWidget.h"
|
||||
#include "ui/RepoView.h"
|
||||
#include "ui/TreeView.h"
|
||||
#include "dialogs/AmendDialog.h"
|
||||
|
||||
#include <QTextEdit>
|
||||
#include <QRadioButton>
|
||||
#include <QDateTimeEdit>
|
||||
#include <QLineEdit>
|
||||
|
||||
#define INIT_REPO(repoPath, /* bool */ useTempDir) \
|
||||
QString path = Test::extractRepository(repoPath, useTempDir); \
|
||||
QVERIFY(!path.isEmpty()); \
|
||||
git::Repository repo = git::Repository::open(path); \
|
||||
QVERIFY(repo.isValid());
|
||||
|
||||
class TestAmend : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void testAmend();
|
||||
void testAmendAddFile();
|
||||
void testAmendDialog();
|
||||
void testAmendDialog2();
|
||||
};
|
||||
|
||||
using namespace git;
|
||||
|
||||
void TestAmend::testAmend() {
|
||||
INIT_REPO("CherryPickAuthorEmail.zip", true);
|
||||
|
||||
git::Reference master = repo.lookupRef(QString("refs/heads/master"));
|
||||
QVERIFY(master.isValid());
|
||||
auto c = master.annotatedCommit().commit();
|
||||
QCOMPARE(c.message(), "changes");
|
||||
QCOMPARE(c.author().email(), "martin.marmsoler@gmail.com");
|
||||
QCOMPARE(c.author().name(), "Martin Marmsoler");
|
||||
QCOMPARE(
|
||||
c.author().date(),
|
||||
QDateTime::fromString("Sun May 22 10:36:26 2022 +0200", Qt::RFC2822Date));
|
||||
QCOMPARE(c.committer().name(), "Martin Marmsoler");
|
||||
QCOMPARE(c.committer().email(), "martin.marmsoler@gmail.com");
|
||||
QCOMPARE(
|
||||
c.committer().date(),
|
||||
QDateTime::fromString("Sun May 22 10:36:26 2022 +0200", Qt::RFC2822Date));
|
||||
|
||||
const QString commitMessage = "New commit message";
|
||||
|
||||
auto authorSignature = repo.signature(
|
||||
"New Author", "New Author Email",
|
||||
QDateTime::fromString("Sun May 23 10:36:26 2022 +0200", Qt::RFC2822Date));
|
||||
auto committerSignature = repo.signature(
|
||||
"New Committer", "New Committer Email",
|
||||
QDateTime::fromString("Sun May 23 11:36:26 2022 +0200", Qt::RFC2822Date));
|
||||
|
||||
Tree tree;
|
||||
c.amend(authorSignature, committerSignature, commitMessage, tree);
|
||||
|
||||
master = repo.lookupRef(QString("refs/heads/master"));
|
||||
QVERIFY(master.isValid());
|
||||
c = master.annotatedCommit().commit();
|
||||
QCOMPARE(c.message(), "New commit message");
|
||||
QCOMPARE(c.author().email(), "New Author Email");
|
||||
QCOMPARE(c.author().name(), "New Author");
|
||||
QCOMPARE(
|
||||
c.author().date(),
|
||||
QDateTime::fromString("Sun May 23 10:36:26 2022 +0200", Qt::RFC2822Date));
|
||||
QCOMPARE(c.committer().name(), "New Committer");
|
||||
QCOMPARE(c.committer().email(), "New Committer Email");
|
||||
QCOMPARE(
|
||||
c.committer().date(),
|
||||
QDateTime::fromString("Sun May 23 11:36:26 2022 +0200", Qt::RFC2822Date));
|
||||
}
|
||||
|
||||
void TestAmend::testAmendAddFile() {
|
||||
|
||||
// Create repo
|
||||
Test::ScratchRepository mRepo;
|
||||
auto mMainBranch = mRepo->unbornHeadName();
|
||||
auto mWindow = new MainWindow(mRepo);
|
||||
mWindow->show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(mWindow));
|
||||
RepoView *view = mWindow->currentView();
|
||||
|
||||
{
|
||||
// Add file and refresh.
|
||||
QFile file(mRepo->workdir().filePath("test"));
|
||||
QVERIFY(file.open(QFile::WriteOnly));
|
||||
QTextStream(&file) << "This will be a test." << endl;
|
||||
|
||||
Test::refresh(view);
|
||||
|
||||
auto doubleTree = view->findChild<DoubleTreeWidget *>();
|
||||
QVERIFY(doubleTree);
|
||||
|
||||
auto files = doubleTree->findChild<TreeView *>("Unstaged");
|
||||
QVERIFY(files);
|
||||
|
||||
QAbstractItemModel *model = files->model();
|
||||
QCOMPARE(model->rowCount(), 1);
|
||||
|
||||
// Click on the check box.
|
||||
QModelIndex index = model->index(0, 0);
|
||||
QTest::mouseClick(files->viewport(), Qt::LeftButton,
|
||||
Qt::KeyboardModifiers(),
|
||||
files->checkRect(index).center());
|
||||
|
||||
// Commit and refresh.
|
||||
QTextEdit *editor = view->findChild<QTextEdit *>("MessageEditor");
|
||||
QVERIFY(editor);
|
||||
|
||||
editor->setText("base commit");
|
||||
view->commit();
|
||||
Test::refresh(view, false);
|
||||
|
||||
// Create branch and stage changes
|
||||
git::Branch branch2 =
|
||||
mRepo->createBranch("branch2", mRepo->head().target());
|
||||
QVERIFY(branch2.isValid());
|
||||
|
||||
view->checkout(branch2);
|
||||
QCOMPARE(mRepo->head().name(), QString("branch2"));
|
||||
|
||||
// Check if file has correct content
|
||||
QVERIFY(branch2.isValid());
|
||||
auto c = branch2.annotatedCommit().commit();
|
||||
QCOMPARE(c.blob("test").content(), "This will be a test.\n");
|
||||
}
|
||||
|
||||
// Stage file with changes
|
||||
{
|
||||
QFile file(mRepo->workdir().filePath("test"));
|
||||
QVERIFY(file.open(QFile::WriteOnly));
|
||||
QTextStream(&file) << "Changes made" << endl;
|
||||
|
||||
Test::refresh(view);
|
||||
|
||||
auto doubleTree = view->findChild<DoubleTreeWidget *>();
|
||||
QVERIFY(doubleTree);
|
||||
|
||||
// Staging the file
|
||||
auto files = doubleTree->findChild<TreeView *>("Unstaged");
|
||||
QVERIFY(files);
|
||||
|
||||
QAbstractItemModel *model = files->model();
|
||||
QCOMPARE(model->rowCount(), 1);
|
||||
|
||||
// Click on the check box. to stage file
|
||||
QModelIndex index = model->index(0, 0);
|
||||
QTest::mouseClick(files->viewport(), Qt::LeftButton,
|
||||
Qt::KeyboardModifiers(),
|
||||
files->checkRect(index).center());
|
||||
}
|
||||
|
||||
// Check that changes applied after amending
|
||||
{
|
||||
// Amend changes
|
||||
git::Reference branch2 = mRepo->lookupRef(QString("refs/heads/branch2"));
|
||||
QVERIFY(branch2.isValid());
|
||||
auto c = branch2.annotatedCommit().commit();
|
||||
auto authorSignature = mRepo->signature("New Author", "New Author Email");
|
||||
auto committerSignature =
|
||||
mRepo->signature("New Committer", "New Committer Email");
|
||||
QCOMPARE(mRepo->amend(c, authorSignature, committerSignature,
|
||||
"New commit message"),
|
||||
true);
|
||||
|
||||
{
|
||||
branch2 = mRepo->lookupRef(QString("refs/heads/branch2"));
|
||||
QVERIFY(branch2.isValid());
|
||||
c = branch2.annotatedCommit().commit();
|
||||
QCOMPARE(c.message(), "New commit message");
|
||||
QCOMPARE(c.author().email(), "New Author Email");
|
||||
QCOMPARE(c.author().name(), "New Author");
|
||||
QCOMPARE(c.committer().name(), "New Committer");
|
||||
QCOMPARE(c.committer().email(), "New Committer Email");
|
||||
|
||||
QCOMPARE(c.blob("test").content(), "Changes made\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestAmend::testAmendDialog() {
|
||||
// Checking that original information is passed to the return function
|
||||
// correctly Name and email will not be changed. Only different datetypes are
|
||||
// tested
|
||||
Test::ScratchRepository repo;
|
||||
auto authorSignature = repo->signature(
|
||||
"New Author", "New Author Email",
|
||||
QDateTime::fromString("Sun May 23 10:36:26 2022 +0200", Qt::RFC2822Date));
|
||||
auto committerSignature = repo->signature(
|
||||
"New Committer", "New Committer Email",
|
||||
QDateTime::fromString("Sun May 23 11:36:26 2022 +0200", Qt::RFC2822Date));
|
||||
|
||||
{
|
||||
AmendDialog d(authorSignature, committerSignature, "Test commit message");
|
||||
d.show();
|
||||
|
||||
{
|
||||
const auto *authorCommitDateTimeTypeSelection =
|
||||
d.findChild<QWidget *>(tr("Author") + "CommitDateType");
|
||||
QVERIFY(authorCommitDateTimeTypeSelection);
|
||||
auto *authorCurrent =
|
||||
authorCommitDateTimeTypeSelection->findChild<QRadioButton *>(
|
||||
"Current");
|
||||
QVERIFY(authorCurrent);
|
||||
auto *authorOriginal =
|
||||
authorCommitDateTimeTypeSelection->findChild<QRadioButton *>(
|
||||
"Original");
|
||||
QVERIFY(authorOriginal);
|
||||
auto *authorManual =
|
||||
authorCommitDateTimeTypeSelection->findChild<QRadioButton *>(
|
||||
"Manual");
|
||||
QVERIFY(authorManual);
|
||||
auto *authorCommitDate =
|
||||
d.findChild<QDateTimeEdit *>(tr("Author") + "CommitDate");
|
||||
QVERIFY(authorCommitDate);
|
||||
authorCommitDate->setDateTime(
|
||||
QDateTime(QDate(2012, 7, 6), QTime(8, 30, 5)));
|
||||
|
||||
// current
|
||||
authorCurrent->click();
|
||||
auto info = d.getInfo();
|
||||
QCOMPARE(info.authorInfo.commitDateType,
|
||||
ContributorInfo::SelectedDateTimeType::Current);
|
||||
QCOMPARE(authorCommitDate->isVisible(), false);
|
||||
|
||||
// original
|
||||
authorOriginal->click();
|
||||
info = d.getInfo();
|
||||
QCOMPARE(info.authorInfo.commitDateType,
|
||||
ContributorInfo::SelectedDateTimeType::Original);
|
||||
QCOMPARE(authorCommitDate->isVisible(), false);
|
||||
QCOMPARE(info.authorInfo.commitDate,
|
||||
QDateTime::fromString("Sun May 23 10:36:26 2022 +0200",
|
||||
Qt::RFC2822Date));
|
||||
|
||||
// manual
|
||||
authorManual->click();
|
||||
info = d.getInfo();
|
||||
QCOMPARE(info.authorInfo.commitDateType,
|
||||
ContributorInfo::SelectedDateTimeType::Manual);
|
||||
QCOMPARE(authorCommitDate->isVisible(), true);
|
||||
QCOMPARE(info.authorInfo.commitDate,
|
||||
QDateTime(QDate(2012, 7, 6), QTime(8, 30, 5)));
|
||||
}
|
||||
|
||||
{
|
||||
const auto *committerCommitDateTimeTypeSelection =
|
||||
d.findChild<QWidget *>(tr("Committer") + "CommitDateType");
|
||||
QVERIFY(committerCommitDateTimeTypeSelection);
|
||||
auto *committerCurrent =
|
||||
committerCommitDateTimeTypeSelection->findChild<QRadioButton *>(
|
||||
"Current");
|
||||
QVERIFY(committerCurrent);
|
||||
auto *committerOriginal =
|
||||
committerCommitDateTimeTypeSelection->findChild<QRadioButton *>(
|
||||
"Original");
|
||||
QVERIFY(committerOriginal);
|
||||
auto *committerManual =
|
||||
committerCommitDateTimeTypeSelection->findChild<QRadioButton *>(
|
||||
"Manual");
|
||||
QVERIFY(committerManual);
|
||||
auto *committerCommitDate =
|
||||
d.findChild<QDateTimeEdit *>(tr("Committer") + "CommitDate");
|
||||
QVERIFY(committerCommitDate);
|
||||
committerCommitDate->setDateTime(
|
||||
QDateTime(QDate(2013, 5, 2), QTime(11, 22, 7)));
|
||||
|
||||
// current
|
||||
committerCurrent->click();
|
||||
auto info = d.getInfo();
|
||||
QCOMPARE(info.committerInfo.commitDateType,
|
||||
ContributorInfo::SelectedDateTimeType::Current);
|
||||
QCOMPARE(committerCommitDate->isVisible(), false);
|
||||
|
||||
// original
|
||||
committerOriginal->click();
|
||||
info = d.getInfo();
|
||||
QCOMPARE(info.committerInfo.commitDateType,
|
||||
ContributorInfo::SelectedDateTimeType::Original);
|
||||
QCOMPARE(committerCommitDate->isVisible(), false);
|
||||
QCOMPARE(info.committerInfo.commitDate,
|
||||
QDateTime::fromString("Sun May 23 11:36:26 2022 +0200",
|
||||
Qt::RFC2822Date));
|
||||
|
||||
// manual
|
||||
committerManual->click();
|
||||
info = d.getInfo();
|
||||
QCOMPARE(info.committerInfo.commitDateType,
|
||||
ContributorInfo::SelectedDateTimeType::Manual);
|
||||
QCOMPARE(committerCommitDate->isVisible(), true);
|
||||
QCOMPARE(info.committerInfo.commitDate,
|
||||
QDateTime(QDate(2013, 5, 2), QTime(11, 22, 7)));
|
||||
}
|
||||
|
||||
auto info = d.getInfo();
|
||||
QCOMPARE(info.authorInfo.name, "New Author");
|
||||
QCOMPARE(info.authorInfo.email, "New Author Email");
|
||||
QCOMPARE(info.committerInfo.name, "New Committer");
|
||||
QCOMPARE(info.committerInfo.email, "New Committer Email");
|
||||
QCOMPARE(info.commitMessage, "Test commit message");
|
||||
}
|
||||
}
|
||||
|
||||
void TestAmend::testAmendDialog2() {
|
||||
// Test changing author name, author email, committer name, committer email
|
||||
|
||||
Test::ScratchRepository repo;
|
||||
auto authorSignature = repo->signature(
|
||||
"New Author", "New Author Email",
|
||||
QDateTime::fromString("Sun May 23 10:36:26 2022 +0200", Qt::RFC2822Date));
|
||||
auto committerSignature = repo->signature(
|
||||
"New Committer", "New Committer Email",
|
||||
QDateTime::fromString("Sun May 23 11:36:26 2022 +0200", Qt::RFC2822Date));
|
||||
|
||||
AmendDialog d(authorSignature, committerSignature, "Test commit message");
|
||||
|
||||
auto commitMessageEditor =
|
||||
d.findChild<QTextEdit *>("Textlabel Commit Message");
|
||||
QVERIFY(commitMessageEditor);
|
||||
commitMessageEditor->setText("Changing the commit message");
|
||||
|
||||
// Author
|
||||
{
|
||||
const auto *author = d.findChild<QWidget *>(tr("Author"));
|
||||
QVERIFY(author);
|
||||
|
||||
auto *name = author->findChild<QLineEdit *>("Name");
|
||||
QVERIFY(name);
|
||||
auto *email = author->findChild<QLineEdit *>("Email");
|
||||
QVERIFY(email);
|
||||
|
||||
name->setText("Another author name");
|
||||
email->setText("Another author email address");
|
||||
}
|
||||
|
||||
// Committer
|
||||
{
|
||||
const auto *committer = d.findChild<QWidget *>(tr("Committer"));
|
||||
QVERIFY(committer);
|
||||
|
||||
auto *name = committer->findChild<QLineEdit *>("Name");
|
||||
QVERIFY(name);
|
||||
auto *email = committer->findChild<QLineEdit *>("Email");
|
||||
QVERIFY(email);
|
||||
|
||||
name->setText("Another committer name");
|
||||
email->setText("Another committer email address");
|
||||
}
|
||||
|
||||
const auto info = d.getInfo();
|
||||
|
||||
QCOMPARE(info.authorInfo.name, "Another author name");
|
||||
QCOMPARE(info.authorInfo.email, "Another author email address");
|
||||
QCOMPARE(info.committerInfo.name, "Another committer name");
|
||||
QCOMPARE(info.committerInfo.email, "Another committer email address");
|
||||
QCOMPARE(info.commitMessage, "Changing the commit message");
|
||||
}
|
||||
|
||||
TEST_MAIN(TestAmend)
|
||||
#include "amend.moc"
|
@ -8,8 +8,10 @@
|
||||
//
|
||||
|
||||
#include "Test.h"
|
||||
#include "dialogs/AmendDialog.h"
|
||||
#include "dialogs/CloneDialog.h"
|
||||
#include "dialogs/StartDialog.h"
|
||||
#include "qnamespace.h"
|
||||
#include "ui/CommitList.h"
|
||||
#include "ui/DetailView.h"
|
||||
#include "ui/DiffView/DiffView.h"
|
||||
@ -22,6 +24,7 @@
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QPushButton>
|
||||
#include <QTextEdit>
|
||||
#include <QTextStream>
|
||||
#include <QToolButton>
|
||||
|
||||
@ -141,6 +144,13 @@ void TestInitRepo::amendCommit() {
|
||||
|
||||
view->amendCommit();
|
||||
|
||||
auto dialog = view->findChild<AmendDialog *>();
|
||||
QVERIFY(dialog);
|
||||
dialog->findChild<QTextEdit *>()->setText("Some other commit message");
|
||||
dialog->accept();
|
||||
|
||||
qWait(300);
|
||||
|
||||
{
|
||||
auto timeout =
|
||||
Timeout(10000, "Repository didn't detect status change in time");
|
||||
@ -153,8 +163,9 @@ void TestInitRepo::amendCommit() {
|
||||
QVERIFY(commitList);
|
||||
QAbstractItemModel *commitModel = commitList->model();
|
||||
QModelIndex index = commitModel->index(0, 0);
|
||||
QString name = commitModel->data(index, Qt::DisplayRole).toString();
|
||||
QCOMPARE(name, QString("Uncommitted changes"));
|
||||
auto commit = commitModel->data(index, CommitList::Role::CommitRole)
|
||||
.value<git::Commit>();
|
||||
QCOMPARE(commit.message(), QString("Some other commit message"));
|
||||
}
|
||||
|
||||
void TestInitRepo::editFile() {
|
||||
|
Loading…
Reference in New Issue
Block a user