File indexing completed on 2024-04-28 04:38:51

0001 /*
0002     SPDX-FileCopyrightText: 2008 Evgeniy Ivanov <powerfox@kde.ru>
0003     SPDX-FileCopyrightText: 2009 Hugo Parente Lima <hugo.pl@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #ifndef KDEVPLATFORM_PLUGIN_GIT_PLUGIN_H
0009 #define KDEVPLATFORM_PLUGIN_GIT_PLUGIN_H
0010 
0011 #include <vcs/interfaces/idistributedversioncontrol.h>
0012 #include <vcs/interfaces/icontentawareversioncontrol.h>
0013 #include <vcs/dvcs/dvcsplugin.h>
0014 #include <vcs/vcsstatusinfo.h>
0015 #include <outputview/outputjob.h>
0016 #include <vcs/vcsjob.h>
0017 
0018 #include <QDateTime>
0019 
0020 class KDirWatch;
0021 class QDir;
0022 
0023 class RepoStatusModel;
0024 class CommitToolViewFactory;
0025 
0026 namespace KDevelop
0027 {
0028     class VcsJob;
0029     class VcsRevision;
0030 }
0031 
0032 class StandardJob : public KDevelop::VcsJob
0033 {
0034     Q_OBJECT
0035     public:
0036         StandardJob(KDevelop::IPlugin* parent, KJob* job, OutputJobVerbosity verbosity);
0037 
0038         QVariant fetchResults() override { return QVariant(); }
0039         void start() override;
0040         JobStatus status() const override { return m_status; }
0041         KDevelop::IPlugin* vcsPlugin() const override { return m_plugin; }
0042 
0043     public Q_SLOTS:
0044         void result(KJob*);
0045 
0046     private:
0047         KJob* m_job;
0048         KDevelop::IPlugin* m_plugin;
0049         JobStatus m_status;
0050 };
0051 
0052 /**
0053  * This is the main class of KDevelop's Git plugin.
0054  *
0055  * It implements the DVCS dependent things not implemented in KDevelop::DistributedVersionControlPlugin
0056  * @author Evgeniy Ivanov <powerfox@kde.ru>
0057  */
0058 class GitPlugin: public KDevelop::DistributedVersionControlPlugin, public KDevelop::IContentAwareVersionControl
0059 {
0060     Q_OBJECT
0061     Q_INTERFACES(KDevelop::IBasicVersionControl KDevelop::IDistributedVersionControl KDevelop::IContentAwareVersionControl)
0062     friend class GitInitTest;
0063 public:
0064 
0065     enum ExtendedState {
0066         /* Unchanged in index (no staged changes) */
0067         GitXX = KDevelop::VcsStatusInfo::ItemUserState, // No changes in worktree
0068 
0069                // Changed in worktree, not staged for commit
0070         GitXM, // Modified in worktree
0071         GitXD, // Deleted in worktree
0072         GitXR, // Renamed in worktree
0073         GitXC, // Copied in worktree
0074 
0075         /* Changes in index (staged changes) */
0076         GitMX, // No changes in worktree
0077                   // Changed in worktree, not staged for commit
0078         GitMM, // Modified in worktree
0079         GitMD, // Deleted in worktree
0080 
0081         /* Added to index (new item) */
0082         GitAX, // No changes in worktree
0083                   // Changes in worktree, not staged for commit
0084         GitAM, // Modified in worktree
0085         GitAD, // Deleted in worktree
0086 
0087         /* Deleted from index */
0088         GitDX, // No changes in worktree (deleted in wt)
0089         GitDR, // Renamed in worktree
0090         GitDC, // Copied in worktree
0091 
0092         /* Renamed in index */
0093         GitRX, // No changes in worktree
0094         GitRM, // Modified in worktree
0095         GitRD, // Deleted in worktree
0096 
0097         /* Copied in index */
0098         GitCX, // No changes in worktree
0099         GitCM, // Modified in worktree
0100         GitCD, // Deleted in worktree
0101 
0102         /* Special states */
0103         GitUntracked, // ? ? --- untracked files
0104         GitConflicts, // U, AA, DD --- conflicts
0105         GitInvalid = -1, // not really a state
0106     };
0107 
0108     /**
0109      * Enums with values which are used as function arguments
0110      * instead of bools for better readability.
0111      *
0112      * The enums are named ${function_name}Params.
0113      */
0114     enum ApplyParams {
0115         Index = 0,
0116         WorkTree = 2,
0117     };
0118 
0119 
0120     explicit GitPlugin(QObject *parent, const QVariantList & args = QVariantList() );
0121     ~GitPlugin() override;
0122 
0123     QString name() const override;
0124 
0125     bool isValidRemoteRepositoryUrl(const QUrl& remoteLocation) override;
0126     bool isVersionControlled(const QUrl &path) override;
0127 
0128     KDevelop::VcsJob* copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn) override;
0129     KDevelop::VcsJob* move(const QUrl& localLocationSrc, const QUrl& localLocationDst) override;
0130 
0131     //TODO
0132     KDevelop::VcsJob* pull(const KDevelop::VcsLocation& localOrRepoLocationSrc, const QUrl& localRepositoryLocation) override;
0133     KDevelop::VcsJob* push(const QUrl& localRepositoryLocation, const KDevelop::VcsLocation& localOrRepoLocationDst) override;
0134     KDevelop::VcsJob* repositoryLocation(const QUrl& localLocation) override;
0135     KDevelop::VcsJob* resolve(const QList<QUrl>& localLocations, RecursionMode recursion) override;
0136     KDevelop::VcsJob* update(const QList<QUrl>& localLocations, const KDevelop::VcsRevision& rev, RecursionMode recursion) override;
0137     KDevelop::VcsLocationWidget* vcsLocation(QWidget* parent) const override;
0138     void setupCommitMessageEditor(const QUrl& localLocation, KTextEdit* editor) const override;
0139     //End of
0140 
0141     KDevelop::VcsJob* add(const QList<QUrl>& localLocations,
0142                           KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override;
0143     KDevelop::VcsJob* createWorkingCopy(const KDevelop::VcsLocation & localOrRepoLocationSrc,
0144                             const QUrl& localRepositoryRoot, KDevelop::IBasicVersionControl::RecursionMode) override;
0145 
0146     KDevelop::VcsJob* remove(const QList<QUrl>& files) override;
0147     KDevelop::VcsJob* status(const QList<QUrl>& localLocations,
0148                              KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override;
0149     KDevelop::VcsJob* commit(const QString& message,
0150                              const QList<QUrl>& localLocations,
0151                              KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override;
0152 
0153     /**
0154      * Commits staged changes to the repo located at repoUrl.
0155      *
0156      * @param message the commit message
0157      * @param repoUrl the url pointing to the repo directory (or a file in the repo)
0158      */
0159     KDevelop::VcsJob* commitStaged(const QString& message, const QUrl& repoUrl);
0160 
0161     KDevelop::VcsJob* diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision,
0162                                    RecursionMode recursion) override;
0163     /**
0164      * Shows a diff of changes between srcRevision and dstRevision.
0165      *
0166      * @param repoPath a path pointing somewhere inside the repo
0167      * @param srcRevision the source revision
0168      * @param dstRevision the destination revision
0169      *
0170      * @note: This differs from the @ref:diff method in @ref:IBasicVersionControl in that it does not require
0171      * a list of files but automatically shows all changed files
0172      */
0173     KDevelop::VcsJob* diff(const QUrl& repoPath, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision);
0174 
0175     KDevelop::VcsJob* log( const QUrl& localLocation, const KDevelop::VcsRevision& rev, unsigned long limit) override;
0176     KDevelop::VcsJob* log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, const KDevelop::VcsRevision& limit) override;
0177     KDevelop::VcsJob* annotate(const QUrl &localLocation, const KDevelop::VcsRevision &rev) override;
0178     KDevelop::VcsJob* revert(const QList<QUrl>& localLocations, RecursionMode recursion) override;
0179 
0180     /**
0181      * Resets all changes in the specified files which were "staged for commit".
0182      *
0183      * @param localLocations the local files/dirs changes to which should be reset
0184      * @param recursion defines whether changes should be reset recursively in all files
0185      * in a directory, if localLocations contain a directory
0186      */
0187     KDevelop::VcsJob* reset(const QList<QUrl>& localLocations, RecursionMode recursion);
0188 
0189     /**
0190      * Applies the patch given by a diff to the repo
0191      *
0192      * @param diff the patch
0193      * @param applyTo where to apply the patch (index or worktree)
0194      */
0195     KDevelop::VcsJob* apply(const KDevelop::VcsDiff& diff, ApplyParams applyTo = WorkTree);
0196 
0197     // Begin:  KDevelop::IDistributedVersionControl
0198     KDevelop::VcsJob* init(const QUrl & directory) override;
0199 
0200     // Branch management
0201     KDevelop::VcsJob* tag(const QUrl& repository, const QString& commitMessage, const KDevelop::VcsRevision& rev, const QString& tagName) override;
0202     KDevelop::VcsJob* branch(const QUrl& repository, const KDevelop::VcsRevision& rev, const QString& branchName) override;
0203     KDevelop::VcsJob* branches(const QUrl& repository) override;
0204     KDevelop::VcsJob* currentBranch(const QUrl& repository) override;
0205     KDevelop::VcsJob* deleteBranch(const QUrl& repository, const QString& branchName) override;
0206     KDevelop::VcsJob* switchBranch(const QUrl& repository, const QString& branchName) override;
0207     KDevelop::VcsJob* renameBranch(const QUrl& repository, const QString& oldBranchName, const QString& newBranchName) override;
0208     KDevelop::VcsJob* mergeBranch(const QUrl& repository, const QString& branchName) override;
0209     KDevelop::VcsJob* rebase(const QUrl& repository, const QString& branchName);
0210 
0211     //graph helpers
0212     QVector<KDevelop::DVcsEvent> allCommits(const QString& repo) override;
0213 
0214     //used in log
0215     void parseLogOutput(const KDevelop::DVcsJob* job,
0216                         QVector<KDevelop::DVcsEvent>& commits) const override;
0217 
0218     void additionalMenuEntries(QMenu* menu, const QList<QUrl>& urls) override;
0219 
0220     // Stash Management
0221 
0222     /**
0223      * Structure to hold information about an item on the stash stack
0224      */
0225     struct StashItem {
0226         int stackDepth = -1;        /* Position on the stack */
0227         QString shortRef;           /* The reflog selector (e.g. stash@{0}) */
0228         QString parentSHA;          /* The short SHA of the commit on which the stash was made */
0229         QString parentDescription;  /* A short description of the commit on which the stash was made */
0230         QString branch;             /* The branch on which the stash was made */
0231         QString message;            /* The message with which the stash was made */
0232         QDateTime creationTime;     /* The date-time the stash item was committed */
0233     };
0234 
0235     /**
0236      * Returns a job to run `git stash` in the repository @p repository with
0237      * additional arguments @p args.
0238      *
0239      * The @p verbosity parameter will determine whether the job output will
0240      * be shown in the VCS Output ToolView.
0241      *
0242      * For example, a job to silently apply the top-most stashed item to the current
0243      * tree would be created as follows:
0244      *
0245      *      gitStash(repoDir, {QStringLiteral("apply")}, KDevelop::OutputJob::Silent)
0246      *
0247      */
0248     KDevelop::VcsJob* gitStash(const QDir& repository, const QStringList& args, KDevelop::OutputJob::OutputJobVerbosity verbosity);
0249 
0250     /**
0251      * The result (job->fetchResults()) will be a @ref QList of @ref StashItem s
0252      *
0253      * @p repository is the repository to work on
0254      * @p verbosity  determines whether the job output will be shown in the VCS Output ToolView
0255      */
0256     KDevelop::VcsJob* stashList(const QDir& repository, KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Silent);
0257 
0258     bool hasStashes(const QDir& repository);
0259     bool hasModifications(const QDir& repository);
0260     bool hasModifications(const QDir& repo, const QUrl& file);
0261 
0262     void registerRepositoryForCurrentBranchChanges(const QUrl& repository) override;
0263 
0264     KDevelop::CheckInRepositoryJob* isInRepository(KTextEditor::Document* document) override;
0265 
0266     KDevelop::DVcsJob* setConfigOption(const QUrl& repository, const QString& key, const QString& value, bool global = false);
0267     QString readConfigOption(const QUrl& repository, const QString& key);
0268 
0269     // this indicates whether the diff() function will generate a diff (patch) which
0270     // includes the working copy directory name or not (in which case git diff is called
0271     // with --no-prefix).
0272     bool usePrefix() const
0273     {
0274         return m_usePrefix;
0275     }
0276 
0277     void setUsePrefix(bool p)
0278     {
0279         m_usePrefix = p;
0280     }
0281 protected:
0282 
0283     QUrl repositoryRoot(const QUrl& path);
0284 
0285     bool isValidDirectory(const QUrl &dirPath) override;
0286 
0287     KDevelop::DVcsJob* lsFiles(const QDir &repository,
0288                      const QStringList &args,
0289                      KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose);
0290     KDevelop::DVcsJob* gitRevList(const QString &directory,
0291                         const QStringList &args);
0292     KDevelop::DVcsJob* gitRevParse(const QString &repository,
0293                          const QStringList &args,
0294                          KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Silent);
0295 
0296 private Q_SLOTS:
0297     void parseGitBlameOutput(KDevelop::DVcsJob *job);
0298     void parseGitLogOutput(KDevelop::DVcsJob *job);
0299     void parseGitDiffOutput(KDevelop::DVcsJob* job);
0300     void parseGitRepoLocationOutput(KDevelop::DVcsJob* job);
0301     void parseGitStatusOutput(KDevelop::DVcsJob* job);
0302     void parseGitStatusOutput_old(KDevelop::DVcsJob* job);
0303     void parseGitVersionOutput(KDevelop::DVcsJob* job);
0304     void parseGitBranchOutput(KDevelop::DVcsJob* job);
0305     void parseGitCurrentBranch(KDevelop::DVcsJob* job);
0306     void parseGitStashList(KDevelop::VcsJob* job);
0307 
0308     void ctxRebase();
0309     void ctxPushStash();
0310     void ctxPopStash();
0311     void ctxStashManager();
0312 
0313     void fileChanged(const QString& file);
0314     void delayedBranchChanged();
0315 
0316 Q_SIGNALS:
0317     void repositoryBranchChanged(const QUrl& repository);
0318 
0319 private:
0320     bool ensureValidGitIdentity(const QDir& dir);
0321     void addNotVersionedFiles(const QDir& dir, const QList<QUrl>& files);
0322 
0323     //commit dialog "main" helper
0324     QStringList getLsFiles(const QDir &directory, const QStringList &args,
0325         KDevelop::OutputJob::OutputJobVerbosity verbosity);
0326     KDevelop::DVcsJob* errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity);
0327 
0328     void initBranchHash(const QString &repo);
0329 
0330     /**
0331      * Parses a git status --porcelain line
0332      *
0333      * @param statusLine a line as returned by `git status --porcelain`
0334      * @returns the appropriate extended status
0335      */
0336     static ExtendedState parseGitState(const QStringRef& statusLine);
0337 
0338     /**
0339      * Maps an extended state to a basic state
0340      *
0341      * @param state the extended state as provided by git (i.e. describing the combined status in the index & worktree)
0342      */
0343     static KDevelop::VcsStatusInfo::State extendedStateToBasic(const ExtendedState state);
0344 
0345     QList<QStringList> branchesShas;
0346     QList<QUrl> m_urls;
0347 
0348     /** Tells if it's older than 1.7.0 or not */
0349     bool m_oldVersion = false;
0350 
0351     KDirWatch* m_watcher;
0352     QList<QUrl> m_branchesChange;
0353     bool m_usePrefix = true;
0354 
0355     /** A tree model tracking and classifying changes into staged, unstaged and untracked */
0356     RepoStatusModel* m_repoStatusModel;
0357 
0358     /** A factory for constructing the tool view for preparing commits */
0359     CommitToolViewFactory* m_commitToolViewFactory;
0360 };
0361 
0362 Q_DECLARE_METATYPE(GitPlugin::StashItem)
0363 
0364 QVariant runSynchronously(KDevelop::VcsJob* job);
0365 
0366 #endif