Commit fc8aea4d authored by Waqar Ahmed's avatar Waqar Ahmed
Browse files

Git status initial implementation

parent 7310246d
......@@ -60,13 +60,15 @@ target_sources(
kateprojectcodeanalysistool.cpp
branchesdialog.cpp
branchesdialogmodel.cpp
gitwidget.cpp
gitstatusmodel.cpp
tools/kateprojectcodeanalysistoolcppcheck.cpp
tools/kateprojectcodeanalysistoolflake8.cpp
tools/kateprojectcodeanalysistoolshellcheck.cpp
tools/kateprojectcodeanalysisselector.cpp
gitutils.cpp
git/gitutils.cpp
plugin.qrc
)
......
......@@ -5,7 +5,7 @@
*/
#include "branchesdialog.h"
#include "branchesdialogmodel.h"
#include "gitutils.h"
#include "git/gitutils.h"
#include <QCoreApplication>
#include <QKeyEvent>
......
......@@ -11,7 +11,7 @@
#include <QVariant>
#include <QVector>
#include "gitutils.h"
#include "git/gitutils.h"
class BranchesDialogModel : public QAbstractTableModel
{
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="32" version="1.1" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 32 32" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
<defs id="defs5455">
<linearGradient inkscape:collect="always" id="linearGradient4172-5">
<stop style="stop-color:#3daee9" id="stop4174-6"/>
<stop offset="1" style="stop-color:#6cc1ef" id="stop4176-6"/>
</linearGradient>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4172-5" id="linearGradient4342" y1="29" y2="8" x2="0" gradientUnits="userSpaceOnUse"/>
</defs>
<metadata id="metadata5458"/>
<g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -384.57143 -515.798)">
<path inkscape:connector-curvature="0" style="fill:#147eb8;fill-rule:evenodd" id="path4308" d="m 386.57144,518.798 0,7 0,1 28,0 0,-6 -14.00001,0 -2,-2 z"/>
<path inkscape:connector-curvature="0" style="fill-opacity:0.235294;fill-rule:evenodd" id="path4306" d="m 397.57143,523.798 -1.99999,1 -9,0 0,1 6.99999,0 3,0 z"/>
<path style="fill:url(#linearGradient4342)" id="rect4294" d="M 13 8 L 11 10 L 2 10 L 1 10 L 1 29 L 12 29 L 13 29 L 31 29 L 31 8 L 13 8 z " transform="matrix(1 0 0 1 384.57143 515.798)"/>
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4304" d="m 397.57143,523.798 -2,2 -10,0 0,1 11,0 z"/>
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4310" d="m 398.57143,518.798 1,3 15.00001,0 0,-1 -14.00001,0 z"/>
<rect width="30" x="385.57144" y="543.79797" height="1" style="fill-opacity:0.235294" id="rect4292"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="10"><rect id="backgroundrect" width="100%" height="100%" x="0" y="0" fill="none" stroke="none" style="" class=""/><g class="currentLayer" style=""><title>Layer 1</title><path fill-rule="evenodd" d="M13.5,4.99809455871582 a3.5,3.5 0 1 1 -7,0 a3.5,3.5 0 0 1 7,0 zm1.444000005722046,-0.75 a5.000999927520752,5.000999927520752 0 0 0 -9.887999534606934,0 H0.75 a0.75,0.75 0 1 0 0,1.5 h4.306000232696533 a5.000999927520752,5.000999927520752 0 0 0 9.887999534606934,0 h4.306000232696533 a0.75,0.75 0 1 0 0,-1.5 h-4.306000232696533 z" id="svg_1" class="selected" fill="#ffffff" fill-opacity="1"/></g></svg>
\ No newline at end of file
#ifndef GITSTATUS_H
#define GITSTATUS_H
#include <QString>
namespace GitUtils
{
enum GitStatus {
Unmerge_BothDeleted,
Unmerge_AddedByUs,
Unmerge_DeletedByThem,
Unmerge_AddedByThem,
Unmerge_DeletedByUs,
Unmerge_BothAdded,
Unmerge_BothModified,
Index_Modified,
Index_Added,
Index_Deleted,
Index_Renamed,
Index_Copied,
WorkingTree_Modified,
WorkingTree_Deleted,
WorkingTree_IntentToAdd,
Untracked,
Ignored
};
enum StatusXY {
DD = 0x4444,
AU = 0x4155,
UD = 0x5544,
UA = 0x5541,
DU = 0x4455,
AA = 0x4141,
UU = 0x5555,
/* underscore represents space */
M_ = 0x4d20,
A_ = 0x4120,
D_ = 0x4420,
R_ = 0x5220,
C_ = 0x4320,
_M = 0x204d,
_D = 0x2044,
_A = 0x2041,
//??
QQ = 0x3f3f,
//!!
II = 0x2121,
};
struct StatusItem {
QString file;
GitStatus status;
};
}
#endif // GITSTATUS_H
......@@ -8,6 +8,8 @@
#include <QString>
#include <functional>
namespace GitUtils
{
enum RefType {
......@@ -35,6 +37,12 @@ struct CheckoutResult {
int returnCode;
};
struct StatusEntry {
QString file;
char x;
char y;
};
/**
* @brief check if @p repo is a git repo
* @param repo is path to the repo
......@@ -61,6 +69,7 @@ CheckoutResult checkoutNewBranch(const QString &repo, const QString &newBranch,
* @brief get all local and remote branches
*/
QVector<Branch> getAllBranches(const QString &repo);
/**
* @brief get all local and remote branches + tags
*/
......
#include "gitstatusmodel.h"
#include <QDebug>
#include <QFont>
#include <QIcon>
static constexpr int Staged = 0;
static constexpr int Changed = 1;
static constexpr int Conflict = 2;
static constexpr int Untrack = 3;
static constexpr quintptr Root = 0xFFFFFFFF;
GitStatusModel::GitStatusModel(QObject *parent)
: QAbstractItemModel(parent)
{
// setup root rows
beginInsertRows(QModelIndex(), 0, 3);
endInsertRows();
}
QModelIndex GitStatusModel::index(int row, int column, const QModelIndex &parent) const
{
auto rootIndex = Root;
if (parent.isValid()) {
if (parent.internalId() == Root) {
rootIndex = parent.row();
}
}
return createIndex(row, column, rootIndex);
;
}
QModelIndex GitStatusModel::parent(const QModelIndex &child) const
{
if (!child.isValid()) {
return QModelIndex();
}
return createIndex(child.internalId(), 0, Root);
}
int GitStatusModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid()) {
return 4;
}
if (parent.internalId() == Root) {
if (parent.row() == Staged) {
return m_staged.size();
} else if (parent.row() == Changed) {
return m_changed.size();
} else if (parent.row() == Untrack) {
return m_untracked.size();
} else if (parent.row() == Conflict) {
return m_unmerge.size();
}
}
return 0;
}
int GitStatusModel::columnCount(const QModelIndex &) const
{
return 1;
}
QVariant GitStatusModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return {};
}
auto row = index.row();
if (index.internalId() == Root) {
if (role == Qt::DisplayRole) {
if (row == Staged) {
return QStringLiteral("Staged");
} else if (row == Untrack) {
return QStringLiteral("Untracked");
} else if (row == Conflict) {
return QStringLiteral("Conflict");
} else if (row == Changed) {
return QStringLiteral("Modified");
}
} else if (role == Qt::FontRole) {
QFont bold;
bold.setBold(true);
return bold;
} else if (role == Qt::DecorationRole) {
static const auto branchIcon = QIcon(QStringLiteral(":/kxmlgui5/kateproject/sc-apps-git.svg"));
return branchIcon;
}
} else {
if (role != Qt::DisplayRole) {
return {};
}
int rootIndex = index.internalId();
if (rootIndex < 0 || rootIndex > 3) {
return QVariant();
}
if (rootIndex == Staged)
return m_staged.at(row).file;
if (rootIndex == Changed)
return m_changed.at(row).file;
if (rootIndex == Conflict)
return m_unmerge.at(row).file;
if (rootIndex == Untrack)
return m_untracked.at(row).file;
}
return {};
}
void GitStatusModel::addItems(const QVector<GitUtils::StatusItem> &staged,
const QVector<GitUtils::StatusItem> &changed,
const QVector<GitUtils::StatusItem> &unmerge,
const QVector<GitUtils::StatusItem> &untracked)
{
beginResetModel();
m_staged = staged;
m_changed = changed;
m_unmerge = unmerge;
m_untracked = untracked;
endResetModel();
}
#ifndef GITSTATUSMODEL_H
#define GITSTATUSMODEL_H
#include <QAbstractItemModel>
#include "git/gitstatus.h"
class GitStatusModel : public QAbstractItemModel
{
public:
explicit GitStatusModel(QObject *parent);
// enum Status { M, A, U };
// struct Item {
// QString file;
// Status status;
// };
// QAbstractItemModel interface
public:
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
QModelIndex parent(const QModelIndex &child) const override;
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
void addItems(const QVector<GitUtils::StatusItem> &staged,
const QVector<GitUtils::StatusItem> &changed,
const QVector<GitUtils::StatusItem> &unmerge,
const QVector<GitUtils::StatusItem> &untracked);
private:
QVector<GitUtils::StatusItem> m_staged;
QVector<GitUtils::StatusItem> m_changed;
QVector<GitUtils::StatusItem> m_unmerge;
QVector<GitUtils::StatusItem> m_untracked;
};
#endif // GITSTATUSMODEL_H
#include "gitwidget.h"
#include "gitstatusmodel.h"
#include "kateproject.h"
#include <QDebug>
#include <QProcess>
#include <QPushButton>
#include <QStringListModel>
#include <QTreeView>
#include <QVBoxLayout>
#include <QtConcurrentRun>
GitWidget::GitWidget(KateProject *project, QWidget *parent)
: QWidget(parent)
, m_project(project)
{
m_menuBtn = new QPushButton(this);
m_commitBtn = new QPushButton(this);
m_treeView = new QTreeView(this);
m_menuBtn->setIcon(QIcon::fromTheme(QStringLiteral("application-menu")));
m_commitBtn->setIcon(QIcon(QStringLiteral(":/kxmlgui5/kateproject/git-commit-dark.svg")));
QVBoxLayout *layout = new QVBoxLayout;
QHBoxLayout *btnsLayout = new QHBoxLayout;
btnsLayout->addWidget(m_commitBtn);
btnsLayout->addWidget(m_menuBtn);
layout->addLayout(btnsLayout);
layout->addWidget(m_treeView);
m_treeView->setHeaderHidden(true);
m_treeView->setRootIsDecorated(false);
m_model = new GitStatusModel(this);
m_treeView->setModel(m_model);
setLayout(layout);
connect(&m_gitStatusWatcher, &QFutureWatcher<GitWidget::GitParsedStatus>::finished, this, &GitWidget::parseStatusReady);
getStatus(m_project->baseDir());
}
void GitWidget::getStatus(const QString &repo, bool submodules)
{
connect(&git, &QProcess::readyRead, this, &GitWidget::gitStatusReady);
auto args = QStringList{QStringLiteral("status"), QStringLiteral("-z"), QStringLiteral("-u")};
if (!submodules) {
args.append(QStringLiteral("--ignore-submodules"));
}
git.setArguments(args);
git.setProgram(QStringLiteral("git"));
git.setWorkingDirectory(repo);
git.start();
}
void GitWidget::gitStatusReady()
{
disconnect(&git, &QProcess::readyRead, this, &GitWidget::gitStatusReady);
QByteArray s = git.readAllStandardOutput();
auto future = QtConcurrent::run(this, &GitWidget::parseStatus, s);
m_gitStatusWatcher.setFuture(future);
auto l = s.split(0x00);
QStringList out;
for (const auto &m : l) {
out.append(QString::fromLocal8Bit(m));
}
}
GitWidget::GitParsedStatus GitWidget::parseStatus(const QByteArray &raw)
{
QVector<GitUtils::StatusItem> untracked;
QVector<GitUtils::StatusItem> unmerge;
QVector<GitUtils::StatusItem> staged;
QVector<GitUtils::StatusItem> changed;
QList<QByteArray> rawList = raw.split(0x00);
for (const auto &r : rawList) {
if (r.isEmpty() || r.length() < 3) {
continue;
}
char x = r.at(0);
char y = r.at(1);
uint16_t xy = (((uint16_t)x) << 8) | y;
using namespace GitUtils;
switch (xy) {
case StatusXY::QQ:
untracked.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::Untracked});
break;
case StatusXY::II:
untracked.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::Ignored});
break;
case StatusXY::DD:
unmerge.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::Unmerge_BothDeleted});
break;
case StatusXY::AU:
unmerge.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::Unmerge_AddedByUs});
break;
case StatusXY::UD:
unmerge.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::Unmerge_DeletedByThem});
break;
case StatusXY::UA:
unmerge.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::Unmerge_AddedByThem});
break;
case StatusXY::DU:
unmerge.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::Unmerge_DeletedByUs});
break;
case StatusXY::AA:
unmerge.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::Unmerge_BothAdded});
break;
case StatusXY::UU:
unmerge.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::Unmerge_BothModified});
break;
case StatusXY::M_:
staged.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::Index_Modified});
break;
case StatusXY::A_:
staged.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::Index_Added});
break;
case StatusXY::D_:
staged.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::Index_Deleted});
break;
case StatusXY::R_:
staged.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::Index_Renamed});
break;
case StatusXY::C_:
staged.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::Index_Copied});
break;
case StatusXY::_M:
changed.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::WorkingTree_Modified});
break;
case StatusXY::_D:
changed.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::WorkingTree_Deleted});
break;
case StatusXY::_A:
changed.append({QString::fromUtf8(r.data() + 3, r.size()), GitStatus::WorkingTree_IntentToAdd});
break;
}
}
return {untracked, unmerge, staged, changed};
}
void GitWidget::parseStatusReady()
{
GitParsedStatus s = m_gitStatusWatcher.result();
m_model->addItems(s.staged, s.changed, s.unmerge, s.untracked);
}
#ifndef GITWIDGET_H
#define GITWIDGET_H
#include <QFutureWatcher>
#include <QProcess>
#include <QWidget>
#include "git/gitstatus.h"
class QTreeView;
class QPushButton;
class QStringListModel;
class GitStatusModel;
class KateProject;
class GitWidget : public QWidget
{
Q_OBJECT
public:
explicit GitWidget(KateProject *project, QWidget *parent = nullptr);
private:
struct GitParsedStatus {
QVector<GitUtils::StatusItem> untracked;
QVector<GitUtils::StatusItem> unmerge;
QVector<GitUtils::StatusItem> staged;
QVector<GitUtils::StatusItem> changed;
};
QPushButton *m_menuBtn;
QPushButton *m_commitBtn;
QTreeView *m_treeView;
GitStatusModel *m_model;
KateProject *m_project;
QProcess git;
QFutureWatcher<GitParsedStatus> m_gitStatusWatcher;
void getStatus(const QString &repo, bool submodules = false);
GitParsedStatus parseStatus(const QByteArray &raw);
Q_SLOT void gitStatusReady();
Q_SLOT void parseStatusReady();
};
#endif // GITWIDGET_H
......@@ -7,7 +7,8 @@
#include "kateprojectview.h"
#include "branchesdialog.h"
#include "gitutils.h"
#include "git/gitutils.h"
#include "gitwidget.h"
#include "kateprojectfiltermodel.h"
#include "kateprojectpluginview.h"
......@@ -29,20 +30,39 @@ KateProjectView::KateProjectView(KateProjectPluginView *pluginView, KateProject
, m_treeView(new KateProjectViewTree(pluginView, project))
, m_filter(new KLineEdit())
, m_branchBtn(new QPushButton)
, m_gitBtn(new QPushButton)
, m_stackWidget(new QStackedWidget)
, m_gitWidget(new GitWidget(project, this))
{
/**
* layout tree view and co.
*/
QVBoxLayout *layout = new QVBoxLayout();
QHBoxLayout *btnLayout = new QHBoxLayout();
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
layout->addLayout(btnLayout);
layout->addWidget(m_branchBtn);
layout->addWidget(m_treeView);
layout->addWidget(m_gitBtn);
layout->addWidget(m_stackWidget);
// layout->addWidget(m_treeView);
layout->addWidget(m_filter);
setLayout(layout);
btnLayout->addWidget(m_branchBtn);
btnLayout->setStretch(0, 2);
btnLayout->addWidget(m_gitBtn);
m_stackWidget->addWidget(m_treeView);
m_stackWidget->addWidget(m_gitWidget);
connect(m_gitBtn, &QPushButton::clicked, this, [this] {
m_stackWidget->setCurrentWidget(m_gitWidget);
});
m_branchBtn->setText(GitUtils::getCurrentBranchName(m_project->baseDir()));
m_branchBtn->setIcon(QIcon(QStringLiteral(":/kxmlgui5/kateproject/sc-apps-git.svg")));
m_gitBtn->setIcon(QIcon(QStringLiteral(":/kxmlgui5/kateproject/sc-apps-git.svg")));
// let tree get focus for keyboard selection of file to open
setFocusProxy(m_treeView);
......@@ -66,6 +86,10 @@ KateProjectView::KateProjectView(KateProjectPluginView *pluginView, KateProject
m_branchBtn->setHidden(true);
m_branchesDialog = new BranchesDialog(this, mainWindow, m_project->baseDir());
connect(m_branchBtn, &QPushButton::clicked, this, [this] {
if (m_stackWidget->currentWidget() != m_treeView) {
m_stackWidget->setCurrentWidget(m_treeView);
return;
}
m_branchesDialog->openDialog();
});
connect(m_branchesDialog, &BranchesDialog::branchChanged, this, [this](const QString &branch) {
......
......@@ -17,6 +17,8 @@ class KLineEdit;
class QPushButton;
class KateProjectPluginView;
class BranchesDialog;
class QStackedWidget;
class GitWidget;
/**
* Class representing a view of a project.
......@@ -92,6 +94,11 @@ private:
*/
QPushButton *m_branchBtn;
/**
checkout branch button