Browse Source

Branch checkout raw version

- Added filesystem watched(too non-optimized)
- Branch/commits checkout added
- Branch upstreaming
Alexey Edelev 7 years ago
parent
commit
3ccf7695e7

+ 8 - 2
NiceGit.pro

@@ -23,7 +23,10 @@ SOURCES += \
     gitdiff.cpp \
     graphlistmodel.cpp \
     gitremote.cpp \
-    gitbaseoid.cpp
+    gitbaseoid.cpp \
+    branchlistmodel.cpp \
+    taglistmodel.cpp \
+    tooltipviewmodel.cpp
 
 HEADERS += \
     githandler.h \
@@ -43,7 +46,10 @@ HEADERS += \
     gitdiff.h \
     graphlistmodel.h \
     gitremote.h \
-    gitbaseoid.h
+    gitbaseoid.h \
+    branchlistmodel.h \
+    taglistmodel.h \
+    tooltipviewmodel.h
 
 RESOURCES += \
     resources.qrc

+ 6 - 0
branchlistmodel.cpp

@@ -0,0 +1,6 @@
+#include "branchlistmodel.h"
+
+BranchListModel::BranchListModel(QObject *parent) : UniversalListModel(parent)
+{
+
+}

+ 14 - 0
branchlistmodel.h

@@ -0,0 +1,14 @@
+#ifndef BRANCHLISTMODEL_H
+#define BRANCHLISTMODEL_H
+
+#include <universallistmodel.h>
+#include <gitbranch.h>
+
+class BranchListModel : public UniversalListModel<GitBranch>
+{
+    Q_OBJECT
+public:
+    BranchListModel(QObject *parent = 0);
+};
+
+#endif // BRANCHLISTMODEL_H

+ 11 - 1
commitgraph.cpp

@@ -24,7 +24,6 @@ void CommitGraph::addHead(GitBranch* branch)
 {
     const GitOid& oid = branch->oid();
     addHead(oid);
-    m_points[oid]->setBranch(branch->name());
 }
 
 void CommitGraph::addHead(const GitOid &oid)
@@ -162,3 +161,14 @@ void CommitGraph::addCommits(QList<GitOid>& reversList)
         }
     }
 }
+
+GraphPoint* CommitGraph::point(const GitOid& oid)
+{
+    GraphPoint* point = m_points.value(oid, nullptr);
+    return point;
+}
+
+GraphPoint* CommitGraph::point(int i)
+{
+    return m_sortedPoints.at(i);
+}

+ 4 - 2
commitgraph.h

@@ -19,9 +19,8 @@ class GitDiff;
 class CommitGraph : public QObject
 {
     Q_OBJECT
-//    Q_PROPERTY(QList<QObject*> points READ points CONSTANT)
-    Q_PROPERTY(int branchesCount READ branchesCount NOTIFY branchesCountChanged)
     Q_PROPERTY(GraphListModel* points READ points NOTIFY pointsChanged)
+    Q_PROPERTY(int branchesCount READ branchesCount NOTIFY branchesCountChanged)
 public:
     CommitGraph();
     void addHead(GitBranch* branch);
@@ -36,6 +35,9 @@ public:
         return m_branchesCount;
     }
 
+    Q_INVOKABLE GraphPoint* point(const GitOid& oid);
+    Q_INVOKABLE GraphPoint* point(int i);
+
 signals:
     void branchesCountChanged(int branchesCount);
 

+ 1 - 2
commitmodel.cpp

@@ -14,8 +14,7 @@ CommitModel::CommitModel(const QString &head, QObject* parent) : UniversalListMo
 
 CommitModel* CommitModel::fromBranch(GitBranch* branch)
 {
-    qDebug() << branch->name();
-    CommitModel* tmpModel = new CommitModel(branch->name(), branch);
+    CommitModel* tmpModel = new CommitModel(branch->fullName(), branch);
     git_revwalk* walk;
     git_revwalk_new(&walk, branch->repository()->raw());
 

+ 13 - 2
gitbranch.cpp

@@ -4,6 +4,10 @@
 #include <gitrepository.h>
 
 #include <git2.h>
+namespace {
+const char remoteAddtion = '/';
+
+}
 
 GitBranch::GitBranch(git_reference *ref, git_branch_t type, GitRepository *parent) : GitReference(ref, parent)
   ,m_commit(nullptr)
@@ -11,10 +15,17 @@ GitBranch::GitBranch(git_reference *ref, git_branch_t type, GitRepository *paren
 {
     const char* tmpName;
     git_branch_name(&tmpName, m_raw);
-    m_name = tmpName;
+    m_fullName = tmpName;
+    m_name = m_fullName;
+
     git_annotated_commit_from_ref(&m_commit, repository()->raw(), m_raw);
     m_oid = GitOid(git_annotated_commit_id(m_commit), m_repository);
-    qDebug() << m_oid.toString();
+
+    git_buf buf = GIT_BUF_INIT_CONST("",0);
+    if(git_branch_remote_name(&buf, repository()->raw(), refName().toUtf8().data()) == 0) {
+        m_remote = QString::fromUtf8(buf.ptr);
+        m_name.remove(m_remote + remoteAddtion);
+    }
 }
 
 GitBranch::~GitBranch()

+ 25 - 2
gitbranch.h

@@ -10,8 +10,12 @@ class GitBranch : public GitReference
 {
     Q_OBJECT
     Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
+    Q_PROPERTY(QString fullName READ fullName NOTIFY fullNameChanged)
+    Q_PROPERTY(QString remote READ remote NOTIFY remoteChanged)
     Q_PROPERTY(BranchType type READ type CONSTANT)
+    Q_PROPERTY(GitOid oid READ oid CONSTANT)
     Q_ENUMS(BranchType)
+
 public:
     enum BranchType {
         Local = GIT_BRANCH_LOCAL,
@@ -21,12 +25,25 @@ public:
     GitBranch(git_reference* ref, git_branch_t type, GitRepository* parent);
     virtual ~GitBranch();
 
+    BranchType type() const;
 
     QString name() const {
         return m_name;
     }
 
-    BranchType type() const;
+    QString fullName() const {
+        return m_fullName;
+    }
+
+    git_annotated_commit* annontatedCommit()
+    {
+        return m_commit;
+    }
+
+    QString remote() const
+    {
+        return m_remote;
+    }
 
 public slots:
     void setName(QString name) {
@@ -40,14 +57,20 @@ public slots:
 signals:
     void nameChanged(QString name);
 
+    void fullNameChanged(QString fullName);
+
+    void remoteChanged(QString remote);
+
 private:
     void free();
     GitBranch();
     Q_DISABLE_COPY(GitBranch)
-    QString m_name;
 
     git_annotated_commit* m_commit;
     BranchType m_type;
+    QString m_name;
+    QString m_fullName;
+    QString m_remote;
 };
 
 #endif // GITBRANCH_H

+ 45 - 14
githandler.cpp

@@ -2,6 +2,7 @@
 
 #include <QDebug>
 #include <QUrl>
+#include <QFileSystemWatcher>
 #include <qqml.h>
 
 #include <gitrepository.h>
@@ -13,6 +14,8 @@
 
 #include <commitgraph.h>
 #include <graphpoint.h>
+#include <branchlistmodel.h>
+#include <taglistmodel.h>
 
 GitHandler::GitHandler() : QObject()
   ,m_repositories(new RepositoryModel(this))
@@ -20,6 +23,9 @@ GitHandler::GitHandler() : QObject()
   ,m_graph(nullptr)
   ,m_activeRepo(nullptr)
   ,m_activeDiff(nullptr)
+  ,m_branchList(new BranchListModel(this))
+  ,m_tagList(new TagListModel(this))
+  ,m_activeRepoWatcher(new QFileSystemWatcher(this))
 {
     git_libgit2_init();
 }
@@ -45,25 +51,25 @@ void GitHandler::open(const QString &path)
         return;
     }
 
+    if(m_activeRepo) {
+        m_activeRepoWatcher->removePath(m_activeRepo->root());
+    }
+
     m_activeRepo = new GitRepository(QString::fromUtf8(root.ptr, root.size));
     if(!m_activeRepo->isValid()) {
         qDebug() << lastError();
         return;
     }
 
-    BranchContainer &branches = m_activeRepo->branches();
-
-    CommitGraph* graph = new CommitGraph();
-    graph->addHead(branches.value("master").data()->oid());
-    foreach(GitBranch* branch, branches) {
-        qDebug() << "Next head " << branch->name();
-        graph->addHead(branch);
-    }
+    connect(m_activeRepo, &GitRepository::branchesChanged, this, &GitHandler::updateModels);
+    updateModels();
+    m_activeRepo->updateHead();
 
-    setGraph(graph);
-    m_commits = CommitModel::fromGraph(graph);
-    emit commitsChanged(m_commits);
-//    m_repositories->add(m_activeRepo);
+    m_activeRepoWatcher->addPath(m_activeRepo->root());
+    connect(m_activeRepoWatcher, &QFileSystemWatcher::directoryChanged, this, &GitHandler::updateModels);
+    connect(m_activeRepoWatcher, &QFileSystemWatcher::directoryChanged, m_activeRepo, &GitRepository::updateHead);
+    //TODO: opened repositories configuraion TBD
+    //    m_repositories->add(m_activeRepo);
 }
 
 QString GitHandler::lastError() const
@@ -103,8 +109,33 @@ GitDiff* GitHandler::activeDiff() const
     return m_activeDiff.data();
 }
 
-
 void GitHandler::pull() const
 {
-//    git_remote_fetch(m_activeRepo->remote)
+    //    git_remote_fetch(m_activeRepo->remote)
+}
+
+void GitHandler::updateModels()
+{
+    if(!m_activeRepo) {
+        return;
+    }
+
+    BranchContainer &branches = m_activeRepo->branches();
+    CommitGraph* graph = new CommitGraph();
+    graph->addHead(branches.value("master").data()->oid());
+
+    foreach(GitBranch* branch, branches) {
+        qDebug() << "Next head " << branch->fullName();
+        graph->addHead(branch);
+    }
+
+    m_graph->deleteLater();
+    m_graph = graph;
+    emit graphChanged(graph);
+
+    m_commits = CommitModel::fromGraph(m_graph);
+    emit commitsChanged(m_commits);
+
+    m_branchList->reset(m_activeRepo->branches().values());
+    m_tagList->reset(m_activeRepo->tags().values());
 }

+ 18 - 7
githandler.h

@@ -7,6 +7,9 @@
 
 class CommitModel;
 class CommitGraph;
+class BranchListModel;
+class TagListModel;
+class QFileSystemWatcher;
 
 typedef QHash<QString, QPointer<CommitModel>> CommitModelContainer;
 
@@ -14,10 +17,12 @@ class GitHandler : public QObject
 {
     Q_OBJECT
     Q_PROPERTY(RepositoryModel* repositories READ repositories NOTIFY repositoriesChanged)
-    Q_PROPERTY(CommitGraph* graph READ graph WRITE setGraph NOTIFY graphChanged)
+    Q_PROPERTY(CommitGraph* graph READ graph NOTIFY graphChanged)
     Q_PROPERTY(GitRepository* activeRepo READ activeRepo CONSTANT)
     Q_PROPERTY(GitDiff* activeDiff READ activeDiff WRITE setActiveDiff NOTIFY activeDiffChanged)
     Q_PROPERTY(CommitModel* commits READ commits WRITE setCommits NOTIFY commitsChanged)
+    Q_PROPERTY(BranchListModel* branchList READ branchList CONSTANT)
+    Q_PROPERTY(TagListModel* tagList READ tagList CONSTANT)
 
 public:
     GitHandler();
@@ -51,16 +56,17 @@ public:
 
     void pull() const;
 
-public slots:
-    void setGraph(CommitGraph* graph)
+    BranchListModel* branchList() const
     {
-        if (m_graph == graph)
-            return;
+        return m_branchList;
+    }
 
-        m_graph = graph;
-        emit graphChanged(graph);
+    TagListModel* tagList() const
+    {
+        return m_tagList;
     }
 
+public slots:
     void setActiveDiff(GitDiff* activeDiff);
 
     void setCommits(CommitModel* commits)
@@ -85,11 +91,16 @@ protected:
     QString lastError() const;
 
 private:
+    void updateModels();
+
     RepositoryModel* m_repositories;
     CommitModel* m_commits;
     CommitGraph* m_graph;
     GitRepository* m_activeRepo;
     QPointer<GitDiff> m_activeDiff;
+    BranchListModel* m_branchList;
+    TagListModel* m_tagList;
+    QFileSystemWatcher* m_activeRepoWatcher;
 };
 
 #endif // GITHANDLER_H

+ 6 - 0
gitoid.cpp

@@ -2,6 +2,12 @@
 
 #include <gitrepository.h>
 
+GitOid::GitOid() : QObject()
+  ,m_oid({0})
+  ,m_repository()
+{
+}
+
 GitOid::GitOid(const git_oid *oid, GitRepository *parent) : QObject(parent)
   ,m_oid({0})
   ,m_repository(parent)

+ 4 - 1
gitoid.h

@@ -12,6 +12,7 @@ class GitOid : public QObject
 {
     Q_OBJECT
 public:
+    GitOid();
     GitOid(const git_oid* oid, GitRepository *parent);
     GitOid(const GitOid& other);
 
@@ -28,7 +29,7 @@ public:
         return m_repository;
     }
 
-    QString toString() const;
+    Q_INVOKABLE QString toString() const;
     QString toShorten() const;
 
     bool isValid() const;
@@ -45,4 +46,6 @@ inline uint qHash(const GitOid& oid) {
     return qHash(QByteArray((const char*)(oid.raw()->id), GIT_OID_RAWSZ));
 }
 
+Q_DECLARE_METATYPE(GitOid)
+
 #endif // GITOID_H

+ 6 - 0
gitreference.cpp

@@ -66,3 +66,9 @@ void GitReference::free()
 
     m_raw = nullptr;
 }
+
+QString GitReference::refName() const
+{
+    return QString(git_reference_name(m_raw));
+}
+

+ 2 - 0
gitreference.h

@@ -28,6 +28,8 @@ public:
         return m_namespace;
     }
 
+    QString refName() const;
+
 protected:
     GitReference(git_reference* ref, GitRepository* parent);
     void free();

+ 5 - 2
gitremote.cpp

@@ -8,9 +8,12 @@ GitRemote::GitRemote(git_remote* raw, GitRepository* parent) : GitBase(raw, pare
 
 GitRemote* GitRemote::fromName(const QString& remoteName, GitRepository* parent)
 {
-    qDebug() << "new remote: " << remoteName;
     git_remote* remote = nullptr;
     git_remote_lookup(&remote, parent->raw(), remoteName.toUtf8().data());
-
     return new GitRemote(remote, parent);
 }
+
+QString GitRemote::name() const
+{
+    return QString(git_remote_name(raw()));
+}

+ 1 - 0
gitremote.h

@@ -14,6 +14,7 @@ public:
     GitRemote(git_remote* raw, GitRepository* parent);
     static GitRemote* fromName(const QString& remoteName, GitRepository* parent);
 
+    QString name() const;
 private:
 };
 

+ 114 - 7
gitrepository.cpp

@@ -51,10 +51,11 @@ void GitRepository::readBranches()
     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();
+        m_branches.insert(branch->fullName(), QPointer<GitBranch>(branch));
+        qDebug() << branch->fullName();
         qDebug() << branch->type();
     }
+    emit branchesChanged();
 }
 
 void GitRepository::readTags()
@@ -83,13 +84,119 @@ void GitRepository::readTags()
 
 void GitRepository::readRemotes()
 {
-    git_reference_foreach(raw(),[](git_reference *reference, void *payload) -> int
-    {
-        if(git_reference_is_remote(reference)) {
-            GitRemote* remote = GitRemote::fromName(QString::fromLatin1(git_reference_name(reference)), static_cast<GitRepository*>(payload));
+    git_strarray str_array;
+    git_remote_list(&str_array, raw());
+    for(int i = 0; i < str_array.count; i++) {
+        GitRemote* remote = GitRemote::fromName(QString::fromLatin1(str_array.strings[i]), this);
+        if(remote) {
+            m_remotes.insert(remote->name(), QPointer<GitRemote>(remote));
         }
-    }, this);
+    }
+//    git_reference_foreach_glob(raw(),"refs/remotes/*", [](const char *name, void *payload) -> int
+//    {
+//        qDebug() << "Remotes: " << name;
+//        return 0;
+////        if(git_reference_is_remote(reference)) {
+////        }
+//    }, this);
 
 //    git_remote* remoteRaw;
 //    git_reference_is_remote()
 }
+
+void GitRepository::checkout(QObject* object)
+{
+    git_checkout_options opts;
+    git_checkout_init_options(&opts,GIT_CHECKOUT_OPTIONS_VERSION);
+
+    opts.checkout_strategy = GIT_CHECKOUT_FORCE;//TODO: modify to merge policy
+    opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
+    opts.notify_cb = [](
+            git_checkout_notify_t why,
+            const char *path,
+            const git_diff_file *baseline,
+            const git_diff_file *target,
+            const git_diff_file *workdir,
+            void *payload) -> int
+    {
+        //TODO: make popup with progressbar
+//        qDebug() << "path:" << path;
+//        switch (why) {
+//        case GIT_CHECKOUT_NOTIFY_CONFLICT:
+//            qDebug() << "conflict";
+//            break;
+//        case GIT_CHECKOUT_NOTIFY_DIRTY:
+//            qDebug() << "dirty";
+//            break;
+//        case GIT_CHECKOUT_NOTIFY_UPDATED:
+//            qDebug() << "updated";
+//            break;
+//        case GIT_CHECKOUT_NOTIFY_UNTRACKED:
+//            qDebug() << "untracked";
+//            break;
+//        case GIT_CHECKOUT_NOTIFY_IGNORED:
+//            qDebug() << "ignored";
+//            break;
+//        default:
+//            break;
+//        }
+
+        return 0;
+    };
+
+    GitOid oid;
+    GitBranch* branch = dynamic_cast<GitBranch*>(object);
+    GitCommit* commit = dynamic_cast<GitCommit*>(object);
+
+    if(branch != nullptr) {
+        commit = GitCommit::fromOid(branch->oid());
+        qDebug() << "Checkout branch: " << git_checkout_tree(raw(), (git_object*)(commit->raw()), &opts);
+        switch(branch->type()) {
+            case GitBranch::Local:
+                break;
+        case GitBranch::Remote:
+            git_reference* ref;
+            //TODO: better to create API for local branch creation
+            if(0 == git_branch_create_from_annotated(&ref, raw(), branch->name().toUtf8().data(), branch->annontatedCommit(), 0)) {
+                branch = new GitBranch(ref, GIT_BRANCH_LOCAL, this);
+                git_branch_set_upstream(ref, branch->fullName().toUtf8().data());
+                m_branches.insert(branch->fullName(), QPointer<GitBranch>(branch));
+                emit branchesChanged();
+            } else if(m_branches.contains(branch->name())) { /*
+                                                               TODO: need to think how valid is this.
+                                                               In case if local branch already exists
+                                                               it will be checked out.
+                                                               But from commit tree perspective it doesn't
+                                                               look as valid behavior.
+                                                             */
+                branch = m_branches.value(branch->name());
+            }
+            break;
+        }
+
+        git_repository_set_head(raw(), branch->refName().toLatin1().data());
+        oid = branch->oid();
+        delete commit;
+    } else if(commit != nullptr) {
+        qDebug() << "Checkout commit: " << git_checkout_tree(raw(), (git_object*)(commit->raw()), &opts);
+        git_repository_set_head_detached(raw(), commit->oid().raw());
+        oid = commit->oid();
+    } else {
+        qDebug() << "Invalid object for checkout";
+        return;
+    }
+
+    setHead(oid);
+}
+
+void GitRepository::updateHead()
+{
+    //Read head
+    git_reference* ref = nullptr;
+    git_repository_head(&ref, raw());
+    git_annotated_commit* commit = nullptr;
+    git_annotated_commit_from_ref(&commit, raw(), ref);
+    setHead(GitOid(git_annotated_commit_id(commit), this));
+    qDebug() << "Repo head" << m_head.toString();
+}
+

+ 27 - 0
gitrepository.h

@@ -27,6 +27,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)
+    Q_PROPERTY(GitOid head READ head WRITE setHead NOTIFY headChanged)
 
 public:
     GitRepository(const QString &root);
@@ -64,6 +65,17 @@ public:
         return m_remotes;
     }
 
+    GitOid head() const
+    {
+        return m_head;
+    }
+
+    Q_INVOKABLE bool isHead(const GitOid& oid) const {
+        return m_head == oid;
+    }
+
+    Q_INVOKABLE void checkout(QObject* object);
+
 public slots:
     void setRoot(QString root) {
         if (m_root == root)
@@ -81,10 +93,24 @@ public slots:
         emit nameChanged(name);
     }
 
+    void setHead(GitOid head)
+    {
+        if (m_head == head)
+            return;
+
+        m_head = head;
+        emit headChanged(head);
+    }
+
+    void updateHead();
+
 signals:
     void rootChanged(QString root);
     void nameChanged(QString name);
 
+    void headChanged(GitOid head);
+    void branchesChanged();
+
 private:
     void close();
     void readBranches();
@@ -99,6 +125,7 @@ private:
     BranchContainer m_branches;
     TagContainer m_tags;
     RemoteContainer m_remotes;
+    GitOid m_head;
 };
 
 #endif // GITREPOSITORY_H

+ 9 - 1
gittag.cpp

@@ -1,7 +1,14 @@
 #include "gittag.h"
 #include <git2/tag.h>
 
+GitTag::GitTag() : GitBaseOid(nullptr, nullptr)
+  ,m_targetId(nullptr, nullptr)
+{
+
+}
+
 GitTag::GitTag(git_tag *raw, GitRepository *parent) : GitBaseOid(raw, parent)
+  ,m_targetId(nullptr, nullptr)
 {
     if(raw == nullptr) {
         return;
@@ -10,9 +17,10 @@ GitTag::GitTag(git_tag *raw, GitRepository *parent) : GitBaseOid(raw, parent)
     const git_oid* oid = git_tag_id(raw);
     m_oid = GitOid(oid, parent);
     m_name = QString::fromUtf8(git_tag_name(raw));
+    m_targetId = GitOid(git_tag_target_id(m_raw), repository());
 }
 
 GitOid GitTag::targetId() const
 {
-    return GitOid(git_tag_target_id(m_raw), repository());
+    return m_targetId;
 }

+ 16 - 0
gittag.h

@@ -10,8 +10,11 @@ class GitTag : public GitBaseOid<git_tag>
     Q_PROPERTY(QString message READ message NOTIFY tagChanged)
     Q_PROPERTY(QString owner READ owner NOTIFY tagChanged)
     Q_PROPERTY(QString sha1 READ sha1 NOTIFY tagChanged)
+    Q_PROPERTY(GitOid targetId READ targetId WRITE setTargetId NOTIFY targetIdChanged)
+
 
 public:
+    GitTag();
     GitTag(git_tag* raw, GitRepository* parent);
 
     QString name() const
@@ -36,14 +39,27 @@ public:
 
     GitOid targetId() const;
 
+public slots:
+    void setTargetId(GitOid targetId)
+    {
+        if (m_targetId == targetId)
+            return;
+
+        m_targetId = targetId;
+        emit targetIdChanged(targetId);
+    }
+
 signals:
     void tagChanged();
 
+    void targetIdChanged(GitOid targetId);
+
 private:
     QString m_name;
     QString m_message;
     QString m_owner;
     QString m_sha1;
+    GitOid m_targetId;
 };
 
 #endif // GITTAG_H

+ 2 - 2
graphlistmodel.h

@@ -21,10 +21,10 @@ public:
         return m_container.at(i).data();
     }
 
-    int count() const
-    {
+    int count() const {
         return m_container.count();
     }
+
 signals:
     void countChanged(int count);
 };

+ 8 - 38
graphpoint.h

@@ -13,59 +13,41 @@ class GraphPoint : public QObject
     Q_PROPERTY(QString color READ color WRITE setColor NOTIFY colorChanged)
     Q_PROPERTY(QString sha1 READ sha1 CONSTANT)
     Q_PROPERTY(QList<QObject*> childPoints READ childPoints CONSTANT)
-    Q_PROPERTY(QString tag READ tag CONSTANT)
-    Q_PROPERTY(QString branch READ branch CONSTANT)
+    Q_PROPERTY(GitOid oid READ oid CONSTANT)
 
 public:
     GraphPoint(const GitOid& commitOid, QObject* parent = 0);
     GraphPoint(const GitOid& commitOid, int x, int y, const QString& color, QObject* parent = 0);
     ~GraphPoint();
 
-    int x() const
-    {
+    int x() const {
         return m_x;
     }
 
-    int y() const
-    {
+    int y() const {
         return m_y;
     }
 
-    QString color() const
-    {
+    QString color() const {
         return m_color;
     }
 
-    const GitOid& oid() const
-    {
+    const GitOid& oid() const {
         return m_commitOid;
     }
 
-    QString sha1() const
-    {
+    QString sha1() const {
         return m_commitOid.toString();
     }
 
-    int childPointsCount() const
-    {
+    int childPointsCount() const {
         return m_childPoints.count();
     }
 
-    QList<QObject*> childPoints() const
-    {
+    QList<QObject*> childPoints() const {
         return m_childPoints;
     }
 
-    QString tag() const
-    {
-        return m_tag;
-    }
-
-    QString branch() const
-    {
-        return m_branch;
-    }
-
     bool addChildPoint(GraphPoint* point);
 
 public slots:
@@ -73,16 +55,6 @@ public slots:
     void setY(int y);
     void setColor(const QString& color);
 
-    void setTag(QString tag)
-    {
-        m_tag = tag;
-    }
-
-    void setBranch(QString branch)
-    {
-        m_branch = branch;
-    }
-
 signals:
     void xChanged(int x);
     void yChanged(int y);
@@ -95,8 +67,6 @@ private:
     int m_x;
     int m_y;
     QString m_color;
-    QString m_tag;
-    QString m_branch;
 };
 
 #endif // GRAPTHPOINT_H

BIN
images/aim.png


File diff suppressed because it is too large
+ 5 - 0
images/aim.svg


File diff suppressed because it is too large
+ 0 - 0
images/arrow.svg


BIN
images/arrow_annotation.png


BIN
images/branch.png


File diff suppressed because it is too large
+ 0 - 0
images/branch.svg


+ 1 - 0
images/tag.svg

@@ -0,0 +1 @@
+<?xml version="1.0" ?><svg height="1792" viewBox="0 0 1792 1792" width="1792" xmlns="http://www.w3.org/2000/svg"><path d="M576 448q0-53-37.5-90.5t-90.5-37.5-90.5 37.5-37.5 90.5 37.5 90.5 90.5 37.5 90.5-37.5 37.5-90.5zm1067 576q0 53-37 90l-491 492q-39 37-91 37-53 0-90-37l-715-716q-38-37-64.5-101t-26.5-117v-416q0-52 38-90t90-38h416q53 0 117 26.5t102 64.5l715 714q37 39 37 91z"/></svg>

+ 22 - 4
main.cpp

@@ -10,12 +10,18 @@
 #include <gitbranch.h>
 #include <commitmodel.h>
 #include <graphpoint.h>
-#include <graphlistmodel.h>
 #include <commitgraph.h>
 #include <gittag.h>
 #include <gitdiff.h>
+#include <gitoid.h>
+#include <tooltipviewmodel.h>
+
+#include <graphlistmodel.h>
+#include <branchlistmodel.h>
+#include <taglistmodel.h>
 
 #include <QDebug>
+static TooltipViewModel* ttmodel = nullptr;
 
 int main(int argc, char *argv[])
 {
@@ -23,10 +29,10 @@ int main(int argc, char *argv[])
     QFontDatabase::addApplicationFont(":/fonts/Inconsolata.otf");
     QQuickView view;
 
+    qmlRegisterUncreatableType<GitOid>("org.semlanik.nicegit", 1, 0, "GitOid", "Owned only by GitHandler");
     qmlRegisterUncreatableType<CommitModel>("org.semlanik.nicegit", 1, 0, "CommitModel", "Owned only by GitHandler");
     qmlRegisterUncreatableType<CommitGraph>("org.semlanik.nicegit", 1, 0, "CommitGraph", "Owned only by GitHandler");
     qmlRegisterUncreatableType<GraphPoint>("org.semlanik.nicegit", 1, 0, "GraphPoint", "Owned only by GitHandler");
-    qmlRegisterUncreatableType<GraphListModel>("org.semlanik.nicegit", 1, 0, "GraphListModel", "Owned only by GitHandler");
     qmlRegisterUncreatableType<RepositoryModel>("org.semlanik.nicegit", 1, 0, "RepositoryModel", "Owned only by GitHandler");
     qmlRegisterUncreatableType<GitRepository>("org.semlanik.nicegit", 1, 0, "GitRepository", "Owned only by GitHandler");
     qmlRegisterUncreatableType<GitBranch>("org.semlanik.nicegit", 1, 0, "GitBranch", "Owned only by GitHandler");
@@ -34,10 +40,22 @@ int main(int argc, char *argv[])
     qmlRegisterUncreatableType<GitTag>("org.semlanik.nicegit", 1, 0, "GitTag", "Global for qml");
     qmlRegisterUncreatableType<GitDiff>("org.semlanik.nicegit", 1, 0, "GitDiff", "Global for qml");
     qmlRegisterUncreatableType<GitCommit>("org.semlanik.nicegit", 1, 0, "GitCommit", "Global for qml");
-
+    qmlRegisterUncreatableType<GraphListModel>("org.semlanik.nicegit", 1, 0, "GraphListModel", "Owned only by GitHandler");
+    qmlRegisterUncreatableType<BranchListModel>("org.semlanik.nicegit", 1, 0, "BranchListModel", "Owned only by GitHandler");
+    qmlRegisterUncreatableType<TagListModel>("org.semlanik.nicegit", 1, 0, "TagListModel", "Owned only by GitHandler");
+    qmlRegisterSingletonType<TooltipViewModel>("org.semlanik.nicegit", 1, 0,"TooltipViewModel",
+                                               [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject*
+    {
+        Q_UNUSED(engine)
+        Q_UNUSED(scriptEngine)
+        if(!ttmodel) {
+            ttmodel = new TooltipViewModel;
+        }
+        return ttmodel;
+    });
 
     GitHandler handler;
-//    handler.open("/home/semlanik/Projects/HCAT/hmi_hcat/demo/default/HCAT/");
+    handler.open("/home/semlanik/Projects/HCAT/hmi_hcat/demo/default/HCAT/");
     view.rootContext()->setContextProperty("_handler", &handler);
     view.setSource(QUrl("qrc:/qml/MainView.qml"));
     view.setResizeMode(QQuickView::SizeRootObjectToView);

+ 29 - 5
qml/CommitList.qml

@@ -1,4 +1,5 @@
 import QtQuick 2.0
+import QtQuick.Controls 1.4
 
 FlickPager {
     id: root
@@ -11,6 +12,7 @@ FlickPager {
         property int commitsWidth: 0
         property int graphWidth: 0
         property int fullMessageWidth: 600
+        property int annotationWidth: 200
     }
 
     width: 120
@@ -21,21 +23,21 @@ FlickPager {
             name: "full"
             PropertyChanges {
                 target: root
-                width: d.fullMessageWidth + d.commitsWidth + d.graphWidth
+                width: d.fullMessageWidth + d.commitsWidth + d.graphWidth + d.annotationWidth
             }
         },
         State {
             name: "commitsOnly"
             PropertyChanges {
                 target: root
-                width: d.commitsWidth + d.graphWidth
+                width: d.commitsWidth + d.graphWidth + d.annotationWidth
             }
         },
         State {
             name: "treeOnly"
             PropertyChanges {
                 target: root
-                width: 120
+                width: d.graphWidth + d.annotationWidth
             }
         }
     ]
@@ -66,7 +68,7 @@ FlickPager {
                     height: graph.elementHeight
                     color: textSelector.containsMouse ? "#bbbbbb" : "#00bbbbbb"
                     Item {
-                        width: root.width - graph.width
+                        width: root.width - graph.width - graphAnnotation.width
                         height: sha1.height
                         anchors.right: parent.right
                         Text {
@@ -110,16 +112,38 @@ FlickPager {
                         anchors.fill: parent
                         hoverEnabled: true
                         focus: true
+                        acceptedButtons: Qt.LeftButton | Qt.RightButton
                         onClicked: {
-                            root.commitClicked(model.modelData)
+                            if(mouse.button === Qt.RightButton) {
+                                commitMenu.popup()
+                            } else {
+                                root.commitClicked(model.modelData)
+                            }
+                        }
+                    }
+                    Menu {
+                        id: commitMenu
+                        MenuItem {
+                            text: "Checkout commit"
+                            onTriggered: {
+                                _handler.activeRepo.checkout(model.modelData)
+                            }
                         }
                     }
                 }
             }
         }
 
+        GraphAnnotation {
+            id: graphAnnotation
+            elementHeight: graph.elementHeight
+            spacing: graph.spacingV
+            width: d.annotationWidth
+        }
+
         Graph {
             id: graph
+            anchors.left: graphAnnotation.right
             model: root.graphModel
             onWidthChanged: {
                 d.graphWidth = graph.width

+ 12 - 1
qml/CommitPlane.qml

@@ -39,6 +39,7 @@ Item {
         width: d.commitInfoWidth
         onOpenDiff: {
             fileDiff.text = diff.unified(file)
+            currentFileName.text = qsTr("Diff for ") + file
         }
     }
 
@@ -60,6 +61,15 @@ Item {
         }
     }
 
+
+    Text {
+        id: currentFileName
+        anchors.top: parent.top
+        anchors.left: files.right
+        anchors.leftMargin: 40
+        font.weight: Font.Bold
+    }
+
     ScrollView {
         id: diffViewport
         anchors.top: topDecor.bottom
@@ -87,13 +97,14 @@ Item {
 
     onDiffChanged: {
         fileDiff.text = ""
+        currentFileName.text = ""
     }
 
     Rectangle {
         id: topDecor
         anchors.right: diffViewport.right
         anchors.left: diffViewport.left
-        anchors.top: parent.top
+        anchors.top: currentFileName.bottom
         height: d.viewportMargins
         gradient: Gradient {
             GradientStop { position: 0.0; color: "#ffffffff" }

+ 27 - 7
qml/Graph.qml

@@ -10,27 +10,47 @@ Canvas {
     property QtObject model: null
     height: model.points.count*(elementWidth + spacingV)
     width: (elementWidth + spacingH)*model.branchesCount
+    property int headY: 0
     QtObject {
         id: d
         property int halfElementWidth: elementWidth/2
         property int halfElementHeight: elementHeight/2
+        Component.onCompleted: {
+            root.loadImage("qrc:///images/aim.svg")
+            _handler.activeRepo.headChanged.connect(update)
+        }
+        function update() {
+            root.requestPaint()
+        }
     }
 
-
     onPaint: {
         var ctx = getContext("2d")
+        ctx.clearRect(region.x, region.y ,region.width, region.height)
         for(var i = 0; i < model.points.count; i++) {
+
             var point = model.points.at(i)
             var pointAbsX = point.x*(elementWidth + spacingH)
             var pointAbsY = point.y*(elementHeight + spacingV)
             var childPoints = point.childPoints
 
-            ctx.beginPath()
-            ctx.lineWidth = root.lineWidth
-            ctx.strokeStyle = "#"+point.color
-            ctx.roundedRect(pointAbsX, pointAbsY, elementWidth, elementHeight, elementWidth, elementHeight)
-            ctx.stroke()
-            ctx.closePath()
+            if(!_handler.activeRepo.isHead(point.oid)) {
+                ctx.beginPath()
+                ctx.lineWidth = root.lineWidth
+                ctx.strokeStyle = "#"+point.color
+                ctx.roundedRect(pointAbsX, pointAbsY, elementWidth, elementHeight, elementWidth, elementHeight)
+                ctx.stroke()
+                ctx.closePath()
+            } else {
+                ctx.beginPath()
+                root.headY = pointAbsY;
+//                ctx.lineWidth = root.lineWidth
+                ctx.fillStyle = "#"+point.color
+                ctx.roundedRect(pointAbsX, pointAbsY, elementWidth, elementHeight, elementWidth, elementHeight)
+                ctx.fill()
+                ctx.closePath()
+                ctx.drawImage("qrc:///images/aim.svg", pointAbsX + 1, pointAbsY + 1, 18, 18)
+            }
 
             for(var j = 0; j < childPoints.length; j++) {
                 var childPoint = childPoints[j]

+ 158 - 0
qml/GraphAnnotation.qml

@@ -0,0 +1,158 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.4
+import org.semlanik.nicegit 1.0
+
+Item {
+    id: root
+    property int elementHeight: 20
+    property int spacing: 10
+
+    QtObject {
+        id: d
+        property int fullHeight: elementHeight + spacing
+    }
+
+    Repeater {
+        id: branches
+        model: _handler.branchList
+        Item {
+            anchors.left: parent.left
+            height: d.fullHeight
+            width: root.width / 2
+            y: _handler.graph.point(model.oid).y*d.fullHeight - spacing/2
+            clip: true
+            Rectangle {
+                anchors.left: parent.left
+                anchors.right: parent.right
+                anchors.verticalCenter: parent.verticalCenter
+                height: 25
+                color:"#77ccff"
+                Rectangle {
+                    id:branchIconRect
+                    width: 20
+                    height: parent.height
+                    color:"#55aaff"
+                    Image {
+                        id: branchIcon
+                        anchors.centerIn: parent
+                        height: 16
+                        width: 10
+                        source: "qrc:///images/branch.svg"
+                    }
+                }
+                Text {
+                    id: branchNameText
+                    anchors.left: branchIconRect.right
+                    anchors.leftMargin: 5
+                    anchors.right: parent.right
+                    anchors.rightMargin: 5
+                    anchors.verticalCenter: parent.verticalCenter
+                    verticalAlignment: Text.AlignVCenter
+                    font.pixelSize: 12
+                    text: model.fullName
+                    elide: Text.ElideMiddle
+                }
+            }
+
+            MouseArea {
+                anchors.fill: parent
+                hoverEnabled: true
+                acceptedButtons: Qt.LeftButton | Qt.RightButton
+                onContainsMouseChanged: {
+                    if(containsMouse) {
+                        if(branchNameText.implicitWidth <= branchNameText.width) {
+                            return
+                        }
+                        var coord = parent.mapToItem(TooltipViewModel.viewport, 0, 0)
+                        TooltipViewModel.x = coord.x
+                        TooltipViewModel.y = coord.y + spacing/2
+                        TooltipViewModel.text = model.fullName
+                        TooltipViewModel.visible = true
+                    } else {
+                        TooltipViewModel.visible = false
+                    }
+                }
+                onClicked: {
+                    if(mouse.button === Qt.RightButton) {
+                        branchMenu.popup()
+                    } else {
+                        root.commitClicked(model.modelData)
+                    }
+                }
+            }
+            Menu {
+                id: branchMenu
+                MenuItem {
+                    text: "Checkout branch"
+                    onTriggered: {
+                        _handler.activeRepo.checkout(model.modelData)
+                    }
+                }
+            }
+        }
+    }
+
+    Repeater {
+        id: tags
+        model: _handler.tagList
+        Item {
+            id: tagContainer
+            anchors.right: parent.right
+            height: d.fullHeight
+            width: root.width / 2
+            y: _handler.graph.point(model.targetId).y*(root.elementHeight + root.spacing) - spacing/2
+            clip: true
+            Rectangle {
+                anchors.left: parent.left
+                anchors.right: parent.right
+                anchors.verticalCenter: parent.verticalCenter
+                height: 25
+                color:"#ccff77"
+                Rectangle {
+                    id: tagIconRect
+                    width: 20
+                    height: parent.height
+                    color:"#aaff55"
+                    Image {
+                        id: tagIcon
+                        anchors.centerIn: parent
+                        height: 14
+                        width: height
+                        source: "qrc:///images/tag.svg"
+                    }
+                }
+                Text {
+                    id: tagNameText
+                    anchors.left: tagIconRect.right
+                    anchors.leftMargin: 5
+                    anchors.right: parent.right
+                    anchors.rightMargin: 5
+                    anchors.verticalCenter: parent.verticalCenter
+                    text: model.name
+                    font.pixelSize: 12
+                    elide: Text.ElideRight
+                }
+            }
+
+            MouseArea {
+                anchors.fill: parent
+                hoverEnabled: true
+                onContainsMouseChanged: {
+                    if(tagNameText.implicitWidth <= tagNameText.width) {
+                        return
+                    }
+
+                    if(containsMouse) {
+                        var coord = parent.mapToItem(TooltipViewModel.viewport, 0, 0)
+                        TooltipViewModel.x = coord.x
+                        TooltipViewModel.y = coord.y + spacing/2
+                        TooltipViewModel.text = model.name
+                        TooltipViewModel.visible = true
+                    } else {
+                        TooltipViewModel.visible = false
+                    }
+                }
+            }
+        }
+    }
+}

+ 3 - 0
qml/MainView.qml

@@ -82,4 +82,7 @@ Item {
             console.log("control released");
         }
     }
+
+    Tooltip {
+    }
 }

+ 33 - 0
qml/Tooltip.qml

@@ -0,0 +1,33 @@
+import QtQuick 2.0
+import org.semlanik.nicegit 1.0
+
+Rectangle {
+    id: root
+    x: TooltipViewModel.x
+    y: TooltipViewModel.y
+    visible: opacity > 0
+    opacity: TooltipViewModel.visible ? 1.0 : 0.0
+    width: childrenRect.width + 10
+    height: 20
+    color: "#eeeeee"
+    border.color: "#000000"
+    border.width: 1
+
+    Behavior on opacity {
+        NumberAnimation {
+            duration: 200
+        }
+    }
+
+    Text {
+        id: textArea
+        anchors.left: parent.left
+        anchors.leftMargin: 5
+        anchors.verticalCenter: parent.verticalCenter
+        width: textArea.contentWidth
+        text: TooltipViewModel.text
+    }
+    Component.onCompleted: {
+        TooltipViewModel.viewport = root.parent
+    }
+}

+ 9 - 0
resources.qrc

@@ -26,5 +26,14 @@
         <file>qml/CommitInfoLine.qml</file>
         <file>images/arrow-203-16_active.png</file>
         <file>images/arrow-203-16.png</file>
+        <file>qml/GraphAnnotation.qml</file>
+        <file>images/tag.svg</file>
+        <file>images/arrow.svg</file>
+        <file>images/arrow_annotation.png</file>
+        <file>images/branch.svg</file>
+        <file>images/branch.png</file>
+        <file>qml/Tooltip.qml</file>
+        <file>images/aim.svg</file>
+        <file>images/aim.png</file>
     </qresource>
 </RCC>

+ 6 - 0
taglistmodel.cpp

@@ -0,0 +1,6 @@
+#include "taglistmodel.h"
+
+TagListModel::TagListModel(QObject *parent) : UniversalListModel(parent)
+{
+
+}

+ 14 - 0
taglistmodel.h

@@ -0,0 +1,14 @@
+#ifndef TAGLISTMODEL_H
+#define TAGLISTMODEL_H
+
+#include <universallistmodel.h>
+#include <gittag.h>
+
+class TagListModel : public UniversalListModel<GitTag>
+{
+    Q_OBJECT
+public:
+    TagListModel(QObject* parent = 0);
+};
+
+#endif // TAGLISTMODEL_H

+ 10 - 0
tooltipviewmodel.cpp

@@ -0,0 +1,10 @@
+#include "tooltipviewmodel.h"
+
+TooltipViewModel::TooltipViewModel() : QObject()
+  ,m_x(0)
+  ,m_y(0)
+  ,m_visible(false)
+  ,m_viewport(nullptr)
+{
+
+}

+ 109 - 0
tooltipviewmodel.h

@@ -0,0 +1,109 @@
+#ifndef TOOLTIPVIEWMODEL_H
+#define TOOLTIPVIEWMODEL_H
+
+#include <QObject>
+
+class QQuickItem;
+
+class TooltipViewModel : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
+    Q_PROPERTY(int x READ x WRITE setX NOTIFY xChanged)
+    Q_PROPERTY(int y READ y WRITE setY NOTIFY yChanged)
+    Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged)
+    Q_PROPERTY(QQuickItem* viewport READ viewport WRITE setViewport NOTIFY viewportChanged)
+public:
+    TooltipViewModel();
+
+    int x() const
+    {
+        return m_x;
+    }
+
+    int y() const
+    {
+        return m_y;
+    }
+
+    bool visible() const
+    {
+        return m_visible;
+    }
+
+    QString text() const
+    {
+        return m_text;
+    }
+
+    QQuickItem* viewport() const
+    {
+        return m_viewport;
+    }
+
+public slots:
+    void setX(int x)
+    {
+        if (m_x == x)
+            return;
+
+        m_x = x;
+        emit xChanged(x);
+    }
+
+    void setY(int y)
+    {
+        if (m_y == y)
+            return;
+
+        m_y = y;
+        emit yChanged(y);
+    }
+
+    void setVisible(bool visible)
+    {
+        if (m_visible == visible)
+            return;
+
+        m_visible = visible;
+        emit visibleChanged(visible);
+    }
+
+    void setText(const QString& text)
+    {
+        if (m_text == text)
+            return;
+
+        m_text = text;
+        emit textChanged(text);
+    }
+
+    void setViewport(QQuickItem* viewport)
+    {
+        if (m_viewport == viewport)
+            return;
+
+        m_viewport = viewport;
+        emit viewportChanged(viewport);
+    }
+
+signals:
+    void xChanged(int x);
+
+    void yChanged(int y);
+
+    void visibleChanged(bool visible);
+
+    void textChanged(const QString& text);
+
+    void viewportChanged(QQuickItem* viewport);
+
+private:
+    int m_x;
+    int m_y;
+    bool m_visible;
+    QString m_text;
+    QQuickItem* m_viewport;
+};
+
+#endif // TOOLTIPVIEWMODEL_H

Some files were not shown because too many files changed in this diff