Browse Source

Added commits oids and branches ideology
TBD: some bug with OID's copying

Alexey Edelev 7 years ago
parent
commit
70273052af
22 changed files with 488 additions and 226 deletions
  1. 12 7
      NiceGit.pro
  2. 34 0
      commitmodel.cpp
  3. 26 0
      commitmodel.h
  4. 5 3
      gitbase.h
  5. 9 8
      gitbranch.cpp
  6. 18 9
      gitbranch.h
  7. 85 0
      gitcommit.cpp
  8. 49 0
      gitcommit.h
  9. 19 1
      githandler.cpp
  10. 5 1
      githandler.h
  11. 41 2
      gitoid.cpp
  12. 25 3
      gitoid.h
  13. 0 6
      gitreflog.cpp
  14. 0 16
      gitreflog.h
  15. 18 62
      gitrepository.cpp
  16. 13 2
      gitrepository.h
  17. 6 0
      main.cpp
  18. 19 1
      qml/MainView.qml
  19. 1 82
      repositorymodel.cpp
  20. 4 23
      repositorymodel.h
  21. 1 0
      universallistmodel.cpp
  22. 98 0
      universallistmodel.h

+ 12 - 7
NiceGit.pro

@@ -9,24 +9,29 @@ SOURCES += \
     githandler.cpp \
     gitrepository.cpp \
     repositorymodel.cpp \
-    gitreflog.cpp \
     gitbranch.cpp \
     gitreference.cpp \
     gitbase.cpp \
-    gitoid.cpp
+    gitoid.cpp \
+    gitcommit.cpp \
+    commitmodel.cpp \
+    universallistmodel.cpp
 
 HEADERS += \
     githandler.h \
     gitrepository.h \
     repositorymodel.h \
-    gitreflog.h \
     gitbranch.h \
     gitreference.h \
     gitbase.h \
-    gitoid.h
-
-DISTFILES += \
-    qml/MainView.qml
+    gitoid.h \
+    gitcommit.h \
+    commitmodel.h \
+    universallistmodel.h
 
 RESOURCES += \
     resources.qrc
+
+debug {
+    DEFINES += DEBUG
+}

+ 34 - 0
commitmodel.cpp

@@ -0,0 +1,34 @@
+#include "commitmodel.h"
+
+#include <gitbranch.h>
+#include <git2/revwalk.h>
+
+CommitModel::CommitModel(const QString &head, QObject* parent) : UniversalListModel(parent)
+  ,m_head(head)
+{
+}
+
+CommitModel* CommitModel::fromBranch(GitBranch* branch)
+{
+    CommitModel* tmpModel = new CommitModel(branch->name(), branch);
+    git_revwalk* walk;
+    git_revwalk_new(&walk, branch->repository()->raw());
+
+    git_revwalk_push(walk, branch->oid().raw());
+    git_revwalk_sorting(walk, GIT_SORT_TIME);
+
+    git_oid newOid;
+    while(git_revwalk_next(&newOid, walk) == 0)
+    {
+        GitOid commitOid(&newOid, branch->repository());
+        if(!tmpModel->m_container.isEmpty()) {
+            qDebug() << "Last:" << tmpModel->m_container.last().data()->sha1();
+        }
+        GitCommit *commit = GitCommit::fromOid(commitOid);
+        qDebug() << commit->sha1();
+        tmpModel->add(commit);
+    }
+
+    git_revwalk_free(walk);
+    return tmpModel;
+}

+ 26 - 0
commitmodel.h

@@ -0,0 +1,26 @@
+#ifndef COMMITMODEL_H
+#define COMMITMODEL_H
+
+#include <universallistmodel.h>
+#include <gitcommit.h>
+
+//GitBranch;
+
+class CommitModel : public UniversalListModel<GitCommit>
+{
+    Q_OBJECT
+    Q_PROPERTY(QString head READ head CONSTANT)
+public:
+    CommitModel(const QString& head, QObject* parent = 0);
+
+    static CommitModel* fromBranch(GitBranch* branch);
+    QString head() const
+    {
+        return m_head;
+    }
+
+private:
+    QString m_head;
+};
+
+#endif // COMMITMODEL_H

+ 5 - 3
gitbase.h

@@ -2,6 +2,7 @@
 #define GITBASE_H
 
 #include <gitrepository.h>
+#include <gitoid.h>
 
 #include <QObject>
 #include <git2/types.h>
@@ -16,6 +17,7 @@ public:
     GitBase(T* raw, GitRepository* parent) : QObject(parent)
       ,m_raw(raw)
       ,m_repository(parent)
+      ,m_oid(nullptr, parent)
     {}
 
     T* raw() const {
@@ -30,14 +32,14 @@ public:
         return m_repository;
     }
 
-    git_oid* oid() const {
-        return &m_oid;
+    const GitOid& oid() const {
+        return m_oid;
     }
 
 protected:
     T* m_raw;
     GitRepository* m_repository;
-    git_oid m_oid;
+    GitOid m_oid;
 };
 
 #endif // GITBASE_H

+ 9 - 8
gitbranch.cpp

@@ -5,20 +5,16 @@
 
 #include <git2.h>
 
-GitBranch::GitBranch(git_reference *ref, GitRepository *parent) : GitReference(ref, parent)
+GitBranch::GitBranch(git_reference *ref, git_branch_t type, GitRepository *parent) : GitReference(ref, parent)
   ,m_commit(nullptr)
+  ,m_type(static_cast<BranchType>(type))
 {
-    char oid_buf[GIT_OID_HEXSZ+1];
     const char* tmpName;
     git_branch_name(&tmpName, m_raw);
     m_name = tmpName;
     git_annotated_commit_from_ref(&m_commit, repository()->raw(), m_raw);
-    const git_oid* tmpOid = git_annotated_commit_id(m_commit);
-    memcpy(m_oid, tmpOid, sizeof(m_oid));
-    git_oid_tostr(oid_buf, GIT_OID_HEXSZ+1,branch_oid);
-    qDebug() << oid_buf;
-    //        m_branches.insert(out)
-
+    m_oid = GitOid(git_annotated_commit_id(m_commit), m_repository);
+    qDebug() << m_oid.toString();
 }
 
 GitBranch::~GitBranch()
@@ -27,3 +23,8 @@ GitBranch::~GitBranch()
         git_annotated_commit_free(m_commit);
     }
 }
+
+GitBranch::BranchType GitBranch::type() const
+{
+    return m_type;
+}

+ 18 - 9
gitbranch.h

@@ -2,27 +2,35 @@
 #define GITBRANCH_H
 
 #include <gitreference.h>
+#include <git2/types.h>
 
 struct git_oid;
 
+class CommitModel;
+
 class GitBranch : public GitReference
 {
     Q_OBJECT
     Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
-
+    Q_PROPERTY(BranchType type READ type CONSTANT)
+    Q_ENUMS(BranchType)
 public:
-    GitBranch(git_reference* ref, GitRepository* parent);
+    enum BranchType {
+        Local = GIT_BRANCH_LOCAL,
+        Remote = GIT_BRANCH_REMOTE
+    };
+
+    GitBranch(git_reference* ref, git_branch_t type, GitRepository* parent);
+    virtual ~GitBranch();
 
-    QString name() const
-    {
+
+    QString name() const {
         return m_name;
     }
-
-    virtual ~GitBranch();
+    BranchType type() const;
 
 public slots:
-    void setName(QString name)
-    {
+    void setName(QString name) {
         if (m_name == name)
             return;
 
@@ -32,6 +40,7 @@ public slots:
 
 signals:
     void nameChanged(QString name);
+    void commitsChanged(CommitModel* commits);
 
 private:
     void free();
@@ -40,7 +49,7 @@ private:
     QString m_name;
 
     git_annotated_commit* m_commit;
-    git_oid* m_oid;
+    BranchType m_type;
 };
 
 #endif // GITBRANCH_H

+ 85 - 0
gitcommit.cpp

@@ -0,0 +1,85 @@
+#include "gitcommit.h"
+
+#include <gitoid.h>
+
+#include <QDebug>
+
+#include <git2/commit.h>
+
+
+GitCommit::GitCommit(git_commit* raw, GitRepository* parent) : GitBase(raw, parent)
+{
+    m_oid = GitOid(git_commit_id(m_raw), m_repository);
+}
+
+GitCommit::GitCommit() : GitBase(nullptr, nullptr)
+{
+}
+
+GitCommit* GitCommit::fromOid(const GitOid& oid)
+{
+    if(!oid.isValid()) {
+        return nullptr;
+    }
+
+    git_commit *commit;
+    if(git_commit_lookup(&commit, oid.repository()->raw(), oid.raw()) != 0) {
+        return nullptr;
+    }
+
+    return new GitCommit(commit, oid.repository());
+}
+
+GitCommit::~GitCommit()
+{
+    git_commit_free(m_raw);
+}
+
+QString GitCommit::author() const
+{
+    return QString(git_commit_author(m_raw)->name);
+}
+
+QDateTime GitCommit::time() const
+{
+    return QDateTime::fromMSecsSinceEpoch(git_commit_time(m_raw));
+}
+
+QString GitCommit::message() const
+{
+    return QString(git_commit_message(m_raw));
+}
+
+QString GitCommit::email() const
+{
+    return QString(git_commit_author(m_raw)->email);
+}
+
+QString GitCommit::sha1() const
+{
+    return oid().toString();
+}
+
+void GitCommit::setAuthor(QString author)
+{
+    Q_UNUSED(author)
+    //TODO
+}
+
+void GitCommit::setTime(QDateTime time)
+{
+    Q_UNUSED(time)
+    //TODO
+}
+
+void GitCommit::setMessage(QString message)
+{
+    Q_UNUSED(message)
+    //TODO
+}
+
+void GitCommit::setEmail(QString email)
+{
+    Q_UNUSED(email)
+    //TODO
+}

+ 49 - 0
gitcommit.h

@@ -0,0 +1,49 @@
+#ifndef GITCOMMIT_H
+#define GITCOMMIT_H
+
+#include <gitbase.h>
+
+#include <QDateTime>
+#include <QString>
+
+#include <git2/types.h>
+
+class GitCommit : public GitBase<git_commit>
+{
+    Q_OBJECT
+    Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY commitChanged)
+    Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY commitChanged)
+    Q_PROPERTY(QDateTime time READ time WRITE setTime NOTIFY commitChanged)
+    Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY commitChanged)
+    Q_PROPERTY(QString sha1 READ sha1 NOTIFY commitChanged)
+
+public:
+    GitCommit(git_commit* raw, GitRepository* parent);
+    ~GitCommit();
+
+    static GitCommit* fromOid(const GitOid& oid);
+
+    QString author() const;
+    QDateTime time() const;
+    QString message() const;
+    QString email() const;
+    QString sha1() const;
+
+public slots:
+    void setAuthor(QString author);
+    void setTime(QDateTime time);
+    void setMessage(QString message);
+    void setEmail(QString email);
+
+signals:
+    void commitChanged();
+
+private:
+    GitCommit();
+    QString m_author;
+    QDateTime m_time;
+    QString m_message;
+    QString m_email;
+};
+
+#endif // GITCOMMIT_H

+ 19 - 1
githandler.cpp

@@ -5,6 +5,8 @@
 #include <qqml.h>
 
 #include <gitrepository.h>
+#include <gitbranch.h>
+#include <commitmodel.h>
 #include <git2.h>
 
 GitHandler::GitHandler() : QObject()
@@ -39,9 +41,25 @@ void GitHandler::open(const QString &path)
         return;
     }
 
-    m_repositories->addRepository(repo);
+    BranchContainer &branches = repo->branches();
+
+//    foreach(GitBranch* branch, branches) {
+//        qDebug() << "Branch: " << branch->name();
+//        CommitModel* model = CommitModel::fromBranch(branch);
+//        m_commits.insert(model->head(), model);
+//    }
+
+    CommitModel* model = CommitModel::fromBranch(branches.value("master"));
+    m_commits.insert(model->head(), model);
+    m_repositories->add(repo);
+}
+
+CommitModel* GitHandler::modelByHead(const QString& head)
+{
+    return m_commits.value(head).data();
 }
 
+
 QString GitHandler::lastError() const
 {
     const git_error *e = giterr_last();

+ 5 - 1
githandler.h

@@ -5,6 +5,9 @@
 
 #include <repositorymodel.h>
 
+class CommitModel;
+typedef QHash<QString, QPointer<CommitModel>> CommitModelContainer;
+
 class GitHandler : public QObject
 {
     Q_OBJECT
@@ -21,7 +24,7 @@ public:
         return m_repositories;
     }
 
-public slots:
+    Q_INVOKABLE CommitModel* modelByHead(const QString& head);
 
 signals:
     void repositoriesChanged(RepositoryModel* repositories);
@@ -31,6 +34,7 @@ protected:
 
 private:
     RepositoryModel* m_repositories;
+    CommitModelContainer m_commits;
 };
 
 #endif // GITHANDLER_H

+ 41 - 2
gitoid.cpp

@@ -1,11 +1,50 @@
 #include "gitoid.h"
 
-GitOid::GitOid(const git_oid *oid, QObject *parent) : QObject(parent)
+#include <gitrepository.h>
+
+GitOid::GitOid(const git_oid *oid, GitRepository *parent) : QObject(parent)
+  ,m_oid({0})
+  ,m_repository(parent)
+{
+    updateOid(oid);
+}
+
+GitOid::GitOid(const GitOid& other) : QObject()
+{
+    updateOid(&(other.m_oid));
+    m_repository = other.m_repository;
+}
+
+const QLatin1String& GitOid::toString() const
 {
+    return m_string;
+}
 
+bool GitOid::operator ==(const GitOid& other) const
+{
+    return git_oid_equal(&m_oid, &(other.m_oid)) == 0
+            && m_repository == other.m_repository;
 }
 
-QString GitOid::toString() const
+GitOid& GitOid::operator=(const GitOid& other)
 {
+    updateOid(&(other.m_oid));
+    m_repository = other.m_repository;
+    return *this;
+}
 
+void GitOid::updateOid(const git_oid* oid)
+{
+    if(oid != nullptr) {
+        git_oid_cpy(&m_oid, oid);
+        m_string = QLatin1String(git_oid_tostr_s(&m_oid));
+    } else {
+        memset(&m_oid, 0, sizeof(m_oid));
+        m_string = QLatin1String("");
+    }
+}
+
+bool GitOid::isValid() const
+{
+    return !git_oid_iszero(&m_oid) && m_repository != nullptr;
 }

+ 25 - 3
gitoid.h

@@ -4,15 +4,37 @@
 #include <QObject>
 #include <git2/oid.h>
 
+class GitRepository;
+
 class GitOid : public QObject
 {
     Q_OBJECT
 public:
-    explicit GitOid(const git_oid* oid = 0, QObject *parent = 0);
-    QString toString() const;
+    GitOid(const git_oid* oid, GitRepository *parent);
+    GitOid(const GitOid& other);
+
+
+    bool operator ==(const GitOid& other) const;
+    GitOid& operator=(const GitOid& other);
+
+    const git_oid* raw() const {
+        return &m_oid;
+    }
+
+    GitRepository* repository() const {
+        return m_repository;
+    }
+
+    const QLatin1String &toString() const;
+
+    bool isValid() const;
+
 private:
+    void updateOid(const git_oid* oid);
+
     git_oid m_oid;
-    bool isValid;
+    QLatin1String m_string;
+    GitRepository *m_repository;
 };
 
 #endif // GITOID_H

+ 0 - 6
gitreflog.cpp

@@ -1,6 +0,0 @@
-#include "gitreflog.h"
-
-GitReflog::GitReflog(GitRepository *repo)
-{
-
-}

+ 0 - 16
gitreflog.h

@@ -1,16 +0,0 @@
-#ifndef GITREFLOG_H
-#define GITREFLOG_H
-
-struct git_reflog;
-
-class GitRepository;
-
-class GitReflog
-{
-public:
-    GitReflog(GitRepository* repo);
-private:
-    git_reflog* m_reflog;
-};
-
-#endif // GITREFLOG_H

+ 18 - 62
gitrepository.cpp

@@ -5,11 +5,10 @@
 #include <QDir>
 
 #include <gitbranch.h>
+#include <gitcommit.h>
 
 #include <git2.h>
 
-char oid_buf[GIT_OID_HEXSZ+1];
-
 GitRepository::GitRepository(const QString& root) : QObject(nullptr)
 {
     if(git_repository_open(&m_raw, root.toUtf8().data()) != 0) {
@@ -22,66 +21,7 @@ GitRepository::GitRepository(const QString& root) : QObject(nullptr)
     m_path = git_repository_workdir(m_raw);
     m_name = m_path;//TODO: replace with Human readable name
     qDebug() << "New repo:" << m_name << m_root << m_path;
-
-    //    git_reflog* reflog;
-
-    //    if(git_reflog_read(&reflog, m_raw, "HEAD") != 0) {
-    //        qDebug() << "reflogs could not be read";
-    //        return;
-    //    }
-
-    //    quint64 count = git_reflog_entrycount(reflog);
-    //    qDebug() << count;
-
-    //    for(quint64 i = 0; i < count; i++)
-    //    {
-    //        git_reflog_entry* entry = git_reflog_entry_byindex(reflog, i);
-    //        git_oid* oid = git_reflog_entry_id_new(entry);
-    //    }
-    //    git_reflog_free(reflog);
-
-    git_reference *out;
-    git_branch_t branch;
-    git_branch_iterator* iter;
-    git_branch_iterator_new(&iter, m_raw, GIT_BRANCH_ALL);
-
-    git_revwalk* walk;
-    git_revwalk_new(&walk, m_raw);
-    qDebug() << "Branches found:";
-    while(git_branch_next(&out, &branch, iter) == 0)
-    {
-        GitBranch testBranch(out, this);
-        qDebug() << testBranch.name();
-    }
-
-    return;
-
-    git_revwalk_push_glob(walk, "refs/heads/*");
-    git_revwalk_sorting(walk, GIT_SORT_TIME);
-
-    git_oid newoid;
-    while(git_revwalk_next(&newoid, walk) == 0)
-    {
-        git_commit *wcommit;
-        if(git_commit_lookup(&wcommit, m_raw, &newoid) != 0 )
-        {
-            qDebug() << "git_commit_lookup error";
-            continue;
-        }
-
-        const git_oid* commit_oid = git_commit_id(wcommit);
-        git_oid_tostr(oid_buf,GIT_OID_HEXSZ+1,commit_oid);
-        qDebug() << oid_buf;
-        qDebug() << git_commit_time( wcommit );
-        qDebug() << git_commit_message( wcommit );
-        qDebug() << git_commit_author( wcommit );
-
-        qDebug() << "=================================================";
-        git_commit_free( wcommit );
-    }
-
-    git_revwalk_free( walk );
-
+    readBranches();
 }
 
 GitRepository::~GitRepository()
@@ -96,3 +36,19 @@ void GitRepository::close()
     }
     m_raw = nullptr;
 }
+
+void GitRepository::readBranches()
+{
+    git_reference *branchRef;
+    git_branch_t branchType;
+    git_branch_iterator* iter;
+    git_branch_iterator_new(&iter, m_raw, GIT_BRANCH_ALL);
+    while(git_branch_next(&branchRef, &branchType, iter) == 0)
+    {
+        GitBranch* branch = new GitBranch(branchRef, branchType, this);
+        m_branches.insert(branch->name(), QPointer<GitBranch>(branch));
+        qDebug() << branch->name();
+        qDebug() << branch->type();
+    }
+}
+

+ 13 - 2
gitrepository.h

@@ -3,9 +3,13 @@
 
 #include <QObject>
 #include <QString>
+#include <QHash>
+#include <QPointer>
 
 struct git_repository;
 
+class GitBranch;
+typedef QHash<QString, QPointer<GitBranch>> BranchContainer;
 
 class GitRepository : public QObject
 {
@@ -13,6 +17,7 @@ class GitRepository : public QObject
     Q_PROPERTY(QString root READ root WRITE setRoot NOTIFY rootChanged)
     Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
     Q_PROPERTY(QString path READ path NOTIFY rootChanged)
+
 public:
     GitRepository(const QString &root);
     ~GitRepository();
@@ -25,8 +30,7 @@ public:
         return m_name;
     }
 
-    QString path() const
-    {
+    QString path() const {
         return m_path;
     }
 
@@ -38,6 +42,10 @@ public:
         return m_raw != nullptr;
     }
 
+    BranchContainer& branches() {
+        return m_branches;
+    }
+
 
 public slots:
     void setRoot(QString root) {
@@ -62,11 +70,14 @@ signals:
 
 private:
     void close();
+    void readBranches();
 
     QString m_root;
     QString m_name;
     QString m_path;
     git_repository* m_raw;
+
+    BranchContainer m_branches;
 };
 
 #endif // GITREPOSITORY_H

+ 6 - 0
main.cpp

@@ -6,6 +6,8 @@
 #include <githandler.h>
 #include <gitrepository.h>
 #include <repositorymodel.h>
+#include <gitbranch.h>
+#include <commitmodel.h>
 
 #include <QDebug>
 
@@ -17,12 +19,16 @@ int main(int argc, char *argv[])
 
     qmlRegisterUncreatableType<GitHandler>("org.semlanik.nicegit", 1, 0, "GitHandler", "Global for qml");
     qmlRegisterUncreatableType<GitRepository>("org.semlanik.nicegit", 1, 0, "GitRepository", "Owned only by GitHandler");
+    qmlRegisterUncreatableType<GitBranch>("org.semlanik.nicegit", 1, 0, "GitRepository", "Owned only by GitHandler");
     qmlRegisterUncreatableType<RepositoryModel>("org.semlanik.nicegit", 1, 0, "RepositoryModel", "Owned only by GitHandler");
+    qmlRegisterUncreatableType<CommitModel>("org.semlanik.nicegit", 1, 0, "CommitModel", "Owned only by GitHandler");
+
 
     GitHandler handler;
     handler.open("/home/semlanik/Projects/testrepo/");
     view.rootContext()->setContextProperty("_handler", &handler);
     view.setSource(QUrl("qrc:/qml/MainView.qml"));
+    view.setResizeMode(QQuickView::SizeRootObjectToView);
     view.showMaximized();
     return app.exec();
 }

+ 19 - 1
qml/MainView.qml

@@ -1,6 +1,7 @@
 import QtQuick 2.0
 import QtQuick.Controls 1.4
 import QtQuick.Dialogs 1.2
+import org.semlanik.nicegit 1.0
 
 Item {
     Row {
@@ -20,7 +21,24 @@ Item {
             model: _handler.repositories
 
             Text {
-                text: model.repoName
+                text: model.name
+            }
+        }
+    }
+
+
+    ListView {
+        height: 200
+        width: 100
+        anchors.right: parent.right
+        model: _handler.modelByHead("master")
+        delegate: Rectangle {
+            color: "#cccccc"
+            width: 200
+            height: 100
+            Text {
+                anchors.centerIn: parent
+                text: model.sha1
             }
         }
     }

+ 1 - 82
repositorymodel.cpp

@@ -1,92 +1,11 @@
 #include "repositorymodel.h"
-#include <gitrepository.h>
 
 #include <QDebug>
 
-QHash<int, QByteArray> RepositoryModel::m_roles;
-
-RepositoryModel::RepositoryModel(QObject* parent) : QAbstractListModel(parent)
+RepositoryModel::RepositoryModel(QObject* parent) : UniversalListModel(parent)
 {
-    if(m_roles.isEmpty()) {
-        m_roles[RepoName] = "repoName";
-        m_roles[RepoRoot] = "repoRoot";
-        m_roles[RepoBranches] = "repoBranches";
-    }
 }
 
 RepositoryModel::~RepositoryModel()
 {
-    foreach (QPointer<GitRepository> repo, m_repositories) {
-        delete repo.data();
-    }
-    m_repositories.clear();
 }
-
-bool RepositoryModel::addRepository(GitRepository* repo)
-{
-    if(repo == nullptr)
-    {
-        qDebug() << "Repo is null";
-        return false;
-    }
-
-    if(m_repositories.contains(repo->root())) {
-        qDebug() << "Repository" << repo->root() << "already exists";
-        return false;
-    }
-    beginInsertRows(QModelIndex(),0,0);
-    m_repositories.insert(repo->root(), QPointer<GitRepository>(repo));
-    endInsertRows();
-    return true;
-}
-
-void RepositoryModel::removeRepository(GitRepository* repo)
-{
-    if(repo == nullptr) {
-        return;
-    }
-
-    if(m_repositories.contains(repo->root())) {
-        beginRemoveRows(QModelIndex(), m_repositories.values().indexOf(repo), m_repositories.values().indexOf(repo));
-        m_repositories.remove(repo->root());
-        endRemoveRows();
-    }
-}
-
-void RepositoryModel::removeRepository(const QString& root)
-{
-    m_repositories.remove(root);
-}
-
-QVariant RepositoryModel::data(const QModelIndex &index, int role) const
-{
-    int row = index.row();
-
-    if(row < 0 || row >= m_repositories.count()) {
-        return QVariant();
-    }
-
-    GitRepository* repo = m_repositories.values().at(row).data();
-
-    switch (role) {
-    case RepoName:
-        return QVariant(repo->name());
-    case RepoRoot:
-        return QVariant(repo->root());
-    default:
-        break;
-    }
-    return QVariant();
-}
-
-int RepositoryModel::rowCount(const QModelIndex &parent) const
-{
-    Q_UNUSED(parent)
-    return m_repositories.count();
-}
-
-QHash<int, QByteArray> RepositoryModel::roleNames() const
-{
-    return m_roles;
-}
-

+ 4 - 23
repositorymodel.h

@@ -1,37 +1,18 @@
 #ifndef REPOSITORYMODEL_H
 #define REPOSITORYMODEL_H
 
-#include <QAbstractListModel>
-#include <QHash>
+#include <universallistmodel.h>
+#include <gitrepository.h>
 #include <QString>
 #include <QPointer>
 
-class GitRepository;
-
-class RepositoryModel : public QAbstractListModel
+class RepositoryModel : public UniversalListModel<GitRepository>
 {
     Q_OBJECT
 public:
-    enum Roles {
-        RepoName,
-        RepoRoot,
-        RepoBranches
-    };
-
     RepositoryModel(QObject *parent = 0);
     ~RepositoryModel();
-
-    bool addRepository(GitRepository* repo);
-    void removeRepository(GitRepository* repo);
-    void removeRepository(const QString& root);
-
-    QVariant data(const QModelIndex &index, int role) const;
-    int rowCount(const QModelIndex &parent) const;
-    QHash<int, QByteArray> roleNames() const;
-
-private:
-    QHash<QString, QPointer<GitRepository>> m_repositories;
-    static QHash<int, QByteArray> m_roles;
 };
 
+
 #endif // REPOSITORYMODEL_H

+ 1 - 0
universallistmodel.cpp

@@ -0,0 +1 @@
+#include "universallistmodel.h"

+ 98 - 0
universallistmodel.h

@@ -0,0 +1,98 @@
+#ifndef UNIVERSALLISTMODEL_H
+#define UNIVERSALLISTMODEL_H
+
+#include <QAbstractListModel>
+
+#include <QObject>
+#include <QList>
+#include <QHash>
+#include <QPointer>
+#include <QMetaProperty>
+#include <QMetaObject>
+
+#include <QDebug>
+
+template <typename T>
+class UniversalListModel : public QAbstractListModel
+{
+public:
+    ~UniversalListModel() {
+        foreach (QPointer<T> value, m_container) {
+            delete value.data();
+        }
+        m_container.clear();
+    }
+
+    int rowCount(const QModelIndex &parent) const {
+        Q_UNUSED(parent)
+        return m_container.count();
+    }
+
+    QHash<int, QByteArray> roleNames() const {
+        if(s_roleNames.isEmpty()) {
+            int propertyCount = T::staticMetaObject.propertyCount();
+            for(int i = 0; i < propertyCount; i++) {
+                s_roleNames.insert(Qt::UserRole + i, T::staticMetaObject.property(i).name());
+            }
+        }
+        return s_roleNames;
+    }
+
+    QVariant data(const QModelIndex &index, int role) const
+    {
+        int row = index.row();
+
+        if(row < 0 || row >= m_container.count()) {
+            return QVariant();
+        }
+
+        T* dataPtr = m_container.at(row).data();
+        return dataPtr->property(s_roleNames.value(role));
+    }
+
+    bool add(T* value) {
+        Q_ASSERT_X(value != nullptr, fullTemplateName(), "Trying to add member of NULL");
+
+        if(m_container.indexOf(value) >= 0) {
+#ifdef DEBUG
+            qDebug() << fullTemplateName() << "Member already exists";
+#endif
+            return false;
+        }
+        beginInsertRows(QModelIndex(), m_container.count(), m_container.count());
+        m_container.append(QPointer<T>(value));
+        endInsertRows();
+        return true;
+    }
+
+    void remove(T* value) {
+        Q_ASSERT_X(value != nullptr, fullTemplateName(), ": Trying to remove member of NULL");
+
+        int valueIndex = m_container.indexOf(value);
+
+        if(valueIndex >= 0) {
+            beginRemoveRows(QModelIndex(), valueIndex, valueIndex);
+            m_container.removeAt(valueIndex);
+            endRemoveRows();
+        }
+    }
+
+protected:
+    UniversalListModel(QObject* parent = 0) : QAbstractListModel(parent) {}
+
+    QList<QPointer<T>> m_container;
+    static QHash<int, QByteArray> s_roleNames;
+
+
+private:
+#ifdef DEBUG
+    static QByteArray fullTemplateName() { //Debug helper
+        return QString("UniversalListModel<%1>").arg(T::staticMetaObject.className()).toLatin1();
+    }
+#endif
+};
+
+template<typename T>
+QHash<int, QByteArray> UniversalListModel<T>::s_roleNames;
+
+#endif // UNIVERSALLISTMODEL_H