├── tests ├── repos │ ├── .gitattributes │ └── testrepo │ │ └── .gitted │ │ ├── HEAD_TRACKER │ │ ├── HEAD │ │ ├── refs │ │ ├── heads │ │ │ ├── br2 │ │ │ ├── dir │ │ │ ├── master │ │ │ ├── subtrees │ │ │ ├── test │ │ │ ├── packed-test │ │ │ └── long-file-name │ │ └── tags │ │ │ ├── test │ │ │ ├── e90810b │ │ │ ├── foo │ │ │ ├── bar │ │ │ └── foo │ │ │ │ └── bar │ │ │ └── point_to_blob │ │ ├── index │ │ ├── objects │ │ ├── 13 │ │ │ └── 85f264afb75a56a5bec74243be9b367ba4ca08 │ │ ├── 14 │ │ │ └── 4344043ba4d4a405da03de3844aa829ae8be0e │ │ ├── 16 │ │ │ └── 8e4ebd1c667499548ae12403b19b22a5c5e925 │ │ ├── 18 │ │ │ ├── 1037049a54a1eb5fab404658a3a250b44335d7 │ │ │ └── 10dff58d8a660512d4832e740f692884338ccd │ │ ├── 27 │ │ │ └── 0b8ea76056d5cad83af921837702d3e3c2924d │ │ ├── 32 │ │ │ └── 59a6bd5b57fb9c1281bb7ed3167b50f224cb54 │ │ ├── 36 │ │ │ └── 97d64be941a53d4ae8f6a271e4e3fa56b022cc │ │ ├── 45 │ │ │ ├── b983be36b73c0788dc9cbcb76cbb80fc7bb057 │ │ │ └── dd856fdd4d89b884c340ba0e047752d9b085d6 │ │ ├── 62 │ │ │ └── eb56dabb4b9929bc15dd9263c2c733b13d2dcc │ │ ├── 66 │ │ │ └── 3adb09143767984f7be83a91effa47e128c735 │ │ ├── 75 │ │ │ └── 057dd4114e74cca1d750d0aee1647c903cb60a │ │ ├── 76 │ │ │ └── 3d71aadf09a7951596c9746c024e7eece7c7af │ │ ├── 81 │ │ │ └── 4889a078c031f61ed08ab5fa863aea9314344d │ │ ├── 84 │ │ │ └── 96071c1b46c854b31185ea97743be6a8774479 │ │ ├── 87 │ │ │ └── 380ae84009e9c503506c2f6143a4fc6c60bf80 │ │ ├── 94 │ │ │ └── 4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 │ │ ├── 09 │ │ │ └── 9fabac3a9ea935598528c27f866e34089c2eff │ │ ├── 1d │ │ │ └── d0968be3ff95fcaecb6fa4245662db9fdc4568 │ │ ├── 1f │ │ │ └── 67fc4386b2d171e0d21be1c447e12660561f9b │ │ ├── 2b │ │ │ └── d0a343aeef7a2cf0d158478966a6e587ff3863 │ │ ├── 4a │ │ │ └── 202b346bb0fb0db7eff3cffeb3c70babbd2045 │ │ ├── 4e │ │ │ ├── 0883eeeeebc1fb1735161cea82f7cb5fab7e63 │ │ │ └── 886e602529caa9ab11d71f86634bd1b6e0de10 │ │ ├── 5b │ │ │ └── 5b025afb0b4c913b4c338a42934a3863bf3644 │ │ ├── 6b │ │ │ ├── 377958d8c6a4906e8573b53672a1a23a4e8ce6 │ │ │ └── 9b767af9992b4abad5e24ffb1ba2d688ca602e │ │ ├── 7b │ │ │ ├── 2417a23b63e1fdde88c80e14b33247c6e5785a │ │ │ └── 4384978d2493e851f9cca7858815fac9b10980 │ │ ├── 9a │ │ │ └── 03079b8a8ee85a0bee58bf9be3da8b62414ed4 │ │ ├── 9f │ │ │ └── d738e8f7967c078dceed8190330fc8648ee56a │ │ ├── a4 │ │ │ └── a7dce85cf63874e984719f4fdd239f5145052f │ │ ├── a6 │ │ │ └── 5fedf39aefe402d3bb6e24df4d4f5fe4547750 │ │ ├── a7 │ │ │ └── 1586c1dfe8a71c6cbf6c129f404c5642ff31bd │ │ ├── a8 │ │ │ └── 233120f6ad708f843d861ce2b7228ec4e3dec6 │ │ ├── ae │ │ │ └── 90f12eea699729ed24555e40b9fd669da12a12 │ │ ├── af │ │ │ └── e4393b2b2a965f06acf2ca9658eaa01e0cd6b6 │ │ ├── b2 │ │ │ └── 5fa35b38051e4ae45d4222e795f9df2e43f1d1 │ │ ├── b6 │ │ │ └── 361fc6a97178d8fc8639fdeed71c775ab52593 │ │ ├── be │ │ │ └── 3563ae3f795b2b4353bcce3a527ad0a4f7f644 │ │ ├── c0 │ │ │ └── 528fd6cc988c0a40ce0be11bc192fc8dc5346e │ │ ├── c4 │ │ │ └── 7800c7266a2be04c571c04d5a6614691ea99bd │ │ ├── ce │ │ │ └── 054d4c5e3c83522aed8bc061987b46b7ede3be │ │ ├── cf │ │ │ └── 80f8de9f1185bf3a05f993f6121880dd0cfbc9 │ │ ├── d4 │ │ │ └── 27e0b2e138501a3d15cc376077a3631e15bd46 │ │ ├── d5 │ │ │ └── 2a8fe84ceedf260afe4f0287bbfca04a117e83 │ │ ├── d6 │ │ │ └── c93164c249c8000205dd4ec5cbca1b516d487f │ │ ├── e6 │ │ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391 │ │ ├── e7 │ │ │ └── b4ad382349ff96dd8199000580b9b1e2042eb0 │ │ ├── f1 │ │ │ └── 425cef211cc08caa31e7b545ffb232acb098c3 │ │ ├── f6 │ │ │ └── 0079018b664e4e79329a7ef9559c8d9e0378d1 │ │ ├── fa │ │ │ └── 49b077972391ad58037050f2a75f74e3671e92 │ │ ├── fd │ │ │ └── 093bff70906175335656e6ce6ae05783708765 │ │ └── pack │ │ │ ├── pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx │ │ │ ├── pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx │ │ │ ├── pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx │ │ │ ├── pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack │ │ │ ├── pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack │ │ │ └── pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack │ │ ├── packed-refs │ │ └── config ├── Init.wl ├── Range.wl ├── Commit.wl ├── Branch.wl ├── Push.wl ├── CheckoutReference.wl ├── Tag.wl └── AddReset.wl ├── logo.png ├── .gitignore ├── GitLink ├── Kernel │ ├── init.wl │ ├── Completions.wl │ ├── NotebookMerge3.wl │ └── Messages.wl ├── PacletInfo.m ├── PacletInfoTemplate.m └── PacletInfoTemplateTeamCity.m ├── .gitLink.sublime-project ├── src ├── interface │ ├── CommitInterface.h │ ├── CherryPickInterface.h │ ├── gitLinkPriv.h │ ├── gitLink.h │ ├── RepoInterface.h │ ├── BlobInterface.cpp │ ├── Message.h │ ├── Message.cpp │ ├── IndexInterface.cpp │ ├── CherryPickInterface.cpp │ ├── gitLink.cpp │ ├── TreeInterface.cpp │ ├── CommitInterface.cpp │ └── RefInterface.cpp ├── classes │ ├── CheckoutManager.h │ ├── GitLinkCommitRange.h │ ├── PathString.h │ ├── GitLinkSuperClass.h │ ├── GitBlob.h │ ├── Signature.h │ ├── RepoStatus.h │ ├── GitTree.h │ ├── RemoteConnector.h │ ├── MergeFactory.h │ ├── GitLinkCommitRange.cpp │ ├── GitBlob.cpp │ ├── GitLinkCommit.h │ ├── GitLinkRepository.h │ ├── MLExpr.h │ ├── CheckoutManager.cpp │ ├── MLHelper.h │ ├── RemoteConnector.cpp │ ├── RepoStatus.cpp │ ├── Signature.cpp │ ├── MLExpr.cpp │ ├── GitTree.cpp │ ├── GitLinkCommit.cpp │ └── MLHelper.cpp ├── build.wl └── re_build.wl ├── COPYING.md ├── scripts ├── library.conf ├── assemblePaclet.wl ├── re_build_GitLink.xml ├── re_build_GitLink.wl └── assemblePacletTeamCity.wl ├── CONTRIBUTING.md ├── HowToBuild.md └── Readme.md /tests/repos/.gitattributes: -------------------------------------------------------------------------------- 1 | * binary 2 | -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/HEAD_TRACKER: -------------------------------------------------------------------------------- 1 | ref: HEAD 2 | -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WolframResearch/GitLink/master/logo.png -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/refs/heads/br2: -------------------------------------------------------------------------------- 1 | a4a7dce85cf63874e984719f4fdd239f5145052f 2 | -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/refs/heads/dir: -------------------------------------------------------------------------------- 1 | 144344043ba4d4a405da03de3844aa829ae8be0e 2 | -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/refs/tags/test: -------------------------------------------------------------------------------- 1 | b25fa35b38051e4ae45d4222e795f9df2e43f1d1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Components 2 | LibraryResources 3 | .project 4 | *.sublime-workspace 5 | .tags* -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/refs/heads/master: -------------------------------------------------------------------------------- 1 | 099fabac3a9ea935598528c27f866e34089c2eff 2 | -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/refs/heads/subtrees: -------------------------------------------------------------------------------- 1 | 763d71aadf09a7951596c9746c024e7eece7c7af 2 | -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/refs/heads/test: -------------------------------------------------------------------------------- 1 | e90810b8df3e80c413d903f631643c716887138d 2 | -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/refs/tags/e90810b: -------------------------------------------------------------------------------- 1 | 7b4384978d2493e851f9cca7858815fac9b10980 2 | -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/refs/tags/foo/bar: -------------------------------------------------------------------------------- 1 | b25fa35b38051e4ae45d4222e795f9df2e43f1d1 2 | -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/refs/heads/packed-test: -------------------------------------------------------------------------------- 1 | 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 2 | -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/refs/tags/foo/foo/bar: -------------------------------------------------------------------------------- 1 | b25fa35b38051e4ae45d4222e795f9df2e43f1d1 2 | -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/refs/tags/point_to_blob: -------------------------------------------------------------------------------- 1 | 1385f264afb75a56a5bec74243be9b367ba4ca08 2 | -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/refs/heads/long-file-name: -------------------------------------------------------------------------------- 1 | 6b377958d8c6a4906e8573b53672a1a23a4e8ce6 2 | -------------------------------------------------------------------------------- /tests/repos/testrepo/.gitted/index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WolframResearch/GitLink/master/tests/repos/testrepo/.gitted/index -------------------------------------------------------------------------------- /GitLink/Kernel/init.wl: -------------------------------------------------------------------------------- 1 | < "GitLink", 5 | Version -> "2100.0", 6 | MathematicaVersion -> "12.1+", 7 | Root -> ".", 8 | Extensions -> 9 | { 10 | {"Kernel", Root -> ".", Context -> "GitLink`"}, 11 | 12 | {"Documentation", Language -> "English"}, 13 | 14 | {"LibraryLink"} 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /GitLink/PacletInfoTemplate.m: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | Paclet[ 4 | Name -> "GitLink", 5 | Version -> "`version`", 6 | MathematicaVersion -> "12.1+", 7 | Root -> ".", 8 | Extensions -> 9 | { 10 | {"Kernel", Root -> ".", Context -> "GitLink`"}, 11 | 12 | {"Documentation", Language -> "English"}, 13 | 14 | {"LibraryLink"} 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /src/interface/RepoInterface.h: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #ifndef RepoInterface_h_ 10 | #define RepoInterface_h_ 1 11 | 12 | #include 13 | #include 14 | 15 | extern std::unordered_map ManagedRepoMap; 16 | 17 | #endif // RepoInterface_h_ 18 | -------------------------------------------------------------------------------- /GitLink/PacletInfoTemplateTeamCity.m: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | Paclet[ 4 | Name -> "GitLink", 5 | Version -> "`Version`", 6 | MathematicaVersion -> "12.1+", 7 | `SystemID` 8 | `Qualifier` 9 | Root -> ".", 10 | Extensions -> 11 | { 12 | {"Kernel", Root -> ".", Context -> "GitLink`"}, 13 | 14 | {"Documentation", Language -> "English"}, 15 | 16 | {"LibraryLink"} 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /scripts/library.conf: -------------------------------------------------------------------------------- 1 | # Libraries used by this paclet 2 | 3 | [Library] libcurl,libgit2,LIBSSH2,OpenSSL 4 | 5 | 6 | # Libraries with additional source checkouts 7 | 8 | [Source] libgit2 9 | 10 | 11 | # SystemID list for this paclet 12 | 13 | [System] Linux-x86-64,MacOSX-x86-64,MacOSX-ARM64,Windows-x86-64 14 | 15 | Source libgit2 0.28.3 Source 16 | Linux-x86-64 libgit2 0.28.3 scientific6-gcc7.3 17 | MacOSX-x86-64 libgit2 0.28.3 libcxx-min10.12 18 | MacOSX-ARM64 libgit2 0.28.3 libcxx-min11.0 19 | Windows-x86-64 libgit2 0.28.3 vc140 20 | 21 | Linux-x86-64 libcurl 7.57.0 scientific6-gcc4.8 22 | -------------------------------------------------------------------------------- /src/classes/CheckoutManager.h: -------------------------------------------------------------------------------- 1 | #ifndef CheckoutManager_h_ 2 | #define CheckoutManager_h_ 1 3 | 4 | #include "GitTree.h" 5 | 6 | class CheckoutManager : public GitLinkSuperClass 7 | { 8 | public: 9 | CheckoutManager(GitLinkRepository& repo); 10 | 11 | bool initCheckout(WolframLibraryData libData, const char* ref, const GitTree& refTree); 12 | bool doCheckout(const GitTree& refTree); 13 | 14 | private: 15 | PathSet refChangedFiles_; 16 | std::string ref_; 17 | GitLinkRepository& repo_; 18 | 19 | void populatePaths_(git_strarray* strarray) const; 20 | void freePaths_(git_strarray* strarray) const; 21 | 22 | }; 23 | 24 | #endif // CheckoutManager_h_ 25 | -------------------------------------------------------------------------------- /src/classes/GitLinkCommitRange.h: -------------------------------------------------------------------------------- 1 | #ifndef GitLinkCommitRange_h_ 2 | #define GitLinkCommitRange_h_ 1 3 | 4 | #include "GitLinkSuperClass.h" 5 | 6 | class GitLinkCommitRange : public GitLinkSuperClass 7 | { 8 | public: 9 | GitLinkCommitRange(const GitLinkRepository& repo); 10 | ~GitLinkCommitRange(); 11 | 12 | void buildRange(MLINK lnk, long argCount); 13 | 14 | /// Resets walker once called 15 | void writeRange(MLINK lnk, bool lengthOnly); 16 | 17 | void addCommitSpecToRange(const GitLinkCommit& commit); 18 | 19 | bool isValid() const { return commitsValid_ && revPushed_; }; 20 | 21 | private: 22 | const GitLinkRepository& repo_; 23 | bool commitsValid_; 24 | bool revPushed_; 25 | }; 26 | 27 | #endif // GitLinkCommitRange_h_ 28 | -------------------------------------------------------------------------------- /src/classes/PathString.h: -------------------------------------------------------------------------------- 1 | #ifndef PathString_h_ 2 | #define PathString_h_ 1 3 | 4 | #include 5 | 6 | class PathString 7 | { 8 | public: 9 | PathString(const char* str) 10 | : PathString(std::string(str)) 11 | { 12 | } 13 | PathString(const std::string& str) 14 | : native_(str) 15 | , git_(str) 16 | { 17 | #if WIN 18 | for (auto c = git_.begin(); c < git_.end(); c++) 19 | if (*c == '\\') 20 | *c = '/'; 21 | for (auto c = native_.begin(); c < native_.end(); c++) 22 | if (*c == '/') 23 | *c = '\\'; 24 | #endif // WIN 25 | }; 26 | const std::string& native() const { return native_; } 27 | const std::string& git() const { return git_; } 28 | private: 29 | std::string native_; 30 | std::string git_; 31 | }; 32 | 33 | 34 | #endif // PathString_h_ 35 | -------------------------------------------------------------------------------- /src/classes/GitLinkSuperClass.h: -------------------------------------------------------------------------------- 1 | #ifndef GitLinkSuperClass_h_ 2 | #define GitLinkSuperClass_h_ 1 3 | 4 | #include "MLHelper.h" 5 | 6 | 7 | class GitLinkSuperClass 8 | { 9 | public: 10 | GitLinkSuperClass() : errCode_(NULL), errCodeParam_(NULL) { }; 11 | virtual ~GitLinkSuperClass() { }; 12 | 13 | virtual void mlHandleError(WolframLibraryData libData, const char* functionName) const 14 | { 15 | MLHandleError(libData, functionName, errCode_, errCodeParam_); 16 | free((void*)errCodeParam_); 17 | errCodeParam_ = NULL; 18 | }; 19 | 20 | void propagateError(const GitLinkSuperClass& obj) { errCode_ = obj.errCode_; errCodeParam_ = obj.errCodeParam_; }; 21 | 22 | protected: 23 | const char* errCode_; 24 | mutable const char* errCodeParam_; 25 | }; 26 | 27 | #endif // GitLinkSuperClass_h_ 28 | -------------------------------------------------------------------------------- /GitLink/Kernel/Completions.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | BeginPackage["GitLink`Completions`"]; 4 | Begin["`Private`"]; 5 | 6 | 7 | (* 8 | 9 | Numeric codes are for certain special types of completions. Zero means 'don't complete': 10 | 11 | Normal argument 0 12 | AbsoluteFilename 2 13 | RelativeFilename 3 14 | Color 4 15 | PackageName 7 16 | DirectoryName 8 17 | InterpreterType 9 18 | 19 | *) 20 | 21 | 22 | 23 | specialArgCompletionsList = 24 | { 25 | "GitClone" -> {0, 8}, 26 | "GitInit" -> {8}, 27 | "GitOpen" -> {8}, 28 | "GitRepoQ" -> {8} 29 | }; 30 | 31 | 32 | If[$Notebooks && Internal`CachedSystemInformation["FrontEnd", "VersionNumber"] > 10.0, 33 | Scan[ 34 | FE`Evaluate[FEPrivate`AddSpecialArgCompletion[#]]&, 35 | specialArgCompletionsList 36 | ] 37 | ] 38 | 39 | 40 | End[]; 41 | EndPackage[]; 42 | -------------------------------------------------------------------------------- /src/classes/GitBlob.h: -------------------------------------------------------------------------------- 1 | #ifndef GitBlob_h_ 2 | #define GitBlob_h_ 1 3 | 4 | class GitBlob : public GitLinkSuperClass 5 | { 6 | public: 7 | GitBlob(const MLExpr& expr); 8 | GitBlob(MLINK lnk) 9 | : GitBlob(MLExpr(lnk)) 10 | { }; 11 | GitBlob(const GitLinkRepository& repo, MLINK lnk, const char* format); 12 | 13 | virtual ~GitBlob(); 14 | 15 | void write(MLINK lnk) const; 16 | void writeContents(MLINK lnk, const char* format) const; 17 | 18 | bool isValid() const { return blob_ != NULL; }; 19 | const GitLinkRepository& repo() const { return repo_; }; 20 | const git_oid* oid() const { return &oid_; }; 21 | operator const git_blob*() const {return blob_; }; 22 | operator const git_object*() const {return (const git_object*) blob_; }; 23 | 24 | private: 25 | const GitLinkRepository repo_; 26 | git_blob* blob_ = NULL; 27 | git_oid oid_; 28 | 29 | }; 30 | 31 | #endif // GitBlob_h_ 32 | -------------------------------------------------------------------------------- /src/classes/Signature.h: -------------------------------------------------------------------------------- 1 | #ifndef Signature_h_ 2 | #define Signature_h_ 1 3 | 4 | class GitLinkRepository; 5 | class MLExpr; 6 | 7 | #include "MLHelper.h" 8 | #include "MLExpr.h" 9 | 10 | class Signature 11 | { 12 | public: 13 | Signature(); 14 | Signature(const Signature& signature); 15 | Signature(const GitLinkRepository& repo); 16 | Signature(const MLExpr& expr); 17 | Signature(const GitLinkRepository& repo, const MLExpr& expr); 18 | Signature(const GitLinkRepository& repo, MLINK lnk) 19 | : Signature(repo, MLExpr(lnk)) 20 | { }; 21 | Signature(const git_signature* signature); 22 | ~Signature() { if (sig_) git_signature_free((git_signature*)sig_); }; 23 | 24 | Signature& operator=(const Signature& signature); 25 | operator const git_signature*() const { return sig_; }; 26 | 27 | void writeAssociation(MLINK lnk) const; 28 | void writeAssociation(MLHelper& helper) const; 29 | 30 | private: 31 | git_signature* sig_ = NULL; 32 | }; 33 | 34 | #endif // Signature_h_ 35 | -------------------------------------------------------------------------------- /src/classes/RepoStatus.h: -------------------------------------------------------------------------------- 1 | #ifndef RepoStatus_h 2 | #define RepoStatus_h 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef std::map FileStatusMap; 10 | class FileNameSet : public std::set 11 | { 12 | public: 13 | std::deque getPathSpecMatches(const PathString& spec); 14 | }; 15 | 16 | class RepoStatus : public GitLinkSuperClass 17 | { 18 | public: 19 | RepoStatus(GitLinkRepository& repo, bool doRenames, bool includeIgnored = false, bool recurseUntrackedDirs = false); 20 | 21 | bool isValid() { return isValid_; }; 22 | void updateStatus(); 23 | void writeStatus(MLINK lnk); 24 | bool fileChanged(const std::string& filePath); 25 | void convertFileNamesToLower(WolframLibraryData libData); 26 | FileNameSet allFileNames(); 27 | 28 | private: 29 | bool isValid_; 30 | bool doRenames_; 31 | bool includeIgnored_; 32 | bool recurseUntrackedDirs_; 33 | GitLinkRepository& repo_; 34 | FileStatusMap indexStatus_; 35 | FileStatusMap workingTreeStatus_; 36 | 37 | 38 | void writeFiles_(MLHelper& helper, const char* keyName, git_status_t status); 39 | 40 | static int statusCallback_(const char* path, unsigned int status_flags, void* payload); 41 | }; 42 | 43 | #endif // RepoStatus_h 44 | -------------------------------------------------------------------------------- /tests/Init.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | BeginTestSection["InitTests"] 4 | 5 | 6 | Needs["GitLink`"]; 7 | $RepoRootDirectory = FileNameJoin[{$TemporaryDirectory, "InitRepos"}]; 8 | Quiet[DeleteDirectory[$RepoRootDirectory, DeleteContents->True]]; 9 | Quiet[CreateDirectory[$RepoRootDirectory]]; 10 | SetAttributes[gitInitBlock,HoldFirst]; 11 | gitInitBlock[code_,opts___]:= 12 | Block[{result, $Repo, $RepoDirectory}, 13 | $RepoDirectory = FileNameJoin[{AbsoluteFileName[$RepoRootDirectory],"InitTestRepo"}]; 14 | $Repo=GitInit[$RepoDirectory,opts]; 15 | result=code; 16 | GitClose[$Repo]; 17 | DeleteDirectory[$RepoDirectory,DeleteContents->True]; 18 | result 19 | ] 20 | 21 | 22 | VerificationTest[ 23 | gitInitBlock[GitProperties[$Repo, List["ShallowQ", "BareQ", "DetachedHeadQ", "Conflicts", "Remotes", "LocalBranches", "RemoteBranches"]]] 24 | , 25 | {False, False, False, {}, Association[], {}, {}} 26 | ] 27 | 28 | 29 | VerificationTest[ 30 | gitInitBlock[GitProperties[$Repo, List["ShallowQ", "BareQ", "DetachedHeadQ", "Conflicts", "Remotes", "LocalBranches", "RemoteBranches", "WorkingDirectory"]], Rule["Bare", True]] 31 | , 32 | {False, True, False, {}, Association[], {}, {}, None} 33 | ] 34 | 35 | 36 | VerificationTest[ 37 | gitInitBlock[SameQ[GitOpen[$RepoDirectory], $Repo]] 38 | , 39 | True 40 | ] 41 | 42 | 43 | EndTestSection[] 44 | -------------------------------------------------------------------------------- /tests/Range.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | Needs["GitLink`"]; 4 | $TestRepos = FileNameJoin[{NotebookDirectory[], "repos"}]; 5 | $RepoDirectory = FileNameJoin[{$TemporaryDirectory, "Range"}]; 6 | Quiet[DeleteDirectory[$RepoDirectory, DeleteContents->True]]; 7 | $Repo = GitClone[FileNameJoin[{$TestRepos, "testrepo", ".gitted"}], $RepoDirectory]; 8 | SetDirectory[$RepoDirectory]; 9 | changefile[filename_] := Module[{strm = OpenAppend[filename]}, 10 | WriteString[strm, "\nnew line\n"]; Close[strm]]; 11 | fullpath[path_] := FileNameJoin[{$RepoDirectory, path}] 12 | 13 | 14 | VerificationTest[ 15 | { 16 | GitRepoQ[$RepoDirectory] 17 | , AssociationQ[GitProperties[$Repo]] 18 | , Flatten[Values[GitStatus[$Repo]]] === {} 19 | }, 20 | {True, True, True} 21 | ] 22 | 23 | 24 | (* ::Subsubsection:: *) 25 | (*Test GitAheadBehind*) 26 | 27 | 28 | VerificationTest[ 29 | { 30 | GitAheadBehind[$Repo, "master", "origin/master"] === {0,0} 31 | , GitAheadBehind[$Repo, "origin/master", "origin/dir"] === {3,3} 32 | , GitAheadBehind[ToGitObject[$Repo, "master"], ToGitObject[$Repo, "origin/dir"]] === {3,3} 33 | }, 34 | {True, True, True} 35 | ] 36 | 37 | 38 | VerificationTest[ 39 | GitAheadBehind[$Repo, "master", "nonexistent"], 40 | $Failed, 41 | GitAheadBehind::badcommitish 42 | ] 43 | 44 | 45 | ResetDirectory[]; 46 | GitClose[$Repo]; 47 | DeleteDirectory[$RepoDirectory, DeleteContents->True]; 48 | -------------------------------------------------------------------------------- /src/interface/BlobInterface.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include "mathlink.h" 10 | #include "WolframLibrary.h" 11 | #include "git2.h" 12 | #include "RepoInterface.h" 13 | #include "GitLinkRepository.h" 14 | #include "GitBlob.h" 15 | #include "Message.h" 16 | #include 17 | 18 | 19 | EXTERN_C DLLEXPORT int GitReadBlob(WolframLibraryData libData, MLINK lnk) 20 | { 21 | long argCount; 22 | MLCheckFunction(lnk, "List", &argCount); 23 | 24 | MLString format(lnk); 25 | GitBlob blob(lnk); 26 | MLExpr pathNameHint(lnk); // to be implemented 27 | 28 | if (blob.isValid()) 29 | blob.writeContents(lnk, format); 30 | else 31 | { 32 | blob.mlHandleError(libData, "GitReadBlob"); 33 | MLPutSymbol(lnk, "$Failed"); 34 | } 35 | 36 | return LIBRARY_NO_ERROR; 37 | } 38 | 39 | EXTERN_C DLLEXPORT int GitWriteBlob(WolframLibraryData libData, MLINK lnk) 40 | { 41 | bool success = false; 42 | long argCount; 43 | MLCheckFunction(lnk, "List", &argCount); 44 | GitLinkRepository repo(lnk); 45 | MLString format(lnk); 46 | MLExpr pathNameHint(lnk); // to be implemented 47 | GitBlob blob(repo, lnk, format); 48 | 49 | if (blob.isValid()) 50 | blob.write(lnk); 51 | else 52 | { 53 | blob.mlHandleError(libData, "GitWriteBlob"); 54 | MLPutSymbol(lnk, "$Failed"); 55 | } 56 | return LIBRARY_NO_ERROR; 57 | } 58 | -------------------------------------------------------------------------------- /src/classes/GitTree.h: -------------------------------------------------------------------------------- 1 | #ifndef GitTree_h_ 2 | #define GitTree_h_ 1 3 | 4 | #include 5 | 6 | typedef std::unordered_set PathSet; 7 | 8 | class GitTree : public GitLinkSuperClass 9 | { 10 | public: 11 | GitTree(const GitLinkRepository& repo, git_index* index); 12 | GitTree(const GitLinkRepository& repo, const char* reference); 13 | GitTree(const MLExpr& expr); 14 | GitTree(MLINK lnk) 15 | : GitTree(MLExpr(lnk)) 16 | { }; 17 | 18 | virtual ~GitTree(); 19 | 20 | void write(MLINK lnk) const; 21 | void writeContents(MLINK lnk, int depth) const; 22 | 23 | PathSet getDiffPaths(const GitTree& theirTree) const; 24 | 25 | int resetIndexToTreeEntry(git_index* index, const char* filename) const; 26 | 27 | bool isValid() const { return tree_ != NULL; }; 28 | const git_oid* oid() const { return &oid_; }; 29 | operator const git_tree*() const {return tree_; }; 30 | operator const git_object*() const {return (const git_object*) tree_; }; 31 | 32 | private: 33 | const GitLinkRepository repo_; 34 | git_tree* tree_ = NULL; 35 | git_oid oid_; 36 | mutable MLHelper* helper_ = NULL; // used for callbacks 37 | mutable int depth_ = 1; // used for callbacks 38 | mutable std::string root_; 39 | 40 | static int writeTreeEntry_(const char* root, const git_tree_entry* entry, void* payload); 41 | static int addTreeEntryToMap_(const char* root, const git_tree_entry* entry, void* payload); 42 | }; 43 | 44 | #endif // GitTree_h_ 45 | -------------------------------------------------------------------------------- /tests/Commit.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | BeginTestSection["CommitTests"] 4 | 5 | 6 | Needs["GitLink`"]; 7 | $RepoRootDirectory = FileNameJoin[{$TemporaryDirectory, "InitRepos"}]; 8 | Quiet[DeleteDirectory[$RepoRootDirectory, DeleteContents->True]]; 9 | Quiet[CreateDirectory[$RepoRootDirectory]]; 10 | SetAttributes[gitInitBlock,HoldFirst]; 11 | gitInitBlock[code_,opts___]:= 12 | Block[{result, $Repo, $RepoDirectory}, 13 | $RepoDirectory = FileNameJoin[{AbsoluteFileName[$RepoRootDirectory],"InitTestRepo"}]; 14 | $Repo=GitInit[$RepoDirectory,opts]; 15 | result=code; 16 | GitClose[$Repo]; 17 | DeleteDirectory[$RepoDirectory,DeleteContents->True]; 18 | result 19 | ] 20 | 21 | 22 | VerificationTest[ 23 | gitInitBlock[ 24 | Module[{c}, 25 | CreateFile[FileNameJoin[{$RepoDirectory, "foo"}]]; 26 | GitAdd[$Repo, FileNameJoin[{$RepoDirectory, "foo"}]]; 27 | c=GitCommit[$Repo, "message"]; 28 | Echo@{GitCommitQ[c], c["Message"], c["Parents"]} 29 | ]] 30 | , 31 | {True, "message", {}} 32 | ] 33 | 34 | 35 | VerificationTest[ 36 | gitInitBlock[ 37 | Module[{c, t = DateObject[Now, "Second"] - Quantity[10^6,"Seconds"]}, 38 | CreateFile[FileNameJoin[{$RepoDirectory, "foo"}]]; 39 | GitAdd[$Repo, FileNameJoin[{$RepoDirectory, "foo"}]]; 40 | c=GitCommit[$Repo, "message", "AuthorSignature"-><|"Name"->"j", "Email"->"k", "TimeStamp"->t|>]; 41 | {GitCommitQ[c], c["Message"], c["Parents"], c["Author"]["Name"], c["Author"]["Email"], Abs[c["Author"]["TimeStamp"] - t] <= Quantity[1, "Seconds"]} 42 | ]] 43 | , 44 | {True, "message", {}, "j", "k", True} 45 | ] 46 | 47 | 48 | EndTestSection[] 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Wolfram® 2 | 3 | Thank you for taking the time to contribute to the [Wolfram Research](https://github.com/wolframresearch) repos on GitHub. 4 | 5 | ## Licensing of Contributions 6 | 7 | By contributing to Wolfram, you agree and affirm that: 8 | 9 | > Wolfram may release your contribution under the terms of the [MIT license](https://opensource.org/licenses/MIT); and 10 | 11 | > You have read and agreed to the [Developer Certificate of Origin](http://developercertificate.org/), version 1.1 or later. 12 | 13 | Please see [COPYING.md](COPYING.md) for licensing conditions pertaining 14 | to individual repositories. 15 | 16 | 17 | ## Bug reports 18 | 19 | ### Security Bugs 20 | 21 | Please **DO NOT** file a public issue regarding a security issue. 22 | Rather, send your report privately to security@wolfram.com. Security 23 | reports are appreciated and we will credit you for it. We do not offer 24 | a security bounty, but the forecast in your neighborhood will be cloudy 25 | with a chance of Wolfram schwag! 26 | 27 | ### General Bugs 28 | 29 | Please use the repository issues page to submit general bug issues. 30 | 31 | Please do not duplicate issues. 32 | 33 | Please do send a complete and well-written report to us. Note: **the 34 | thoroughness of your report will positively correlate to our willingness 35 | and ability to address it**. 36 | 37 | When reporting issues, always include: 38 | 39 | * Your version of *Mathematica*® or the Wolfram Language. 40 | * Your operating system. 41 | * If you built GitLink yourself, then the version of libgit2 you are using. 42 | -------------------------------------------------------------------------------- /src/interface/Message.h: -------------------------------------------------------------------------------- 1 | #ifndef Message_h_ 2 | #define Message_h_ 1 3 | 4 | namespace Message 5 | { 6 | extern const char* BadRepo; // probably should never happen if we do our error-checking right 7 | extern const char* BadRemote; 8 | extern const char* BadRemoteName; 9 | extern const char* BareRepo; 10 | extern const char* FetchFailed; 11 | extern const char* RemoteConnectionFailed; 12 | extern const char* DownloadFailed; 13 | extern const char* UpdateTipsFailed; 14 | extern const char* BadCommitish; 15 | extern const char* NoParent; 16 | extern const char* NoIndex; 17 | extern const char* NoWorkingTree; 18 | extern const char* NoMessage; 19 | extern const char* GitCommitError; 20 | extern const char* CantWriteTree; 21 | extern const char* CantWriteIndex; 22 | extern const char* NoDefaultUserName; 23 | extern const char* UploadFailed; 24 | extern const char* RefNotPushed; 25 | extern const char* InvalidSpec; 26 | extern const char* RefExists; 27 | extern const char* BranchNotCreated; 28 | extern const char* NoLocalBranch; 29 | extern const char* NoRemoteBranch; 30 | extern const char* SetUpstreamFailed; 31 | extern const char* UpstreamFailed; 32 | extern const char* InvalidSource; 33 | extern const char* InvalidDest; 34 | extern const char* NoTree; 35 | extern const char* NoBlob; 36 | extern const char* BadSHA; 37 | extern const char* GitOperationFailed; 38 | extern const char* CheckoutFailed; 39 | extern const char* BadTreeEntry; 40 | extern const char* InconsistentRepos; 41 | extern const char* RepoExists; 42 | extern const char* CheckoutConflict; 43 | extern const char* BadFormat; 44 | extern const char* InvalidArguments; 45 | } 46 | #endif // Message_h_ 47 | 48 | -------------------------------------------------------------------------------- /tests/Branch.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | Needs["GitLink`"]; 4 | $TestRepos = FileNameJoin[{NotebookDirectory[], "repos"}]; 5 | $RepoDirectory = FileNameJoin[{$TemporaryDirectory, "Branch"}]; 6 | Quiet[DeleteDirectory[$RepoDirectory, DeleteContents->True]]; 7 | $Repo = GitClone[FileNameJoin[{$TestRepos, "testrepo", ".gitted"}], $RepoDirectory]; 8 | SetDirectory[$RepoDirectory]; 9 | changefile[filename_] := Module[{strm = OpenAppend[filename]}, 10 | WriteString[strm, "\nnew line\n"]; Close[strm]]; 11 | fullpath[path_] := FileNameJoin[{$RepoDirectory, path}] 12 | 13 | 14 | VerificationTest[ 15 | { 16 | GitRepoQ[$RepoDirectory] 17 | , AssociationQ[GitProperties[$Repo]] 18 | , Flatten[Values[GitStatus[$Repo]]] === {} 19 | }, 20 | {True, True, True} 21 | ] 22 | 23 | 24 | (* ::Subsubsection:: *) 25 | (*Create and delete a branch*) 26 | 27 | 28 | VerificationTest[ 29 | { 30 | GitCreateBranch[$Repo, "branch1"] 31 | , GitBranchQ[$Repo, "branch1"] 32 | , GitDeleteBranch[$Repo, "branch1"] === Null 33 | , !GitBranchQ[$Repo, "branch1"] 34 | }, 35 | {True, True, True, True} 36 | ] 37 | 38 | 39 | (* ::Subsubsection:: *) 40 | (*Test setting upstream branch*) 41 | 42 | 43 | VerificationTest[ 44 | { 45 | GitSetUpstreamBranch[$Repo, "master", "origin/master"] 46 | , !GitSetUpstreamBranch[$Repo, "master", "origin/dir"] 47 | , GitUpstreamBranch[$Repo, "master"] === "origin/master" 48 | }, 49 | {True, True, True} 50 | ] 51 | 52 | 53 | VerificationTest[ 54 | { 55 | GitSetUpstreamBranch[$Repo, "master", "origin/dir", "Force"->True] 56 | , GitUpstreamBranch[$Repo, "master"] === "origin/dir" 57 | , GitSetUpstreamBranch[$Repo, "master", "origin/master", "Force"->True] 58 | }, 59 | {True, True, True} 60 | ] 61 | 62 | 63 | ResetDirectory[]; 64 | GitClose[$Repo]; 65 | DeleteDirectory[$RepoDirectory, DeleteContents->True]; 66 | -------------------------------------------------------------------------------- /src/classes/RemoteConnector.h: -------------------------------------------------------------------------------- 1 | #ifndef RemoteConnector_h_ 2 | #define RemoteConnector_h_ 1 3 | 4 | #include 5 | #include 6 | 7 | #include "git2.h" 8 | #include "WolframLibrary.h" 9 | #include "MLExpr.h" 10 | 11 | /// Manages all of the connectors in libgit2, including all of the callbacks. 12 | class RemoteConnector 13 | { 14 | public: 15 | RemoteConnector(WolframLibraryData libData, git_repository* repo, const char* remoteName, const char* theKeyFile); 16 | ~RemoteConnector(); 17 | 18 | bool fetch() { return connect_(GIT_DIRECTION_FETCH); }; 19 | bool push() { return connect_(GIT_DIRECTION_PUSH); }; 20 | bool clone(git_repository** repo, const char* uri, const char* localPath, git_clone_options* options, const MLExpr& progressFunction); 21 | 22 | git_remote* remote() const { return remote_; }; 23 | const git_remote_callbacks& callbacks() const { return callbacks_; }; 24 | bool isValidRemote() const { return isValidRemote_; }; 25 | 26 | private: 27 | static int AcquireCredsCallback(git_cred** cred,const char* url,const char *username,unsigned int allowed_types, void* payload); 28 | int acquireCredsCallback_(git_cred** cred, const char* url, const char* username, unsigned int allowed_types); 29 | 30 | static int TransferProgressCallback(const git_transfer_progress* stats, void* payload); 31 | static int SidebandProgressCallback(const char* str, int len, void* payload); 32 | bool connect_(git_direction direction); 33 | 34 | std::string remoteName_; 35 | std::string keyFile_; 36 | WolframLibraryData libData_ = NULL; 37 | int credentialAttempts_ = 0; 38 | bool triedSshAgent_ = false; 39 | git_remote* remote_ = NULL; 40 | git_remote_callbacks callbacks_; 41 | MLExpr progressFunction_; 42 | std::chrono::steady_clock::time_point lastProgressCheckpoint_ = std::chrono::steady_clock::now(); 43 | bool isValidRemote_; 44 | }; 45 | 46 | #endif // RemoteConnector_h_ 47 | -------------------------------------------------------------------------------- /src/interface/Message.cpp: -------------------------------------------------------------------------------- 1 | #include "Message.h" 2 | 3 | namespace Message 4 | { 5 | const char* BadRepo = "badrepo"; 6 | const char* BadRemote = "badremote"; 7 | const char* BadRemoteName = "badremotename"; 8 | const char* BareRepo = "barerepo"; 9 | const char* FetchFailed = "fetchfailed"; 10 | const char* RemoteConnectionFailed = "noconnect"; 11 | const char* DownloadFailed = "downloadfailed"; 12 | const char* UpdateTipsFailed = "updatetipsfailed"; 13 | const char* BadCommitish = "badcommitish"; 14 | const char* NoParent = "noparent"; 15 | const char* NoIndex = "noindex"; 16 | const char* NoWorkingTree = "noworkingtree"; 17 | const char* NoMessage = "nomessage"; 18 | const char* GitCommitError = "gitcommiterror"; 19 | const char* CantWriteTree = "cantwritetree"; 20 | const char* CantWriteIndex = "cantwriteindex"; 21 | const char* NoDefaultUserName = "nodefaultusername"; 22 | const char* UploadFailed = "uploadfailed"; 23 | const char* RefNotPushed = "refnotpushed"; 24 | const char* InvalidSpec = "invalidspec"; 25 | const char* RefExists = "refexists"; 26 | const char* BranchNotCreated = "branchnotcreated"; 27 | const char* NoLocalBranch = "nolocalbranch"; 28 | const char* NoRemoteBranch = "noremotebranch"; 29 | const char* SetUpstreamFailed = "setupstreamfailed"; 30 | const char* UpstreamFailed = "upstreamfailed"; 31 | const char* InvalidSource = "invalidsource"; 32 | const char* InvalidDest = "invaliddest"; 33 | const char* NoTree = "notree"; 34 | const char* NoBlob = "noblob"; 35 | const char* BadSHA = "badsha"; 36 | const char* GitOperationFailed = "gitoperationfailed"; 37 | const char* CheckoutFailed = "checkoutfailed"; 38 | const char* BadTreeEntry = "badtreeentry"; 39 | const char* InconsistentRepos = "inconsistentrepos"; 40 | const char* RepoExists = "repoexists"; 41 | const char* CheckoutConflict = "checkoutconflict"; 42 | const char* BadFormat = "badformat"; 43 | const char* InvalidArguments = "invalidarguments"; 44 | } 45 | -------------------------------------------------------------------------------- /HowToBuild.md: -------------------------------------------------------------------------------- 1 | 2 | ## How to build GitLink 3 | 4 | GitLink has two buildable components: the documentation, and a shared library component which is loaded via LibraryLink. This document covers only how to build and run the shared library component. 5 | 6 | #### Prerequisites 7 | * Version 11.1 or greater of Mathematica or Wolfram Desktop (earlier versions might work but have not been tested). 8 | * A C++ compiler supporting the C++14 standard and the C Compiler Driver feature of the Wolfram Language. 9 | * Visual Studio 2015 or later is preferred under Windows. 10 | * Xcode bundled with a 10.9 or later SDK is preferred on Mac. 11 | * gcc 5.0 or above on Linux should work. 12 | * A built version of libgit2 (0.26.x or 0.27.x). While it's possible to use a dynamic library build, we prefer to use the static library build, which requires a small change to the default libgit2 cmake files. 13 | 14 | #### Building GitLink 15 | * Open the file src/build.wl in the Wolfram system. 16 | * Evaluate the first three cells. Then determine the values of the `libDirs` and `includeDir`. Ensure that the built libgit2 and its header files can be found in these locations. 17 | * Run the entire package. 18 | * If there are build errors and you need to see the unedited output, uncomment the line(s) setting the `"ShellOutputFunction"` option. 19 | 20 | #### Running GitLink 21 | * Build GitLink. 22 | * Quit and restart the kernel. 23 | * Run ``PacletManager`PacletDirectoryAdd["/GitLink"]``. 24 | * Load the package using `Get` or `Needs`. E.g., ``Get["GitLink`"]``. 25 | * When loading the package this way, the system may choose to try to load the unbuilt source documentation pages rather than any final built documentation pages. If you want to use the documentation while in this state, it may be easiest to simply run another copy of the Wolfram system where you can access the documentation. 26 | * You can test that you're running the library you think you are, and the features which were used to compile libgit2, by evaluating `$GitLibraryInformation` and examining the results. 27 | -------------------------------------------------------------------------------- /src/classes/MergeFactory.h: -------------------------------------------------------------------------------- 1 | #ifndef MergeFactory_h_ 2 | #define MergeFactory_h_ 1 3 | 4 | #include "GitLinkRepository.h" 5 | #include "GitLinkCommit.h" 6 | #include "MLExpr.h" 7 | #include 8 | 9 | enum MergeFactoryMergeType 10 | { 11 | eMergeTypeMerge, 12 | eMergeTypeRebase, 13 | eMergeTypeCherryPick 14 | }; 15 | 16 | class MergeFactory : public GitLinkSuperClass 17 | { 18 | public: 19 | MergeFactory(MLExpr& argv) 20 | : repo_(argv.part(1)) 21 | , argv_(argv) 22 | , isValid_(false) 23 | , dest_(NULL) 24 | , resultSuccess_(false) 25 | { }; 26 | 27 | ~MergeFactory() 28 | { delete dest_; }; 29 | 30 | /// this validates argv, and it can fail. 31 | bool initialize(MergeFactoryMergeType mergeType); 32 | 33 | virtual void mlHandleError(WolframLibraryData libData, const char* functionName) const; 34 | 35 | void write(MLINK lnk); 36 | 37 | void doMerge(WolframLibraryData libData); 38 | 39 | MLExpr handleConflicts(WolframLibraryData libData, git_index* index); 40 | 41 | 42 | private: 43 | const MLExpr argv_; 44 | const GitLinkRepository repo_; 45 | bool isValid_; 46 | GitLinkCommitDeque mergeSources_; 47 | GitLinkCommitDeque strippedMergeSources_; 48 | GitLinkCommit* dest_; 49 | const char* commitLog_; 50 | std::string commitMessage_; 51 | MLExpr conflictFunctions_; 52 | MLExpr finalFunctions_; 53 | MLExpr progressFunction_; 54 | bool allowCommit_; 55 | bool allowFastForward_; 56 | bool allowIndexChanges_; 57 | int mergeFlags_; 58 | 59 | bool resultSuccess_; 60 | git_oid resultOid_; 61 | const char* resultFailureType_; 62 | MLExpr resultFailureData_; 63 | 64 | // Builds strippedMergeSources_, which strips sources that can be fast-forwarded 65 | // to other sources 66 | bool buildStrippedMergeSources_(); 67 | 68 | git_tree* ancestorCopyTree_(); 69 | 70 | bool resolveConflictsWithUserFunction_(WolframLibraryData libData, git_index* index, 71 | const git_index_entry* ancestor, const git_index_entry* ours, const git_index_entry* theirs); 72 | void putConflictData_(MLHelper& helper, const char* input, const git_index_entry* entry); 73 | }; 74 | 75 | 76 | #endif // MergeFactory_h_ 77 | -------------------------------------------------------------------------------- /tests/Push.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | BeginTestSection["Push"] 4 | 5 | 6 | Needs["GitLink`"]; 7 | $TestRepos = FileNameJoin[{NotebookDirectory[], "repos"}]; 8 | $RepoDirectory = FileNameJoin[{$TemporaryDirectory, "PushTestRepo"}]; 9 | $LocalDestRepoDir = FileNameJoin[{$TemporaryDirectory, "DestRepo"}]; 10 | $RemoteDestRepoURI = "ssh://git@stash.wolfram.com:7999/~jfultz/pushunittest.git"; 11 | Quiet[DeleteDirectory[$RepoDirectory, DeleteContents->True]]; 12 | $Repo = GitClone[FileNameJoin[{$TestRepos, "testrepo", ".gitted"}], $RepoDirectory]; 13 | Quiet[DeleteDirectory[$LocalDestRepoDir, DeleteContents->True]]; 14 | 15 | 16 | VerificationTest[ 17 | GitRepoQ[$RepoDirectory] && AssociationQ[GitProperties[$Repo]] 18 | ] 19 | 20 | 21 | (* ::Text:: *) 22 | (*Push to an empty local repo*) 23 | 24 | 25 | VerificationTest[ 26 | repo2 = GitInit[$LocalDestRepoDir, "Bare"->True]; 27 | GitAddRemote[$Repo, "localdest", $LocalDestRepoDir]; 28 | 29 | GitPush[$Repo, "localdest", "refs/heads/master:refs/heads/master"] 30 | && GitSHA[$Repo, "master"] === GitSHA[repo2, "master"] 31 | ] 32 | GitClose[repo2] 33 | 34 | 35 | (* ::Text:: *) 36 | (*Push to a remote repo (force push to a known state)*) 37 | 38 | 39 | VerificationTest[ 40 | GitAddRemote[$Repo, "remotedest", $RemoteDestRepoURI]; 41 | 42 | GitPush[$Repo, "remotedest","+refs/heads/master:refs/heads/master"] 43 | && GitSHA[$Repo, "master"] === GitSHA[$Repo, "remotedest/master"] 44 | ] 45 | sha = GitSHA[$Repo, "Master"]; 46 | 47 | 48 | (* ::Text:: *) 49 | (*Push a normal commit*) 50 | 51 | 52 | VerificationTest[ 53 | commit = GitCommit[$Repo, "push unit test", 54 | GitProperties[ToGitObject[$Repo,"master"]]["Tree"], {"master"}]; 55 | GitPush[$Repo, "remotedest","refs/heads/master:refs/heads/master"] 56 | && GitSHA[commit] === GitSHA[$Repo, "remotedest/master"] 57 | ] 58 | 59 | 60 | (* ::Text:: *) 61 | (*Force push back to previous state*) 62 | 63 | 64 | VerificationTest[ 65 | GitCheckoutReference[$Repo, sha]; 66 | GitPush[$Repo, "remotedest", "+HEAD:refs/heads/master"] 67 | && sha === GitSHA[$Repo, "remotedest/master"] 68 | ] 69 | 70 | 71 | GitClose[$Repo]; 72 | DeleteDirectory[$RepoDirectory, DeleteContents->True]; 73 | 74 | 75 | EndTestSection[] 76 | -------------------------------------------------------------------------------- /src/classes/GitLinkCommitRange.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include 10 | 11 | #include "mathlink.h" 12 | #include "WolframLibrary.h" 13 | #include "git2.h" 14 | #include "GitLinkRepository.h" 15 | #include "GitLinkCommit.h" 16 | #include "GitLinkCommitRange.h" 17 | 18 | #include "Message.h" 19 | #include "MLHelper.h" 20 | #include "RepoInterface.h" 21 | 22 | 23 | GitLinkCommitRange::GitLinkCommitRange(const GitLinkRepository& repo) : 24 | repo_(repo), commitsValid_(true), revPushed_(false) 25 | { 26 | if (repo.revWalker()) 27 | git_revwalk_sorting(repo.revWalker(), GIT_SORT_TOPOLOGICAL); 28 | else 29 | { 30 | commitsValid_ = false; 31 | } 32 | } 33 | 34 | GitLinkCommitRange::~GitLinkCommitRange() 35 | { 36 | } 37 | 38 | void GitLinkCommitRange::buildRange(MLINK link, long argCount) 39 | { 40 | while (argCount-- > 0) 41 | { 42 | GitLinkCommit commit(repo_, link); 43 | addCommitSpecToRange(commit); 44 | } 45 | } 46 | 47 | void GitLinkCommitRange::writeRange(MLINK link, bool lengthOnly) 48 | { 49 | MLHelper helper(link); 50 | 51 | if (isValid()) 52 | { 53 | int i = 0; 54 | git_oid oid; 55 | char sha[GIT_OID_HEXSZ + 1]; 56 | 57 | if (lengthOnly) 58 | { 59 | while (git_revwalk_next(&oid, repo_.revWalker()) == 0) 60 | i++; 61 | helper.putInt(i); 62 | } 63 | else 64 | { 65 | helper.beginList(); 66 | 67 | while (git_revwalk_next(&oid, repo_.revWalker()) == 0) 68 | helper.putGitObject(oid, repo_); 69 | helper.endList(); 70 | } 71 | } 72 | else 73 | helper.putSymbol("$Failed"); 74 | 75 | // A successful walk automatically resets, so we'll just be consistent and reset 76 | // on unsuccessful walks, too. 77 | git_revwalk_reset(repo_.revWalker()); 78 | revPushed_ = false; 79 | } 80 | 81 | void GitLinkCommitRange::addCommitSpecToRange(const GitLinkCommit& commit) 82 | { 83 | if (!commit.isValid()) 84 | commitsValid_ = false; 85 | if (!commitsValid_) 86 | return; 87 | 88 | if (commit.isHidden()) 89 | git_revwalk_hide(repo_.revWalker(), commit.oid()); 90 | else 91 | { 92 | revPushed_ = true; 93 | git_revwalk_push(repo_.revWalker(), commit.oid()); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/interface/IndexInterface.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include "mathlink.h" 10 | #include "WolframLibrary.h" 11 | #include "git2.h" 12 | #include "GitLinkRepository.h" 13 | #include "GitTree.h" 14 | #include "Message.h" 15 | #include "RepoStatus.h" 16 | #include "Signature.h" 17 | 18 | 19 | EXTERN_C DLLEXPORT int GitAddRemovePath(WolframLibraryData libData, MLINK lnk) 20 | { 21 | long argCount; 22 | MLCheckFunction(lnk, "List", &argCount); 23 | 24 | GitLinkRepository repo(lnk); 25 | MLString pathArg(lnk); 26 | MLString command(lnk); 27 | MLExpr force(lnk); 28 | 29 | PathString path(pathArg); 30 | 31 | MLExpr returnList(MLLinkEnvironment(lnk), MLExpr::eConstructEmptyFunction, "List"); 32 | 33 | if (repo.isValid()) 34 | { 35 | FileNameSet candidateFilenames = RepoStatus(repo, false, force.asBool()).allFileNames(); 36 | std::deque actualFilenames = candidateFilenames.getPathSpecMatches(path); 37 | 38 | int result = 0; 39 | const char* errCode = Message::GitOperationFailed; 40 | const GitTree tree(repo, "HEAD"); 41 | git_index* index; 42 | git_repository_index(&index, repo.repo()); 43 | 44 | giterr_clear(); 45 | for (const auto& it : actualFilenames) 46 | { 47 | if (strcmp(command, "GitAdd") == 0) 48 | { 49 | result = git_index_add_bypath(index, it.c_str()); 50 | if (result == GIT_ENOTFOUND) 51 | { 52 | giterr_clear(); 53 | result = git_index_remove_bypath(index, it.c_str()); 54 | } 55 | } 56 | else if (strcmp(command, "GitReset") == 0) 57 | result = tree.resetIndexToTreeEntry(index, it.c_str()); 58 | else 59 | { 60 | result = -1; 61 | errCode = Message::InvalidArguments; 62 | giterr_clear(); 63 | } 64 | if (result == 0) 65 | returnList.append(MLExpr(MLLinkEnvironment(lnk), MLExpr::eConstructString, PathString(it))); 66 | } 67 | 68 | if (result == 0) 69 | { 70 | errCode = Message::CantWriteIndex; 71 | result = git_index_write(index); 72 | } 73 | git_index_free(index); 74 | 75 | if (result != 0) 76 | { 77 | const char* errParam = (giterr_last() == NULL) ? NULL : giterr_last()->message; 78 | MLHandleError(libData, command, errCode, errParam); 79 | } 80 | } 81 | else 82 | repo.mlHandleError(libData, command); 83 | 84 | returnList.putToLink(lnk); 85 | return LIBRARY_NO_ERROR; 86 | } 87 | -------------------------------------------------------------------------------- /src/classes/GitBlob.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | #include "mathlink.h" 13 | #include "WolframLibrary.h" 14 | #include "git2.h" 15 | #include "GitLinkRepository.h" 16 | #include "GitLinkCommit.h" 17 | #include "GitBlob.h" 18 | 19 | #include "Message.h" 20 | #include "MLHelper.h" 21 | 22 | GitBlob::GitBlob(const MLExpr& expr) 23 | : repo_(expr) 24 | { 25 | MLExpr e = expr; 26 | 27 | if (e.testHead("GitObject") && e.length() == 2 && e.part(1).isString()) 28 | e = e.part(1); 29 | if (repo_.isValid() && e.isString()) 30 | { 31 | const char* sha = e.asString(); 32 | if (git_oid_fromstr(&oid_, sha) != 0) 33 | errCode_ = Message::BadSHA; 34 | else if (git_blob_lookup(&blob_, repo_.repo(), &oid_) != 0) 35 | errCode_ = Message::NoBlob; 36 | } 37 | } 38 | 39 | GitBlob::GitBlob(const GitLinkRepository& repo, MLINK lnk, const char* format) 40 | : repo_(repo.key()) 41 | { 42 | if (!repo.isValid()) 43 | return; 44 | if (strcmp(format, "UTF8String") == 0 || strcmp(format, "ByteString") == 0) 45 | { 46 | bool utf = (strcmp(format, "UTF8String") == 0); 47 | const unsigned char* bytes; 48 | int len, unused; 49 | if (utf) 50 | MLGetUTF8String(lnk, &bytes, &len, &unused); 51 | else 52 | MLGetByteString(lnk, &bytes, &len, 0); 53 | 54 | if (git_blob_create_frombuffer(&oid_, repo.repo(), bytes, len) == 0) 55 | git_blob_lookup(&blob_, repo.repo(), &oid_); 56 | 57 | if (utf) 58 | MLReleaseUTF8String(lnk, bytes, len); 59 | else 60 | MLReleaseByteString(lnk, bytes, len); 61 | return; 62 | } 63 | errCode_ = Message::BadFormat; 64 | errCodeParam_ = strdup(format); 65 | } 66 | 67 | GitBlob::~GitBlob() 68 | { 69 | if (blob_) 70 | git_blob_free(blob_); 71 | } 72 | 73 | void GitBlob::write(MLINK lnk) const 74 | { 75 | MLHelper helper(lnk); 76 | if (blob_ != NULL) 77 | helper.putGitObject(oid_, repo_); 78 | else 79 | helper.putSymbol("$Failed"); 80 | } 81 | 82 | void GitBlob::writeContents(MLINK lnk, const char* format) const 83 | { 84 | MLHelper helper(lnk); 85 | if (blob_ == NULL) 86 | helper.putSymbol("$Failed"); 87 | else if (strcmp(format, "UTF8String") == 0) 88 | helper.putBlobUTF8String(blob_); 89 | else if (strcmp(format, "ByteString") == 0) 90 | helper.putBlobByteString(blob_); 91 | else 92 | helper.putSymbol("$Failed"); 93 | } 94 | 95 | -------------------------------------------------------------------------------- /src/classes/GitLinkCommit.h: -------------------------------------------------------------------------------- 1 | #ifndef GitLinkCommit_h_ 2 | #define GitLinkCommit_h_ 1 3 | 4 | #include "GitLinkRepository.h" 5 | #include "MLExpr.h" 6 | #include 7 | 8 | class GitLinkCommitDeque; 9 | class GitTree; 10 | 11 | class GitLinkCommit : public GitLinkSuperClass 12 | { 13 | public: 14 | GitLinkCommit(const GitLinkRepository& repo, const MLExpr& expr); 15 | GitLinkCommit(const GitLinkRepository& repo, const char* refName); 16 | GitLinkCommit(const GitLinkRepository& repo, const git_oid* oid); 17 | GitLinkCommit(const GitLinkRepository& repo, MLINK link) : GitLinkCommit(repo, MLExpr(link)) { }; 18 | GitLinkCommit(const GitLinkRepository& repo, const GitTree& tree, const GitLinkCommitDeque& parents, 19 | const git_signature* author, const git_signature* committer, const char* message); 20 | GitLinkCommit(const GitLinkCommit& commit); 21 | ~GitLinkCommit(); 22 | 23 | bool operator==(GitLinkCommit& c); 24 | 25 | void write(MLINK link) const; 26 | void writeSHA(MLINK link) const; 27 | 28 | void writeProperties(MLINK link); 29 | 30 | bool isValid() const { return valid_; }; 31 | 32 | bool isHidden() const { return notSpec_; }; 33 | 34 | const git_oid* oid() const { return &oid_; }; 35 | 36 | int parentCount(); 37 | 38 | git_commit* commit(); 39 | 40 | git_object* object() { return (git_object*) commit(); }; 41 | 42 | git_tag* tag() const { return tag_; }; 43 | 44 | bool createBranch(const char* branchName, bool force); 45 | 46 | git_tree* copyTree(); 47 | 48 | const git_signature* author() { return isValid() ? git_commit_author(commit()) : NULL; }; 49 | 50 | const git_signature* committer() { return isValid() ? git_commit_committer(commit()) : NULL; }; 51 | 52 | const char* message() { return isValid() ? git_commit_message(commit()) : NULL; }; 53 | 54 | private: 55 | const GitLinkRepository& repo_; 56 | git_oid oid_; 57 | bool valid_; 58 | bool notSpec_; 59 | git_commit* commit_ = NULL; 60 | git_tag* tag_ = NULL; 61 | }; 62 | 63 | class GitLinkCommitDeque : public std::deque, public GitLinkSuperClass 64 | { 65 | public: 66 | GitLinkCommitDeque(); 67 | GitLinkCommitDeque(const GitLinkCommit& commit); 68 | GitLinkCommitDeque(const GitLinkRepository& repo, MLExpr expr); 69 | GitLinkCommitDeque& operator=(const GitLinkCommitDeque& theDeque); 70 | 71 | const git_commit** commits() const; 72 | bool isValid() const { return isValid_; }; 73 | 74 | private: 75 | bool isValid_; 76 | mutable std::vector commits_; 77 | }; 78 | 79 | #endif // GitLinkCommit_h_ 80 | -------------------------------------------------------------------------------- /src/interface/CherryPickInterface.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include "mathlink.h" 10 | #include "WolframLibrary.h" 11 | #include "git2.h" 12 | #include "GitLinkRepository.h" 13 | #include "GitLinkCommit.h" 14 | #include "GitLinkCommitRange.h" 15 | #include "Message.h" 16 | #include "GitTree.h" 17 | #include "Signature.h" 18 | 19 | 20 | EXTERN_C DLLEXPORT int GitCherryPick(WolframLibraryData libData, MLINK lnk) 21 | { 22 | bool success = false; 23 | long argCount; 24 | MLCheckFunction(lnk, "List", &argCount); 25 | 26 | GitLinkRepository repo(lnk); 27 | GitLinkCommit commit(repo, lnk); 28 | 29 | if (commit.isValid()) 30 | { 31 | git_cherrypick_options opts; 32 | git_cherrypick_init_options(&opts, GIT_CHERRYPICK_OPTIONS_VERSION); 33 | success = (git_cherrypick(repo.repo(), commit.commit(), &opts) == 0); 34 | } 35 | MLPutSymbol(lnk, success ? "True" : "False"); 36 | 37 | return LIBRARY_NO_ERROR; 38 | } 39 | 40 | 41 | EXTERN_C DLLEXPORT int GitCherryPickCommit(WolframLibraryData libData, MLINK lnk) 42 | { 43 | bool success = false; 44 | long argCount; 45 | MLCheckFunction(lnk, "List", &argCount); 46 | 47 | GitLinkRepository repo(lnk); 48 | GitLinkCommit pickedCommit(repo, lnk); 49 | GitLinkCommit parentCommit(repo, lnk); 50 | MLString branch(lnk); 51 | 52 | if (pickedCommit.isValid() && pickedCommit.parentCount() == 1 && parentCommit.isValid()) 53 | { 54 | git_merge_options opts; 55 | git_index* index; 56 | int pickErr; 57 | 58 | git_merge_init_options(&opts, GIT_MERGE_OPTIONS_VERSION); 59 | opts.flags = (git_merge_flag_t) (opts.flags | GIT_MERGE_FAIL_ON_CONFLICT); 60 | pickErr = git_cherrypick_commit(&index, repo.repo(), pickedCommit.commit(), parentCommit.commit(), 0, &opts); 61 | 62 | if (!pickErr) 63 | { 64 | GitTree tree(repo, index); 65 | GitLinkCommit newCommit(repo, tree, parentCommit, pickedCommit.author(), NULL, pickedCommit.message()); 66 | if (newCommit.isValid()) 67 | { 68 | newCommit.write(lnk); 69 | success = true; 70 | if (strcmp(branch, "None") != 0) 71 | { 72 | git_reference* ref; 73 | git_branch_create(&ref, repo.repo(), branch, newCommit.commit(), true); 74 | git_reference_free(ref); 75 | } 76 | } 77 | else 78 | newCommit.mlHandleError(libData, "CherryPick"); 79 | } 80 | git_index_free(index); 81 | } 82 | if (!success) 83 | MLPutSymbol(lnk, "$Failed"); 84 | 85 | return LIBRARY_NO_ERROR; 86 | } 87 | -------------------------------------------------------------------------------- /tests/CheckoutReference.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | Needs["GitLink`"]; 4 | $TestRepos = FileNameJoin[{NotebookDirectory[], "repos"}]; 5 | $RepoDirectory = FileNameJoin[{$TemporaryDirectory, "CheckoutReferenceRepo"}]; 6 | Quiet[DeleteDirectory[$RepoDirectory, DeleteContents->True]]; 7 | $Repo = GitClone[FileNameJoin[{$TestRepos, "testrepo", ".gitted"}], $RepoDirectory]; 8 | SetDirectory[$RepoDirectory]; 9 | changefile[filename_] := Module[{strm = OpenAppend[filename]}, 10 | WriteString[strm, "\nnew line\n"]; Close[strm]]; 11 | fullpath[path_] := FileNameJoin[{$RepoDirectory, path}] 12 | 13 | 14 | VerificationTest[ 15 | { 16 | GitRepoQ[$RepoDirectory] 17 | , AssociationQ[GitProperties[$Repo]] 18 | , Flatten[Values[GitStatus[$Repo]]] === {} 19 | }, 20 | {True, True, True} 21 | ] 22 | 23 | 24 | (* ::Subsubsection:: *) 25 | (*Check out branch at same position*) 26 | 27 | 28 | VerificationTest[ 29 | { 30 | GitCreateBranch[$Repo, "test_branch", "Force"->True] 31 | , GitCheckoutReference[$Repo, "test_branch"] === ToGitObject[$Repo, "master"] 32 | , ToGitObject[$Repo, "HEAD"] === ToGitObject[$Repo, "master"] 33 | , Join @@ Values[GitStatus[$Repo]] === {} 34 | , GitCheckoutReference[$Repo, "master"] === ToGitObject[$Repo, "master"] 35 | }, 36 | {True, True, True, True, True} 37 | ] 38 | 39 | 40 | (* ::Subsubsection:: *) 41 | (*Check out branch at a different position*) 42 | 43 | 44 | VerificationTest[ 45 | { 46 | GitCreateBranch[$Repo, "test_branch", "master~3", "Force"->True] 47 | , GitCheckoutReference[$Repo, "test_branch"] === ToGitObject[$Repo, "master~3"] 48 | , ToGitObject[$Repo, "HEAD"] === ToGitObject[$Repo, "master~3"] 49 | , Join @@ Values[GitStatus[$Repo]] === {} 50 | , GitCheckoutReference[$Repo, "master"] === ToGitObject[$Repo, "master"] 51 | , Join @@ Values[GitStatus[$Repo]] === {} 52 | }, 53 | {True, True, True, True, True, True} 54 | ] 55 | 56 | 57 | (* ::Subsubsection:: *) 58 | (*Changing a file which doesn't change between refs doesn't prevent a checkout*) 59 | 60 | 61 | VerificationTest[ 62 | changefile["new.txt"]; 63 | { 64 | GitCreateBranch[$Repo, "test_branch", "master~3", "Force"->True] 65 | , GitCheckoutReference[$Repo, "test_branch"] === ToGitObject[$Repo, "master~3"] 66 | , ToGitObject[$Repo, "HEAD"] === ToGitObject[$Repo, "master~3"] 67 | , GitStatus[$Repo]["Modified"] === {"new.txt"} 68 | , GitCheckoutReference[$Repo, "master"] === ToGitObject[$Repo, "master"] 69 | , GitStatus[$Repo]["Modified"] === {"new.txt"} 70 | }, 71 | {True, True, True, True, True, True} 72 | ] 73 | 74 | 75 | GitClose[$Repo]; 76 | DeleteDirectory[$RepoDirectory, DeleteContents->True]; 77 | 78 | 79 | EndTestSection[] 80 | -------------------------------------------------------------------------------- /src/classes/GitLinkRepository.h: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #ifndef GitLinkRepository_h_ 10 | #define GitLinkRepository_h_ 1 11 | 12 | #include "GitLinkSuperClass.h" 13 | #include "MLExpr.h" 14 | 15 | const mint BAD_KEY = -1; 16 | 17 | class Signature; 18 | 19 | class GitLinkRepository : public GitLinkSuperClass 20 | { 21 | public: 22 | GitLinkRepository(const std::string& key); 23 | GitLinkRepository(const MLExpr& expr); 24 | GitLinkRepository(MLINK link) 25 | : GitLinkRepository(MLExpr(link)) 26 | { }; 27 | 28 | /// For newly created git_repositories which don't have don't have 29 | /// an in-kernel instance, yet 30 | GitLinkRepository(git_repository* repo, WolframLibraryData libData); 31 | 32 | ~GitLinkRepository(); 33 | 34 | 35 | 36 | bool isValid() const { return repo_ != NULL; }; 37 | 38 | std::string key() const { return key_; }; 39 | void unsetKey(); 40 | 41 | git_repository* repo() const { return repo_; }; 42 | 43 | /// Recreates the signature every time...but the signature is mutable in this class 44 | /// Note that you *must* NULL-check this, as it can fail 45 | const git_signature* committer() const; 46 | 47 | bool fetch(WolframLibraryData libData, const char* remoteName, const char* privateKeyFile, const MLExpr& prune, const MLExpr& downloadTags); 48 | 49 | bool push(WolframLibraryData libData, const char* remoteName, const char* privateKeyFile, const char* branch); 50 | 51 | bool setHead(const char* refName); 52 | 53 | bool checkoutHead(WolframLibraryData libData, const MLExpr& strategy, const MLExpr& notifyFlags); 54 | 55 | void writeProperties(MLINK lnk, bool shortForm) const; 56 | 57 | void writeRemotes(MLHelper& helper) const; 58 | 59 | git_tree* copyTree(const MLExpr& expr); 60 | 61 | git_revwalk* revWalker() const; 62 | 63 | static int AcquireCredsCallBack(git_cred** cred,const char* url,const char *username,unsigned int allowed_types, void* payload); 64 | 65 | private: 66 | git_repository* repo_; 67 | mutable Signature* committer_; 68 | std::string key_; 69 | char* remoteName_; 70 | mutable git_revwalk* revWalker_ = NULL; 71 | 72 | GitLinkRepository(const GitLinkRepository& repo) { }; 73 | 74 | void openCachedRepo_(); 75 | bool setRemote_(WolframLibraryData libData, const char* remoteName, const char* privateKeyFile); 76 | bool connectRemote_(git_direction direction); 77 | void writeConflictList_(MLHelper& helper) const; 78 | void writeBranchList_(MLHelper& helper, git_branch_t flag) const; 79 | void writeTagList_(MLHelper& helper) const; 80 | 81 | 82 | static int pushCallBack_(const char* ref, const char* msg, void* data); 83 | 84 | }; 85 | 86 | #endif // GitLinkRepository_h_ 87 | -------------------------------------------------------------------------------- /src/classes/MLExpr.h: -------------------------------------------------------------------------------- 1 | #ifndef MLExpr_h_ 2 | #define MLExpr_h_ 1 3 | 4 | #include "WolframLibrary.h" 5 | #include "PathString.h" 6 | 7 | typedef struct git_oid git_oid; 8 | 9 | class MLExpr 10 | { 11 | public: 12 | enum ConstructType 13 | { 14 | eConstructEmptyFunction, 15 | eConstructSymbol, 16 | eConstructString 17 | }; 18 | 19 | MLExpr() : loopbackLink_(NULL), str_(NULL), len_(0) { }; 20 | MLExpr(MLINK lnk); 21 | MLExpr(MLEnvironment mle, ConstructType type, const char* str); 22 | MLExpr(MLEnvironment mle, ConstructType type, const std::string& str) : MLExpr(mle, type, str.c_str()) { }; 23 | MLExpr(MLEnvironment mle, ConstructType type, const PathString& str) : MLExpr(mle, type, str.native()) { }; 24 | MLExpr(const MLExpr& expr); 25 | MLExpr(MLExpr&& expr); 26 | ~MLExpr() { if (str_) MLReleaseUTF8String(loopbackLink_, (const unsigned char*) str_, len_); MLClose(loopbackLink_); }; 27 | MLExpr& operator=(const MLExpr& expr); 28 | MLExpr& operator=(MLExpr&& expr); 29 | 30 | MLINK initializeLink(MLEnvironment env); 31 | MLEnvironment mle() const { return (loopbackLink_ == NULL) ? NULL : MLLinkEnvironment(loopbackLink_); }; 32 | 33 | void putToLink(MLINK lnk) const; 34 | MLINK putToLoopbackLink() const; 35 | 36 | void append(const MLExpr& expr); 37 | 38 | bool testString(const char* str) const; 39 | bool testSymbol(const char* sym) const; 40 | bool testHead(const char* sym) const; 41 | int asInt() const; 42 | mint asMint() const; 43 | double asDouble() const; 44 | bool asBool() const { return testSymbol("True"); }; 45 | 46 | // warning...the returned string only lives as long as the MLExpr does. So, e.g., 47 | // calling expr.part(1).asString() would be a bad idea. 48 | const char* asString() const; 49 | const git_oid* asOid() const; 50 | MLExpr part(int i) const; 51 | MLExpr part(int i, int j) const { return part(i).part(j); }; 52 | int length() const; 53 | int partLength(int i) const { return part(i).length(); }; 54 | bool isNull() const { return loopbackLink_ == NULL; }; 55 | bool isInteger() const; 56 | bool isReal() const; 57 | bool isSymbol() const; 58 | bool isString() const; 59 | bool isFunction() const; 60 | bool isList() const { return testHead("List"); }; 61 | bool isRule() const { return (length() == 2 && (testHead("Rule") || testHead("RuleDelayed"))); }; 62 | 63 | // returns matches on both strings or symbols, and doesn't check heads 64 | bool contains(const char* str) const; 65 | 66 | // for an Expr of head Association, looks up the key 'str' 67 | bool containsKey(const char* str) const { return !lookupKey(str).isNull(); }; 68 | MLExpr lookupKey(const char* str) const; 69 | 70 | private: 71 | mutable MLINK loopbackLink_; 72 | mutable const char* str_; 73 | mutable int len_; 74 | }; 75 | 76 | 77 | #endif // MLExpr_h_ 78 | -------------------------------------------------------------------------------- /GitLink/Kernel/NotebookMerge3.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | BeginPackage["NotebookMerge3`"]; 4 | 5 | NotebookMerge3::usage = "NotebookMerge3 is an internal utility."; 6 | 7 | Begin["`Private`"]; 8 | 9 | $ContextPath = Append[DeleteCases[$ContextPath, "NotebookTools`"], "NotebookTools`"]; 10 | 11 | 12 | NotebookMerge3[ 13 | Notebook[aCells_List, aOpts___], 14 | Notebook[lCells_List, lOpts___], 15 | Notebook[rCells_List, rOpts___] 16 | ]:= 17 | Catch[Module[{ 18 | ancestorOpts,leftOpts,rightOpts, 19 | ancestorCells,leftCells,rightCells, 20 | ancestorGroups,leftGroups,rightGroups, 21 | patchedCells,patchedOpts,patchedGroups}, 22 | 23 | ancestorOpts = Sort@purgeOpts[{aOpts}]; 24 | leftOpts = Sort[{lOpts}]; 25 | rightOpts = Sort@purgeOpts[{rOpts}]; 26 | 27 | ancestorCells = purgeBoxes @ FlattenCellGroups[aCells]; 28 | leftCells = purgeBoxes @ FlattenCellGroups[lCells]; 29 | rightCells = purgeBoxes @ FlattenCellGroups[rCells]; 30 | 31 | ancestorGroups = cellGroupStates[aCells]; 32 | leftGroups = cellGroupStates[lCells]; 33 | rightGroups = cellGroupStates[rCells]; 34 | 35 | patchedCells = ApplyPatch[ancestorCells, MultiAlignmentPatch[ancestorCells, leftCells, rightCells]]; 36 | patchedOpts = ApplyPatch[ancestorOpts, MultiAlignmentPatch[ancestorOpts, leftOpts, rightOpts]]; 37 | patchedGroups = ApplyPatch[ancestorGroups, MultiAlignmentPatch[ancestorGroups, leftGroups, rightGroups]]; 38 | 39 | If[!MatchQ[patchedCells, {___Cell}] || !MatchQ[patchedOpts, OptionsPattern[]], 40 | Throw[$Failed, "NotebookMerge3Conflict"] 41 | ]; 42 | 43 | (* Open / close grouping conflicts are easier to resolve *) 44 | If[!FreeQ[patchedGroups, _NotebookTools`NotebookDiffDump`CONFLICT], 45 | (*Message[NotebookMerge3::groupingautofix];*) 46 | patchedGroups = Replace[patchedGroups, { 47 | NotebookTools`NotebookDiffDump`CONFLICT[reason_, left_List, right_] :> Sequence @@ left, 48 | NotebookTools`NotebookDiffDump`CONFLICT[reason_, left_, right_] :> Sequence[] }, {1}] 49 | ]; 50 | 51 | (* return the string for the merged notebook *) 52 | If[StringQ[#], #, $Failed]& @ UsingFrontEnd[MathLink`CallFrontEnd[ 53 | FrontEnd`NotebookToString[Notebook[patchedCells, Sequence @@ patchedOpts], patchedGroups]]] 54 | 55 | ], "NotebookMerge3Conflict"] 56 | 57 | 58 | purgeOpts[opts_List] := 59 | (* level spec catches notebook-level and stylesheet-notebook-level options *) 60 | DeleteCases[opts, (Rule|RuleDelayed)[WindowMargins | WindowSize | FrontEndVersion, _], Infinity] 61 | 62 | 63 | purgeBoxes[cells_List] := 64 | Replace[ 65 | (* purge cell and inline cell options that we're not yet prepared to deal with *) 66 | ReplaceRepeated[cells, Cell[a__, (ExpressionUUID) -> _, b___] :> Cell[a, b]], 67 | (* only purge ImageSizeCache from non-StyleData cells *) 68 | cell: Cell[Except[_StyleData], ___] :> DeleteCases[cell, (Rule|RuleDelayed)[ImageSizeCache, _], Infinity], 69 | {1} 70 | ] 71 | 72 | 73 | cellGroupStates[list_List] := cellGroupStates /@ list; 74 | cellGroupStates[Cell[CellGroupData[list_List, state_]]] := Sequence[state, Sequence @@ cellGroupStates /@ list] 75 | cellGroupStates[other_] := Sequence[] 76 | 77 | (*CellGroupStates[Notebook[cells_List, ___]] := cellGroupStates[cells]*) 78 | 79 | 80 | End[]; 81 | EndPackage[]; 82 | -------------------------------------------------------------------------------- /src/build.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | $Debug = False; 4 | 5 | 6 | Needs["CCompilerDriver`"] 7 | base = ParentDirectory[NotebookDirectory[]]; 8 | src = FileNames["*.cpp", FileNameJoin[{base, "src"}], Infinity]; 9 | srcDirs = Select[FileNames["*", FileNameJoin[{base, "src"}]], DirectoryQ]; 10 | 11 | 12 | component = FileNameJoin[{ParentDirectory[base], "Components", "libgit2", "0.28.3"}]; 13 | environmentNewSSL = Switch[$SystemID, 14 | "Windows-x86-64", "vc140", 15 | "MacOSX-x86-64", "libcxx-min10.12", 16 | "MacOSX-ARM64", "libcxx-min11.0", 17 | "Linux-x86-64", "scientific6-gcc7.3" 18 | ]; 19 | libDirsNewSSL = {FileNameJoin[{component, $SystemID, environmentNewSSL}]}; 20 | 21 | (* Try to use $InstallationDirecotry as a backup for some libraries *) 22 | AppendTo[libDirsNewSSL, FileNameJoin[{$InstallationDirectory, "SystemFiles", "Libraries", $SystemID}]]; 23 | If[$Debug, 24 | PrependTo[libDirsNewSSL, FileNameJoin[{component, $SystemID, environmentNewSSL<>".debug"}]]'; 25 | ]; 26 | 27 | libDirsNewSSL = Join[Switch[$SystemID, 28 | "Windows-x86-64", {FileNameJoin[{ParentDirectory[base], "Components", "LIBSSH2", "1.10.0", $SystemID, "vc141", "lib"}]}, 29 | "MacOSX-x86-64", { 30 | FileNameJoin[{ParentDirectory[base], "Components", "OpenSSL", "1.1.1q", $SystemID, "libcxx-min10.14", "lib"}], 31 | FileNameJoin[{ParentDirectory[base], "Components", "LIBSSH2", "1.10.0", $SystemID, "libcxx-min10.14", "lib"}] 32 | }, 33 | "MacOSX-ARM64", { 34 | FileNameJoin[{ParentDirectory[base], "Components", "OpenSSL", "1.1.1q", $SystemID, "libcxx-min11.0", "lib"}], 35 | FileNameJoin[{ParentDirectory[base], "Components", "LIBSSH2", "1.10.0", $SystemID, "libcxx-min11.0", "lib"}] 36 | }, 37 | "Linux-x86-64", { 38 | FileNameJoin[{ParentDirectory[base], "Components", "OpenSSL", "1.1.1q", $SystemID, "nocona-glibc2.17", "lib"}], 39 | FileNameJoin[{ParentDirectory[base], "Components", "LIBSSH2", "1.10.0", $SystemID, "nocona-glibc2.17", "lib"}] 40 | }, 41 | _, {} 42 | ], libDirsNewSSL]; 43 | includeDir = FileNameJoin[{component, "Source", "include"}]; 44 | compileOpts = ""; 45 | 46 | 47 | compileOpts = Switch[$OperatingSystem, 48 | "Windows", "/EHsc /MT" <> If[$Debug, "D", ""], 49 | "MacOSX", "-std=c++14 -stdlib=libc++ -mmacosx-version-min=10.14 -framework Security", 50 | "Unix", "-Wno-deprecated -std=c++14"]; 51 | linkerOpts = Switch[$OperatingSystem, 52 | "Windows", "/NODEFAULTLIB:msvcrt", 53 | _, ""]; 54 | oslibs = Switch[$OperatingSystem, 55 | "Windows", {"advapi32", "ole32", "rpcrt4", "shlwapi", "user32", "winhttp", "crypt32", "libssh2"}, 56 | "MacOSX", {"z", "iconv", "crypto", "ssh2"}, 57 | "Unix", {"z", "rt", "pthread", "ssh2", "ssl"} 58 | ]; 59 | defines = {Switch[$OperatingSystem, 60 | "Windows", "WIN", 61 | "MacOSX", "MAC", 62 | "Unix", "UNIX"]}; 63 | If[$SystemWordLength===64, AppendTo[defines, "SIXTYFOURBIT"]]; 64 | If[$Debug, AppendTo[defines, "DEBUG"]]; 65 | 66 | 67 | destDir = FileNameJoin[{base, "GitLink", "LibraryResources", $SystemID}]; 68 | If[!DirectoryQ[destDir], CreateDirectory[destDir]]; 69 | 70 | 71 | libNewSSL = CreateLibrary[src, "gitLink", 72 | (* "ShellOutputFunction"->Print,*) 73 | "Debug"->$Debug, 74 | "TargetDirectory"->destDir, 75 | "Language"->"C++", 76 | "CompileOptions"->compileOpts, 77 | "Defines"->defines, 78 | "LinkerOptions"->linkerOpts, 79 | "IncludeDirectories"->Flatten[{includeDir, srcDirs}], 80 | "LibraryDirectories"->libDirsNewSSL, 81 | "Libraries"->Prepend[oslibs, "git2"] 82 | ] 83 | 84 | -------------------------------------------------------------------------------- /src/classes/CheckoutManager.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include "mathlink.h" 10 | #include "WolframLibrary.h" 11 | #include "git2.h" 12 | #include "GitLinkRepository.h" 13 | 14 | #include "CheckoutManager.h" 15 | #include "GitLinkCommit.h" 16 | #include "RepoStatus.h" 17 | 18 | #include "Message.h" 19 | #include "MLExpr.h" 20 | #include "MLHelper.h" 21 | #include "RepoStatus.h" 22 | 23 | CheckoutManager::CheckoutManager(GitLinkRepository& repo) 24 | : repo_(repo) 25 | { 26 | 27 | } 28 | 29 | bool CheckoutManager::initCheckout(WolframLibraryData libData, const char* ref, const GitTree& refTree) 30 | { 31 | refChangedFiles_.clear(); 32 | ref_ = ""; 33 | 34 | if (!repo_.isValid()) 35 | { 36 | propagateError(repo_); 37 | return false; 38 | } 39 | 40 | GitTree headTree(repo_, "HEAD"); 41 | RepoStatus status(repo_, false); 42 | 43 | #if MAC || WIN // case-insensitive file systems 44 | status.convertFileNamesToLower(libData); 45 | #endif // MAC || WIN 46 | 47 | if (!headTree.isValid()) 48 | { 49 | propagateError(headTree); 50 | return false; 51 | } 52 | if (!refTree.isValid()) 53 | { 54 | propagateError(refTree); 55 | return false; 56 | } 57 | if (!status.isValid()) 58 | { 59 | propagateError(status); 60 | return false; 61 | } 62 | 63 | refChangedFiles_ = headTree.getDiffPaths(refTree); 64 | ref_ = ref; 65 | 66 | for (const auto& file : refChangedFiles_) 67 | { 68 | std::string decasedfile = file; 69 | #if MAC || WIN // case-insensitive file systems 70 | decasedfile = MLToLower(libData, decasedfile); 71 | #endif // MAC || WIN 72 | if (status.fileChanged(decasedfile)) 73 | { 74 | errCode_ = Message::CheckoutConflict; 75 | return false; 76 | } 77 | } 78 | 79 | return true; 80 | } 81 | 82 | bool CheckoutManager::doCheckout(const GitTree& refTree) 83 | { 84 | git_checkout_options options; 85 | git_checkout_init_options(&options, GIT_CHECKOUT_OPTIONS_VERSION); 86 | 87 | options.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; 88 | populatePaths_(&options.paths); 89 | 90 | if (options.paths.count == 0) 91 | { 92 | // the tree we're checking out hasn't changed, so don't touch the working tree 93 | } 94 | else if (git_checkout_tree(repo_.repo(), refTree, &options)) 95 | { 96 | freePaths_(&options.paths); 97 | errCode_ = Message::CheckoutFailed; 98 | errCodeParam_ = strdup(giterr_last()->message); 99 | return false; 100 | } 101 | 102 | if (!repo_.setHead(ref_.c_str())) 103 | { 104 | // I hope this can't happen...this could be pretty disastrous. 105 | git_checkout_tree(repo_.repo(), GitTree(repo_, "HEAD"), &options); 106 | propagateError(repo_); 107 | } 108 | 109 | freePaths_(&options.paths); 110 | 111 | return true; 112 | } 113 | 114 | void CheckoutManager::populatePaths_(git_strarray* strarray) const 115 | { 116 | strarray->strings = (char **) malloc (sizeof(char *) * refChangedFiles_.size()); 117 | strarray->count = refChangedFiles_.size(); 118 | 119 | int i = 0; 120 | for (const auto& file : refChangedFiles_) 121 | strarray->strings[i++] = (char*) file.c_str(); 122 | } 123 | 124 | void CheckoutManager::freePaths_(git_strarray* strarray) const 125 | { 126 | if (strarray->count > 0) 127 | { 128 | free((void*)strarray->strings); 129 | strarray->count = 0; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /scripts/assemblePaclet.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | (* ::Section:: *) 4 | (*Assemble the paclet, and build a new .paclet file*) 5 | 6 | 7 | date = DateString[{"Year", "Month", "Day"}]; 8 | time = DateString[{"Hour24", "Minute", "Second"}]; 9 | 10 | 11 | $scriptsDirectory = Which[ 12 | Environment["WORKSPACE"] =!= $Failed, 13 | FileNameJoin[{Environment["WORKSPACE"],"scripts"}], 14 | $InputFileName =!= "", 15 | DirectoryName[$InputFileName], 16 | True, 17 | NotebookDirectory[] 18 | ]; 19 | 20 | $versionNumber = If[Environment["SET_VERSION_NUMBER"] =!= $Failed, 21 | Environment["SET_VERSION_NUMBER"], 22 | "0.0."<>date<>"."<>time 23 | ]; 24 | 25 | 26 | $source = ToFileName[{ParentDirectory[$scriptsDirectory], "GitLink"}]; 27 | $assembled = ToFileName[{$scriptsDirectory, date <> "-" <> time, "GitLink"}]; 28 | 29 | CreateDirectory[$assembled, CreateIntermediateDirectories -> True]; 30 | 31 | $sourceFolderSet = {"FrontEnd", "Kernel", "LibraryResources"}; 32 | $builtDocs = FileNameJoin[{ 33 | ParentDirectory[$scriptsDirectory], 34 | "Built-Documentation", 35 | "GitLink", 36 | "Documentation" 37 | } 38 | ]; 39 | 40 | If[Environment["WORKSPACE"]=!=$Failed, 41 | CopyDirectory[$builtDocs, FileNameJoin[{$assembled, "Documentation"}]], 42 | AppendTo[$sourceFolderSet, "Documentation"] 43 | ]; 44 | 45 | CopyDirectory[ToFileName[{$source, #}], ToFileName[{$assembled, #}]]& /@ $sourceFolderSet; 46 | 47 | FileTemplateApply[ 48 | FileTemplate[ToFileName[{$source}, "PacletInfoTemplate.m"]], 49 | <| "version" -> $versionNumber |>, 50 | ToFileName[{$assembled}, "PacletInfo.m"] 51 | ]; 52 | 53 | (* get rid of any .DS* files or other hidden files *) 54 | DeleteFile /@ FileNames[".*", $assembled, Infinity]; 55 | 56 | 57 | PacletManager`PackPaclet[$assembled] 58 | 59 | 60 | (* ::Input:: *) 61 | (*SystemOpen[ParentDirectory[$assembled]]*) 62 | 63 | 64 | (* ::Section::Closed:: *) 65 | (*notes*) 66 | 67 | 68 | (* 69 | Re version numbering: 70 | 71 | The code above which builds the .paclet file starts from a PacletInfoTemplate.m 72 | file, and creates a new PacletInfo.m file, using the current date and time as 73 | part of a newly synthesized version number. 74 | 75 | One consequence of this is that the static PacletInfo.m file will not be used, 76 | except by developers who install the original source of GitLink, which should be 77 | limited to only people who are developing GitLink. 78 | 79 | For this reason, the version number in the static PacletInfo.m file should 80 | always be greater than the version number synthesized here. That way, GitLink 81 | developers can install the original source, and have it preferred over versions 82 | of this paclet installed from the internal paclet server. 83 | *) 84 | 85 | 86 | (* 87 | To deploy a .paclet file to the internal paclet server: 88 | 89 | -- 90 | % scp GitLink-x.y.z.paclet username@paclet-int:/mnt/paclets/to-deploy/internal/. 91 | 92 | % ssh paclet-int 93 | 94 | paclet-int% ls -la /mnt/paclets/to-deploy/internal 95 | 96 | paclet-int% cd /PacletProcessing/PacletSystem/Paclet-Int/scripts/ 97 | 98 | paclet-int% ant pushInternal 99 | -- 100 | 101 | It's not uncommon for this ant script to take 40 minutes or more to complete. 102 | *) 103 | 104 | 105 | (* 106 | To install / update from the internal paclet server: 107 | 108 | PacletManager`PacletUpdate[#1,"Site"->"http://paclet-int.wolfram.com:8080/PacletServerInternal"]& /@ 109 | {"GitLink", "StashLink"} 110 | *) 111 | -------------------------------------------------------------------------------- /tests/Tag.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | BeginTestSection["Tag"] 4 | 5 | 6 | Needs["GitLink`"]; 7 | $TestRepos = FileNameJoin[{NotebookDirectory[], "repos"}]; 8 | $RepoDirectory = FileNameJoin[{$TemporaryDirectory, "TagTestRepo"}]; 9 | Quiet[DeleteDirectory[$RepoDirectory, DeleteContents->True]]; 10 | $Repo = GitClone[FileNameJoin[{$TestRepos, "testrepo", ".gitted"}], $RepoDirectory]; 11 | 12 | 13 | VerificationTest[ 14 | GitRepoQ[$RepoDirectory] && AssociationQ[GitProperties[$Repo]] 15 | ] 16 | 17 | 18 | (* ::Subsubsection:: *) 19 | (*Detect existing tags*) 20 | 21 | 22 | VerificationTest[ 23 | GitProperties[$Repo, "Tags"] 24 | , 25 | {"e90810b","foo/bar","foo/foo/bar","packed-tag","point_to_blob","test"} 26 | ] 27 | 28 | 29 | (* ::Subsubsection:: *) 30 | (*Delete multiple tags*) 31 | 32 | 33 | VerificationTest[ 34 | GitDeleteTag[$Repo, GitProperties[$Repo, "Tags"]] === Null 35 | && GitProperties[$Repo, "Tags"] === {} 36 | ] 37 | 38 | 39 | (* ::Subsubsection:: *) 40 | (*Create a lightweight tag*) 41 | 42 | 43 | VerificationTest[ 44 | GitCreateTag[$Repo, "tag1"] === ToGitObject[$Repo, "HEAD"] 45 | ] 46 | 47 | 48 | (* ::Subsubsection:: *) 49 | (*Create a tag at a custom commit and delete it*) 50 | 51 | 52 | VerificationTest[ 53 | GitCreateTag[$Repo, "tag2", "HEAD~1"] === ToGitObject[$Repo, "HEAD~1"] 54 | && GitSHA[$Repo, "tag2"] === GitSHA[$Repo, "HEAD~1"] 55 | && GitDeleteTag[$Repo, "tag2"] === Null 56 | && GitSHA[$Repo, "tag2"] === $Failed 57 | ] 58 | 59 | 60 | (* ::Subsubsection:: *) 61 | (*Create a dupe tag without force*) 62 | 63 | 64 | VerificationTest[ 65 | GitCreateTag[$Repo, "tag1", "HEAD~2"] === $Failed 66 | && GitSHA[$Repo, "tag1"] === GitSHA[$Repo, "HEAD"], 67 | True, 68 | {GitCreateTag::gitoperationfailed} 69 | ] 70 | 71 | 72 | (* ::Subsubsection:: *) 73 | (*Create a dupe tag with force*) 74 | 75 | 76 | VerificationTest[ 77 | GitCreateTag[$Repo, "tag1", "HEAD~2", "Force"->True] === ToGitObject[$Repo, "HEAD~2"] 78 | ] 79 | 80 | 81 | (* ::Subsubsection:: *) 82 | (*Create an annotated tag*) 83 | 84 | 85 | VerificationTest[ 86 | sig = GitSignature[]; 87 | 88 | GitType[GitCreateTag[$Repo, "tag/annotated", "HEAD", "An annotated tag", "Signature"->sig]] === "Tag" 89 | ] 90 | 91 | 92 | (* ::Subsubsection:: *) 93 | (*GitProperties on an annotated tag*) 94 | 95 | 96 | VerificationTest[ 97 | props = GitProperties[ToGitObject[$Repo, "tag/annotated"]]; 98 | cprops = GitProperties[ToGitObject[$Repo, "HEAD"]]; 99 | KeyDropFrom[cprops, "Type"]; 100 | 101 | props["TagCommitter"] === sig 102 | && props["TagMessage"] === "An annotated tag" 103 | && Merge[{props, cprops}, Last] === props 104 | ] 105 | 106 | 107 | (* ::Subsubsection:: *) 108 | (*Annotated tag on a tree*) 109 | 110 | 111 | VerificationTest[ 112 | tree = GitProperties[ToGitObject[$Repo, "HEAD"], "Tree"]; 113 | 114 | GitType[GitCreateTag[$Repo, "treetag", tree, "the head tree"]] === "Tag" 115 | && GitProperties[ToGitObject[$Repo, "treetag"], "TagTargetType"] === "Tree" 116 | && GitProperties[ToGitObject[$Repo, "treetag"], "TagTarget"] === GitSHA[tree] 117 | ] 118 | 119 | 120 | (* ::Subsubsection:: *) 121 | (*Lightweight tags fail on non-commit objs*) 122 | 123 | 124 | VerificationTest[ 125 | GitCreateTag[$Repo, "lightweighttreetag", tree] === $Failed, 126 | True, 127 | {GitCreateTag::gitoperationfailed} 128 | ] 129 | 130 | 131 | GitClose[$Repo]; 132 | DeleteDirectory[$RepoDirectory, DeleteContents->True]; 133 | 134 | 135 | EndTestSection[] 136 | -------------------------------------------------------------------------------- /src/interface/gitLink.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include "mathlink.h" 10 | #include "WolframLibrary.h" 11 | #include "git2.h" 12 | #include "MLExpr.h" 13 | #include "MLHelper.h" 14 | #include "RepoInterface.h" 15 | 16 | #include 17 | 18 | 19 | /* Return the version of Library Link */ 20 | EXTERN_C DLLEXPORT mint WolframLibrary_getVersion() 21 | { 22 | return WolframLibraryVersion; 23 | } 24 | 25 | /* Initialize Library */ 26 | EXTERN_C DLLEXPORT int WolframLibrary_initialize(WolframLibraryData libData) 27 | { 28 | git_libgit2_init(); 29 | return 0; 30 | } 31 | 32 | /* Uninitialize Library */ 33 | EXTERN_C DLLEXPORT void WolframLibrary_uninitialize(WolframLibraryData libData) 34 | { 35 | git_libgit2_shutdown(); 36 | } 37 | 38 | EXTERN_C DLLEXPORT int GitLibraryInformation(WolframLibraryData libData, MLINK lnk) 39 | { 40 | MLExpr dummy(lnk); 41 | MLHelper helper(lnk); 42 | 43 | helper.beginFunction("Association"); 44 | 45 | // Versioning 46 | int major, minor, rev; 47 | git_libgit2_version(&major, &minor, &rev); 48 | std::ostringstream verString; 49 | verString << major << "." << minor << "." << rev; 50 | double versionNumber = minor; 51 | while (versionNumber > 1) versionNumber /= 10.; 52 | versionNumber += major; 53 | 54 | helper.putRule("VersionString", verString.str()); 55 | helper.putRule("VersionNumber", versionNumber); 56 | helper.putRule("ReleaseNumber"); helper.putMint(rev); 57 | 58 | // Features 59 | int features = git_libgit2_features(); 60 | helper.putRule("Features"); 61 | helper.beginList(); 62 | if ((features & GIT_FEATURE_THREADS) != 0) 63 | helper.putString("Threads"); 64 | if ((features & GIT_FEATURE_HTTPS) != 0) 65 | helper.putString("Https"); 66 | if ((features & GIT_FEATURE_SSH) != 0) 67 | helper.putString("Ssh"); 68 | helper.endList(); 69 | 70 | // Options 71 | size_t size, size2; 72 | git_libgit2_opts(GIT_OPT_GET_MWINDOW_SIZE, &size); 73 | helper.putRule("MemoryMapWindowSizeLimit"); helper.putMint(size); 74 | 75 | git_libgit2_opts(GIT_OPT_GET_MWINDOW_MAPPED_LIMIT, &size); 76 | helper.putRule("MemoryMapTotalLimit"); helper.putMint(size); 77 | 78 | git_buf buf = GIT_BUF_INIT_CONST(NULL, 0); 79 | git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, &buf); 80 | helper.putRule("SystemConfigSearchPath", buf.ptr); 81 | git_buf_free(&buf); 82 | 83 | buf = GIT_BUF_INIT_CONST(NULL, 0); 84 | git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &buf); 85 | helper.putRule("GlobalConfigSearchPath", buf.ptr); 86 | git_buf_free(&buf); 87 | 88 | buf = GIT_BUF_INIT_CONST(NULL, 0); 89 | git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, &buf); 90 | helper.putRule("XdgConfigSearchPath", buf.ptr); 91 | git_buf_free(&buf); 92 | 93 | git_libgit2_opts(GIT_OPT_GET_CACHED_MEMORY, &size, &size2); 94 | helper.putRule("MemoryCacheInUse"); helper.putMint(size); 95 | helper.putRule("MemoryCacheLimit"); helper.putMint(size2); 96 | 97 | buf = GIT_BUF_INIT_CONST(NULL, 0); 98 | git_libgit2_opts(GIT_OPT_GET_TEMPLATE_PATH, &buf); 99 | helper.putRule("DefaultTemplatePath", buf.ptr); 100 | git_buf_free(&buf); 101 | 102 | #if DEBUG 103 | const char* debugBuild = "True"; 104 | #else 105 | const char* debugBuild = "False"; 106 | #endif 107 | helper.putRule("DebugBuild"); 108 | helper.putSymbol(debugBuild); 109 | 110 | helper.endFunction(); 111 | 112 | return LIBRARY_NO_ERROR; 113 | } 114 | 115 | -------------------------------------------------------------------------------- /scripts/re_build_GitLink.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # GitLink for Wolfram Language 3 | 4 | ![GitLinkLogo](logo.png) 5 | 6 | [![View notebooks](https://wolfr.am/lA6mO5hv)](https://wolfr.am/Dz9P40ip) 7 | 8 | GitLink is a package for integrating [git](https://git-scm.com/) functionality into the [Wolfram Language](https://www.wolfram.com/language/). GitLink supports 11.1 and later versions of Wolfram Language deployments for the desktop, including [Wolfram Desktop](https://www.wolfram.com/desktop/) and [Mathematica](https://www.wolfram.com/mathematica/). 9 | 10 | ### Installing the GitLink release 11 | 12 | The GitLink release comes in the form of a `.paclet` file, which contains the entire package and its documentation. Download the latest release from the [Github repo's releases page](https://github.com/WolframResearch/GitLink/releases). To install, run the following command in the Wolfram Language: 13 | 14 | PacletManager`PacletInstall["/full/path/to/GitLink.paclet"] 15 | 16 | This will permanently install the GitLink paclet. The Wolfram Language will always use the latest installed version of GitLink. Installed versions can be enumerated using the command: 17 | 18 | PacletManager`PacletFind["GitLink"] 19 | 20 | And all versions can be uninstalled using the command: 21 | 22 | PacletManager`PacletUninstall["GitLink"] 23 | 24 | ### Using GitLink 25 | 26 | To access the documentation, open the notebook interface help viewer, and search for GitLink. The first hit will be a summary page enumerating the most commonly used functions in GitLink. It also includes links to other summary pages enumerating a full list of functions for branches and references, and for low-level git operations. 27 | 28 | To start, load the GitLink package, and try opening a repository and enumerating the tip of its master branch. 29 | 30 | Needs["GitLink`"] 31 | r = GitOpen["/full/path/to/a/git/repo"] 32 | c = ToGitObject[r, "master"] 33 | GitProperties[c] 34 | 35 | Congratulations! You're well on your way to reading and manipulating git repos with GitLink. 36 | 37 | ### Where did this come from? 38 | 39 | GitLink is a paclet maintained by [John Fultz](https://github.com/jfultz) of Wolfram Research, with significant help from Lou D'Andria, Alex Newman, and other Wolfram Research staff. John Fultz began building GitLink for his own use, and later worked with Lou and Alex to create and deploy tools that are used widely within Wolfram Research. 40 | 41 | GitLink is implemented on top of the excellent [libgit2 project](https://libgit2.github.com/). However, rather than expose a simple set of libgit2 bindings, we've chosen to expose a high level interface to git which plays to the strengths of the Wolfram Language. 42 | 43 | ### ...and where's it going? 44 | 45 | GitLink is ultimately slated to become a part of the official Wolfram Language release. Before that happens, more functionality will need to be implemented, and the product will be subjected to our rigorous design review process, which may introduce incompatibilities with the existing version. However, it is our intent to keep the source open even after we ship the final product, and to continue to be welcome to community contributions that can improve future versions of GitLink. 46 | 47 | Major areas of GitLink which are not yet implemented include support for diff, blame, rebase, submodule, and config functionality. Additionally, we need to improve support for various git protocols for pushing and fetching. 48 | 49 | ### More... 50 | 51 | See the following files for more information: 52 | 53 | * [COPYING.md](COPYING.md) - GitLink license 54 | * [CONTRIBUTING.md](CONTRIBUTING.md) - Guidelines for contributing to GitLink 55 | * [HowToBuild.md](HowToBuild.md) - Instructions for building and debugging GitLink 56 | -------------------------------------------------------------------------------- /src/classes/MLHelper.h: -------------------------------------------------------------------------------- 1 | #ifndef MLHelper_h_ 2 | #define MLHelper_h_ 1 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "PathString.h" 9 | 10 | class GitLinkRepository; 11 | class MLExpr; 12 | class Signature; 13 | 14 | class MLHelper 15 | { 16 | public: 17 | MLHelper(MLINK lnk); 18 | MLHelper(MLEnvironment env, MLExpr& expr); 19 | ~MLHelper(); 20 | 21 | void processAndIgnore(WolframLibraryData libData); 22 | 23 | void beginFunction(const char* head); 24 | void beginFunction(const MLExpr& expr); 25 | void endFunction(); 26 | void endAllFunctions(); 27 | 28 | void beginList() { beginFunction("List"); }; 29 | void endList() { endFunction(); }; 30 | 31 | void putString(const char* value); 32 | void putString(const std::string& value) { putString(value.c_str()); }; 33 | void putString(const PathString& value) { putString(value.native()); }; 34 | void putSymbol(const char* value); 35 | void putMint(mint value); 36 | void putInt(int value); 37 | void putOid(const git_oid& value); 38 | void putRepo(const GitLinkRepository& repo); 39 | void putGitObject(const git_oid& value, const GitLinkRepository& repo); 40 | void putGitObject(const git_oid& value, const std::string& repoKey); 41 | void putExpr(const MLExpr& expr); 42 | void putMessage(const char* symbol, const char* tag); 43 | void putBlobUTF8String(const git_blob* value); 44 | void putBlobByteString(const git_blob* value); 45 | 46 | void putRule(const char* key); 47 | void putRule(const char* key, int value); // boolean 48 | void putRule(const char* key, double value); 49 | void putRule(const char* key, const MLExpr& value); 50 | void putRule(const char* key, const git_time& value); 51 | void putRule(const char* key, const char* value, const char* symbolFallback = "$Failed"); 52 | void putRule(const char* key, const std::string& value) { putRule(key, value.c_str()); }; 53 | void putRule(const char* key, const PathString& value) { putRule(key, value.native()); }; 54 | void putRule(const char* key, const git_oid& value); 55 | void putRule(const char* key, const git_oid& value, const GitLinkRepository& repo); 56 | void putRule(const char* key, git_repository_state_t value); 57 | void putRule(const char* key, const Signature& value); 58 | 59 | private: 60 | MLINK lnk_; 61 | std::deque tmpLinks_; 62 | std::deque argCounts_; 63 | std::deque unfinishedRule_; 64 | 65 | inline void incrementArgumentCount_() { if (unfinishedRule_.front()) unfinishedRule_.front() = false; else argCounts_.front()++; }; 66 | }; 67 | 68 | class MLString 69 | { 70 | public: 71 | MLString(MLINK lnk) : lnk_(lnk) 72 | { 73 | int unused; 74 | MLGetUTF8String(lnk, &str_, &len_, &unused); 75 | }; 76 | virtual ~MLString() 77 | { 78 | MLReleaseUTF8String(lnk_, str_, len_); 79 | }; 80 | 81 | const char* str() const {return (const char*) str_; }; 82 | operator const char*() const { return (const char*)str_; }; 83 | 84 | private: 85 | const unsigned char* str_; 86 | int len_; 87 | MLINK lnk_; 88 | }; 89 | 90 | class MLBoolean : MLString 91 | { 92 | public: 93 | MLBoolean(MLINK lnk) : MLString(lnk) { }; 94 | virtual ~MLBoolean() { }; 95 | 96 | operator bool() const { return (strcmp(str(), "True") == 0);} 97 | }; 98 | 99 | class MLAutoMark 100 | { 101 | public: 102 | MLAutoMark(MLINK lnk, bool rewindOnDestroy) : 103 | rewindOnDestroy_(rewindOnDestroy), lnk_(lnk), mark_(MLCreateMark(lnk)) 104 | { }; 105 | 106 | ~MLAutoMark() 107 | { 108 | if (rewindOnDestroy_) 109 | rewind(); 110 | MLDestroyMark(lnk_, mark_); 111 | MLClearError(lnk_); 112 | } 113 | 114 | void rewind() { MLSeekToMark(lnk_, mark_, 0); }; 115 | 116 | private: 117 | bool rewindOnDestroy_; 118 | MLINK lnk_; 119 | MLMARK mark_; 120 | }; 121 | 122 | extern void MLHandleError(WolframLibraryData libData, const char* functionName, 123 | const char* messageName, const char* param = NULL, 124 | const char* param2 = NULL); 125 | 126 | extern MLExpr MLToExpr(WolframLibraryData libData, const MLExpr& expr); 127 | extern std::string MLToLower(WolframLibraryData libData, const std::string& str); 128 | extern std::string MLGetCPPString(MLINK lnk); 129 | 130 | inline int MLGetMint(MLINK mlp, mint* mp) 131 | { 132 | mlint64 i; 133 | int result = MLGetInteger64(mlp, &i); 134 | *mp = (mint) i; 135 | return result; 136 | } 137 | 138 | inline int MLPutMint(MLINK mlp, mint w) 139 | { 140 | return MLPutInteger64(mlp, (mlint64) w); 141 | } 142 | 143 | const char* OtypeToString(git_otype otype); 144 | #endif // MLHelper_h_ 145 | -------------------------------------------------------------------------------- /scripts/re_build_GitLink.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | (* Wolfram Language Package *) 4 | 5 | $Debug = False; 6 | Needs[ "CCompilerDriver`" ]; 7 | 8 | $GitLink = FileNameJoin[ { DirectoryName[ $InputFileName, 2 ], "src" } ]; 9 | 10 | $workspace = DirectoryName[ $InputFileName, 3 ]; 11 | $libgit2 = FileNameJoin[ { $workspace, "libgit2" } ]; 12 | $libssh2 = FileNameJoin[ { $workspace, "LIBSSH2" } ]; 13 | $openssl = FileNameJoin[ { $workspace, "OpenSSL" } ]; 14 | 15 | $mathlink = FileNameJoin[ { $InstallationDirectory, "SystemFiles", "Links", "MathLink", "DeveloperKit", $SystemID } ]; 16 | 17 | AntLog[ StringRepeat[ "=", 75 ] ]; 18 | AntLog[ " component == \"" <> AntProperty[ "component" ] <> "\"" ]; 19 | AntLog[ " system_id == \"" <> AntProperty[ "system_id" ] <> "\"" ]; 20 | AntLog[ "" ]; 21 | AntLog[ " files_directory == \"" <> AntProperty[ "files_directory" ] <> "\"" ]; 22 | AntLog[ "scratch_directory == \"" <> AntProperty[ "scratch_directory" ] <> "\"" ]; 23 | AntLog[ "" ]; 24 | AntLog[ " $GitLink == \"" <> $GitLink <> "\"" ]; 25 | AntLog[ " $libgit2 == \"" <> $libgit2 <> "\"" ]; 26 | AntLog[ " $libssh2 == \"" <> $libssh2 <> "\"" ]; 27 | AntLog[ " $openssl == \"" <> $openssl <> "\"" ]; 28 | AntLog[ "" ]; 29 | AntLog[ " $mathlink == \"" <> $mathlink <> "\"" ]; 30 | AntLog[ StringRepeat[ "=", 75 ] ]; 31 | AntLog[ "" ]; 32 | 33 | $GitLinkLib = CreateLibrary[ 34 | 35 | FileNames[ "*.cpp", { $GitLink }, Infinity ], 36 | "gitLink", 37 | 38 | "CleanIntermediate" -> True, 39 | 40 | "CompileOptions" -> Switch[ $OperatingSystem, 41 | "MacOSX", "-std=c++14 -stdlib=libc++ -mmacosx-version-min=10.9 -framework Security", 42 | "Unix", "-Wno-deprecated -std=c++14", 43 | "Windows", "/EHsc " <> If[ $Debug, "/MTd", "/MT" ] 44 | ], 45 | 46 | "CompilerInstallation" -> If[ $OperatingSystem == "Windows", 47 | Environment[ "COMPILER_INSTALLATION" ], 48 | Automatic 49 | ], 50 | 51 | "CompilerName" -> Automatic, 52 | 53 | "Debug" -> $Debug, 54 | 55 | "Defines" -> { 56 | Switch[ $OperatingSystem, 57 | "MacOSX", "MAC", 58 | "Unix", "UNIX", 59 | "Windows", "WIN" 60 | ], 61 | If[ $SystemWordLength === 64, "SIXTYFOURBIT", Nothing ], 62 | If[ $Debug, "DEBUG", Nothing ] 63 | }, 64 | 65 | "IncludeDirectories" -> Flatten[ { 66 | FileNameJoin[ { $libgit2, "Source", "include" } ], 67 | Select[ FileNames[ "*", $GitLink ], DirectoryQ ] 68 | } ], 69 | 70 | "Language" -> "C++", 71 | 72 | "Libraries" -> Switch[ $OperatingSystem, 73 | "MacOSX", 74 | { "git2", "z", "iconv", "crypto", "ssh2" }, 75 | "Unix", 76 | { "git2", "z", "rt", "pthread", "ssh2", "ssl"}, 77 | "Windows", 78 | { "git2", "advapi32", "ole32", "rpcrt4", "shlwapi", "user32", "winhttp", "crypt32", "libssh2" } 79 | ], 80 | 81 | "LibraryDirectories" -> Switch[ $OperatingSystem, 82 | "MacOSX", 83 | { 84 | FileNameJoin[ { $libssh2, "lib" } ], 85 | FileNameJoin[ { $openssl, "lib" } ],(*for crypto library*) 86 | $libgit2 <> If[ $Debug, ".debug", "" ] 87 | }, 88 | "Unix", 89 | { 90 | FileNameJoin[ { $openssl, "lib" } ], 91 | FileNameJoin[ { $libssh2, "lib" } ], 92 | $libgit2 <> If[ $Debug, ".debug", "" ] 93 | }, 94 | "Windows", 95 | { 96 | FileNameJoin[ { $libssh2, "lib" } ], 97 | $libgit2 <> If[ $Debug, ".debug", "" ] 98 | } 99 | ], 100 | 101 | "LinkerOptions" -> Switch[$OperatingSystem, 102 | "MacOSX", { "-install_name", "@rpath/gitLink.dylib", "-rpath", "@loader_path"}, 103 | "Unix", { "-rpath='$ORIGIN'" }, 104 | "Windows", { "/NODEFAULTLIB:msvcrt" } 105 | ], 106 | 107 | "ShellCommandFunction" -> Global`AntLog, 108 | "ShellOutputFunction" -> Global`AntLog, 109 | 110 | "SystemIncludeDirectories" -> { 111 | FileNameJoin[ { $mathlink, "CompilerAdditions" } ], 112 | FileNameJoin[ { $InstallationDirectory, "SystemFiles", "IncludeFiles", "C" } ] 113 | }, 114 | 115 | "SystemLibraryDirectories" -> { 116 | If[ $OperatingSystem == "MacOSX" , "-F", "" ] <> FileNameJoin[ { $mathlink, "CompilerAdditions" } ], 117 | FileNameJoin[ { $InstallationDirectory, "SystemFiles", "Libraries", $SystemID } ] 118 | }, 119 | 120 | "TargetDirectory" -> FileNameJoin[ { AntProperty[ "files_directory" ], AntProperty[ "component" ], "LibraryResources", AntProperty[ "system_id" ] } ], 121 | "TargetSystemID" -> AntProperty[ "system_id" ], 122 | 123 | "WorkingDirectory" -> AntProperty[ "scratch_directory" ] 124 | 125 | ]; 126 | 127 | If[ FailureQ[ $GitLinkLib ], 128 | AntFail[ "Library was not generated." ], 129 | AntLog[ "$GitLinkLib == \"" <> $GitLinkLib <> "\"" ] 130 | ]; 131 | -------------------------------------------------------------------------------- /GitLink/Kernel/Messages.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | General::badrepo = "GitLink tried to access a repo which is invalid."; 4 | General::badremote = "GitLink tried to access a remote which is invalid."; 5 | GitAddRemote::badremotename = "GitAddRemote failed because the invalid remote name `1` was given."; 6 | GitStatus::barerepo = "GitStatus was called on a bare repo."; 7 | General::fetchfailed = "A git fetch operation failed with the error \"`1`\"."; 8 | General::noconnect = "The git remote failed to connect with the error \"`1`\"."; 9 | General::downloadfailed = "The git remote connected, but the download operation failed with the error \"`1`\"."; 10 | General::updatetipsfailed = "The git fetch operation failed to update the branch references."; 11 | General::badcommitish = "GitLink expected a valid commit specification."; 12 | General::noparent = "GitLink failed to create a valid commit because the parent commit was invalid."; 13 | General::noindex = "The operation failed because the git index could not be found."; 14 | General::noworkingtree = "The operation failed because the git working tree could not be found."; 15 | General::nomessage = "GitLink failed to create a valid commit because a log message was not provided."; 16 | General::gitcommiterror = "A git commit failed."; 17 | General::cantwritetree = "GitLink failed to write a tree."; 18 | General::cantwriteindex = "GitLink failed to update the index."; 19 | General::nodefaultusername = "GitLink failed to create a valid commit because there is no default username. Configure a username in git by calling, e.g., 'git config --global user.name \"John Doe\"', and 'git config --global user.email \"johndoe@example.com\"."; 20 | GitPush::uploadfailed = "GitPush failed to upload its objects to the remote."; 21 | General::refnotpushed = "Git failed to push a reference with the message \"`1`\""; 22 | GitCreateBranch::invalidspec = "GitCreateBranch failed to create a branch because it was given the invalid branch name \"`1`\"."; 23 | GitCreateBranch::refexists = "GitCreateBranch failed to create a branch because it was given the branch name \"`1`\", which already exists."; 24 | GitCreateBranch::branchnotcreated = "GitCreateBranch failed with the error \"`1`\"."; 25 | General::nolocalbranch = "The operation failed because there is no local branch named \"`1`\"."; 26 | General::noremotebranch = "The operation failed because there is no remote branch named \"`1`\"."; 27 | GitSetUpstreamBranch::setupstreamfailed = "GitSetUpstreamBranch failed to set the upstream branch of \"`1`\" to \"`2`\"."; 28 | GitUpstreamBranch::upstreamfailed = "GitUpstreamBranch failed on the branch \"`1`\"."; 29 | General::invalidsource = "An invalid source was used for a merging operation."; 30 | General::invaliddest = "An invalid destination was used for a merging operation."; 31 | General::notree = "GitLink failed to create a valid commit because the tree was invalid."; 32 | General::noblob = "The reference does not point to a git blob."; 33 | General::noref = "The reference `2` does not point to an object in the repo `1`."; 34 | General::nopath = "The path `1` was not found in the entities referenced by `2`."; 35 | General::badsha = "A standard git SHA was expected."; 36 | General::gitoperationfailed = "A git operation failed with the message \"`1`\"."; 37 | General::checkoutfailed = "A checkout failed with the message \"`1`\"."; 38 | GitWriteTree::badtreeentry = "GitWriteTree was given a bad tree specification."; 39 | General::inconsistentrepos = "The operation failed because the given objects do not match the specified repo."; 40 | General::mismatchedgitobj = "The git object does not match the repo specified for the operation."; 41 | General::repoexists = "The operation failed because it would overwrite an existing repo."; 42 | General::checkoutconflict = "The git checkout operation failed because it would lead to a conflict with the files presently in the working tree directory."; 43 | General::badformat = "The git blob cannot be imported using the format \"`1`\"."; 44 | General::invalidarguments = "The function was given invalid arguments."; 45 | General::nosrcdir = "The remote URL \"`1`\" does not exist or cannot be accessed for git operations."; 46 | GitPull::detachedhead = "GitPull cannot be performed on a detached head."; 47 | GitPull::noupstream = "GitPull failed to work because no upstream branch is defined for the `1`."; 48 | General::noancfile = "GitLink could not determine an ancestor file for resolving conflicts while merging."; 49 | General::noconfunc = "A git merge failed, and no conflict functions were found to address the failure."; 50 | General::incompatexprs = "Encountered inconsistent heads `1` while attempting the \"ChooseBothArguments\" merge handler."; 51 | -------------------------------------------------------------------------------- /scripts/assemblePacletTeamCity.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | (* ::Title:: *) 4 | (*Assemble the paclet, and build a new .paclet file*) 5 | 6 | 7 | (* ::Section:: *) 8 | (*Find directory locations*) 9 | 10 | 11 | date = DateString[{"Year", "Month", "Day"}]; 12 | time = DateString[{"Hour24", "Minute", "Second"}]; 13 | 14 | (* TeamCity should set the version number using a timestamp and build number, in the SET_VERSION_NUMBER environment variable *) 15 | $versionNumber = If[Environment["SET_VERSION_NUMBER"] =!= $Failed, 16 | Environment["SET_VERSION_NUMBER"], 17 | "0.0."<>date<>"."<>time 18 | ]; 19 | 20 | (* TeamCity should set the workspace location in the WORKSPACE environment variable *) 21 | $workspaceDirectory = Which[ 22 | Environment["WORKSPACE"] =!= $Failed, 23 | Environment["WORKSPACE"], 24 | $InputFileName =!= "", 25 | DirectoryName[$InputFileName], 26 | True, 27 | NotebookDirectory[] 28 | ]; 29 | 30 | (* The location of the Kernel files should be ./GitLink/GitLink/Kernel *) 31 | $kernelDirectory = FileNameJoin[{$workspaceDirectory,"GitLink","GitLink","Kernel"}]; 32 | 33 | (* The PacletInfoTemplate.m file should be in the directory above the Kernel files *) 34 | $templateFile = FileNameJoin[{ParentDirectory[$kernelDirectory], "PacletInfoTemplateTeamCity.m"}]; 35 | 36 | (* Where to find inputs provided by chain builds *) 37 | $inputDirectory = FileNameJoin[{$workspaceDirectory,"output","Files"}]; 38 | 39 | (* Where to output the paclet file *) 40 | $outputDirectory = FileNameJoin[{$workspaceDirectory, "output"}]; 41 | 42 | (* Where to assemble GitLink *) 43 | $assembled = FileNameJoin[{$workspaceDirectory,"output","Files", date <> "-" <> time, "assembled", "GitLink"}]; 44 | 45 | CreateDirectory[$assembled, CreateIntermediateDirectories -> True]; 46 | 47 | 48 | (* ::Section:: *) 49 | (*Copy files to be assembled into assembly directory*) 50 | 51 | 52 | CopyDirectory[$kernelDirectory, FileNameJoin[{$assembled, "Kernel"}]]; 53 | CopyDirectory[FileNameJoin[{$inputDirectory, "GitLink", "Documentation"}], FileNameJoin[{$assembled, "Documentation"}]]; 54 | CopyDirectory[FileNameJoin[{$inputDirectory, "GitLink", "LibraryResources"}], FileNameJoin[{$assembled, "LibraryResources"}]]; 55 | 56 | 57 | (* ::Section:: *) 58 | (*Assemble the paclet*) 59 | 60 | 61 | (* Pull SystemID/Qualifier from environment if they exist, otherwise leave them empty for MULT build *) 62 | $systemID = Which[ 63 | Environment["SYSTEM_ID"] =!= $Failed && Environment["SYSTEM_ID"] =!= "any", 64 | "SystemID -> \"" <> Environment["SYSTEM_ID"] <> "\",", 65 | True, 66 | "" 67 | ]; 68 | 69 | $qualifier = Which[ 70 | Environment["QUALIFIER"] =!= $Failed && Environment["QUALIFIER"] =!= "any", 71 | "Qualifier -> \"" <> Environment["QUALIFIER"] <> "\",", 72 | True, 73 | "" 74 | ]; 75 | 76 | (* Fill the template info file with information about this build *) 77 | FileTemplateApply[ 78 | FileTemplate[$templateFile], 79 | <| "Version" -> $versionNumber, "SystemID" -> $systemID, "Qualifier" -> $qualifier |>, 80 | FileNameJoin[{$assembled, "PacletInfo.m"}] 81 | ]; 82 | 83 | (* get rid of any .DS* files or other hidden files *) 84 | DeleteFile /@ FileNames[".*", $assembled, Infinity]; 85 | 86 | 87 | (* ::Section:: *) 88 | (*Create .paclet file*) 89 | 90 | 91 | PackPaclet[$assembled]; 92 | (* Copy the assembled paclet into the output directory *) 93 | $pacletName = FileNameTake[FileNames["*.paclet",ParentDirectory[$assembled]][[1]]]; 94 | CopyFile[ 95 | FileNameJoin[{ParentDirectory[$assembled], $pacletName}], 96 | FileNameJoin[{$outputDirectory, $pacletName}] 97 | ]; 98 | 99 | 100 | (* ::Section:: *) 101 | (*notes*) 102 | 103 | 104 | (* ::Text:: *) 105 | (*This .wl file takes the Documentation, Kernel, and NativeLibrary files and uses them to create a full paclet file. It should be able to entirely replace the ANT script for Paclet builds, but it is still wrapped in the ANT script for the sake of generating a checksum, as well as keeping consistency with changes to the AntLibrary which are meant to affect all projects.*) 106 | 107 | 108 | (* 109 | Re version numbering: 110 | 111 | The code above which builds the .paclet file starts from a PacletInfoTemplate.m 112 | file, and creates a new PacletInfo.m file, using the current date and time as 113 | part of a newly synthesized version number. 114 | 115 | One consequence of this is that the static PacletInfo.m file will not be used, 116 | except by developers who install the original source of GitLink, which should be 117 | limited to only people who are developing GitLink. 118 | 119 | For this reason, the version number in the static PacletInfo.m file should 120 | always be greater than the version number synthesized here. That way, GitLink 121 | developers can install the original source, and have it preferred over versions 122 | of this paclet installed from the internal paclet server. 123 | *) 124 | -------------------------------------------------------------------------------- /src/interface/TreeInterface.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include "mathlink.h" 10 | #include "WolframLibrary.h" 11 | #include "git2.h" 12 | #include "RepoInterface.h" 13 | #include "GitLinkRepository.h" 14 | #include "GitTree.h" 15 | #include "Message.h" 16 | #include 17 | 18 | 19 | EXTERN_C DLLEXPORT int GitExpandTree(WolframLibraryData libData, MLINK lnk) 20 | { 21 | long argCount; 22 | MLCheckFunction(lnk, "List", &argCount); 23 | 24 | GitTree tree(lnk); 25 | MLExpr depth(lnk); 26 | int depthInt = 1; 27 | 28 | if (depth.isInteger()) 29 | depthInt = depth.asInt(); 30 | else if (depth.testSymbol("Infinity") || depth.testHead("DirectedInfinity")) 31 | depthInt = INT_MAX; 32 | 33 | if (tree.isValid()) 34 | tree.writeContents(lnk, depthInt); 35 | else 36 | { 37 | tree.mlHandleError(libData, "GitExpandTree"); 38 | MLPutSymbol(lnk, "$Failed"); 39 | } 40 | 41 | return LIBRARY_NO_ERROR; 42 | } 43 | 44 | EXTERN_C DLLEXPORT int GitWriteTree(WolframLibraryData libData, MLINK lnk) 45 | { 46 | bool success = false; 47 | long argCount; 48 | MLCheckFunction(lnk, "List", &argCount); 49 | MLExpr treeArgs(lnk); 50 | 51 | git_treebuilder* builder = NULL; 52 | std::string repoKey; 53 | for (int i = 1; i <= treeArgs.length(); i++) 54 | { 55 | MLExpr arg = treeArgs.part(i); 56 | MLExpr object = arg.lookupKey("Object"); 57 | MLExpr name = arg.lookupKey("Name"); 58 | MLExpr filemode = arg.lookupKey("FileMode"); 59 | git_filemode_t resolvedFileMode = GIT_FILEMODE_UNREADABLE; 60 | 61 | if (filemode.isInteger()) 62 | resolvedFileMode = (git_filemode_t) filemode.asInt(); 63 | else if (filemode.isString()) 64 | { 65 | if (strcmp(filemode.asString(), "Tree") == 0) 66 | resolvedFileMode = GIT_FILEMODE_TREE; 67 | else if (strcmp(filemode.asString(), "Blob") == 0) 68 | resolvedFileMode = GIT_FILEMODE_BLOB; 69 | else if (strcmp(filemode.asString(), "BlobExecutable") == 0) 70 | resolvedFileMode = GIT_FILEMODE_BLOB_EXECUTABLE; 71 | else if (strcmp(filemode.asString(), "Link") == 0) 72 | resolvedFileMode = GIT_FILEMODE_LINK; 73 | } 74 | 75 | if (!object.testHead("GitObject") || !name.isString() || resolvedFileMode == GIT_FILEMODE_UNREADABLE || 76 | object.length() != 2 || !object.part(1).isString() || !object.part(2).testHead("GitRepo")) 77 | { 78 | MLHandleError(libData, "GitWriteTree", Message::BadTreeEntry); 79 | break; 80 | } 81 | GitLinkRepository repo(object.part(2)); 82 | if (repoKey.empty()) 83 | repoKey = repo.key(); 84 | if (repo.key() != repoKey || !repo.isValid()) 85 | { 86 | MLHandleError(libData, "GitWriteTree", Message::InconsistentRepos); 87 | break; 88 | } 89 | if (builder == NULL && git_treebuilder_new(&builder, repo.repo(), NULL)) 90 | { 91 | MLHandleError(libData, "GitWriteTree", Message::GitOperationFailed); 92 | break; 93 | } 94 | if (git_treebuilder_insert(NULL, builder, name.asString(), object.asOid(), resolvedFileMode)) 95 | { 96 | MLHandleError(libData, "GitWriteTree", Message::GitOperationFailed); 97 | break; 98 | } 99 | } 100 | 101 | if (builder != NULL && git_treebuilder_entrycount(builder) == treeArgs.length()) 102 | { 103 | git_oid writtenTree; 104 | if (git_treebuilder_write(&writtenTree, builder) == 0) 105 | { 106 | MLHelper helper(lnk); 107 | helper.putGitObject(writtenTree, GitLinkRepository(repoKey)); 108 | success = true; 109 | } 110 | else 111 | MLHandleError(libData, "GitWriteTree", Message::GitOperationFailed); 112 | } 113 | 114 | if (!success) 115 | MLPutSymbol(lnk, "$Failed"); 116 | if (builder) 117 | git_treebuilder_free(builder); 118 | return LIBRARY_NO_ERROR; 119 | } 120 | 121 | 122 | EXTERN_C DLLEXPORT int GitDiffTrees(WolframLibraryData libData, MLINK lnk) 123 | { 124 | long argCount; 125 | MLCheckFunction(lnk, "List", &argCount); 126 | 127 | GitTree tree1(lnk); 128 | GitTree tree2(lnk); 129 | 130 | MLHelper helper(lnk); 131 | if (tree1.isValid() && tree2.isValid()) 132 | { 133 | PathSet changedFiles = tree1.getDiffPaths(tree2); 134 | helper.beginList(); 135 | for (const auto& path : changedFiles) 136 | helper.putString(path); 137 | helper.endList(); 138 | } 139 | else 140 | { 141 | tree1.mlHandleError(libData, "GitDiffTrees"); 142 | tree2.mlHandleError(libData, "GitDiffTrees"); 143 | helper.putSymbol("$Failed"); 144 | } 145 | 146 | return LIBRARY_NO_ERROR; 147 | } 148 | 149 | EXTERN_C DLLEXPORT int GitIndexTree(WolframLibraryData libData, MLINK lnk) 150 | { 151 | long argCount; 152 | MLCheckFunction(lnk, "List", &argCount); 153 | 154 | GitLinkRepository repo(lnk); 155 | 156 | git_index* index; 157 | if (!git_repository_index(&index, repo.repo())) 158 | { 159 | GitTree indexTree(repo, index); 160 | indexTree.write(lnk); 161 | } 162 | else 163 | MLPutSymbol(lnk, "$Failed"); 164 | 165 | return LIBRARY_NO_ERROR; 166 | } 167 | -------------------------------------------------------------------------------- /src/interface/CommitInterface.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include "mathlink.h" 10 | #include "WolframLibrary.h" 11 | #include "git2.h" 12 | #include "RepoInterface.h" 13 | #include "CommitInterface.h" 14 | #include "GitLinkRepository.h" 15 | #include "GitLinkCommit.h" 16 | #include "GitLinkCommitRange.h" 17 | #include "GitTree.h" 18 | #include "Message.h" 19 | #include "Signature.h" 20 | 21 | 22 | EXTERN_C DLLEXPORT int GitCommitQ(WolframLibraryData libData, MLINK lnk) 23 | { 24 | long argCount; 25 | MLCheckFunction(lnk, "List", &argCount); 26 | 27 | GitLinkRepository repo(lnk); 28 | GitLinkCommit commit(repo, lnk); 29 | 30 | MLPutSymbol(lnk, commit.isValid() ? "True" : "False"); 31 | 32 | return LIBRARY_NO_ERROR; 33 | } 34 | 35 | EXTERN_C DLLEXPORT int GitSHA(WolframLibraryData libData, MLINK lnk) 36 | { 37 | long argCount; 38 | MLCheckFunction(lnk, "List", &argCount); 39 | 40 | GitLinkRepository repo(lnk); 41 | GitLinkCommit commit(repo, lnk); 42 | 43 | commit.writeSHA(lnk); 44 | 45 | return LIBRARY_NO_ERROR; 46 | } 47 | 48 | EXTERN_C DLLEXPORT int GitCommitProperties(WolframLibraryData libData, MLINK lnk) 49 | { 50 | long argCount; 51 | MLCheckFunction(lnk, "List", &argCount); 52 | 53 | GitLinkRepository repo(lnk); 54 | GitLinkCommit commit(repo, lnk); 55 | 56 | commit.writeProperties(lnk); 57 | 58 | return LIBRARY_NO_ERROR; 59 | } 60 | 61 | EXTERN_C DLLEXPORT int GitCommit(WolframLibraryData libData, MLINK lnk) 62 | { 63 | long argCount; 64 | MLCheckFunction(lnk, "List", &argCount); 65 | GitLinkRepository repo(lnk); 66 | MLString message(lnk); 67 | GitTree tree(lnk); 68 | MLExpr parentsExpr(lnk); 69 | Signature author(repo, lnk); 70 | Signature committer(repo, lnk); 71 | git_oid treeSHA; 72 | 73 | if (!repo.isValid()) 74 | { 75 | repo.mlHandleError(libData, "GitCommit"); 76 | MLPutSymbol(lnk, "$Failed"); 77 | return LIBRARY_NO_ERROR; 78 | } 79 | 80 | GitLinkCommitDeque parents(repo, parentsExpr); 81 | if (!parents.isValid()) 82 | { 83 | parents.mlHandleError(libData, "GitCommit"); 84 | MLPutSymbol(lnk, "$Failed"); 85 | } 86 | else 87 | { 88 | GitLinkCommit commit(repo, tree, parents, author, committer, message); 89 | if (!commit.isValid()) 90 | commit.mlHandleError(libData, "GitCommit"); 91 | commit.write(lnk); 92 | } 93 | 94 | return LIBRARY_NO_ERROR; 95 | } 96 | 97 | EXTERN_C DLLEXPORT int GitRange(WolframLibraryData libData, MLINK lnk) 98 | { 99 | long argCount; 100 | MLCheckFunction(lnk, "List", &argCount); 101 | 102 | GitLinkRepository repo(lnk); 103 | MLExpr lengthOnly(lnk); 104 | GitLinkCommitRange range(repo); 105 | 106 | range.buildRange(lnk, argCount - 2); 107 | range.writeRange(lnk, lengthOnly.testSymbol("True")); 108 | 109 | return LIBRARY_NO_ERROR; 110 | } 111 | 112 | EXTERN_C DLLEXPORT int GitMergeBase(WolframLibraryData libData, MLINK lnk) 113 | { 114 | long argCount; 115 | MLCheckFunction(lnk, "List", &argCount); 116 | 117 | GitLinkRepository repo(lnk); 118 | std::vector commits; 119 | argCount--; 120 | while (argCount-- > 0) 121 | commits.push_back(GitLinkCommit(repo, lnk)); 122 | 123 | for (const auto& commit : commits) 124 | { 125 | if (!commit.isValid()) 126 | { 127 | commit.mlHandleError(libData, "GitMergeBase"); 128 | MLPutSymbol(lnk, "$Failed"); 129 | return LIBRARY_NO_ERROR; 130 | } 131 | } 132 | 133 | std::vector oidArray(commits.size()); 134 | for (int i = 0; i < commits.size(); i++) 135 | git_oid_cpy(&oidArray[i], commits[i].oid()); 136 | 137 | git_oid result; 138 | if (git_merge_base_many(&result, repo.repo(), commits.size(), oidArray.data()) == 0) 139 | GitLinkCommit(repo, &result).write(lnk); 140 | else 141 | MLPutSymbol(lnk, "None"); 142 | 143 | return LIBRARY_NO_ERROR; 144 | } 145 | 146 | EXTERN_C DLLEXPORT int GitAheadBehind(WolframLibraryData libData, MLINK lnk) 147 | { 148 | long argCount; 149 | MLCheckFunction(lnk, "List", &argCount); 150 | 151 | GitLinkRepository repo(lnk); 152 | MLExpr commit1Expr(lnk); 153 | MLExpr commit2Expr(lnk); 154 | 155 | GitLinkCommit commit1(repo, commit1Expr); 156 | GitLinkCommit commit2(repo, commit2Expr); 157 | 158 | size_t ahead, behind; 159 | 160 | const char* error = NULL; 161 | const char* param = NULL; 162 | if (!repo.isValid()) 163 | error = Message::BadRepo; 164 | else if (!commit1.isValid() || !commit2.isValid()) 165 | error = Message::BadCommitish; 166 | else if (git_graph_ahead_behind(&ahead, &behind, repo.repo(), commit1.oid(), commit2.oid()) != 0) 167 | { 168 | error = Message::GitOperationFailed; 169 | param = giterr_last() ? giterr_last()->message : NULL; 170 | } 171 | 172 | if (error) 173 | { 174 | MLHandleError(libData, "GitAheadBehind", error, param); 175 | MLPutSymbol(lnk, "$Failed"); 176 | } 177 | else 178 | { 179 | MLPutFunction(lnk, "List", 2); 180 | MLPutInteger(lnk, ahead); 181 | MLPutInteger(lnk, behind); 182 | } 183 | 184 | return LIBRARY_NO_ERROR; 185 | } 186 | 187 | -------------------------------------------------------------------------------- /tests/AddReset.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | Needs["GitLink`"]; 4 | $TestRepos = FileNameJoin[{NotebookDirectory[], "repos"}]; 5 | $RepoDirectory = FileNameJoin[{$TemporaryDirectory, "AddResetTestRepo"}]; 6 | Quiet[DeleteDirectory[$RepoDirectory, DeleteContents->True]]; 7 | $Repo = GitClone[FileNameJoin[{$TestRepos, "testrepo", ".gitted"}], $RepoDirectory]; 8 | SetDirectory[$RepoDirectory]; 9 | changefile[filename_] := Module[{strm = OpenAppend[filename]}, 10 | WriteString[strm, "\nnew line\n"]; Close[strm]]; 11 | fullpath[path_] := FileNameJoin[{$RepoDirectory, path}] 12 | 13 | 14 | VerificationTest[ 15 | { 16 | GitRepoQ[$RepoDirectory] 17 | , AssociationQ[GitProperties[$Repo]] 18 | , Flatten[Values[GitStatus[$Repo]]] === {} 19 | }, 20 | {True, True, True} 21 | ] 22 | 23 | 24 | (* ::Subsubsection:: *) 25 | (*Add a new file*) 26 | 27 | 28 | VerificationTest[ 29 | Export["newfile.txt", Range[5], "Text"]; 30 | { 31 | GitAdd[$Repo, "newfile.txt"] === {"newfile.txt"} 32 | , GitStatus[$Repo]["IndexNew"] === {"newfile.txt"} 33 | }, 34 | {True, True} 35 | ] 36 | 37 | 38 | (* ::Subsubsection:: *) 39 | (*Reset the file*) 40 | 41 | 42 | VerificationTest[ 43 | { 44 | GitReset[$Repo, "newfile.txt"] === {"newfile.txt"} 45 | , GitStatus[$Repo]["IndexNew"] === {} 46 | }, 47 | {True, True} 48 | ] 49 | DeleteFile["newfile.txt"] 50 | 51 | 52 | (* ::Subsubsection:: *) 53 | (*One-argument add*) 54 | 55 | 56 | VerificationTest[ 57 | Export["newfile.txt", Range[5], "Text"]; 58 | { 59 | GitAdd[FileNameJoin[fullPath["newfile.txt"]]] === {fullPath["newfile.txt"]} 60 | , Values@GitStatus[$Repo][[{"New","IndexNew"}]] === {{},{"newfile.txt"}} 61 | }, 62 | {True, True} 63 | ] 64 | 65 | 66 | (* ::Subsubsection:: *) 67 | (*One-argument reset*) 68 | 69 | 70 | VerificationTest[ 71 | { 72 | GitReset[fullPath["newfile.txt"]] === {fullPath["newfile.txt"]} 73 | , Values@GitStatus[$Repo][[{"New","IndexNew"}]] === {{"newfile.txt"},{}} 74 | }, 75 | {True, True} 76 | ] 77 | DeleteFile["newfile.txt"] 78 | 79 | 80 | (* ::Subsubsection:: *) 81 | (*Add a directory*) 82 | 83 | 84 | VerificationTest[ 85 | CreateDirectory["newdir"]; 86 | filelist = FileNameJoin[{"newdir", #}]& /@ {"abc.txt", "def.txt"}; 87 | Scan[Export[#, "abc", "Text"]&, filelist]; 88 | 89 | { 90 | GitAdd[$Repo, "newdir"] === filelist 91 | , Sort[Values@GitStatus[$Repo][[{"New","IndexNew"}]]] === {{}, Sort[filelist]} 92 | } 93 | , 94 | {True, True} 95 | ] 96 | 97 | 98 | (* ::Subsubsection:: *) 99 | (*Reset the directory*) 100 | 101 | 102 | VerificationTest[ 103 | { 104 | Sort[GitReset[$Repo, "newdir"]] === Sort[filelist] 105 | , Values@GitStatus[$Repo][[{"New","IndexNew"}]] === {Sort[filelist],{}} 106 | } 107 | , 108 | {True, True} 109 | ] 110 | DeleteDirectory["newdir", DeleteContents->True]; 111 | 112 | 113 | (* ::Subsubsection:: *) 114 | (*Add a change*) 115 | 116 | 117 | VerificationTest[ 118 | changefile["new.txt"]; 119 | 120 | { 121 | GitAdd[$Repo, "new.txt"] === {"new.txt"} 122 | , Values@GitStatus[$Repo][[{"Modified","IndexModified"}]] === {{},{"new.txt"}} 123 | } 124 | , 125 | {True, True} 126 | ] 127 | 128 | 129 | (* ::Subsubsection:: *) 130 | (*Reset a change*) 131 | 132 | 133 | VerificationTest[ 134 | { 135 | GitReset[$Repo, "new.txt"] === {"new.txt"} 136 | , Values@GitStatus[$Repo][[{"Modified","IndexModified"}]] === {{"new.txt"},{}} 137 | } 138 | , 139 | {True, True} 140 | ] 141 | GitLink`Private`GitCheckoutFiles[$Repo,"HEAD","CheckoutStrategy"->"Force"]; 142 | 143 | 144 | (* ::Subsubsection:: *) 145 | (*Add a deletion*) 146 | 147 | 148 | VerificationTest[ 149 | DeleteFile["README"]; 150 | 151 | { 152 | Values@GitStatus[$Repo][[{"Deleted", "IndexDeleted"}]] === {{"README"}, {}} 153 | , GitAdd[$Repo, "README"] === {"README"} 154 | , Values@GitStatus[$Repo][[{"Deleted", "IndexDeleted"}]] === {{}, {"README"}} 155 | } 156 | , 157 | {True, True, True} 158 | ] 159 | 160 | 161 | (* ::Subsubsection:: *) 162 | (*Reset a deletion*) 163 | 164 | 165 | VerificationTest[ 166 | { 167 | GitReset[$Repo, "README"] === {"README"} 168 | , Values@GitStatus[$Repo][[{"Deleted","IndexDeleted"}]] === {{"README"},{}} 169 | } 170 | , 171 | {True, True} 172 | ] 173 | GitLink`Private`GitCheckoutFiles[$Repo,"HEAD","CheckoutStrategy"->"Force"]; 174 | 175 | 176 | (* ::Subsubsection:: *) 177 | (*Add a change to a new file*) 178 | 179 | 180 | VerificationTest[ 181 | Export["newfile.txt", Range[5], "Text"]; 182 | GitAdd[$Repo, "newfile.txt"]; 183 | changefile["newfile.txt"]; 184 | 185 | { 186 | Values@GitStatus[$Repo][[{"Modified", "IndexNew"}]] === {{"newfile.txt"}, {"newfile.txt"}} 187 | , GitAdd[$Repo, "newfile.txt"] === {"newfile.txt"} 188 | , Values@GitStatus[$Repo][[{"Modified", "IndexNew"}]] === {{}, {"newfile.txt"}} 189 | } 190 | , 191 | {True, True, True} 192 | ] 193 | 194 | 195 | (* ::Subsubsection:: *) 196 | (*Delete new file, then reset the file*) 197 | 198 | 199 | VerificationTest[ 200 | DeleteFile["newfile.txt"]; 201 | { 202 | GitReset[$Repo, "newfile.txt"] === {"newfile.txt"} 203 | , Flatten[Values@GitStatus[$Repo]] === {} 204 | } 205 | , 206 | {True, True} 207 | ] 208 | 209 | 210 | ResetDirectory[]; 211 | GitClose[$Repo]; 212 | DeleteDirectory[$RepoDirectory, DeleteContents->True]; 213 | -------------------------------------------------------------------------------- /src/classes/RemoteConnector.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include 10 | #include 11 | #include "mathlink.h" 12 | #include "RemoteConnector.h" 13 | #include "WolframLibrary.h" 14 | #include "MLHelper.h" 15 | 16 | RemoteConnector::RemoteConnector(WolframLibraryData libData, git_repository* repo, const char* remoteName, const char* privateKeyFile) 17 | : remoteName_(remoteName == NULL ? "" : remoteName) 18 | , keyFile_(privateKeyFile == NULL ? "" : privateKeyFile) 19 | , libData_(libData) 20 | { 21 | git_remote_init_callbacks(&callbacks_, GIT_REMOTE_CALLBACKS_VERSION); 22 | callbacks_.credentials = &AcquireCredsCallback; 23 | callbacks_.payload = this; 24 | callbacks_.transfer_progress = &TransferProgressCallback; 25 | callbacks_.sideband_progress = &SidebandProgressCallback; 26 | 27 | if (!remoteName || git_remote_lookup(&remote_, repo, remoteName)) 28 | remote_ = NULL; 29 | 30 | isValidRemote_ = (remote_ != NULL); 31 | } 32 | 33 | RemoteConnector::~RemoteConnector() 34 | { 35 | if (remote_) 36 | git_remote_free(remote_); 37 | } 38 | 39 | 40 | bool RemoteConnector::clone(git_repository** repo, const char* uri, const char* localPath, git_clone_options* options, const MLExpr& progressFunction) 41 | { 42 | options->fetch_opts.callbacks = callbacks_; 43 | progressFunction_ = progressFunction; 44 | 45 | int err = git_clone(repo, uri, localPath, options); 46 | if (err != 0 && credentialAttempts_ == 1 && triedSshAgent_) 47 | err = git_clone(repo, uri, localPath, options); // Necessary under Windows, not on MacOS 48 | progressFunction_ = MLExpr(); 49 | credentialAttempts_ = 0; 50 | return (err == 0); 51 | } 52 | 53 | bool RemoteConnector::connect_(git_direction direction) 54 | { 55 | int result = git_remote_connect(remote_, direction, &callbacks_, NULL, NULL); 56 | if (result != 0 && credentialAttempts_ == 1 && triedSshAgent_) 57 | result = git_remote_connect(remote_, direction, &callbacks_, NULL, NULL); 58 | credentialAttempts_ = 0; 59 | return (result == 0); 60 | } 61 | 62 | 63 | int RemoteConnector::AcquireCredsCallback(git_cred** cred, const char* url, const char *username, unsigned int allowed_types, void* payload) 64 | { 65 | RemoteConnector* connector = static_cast(payload); 66 | const int MaxAttempts = 3; 67 | connector->credentialAttempts_++; 68 | 69 | if (connector->credentialAttempts_ > MaxAttempts) 70 | return -1; 71 | 72 | if ((allowed_types & GIT_CREDTYPE_DEFAULT) != 0) 73 | { 74 | git_cred_default_new(cred); 75 | } 76 | else if ((allowed_types & GIT_CREDTYPE_SSH_KEY) != 0 && !connector->keyFile_.empty()) 77 | { 78 | if (connector->credentialAttempts_ == 1) 79 | { 80 | git_cred_ssh_key_from_agent(cred, username); 81 | connector->triedSshAgent_ = true; 82 | } 83 | else 84 | { 85 | std::string keyFile(connector->keyFile_); 86 | git_cred_ssh_key_new(cred, username, NULL, keyFile.c_str(), ""); 87 | } 88 | } 89 | else if ((allowed_types & GIT_CREDTYPE_USERPASS_PLAINTEXT) != 0) 90 | { 91 | // git_cred_userpass_plaintext(cred, userName, password); 92 | } 93 | else if ((allowed_types & GIT_CREDTYPE_SSH_INTERACTIVE) != 0) 94 | { 95 | // git_cred_ssh_interactive_new(cred, username_from_url, promptCallback, payload); 96 | } 97 | else if ((allowed_types & GIT_CREDTYPE_SSH_CUSTOM) != 0) 98 | { 99 | // git_cred_ssh_custom_new(cred, username_from_url, promptCallback, payload); 100 | } 101 | // not implemented and doesn't need to be 102 | // else if ((allowed_types & GIT_CREDTYPE_SSH_CUSTOM) != 0) 103 | return 0; 104 | } 105 | 106 | int RemoteConnector::TransferProgressCallback(const git_transfer_progress* stats, void* payload) 107 | { 108 | RemoteConnector* connector = static_cast(payload); 109 | if (connector->libData_ && connector->libData_->AbortQ()) 110 | return -1; 111 | if (std::chrono::steady_clock::now() - connector->lastProgressCheckpoint_ < std::chrono::milliseconds(200)) 112 | return 0; 113 | if (!connector->progressFunction_.isNull() && !connector->progressFunction_.testSymbol("None")) 114 | { 115 | MLHelper helper(connector->libData_->getMathLink(connector->libData_)); 116 | helper.beginFunction("EvaluatePacket"); 117 | helper.beginFunction(connector->progressFunction_); 118 | helper.beginFunction("Association"); 119 | 120 | helper.putRule("TotalObjects"); 121 | helper.putInt(stats->total_objects); 122 | 123 | helper.putRule("IndexedObjects"); 124 | helper.putInt(stats->indexed_objects); 125 | 126 | helper.putRule("ReceivedObjects"); 127 | helper.putInt(stats->received_objects); 128 | 129 | helper.putRule("LocalObjects"); 130 | helper.putInt(stats->local_objects); 131 | 132 | helper.putRule("TotalDeltas"); 133 | helper.putInt(stats->total_deltas); 134 | 135 | helper.putRule("IndexedDeltas"); 136 | helper.putInt(stats->indexed_deltas); 137 | 138 | helper.processAndIgnore(connector->libData_); 139 | 140 | connector->lastProgressCheckpoint_ = std::chrono::steady_clock::now(); 141 | } 142 | return 0; 143 | } 144 | 145 | int RemoteConnector::SidebandProgressCallback(const char* str, int len, void* payload) 146 | { 147 | RemoteConnector* connector = static_cast(payload); 148 | if (connector->libData_ && connector->libData_->AbortQ()) 149 | return -1; 150 | return 0; 151 | } 152 | -------------------------------------------------------------------------------- /src/classes/RepoStatus.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "mathlink.h" 14 | #include "WolframLibrary.h" 15 | #include "git2.h" 16 | #include "GitLinkRepository.h" 17 | 18 | #include "Message.h" 19 | #include "MLExpr.h" 20 | #include "MLHelper.h" 21 | #include "RepoInterface.h" 22 | #include "RepoStatus.h" 23 | #include "Signature.h" 24 | 25 | #if WIN 26 | #include 27 | #include 28 | #endif 29 | 30 | RepoStatus::RepoStatus(GitLinkRepository& repo, bool doRenames, bool includeIgnored, bool recurseUntrackedDirs) 31 | : repo_(repo) 32 | , isValid_(false) 33 | , doRenames_(doRenames) 34 | , includeIgnored_(includeIgnored) 35 | , recurseUntrackedDirs_(recurseUntrackedDirs) 36 | { 37 | updateStatus(); 38 | } 39 | 40 | void RepoStatus::updateStatus() 41 | { 42 | isValid_ = false; 43 | indexStatus_.clear(); 44 | workingTreeStatus_.clear(); 45 | 46 | if (!repo_.isValid()) 47 | { 48 | propagateError(repo_); 49 | return; 50 | } 51 | if (git_repository_is_bare(repo_.repo())) 52 | { 53 | errCode_ = Message::BareRepo; 54 | return; 55 | } 56 | 57 | git_status_options options; 58 | git_status_init_options(&options, GIT_STATUS_OPTIONS_VERSION); 59 | 60 | options.show = GIT_STATUS_SHOW_INDEX_ONLY; 61 | options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_EXCLUDE_SUBMODULES | GIT_STATUS_OPT_UPDATE_INDEX; 62 | if (doRenames_) 63 | options.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | 64 | GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR | 65 | GIT_STATUS_OPT_RENAMES_FROM_REWRITES; 66 | if (includeIgnored_) 67 | options.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED; 68 | if (recurseUntrackedDirs_) 69 | options.flags |= GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; 70 | 71 | if (git_status_foreach_ext(repo_.repo(), &options, RepoStatus::statusCallback_, (void *)&indexStatus_)) 72 | { 73 | errCode_ = Message::NoIndex; 74 | return; 75 | } 76 | 77 | options.show = GIT_STATUS_SHOW_WORKDIR_ONLY; 78 | if (git_status_foreach_ext(repo_.repo(), &options, RepoStatus::statusCallback_, (void*)&workingTreeStatus_)) 79 | { 80 | errCode_ = Message::NoWorkingTree; 81 | return; 82 | } 83 | isValid_ = true; 84 | } 85 | 86 | void RepoStatus::writeStatus(MLINK lnk) 87 | { 88 | MLHelper helper(lnk); 89 | helper.beginFunction("Association"); 90 | 91 | writeFiles_(helper, "New", GIT_STATUS_WT_NEW); 92 | writeFiles_(helper, "Modified", GIT_STATUS_WT_MODIFIED); 93 | writeFiles_(helper, "Deleted", GIT_STATUS_WT_DELETED); 94 | if (doRenames_) 95 | writeFiles_(helper, "Renamed", GIT_STATUS_WT_RENAMED); 96 | writeFiles_(helper, "TypeChange", GIT_STATUS_WT_TYPECHANGE); 97 | if (includeIgnored_) 98 | writeFiles_(helper, "Ignored", GIT_STATUS_IGNORED); 99 | writeFiles_(helper, "IndexNew", GIT_STATUS_INDEX_NEW); 100 | writeFiles_(helper, "IndexModified", GIT_STATUS_INDEX_MODIFIED); 101 | writeFiles_(helper, "IndexDeleted", GIT_STATUS_INDEX_DELETED); 102 | if (doRenames_) 103 | writeFiles_(helper, "IndexRenamed", GIT_STATUS_INDEX_RENAMED); 104 | writeFiles_(helper, "IndexTypeChange", GIT_STATUS_INDEX_TYPECHANGE); 105 | 106 | helper.endFunction(); 107 | } 108 | 109 | void RepoStatus::convertFileNamesToLower(WolframLibraryData libData) 110 | { 111 | // Implementation is a bit wasteful with time and memory, but the results of 112 | // RepoStatus are often small, so I'm valuing the compact code more here. 113 | FileStatusMap tmp; 114 | 115 | for (const auto& entry : indexStatus_) 116 | tmp.insert({MLToLower(libData, entry.first), entry.second}); 117 | indexStatus_ = tmp; 118 | tmp.clear(); 119 | 120 | for (const auto& entry : workingTreeStatus_) 121 | tmp.insert({MLToLower(libData, entry.first), entry.second}); 122 | workingTreeStatus_ = tmp; 123 | } 124 | 125 | FileNameSet RepoStatus::allFileNames() 126 | { 127 | FileNameSet fileSet; 128 | std::string str; 129 | 130 | for (const auto& it : indexStatus_) 131 | { 132 | str = it.first; 133 | fileSet.insert(str); 134 | } 135 | for (const auto& it : workingTreeStatus_) 136 | { 137 | str = it.first; 138 | fileSet.insert(str); 139 | } 140 | 141 | return fileSet; 142 | } 143 | 144 | bool RepoStatus::fileChanged(const std::string& filePath) 145 | { 146 | if (indexStatus_.count(filePath)) 147 | return true; 148 | if (workingTreeStatus_.count(filePath)) 149 | return true; 150 | return false; 151 | } 152 | 153 | void RepoStatus::writeFiles_(MLHelper& helper, const char* keyName, git_status_t status) 154 | { 155 | FileStatusMap& statusList = workingTreeStatus_; 156 | if (status == GIT_STATUS_INDEX_NEW || 157 | status == GIT_STATUS_INDEX_MODIFIED || 158 | status == GIT_STATUS_INDEX_DELETED || 159 | status == GIT_STATUS_INDEX_RENAMED || 160 | status == GIT_STATUS_INDEX_TYPECHANGE) 161 | { 162 | statusList = indexStatus_; 163 | } 164 | 165 | helper.putRule(keyName); 166 | helper.beginList(); 167 | 168 | for (auto& entry : statusList) 169 | { 170 | if ((entry.second & status) != 0) 171 | helper.putString(PathString(entry.first)); 172 | } 173 | 174 | helper.endList(); 175 | } 176 | 177 | int RepoStatus::statusCallback_(const char* path, unsigned int status_flags, void* payload) 178 | { 179 | FileStatusMap* statusList = (FileStatusMap*) payload; 180 | statusList->insert({path, status_flags}); 181 | return 0; 182 | } 183 | 184 | std::deque FileNameSet::getPathSpecMatches(const PathString& spec) 185 | { 186 | std::deque matches; 187 | const char* specCstr = spec.git().c_str(); 188 | const git_strarray specArray { const_cast(&specCstr), 1 }; 189 | git_pathspec* pathspec; 190 | 191 | git_pathspec_new(&pathspec, &specArray); 192 | 193 | for (const auto& it : *this) 194 | if (git_pathspec_matches_path(pathspec, 0, it.c_str())) 195 | matches.push_back(it); 196 | 197 | git_pathspec_free(pathspec); 198 | return matches; 199 | } 200 | -------------------------------------------------------------------------------- /src/classes/Signature.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include 10 | 11 | #include "mathlink.h" 12 | #include "WolframLibrary.h" 13 | #include "git2.h" 14 | #include "Signature.h" 15 | #include "GitLinkRepository.h" 16 | #include "GitLinkCommit.h" 17 | 18 | #include "Message.h" 19 | #include "MLHelper.h" 20 | #include "RepoInterface.h" 21 | 22 | 23 | Signature::Signature() 24 | { 25 | const char* name = NULL; 26 | const char* email = NULL; 27 | git_config* config; 28 | git_buf buf = GIT_BUF_INIT_CONST(NULL, 0); 29 | 30 | if (git_config_open_default(&config)) 31 | return; 32 | 33 | if (!git_config_get_string_buf(&buf, config, "user.name")) 34 | { 35 | name = strdup(buf.ptr); 36 | git_buf_free(&buf); 37 | buf = GIT_BUF_INIT_CONST(NULL, 0); 38 | } 39 | if (!git_config_get_string_buf(&buf, config, "user.email")) 40 | { 41 | email = strdup(buf.ptr); 42 | git_buf_free(&buf); 43 | } 44 | 45 | if (name && email) 46 | git_signature_now(&sig_, name, email); 47 | if (name) 48 | free((void*)name); 49 | if (email) 50 | free((void*)email); 51 | git_config_free(config); 52 | } 53 | 54 | Signature::Signature(const Signature& signature) 55 | { 56 | git_signature_dup(&sig_, signature.sig_); 57 | } 58 | 59 | Signature::Signature(const GitLinkRepository& repo) 60 | { 61 | git_signature_default(&sig_, const_cast(repo.repo())); 62 | if (sig_ == NULL) 63 | *this = Signature(); 64 | } 65 | 66 | Signature::Signature(const GitLinkRepository& repo, const MLExpr& expr) 67 | { 68 | if (expr.testSymbol("Automatic") || expr.testSymbol("None") || (expr.isList() && expr.length() == 0)) 69 | *this = Signature(repo); 70 | else 71 | *this = Signature(expr); 72 | } 73 | 74 | Signature::Signature(const MLExpr& expr) 75 | { 76 | MLExpr e = expr; 77 | 78 | if (e.isList()) 79 | { 80 | if (e.length() == 0) 81 | { 82 | *this = Signature(); 83 | return; 84 | } 85 | else if (expr.length() == 1) 86 | e = e.part(1); 87 | else if (expr.length() >= 2) 88 | { 89 | GitLinkRepository repo(expr.part(1)); 90 | GitLinkCommit commit(repo, expr.part(2)); 91 | const git_commit* c = commit.commit(); 92 | const git_signature* s; 93 | 94 | if (c == NULL) 95 | return; 96 | if (expr.length() >= 3 && expr.part(3).testString("Author")) 97 | s = git_commit_author(c); 98 | else 99 | s = git_commit_committer(c); 100 | git_signature_dup(&sig_, s); 101 | return; 102 | } 103 | } 104 | if (e.testHead("Association")) 105 | { 106 | MLExpr nameExpr; 107 | MLExpr emailExpr; 108 | MLExpr timeExpr; 109 | 110 | for (int i = 1; i <= e.length(); i++) 111 | { 112 | if (!e.part(i).isRule()) 113 | continue; 114 | MLExpr key = e.part(i).part(1); 115 | MLExpr value = e.part(i).part(2); 116 | if (key.testString("Name")) 117 | nameExpr = value; 118 | else if (key.testString("Email")) 119 | emailExpr = value; 120 | else if (key.testString("TimeStamp")) 121 | timeExpr = value; 122 | } 123 | 124 | const char* name = nameExpr.asString(); 125 | const char* email = emailExpr.asString(); 126 | time_t timeStamp = 0; 127 | int offset = 0; 128 | if (timeExpr.testHead("DateObject") && timeExpr.length() >= 1 && 129 | timeExpr.part(1).isList() && timeExpr.part(1).length() >= 3) 130 | { 131 | double timeZone = 0.; 132 | struct tm local_tm = { 0, 0, 0, 1, 0, 0, 0, 0, -1}; 133 | local_tm.tm_mday = timeExpr.part(1).part(3).asInt(); 134 | local_tm.tm_mon = timeExpr.part(1).part(2).asInt() - 1; 135 | local_tm.tm_year = timeExpr.part(1).part(1).asInt() - 1900; 136 | 137 | if (timeExpr.part(1).length() >= 4) 138 | { 139 | local_tm.tm_hour = timeExpr.part(1).part(4).asInt(); 140 | if (timeExpr.part(1).length() >= 5) 141 | local_tm.tm_min = timeExpr.part(1).part(5).asInt(); 142 | if (timeExpr.part(1).length() >= 6) 143 | local_tm.tm_sec = timeExpr.part(1).part(6).asInt(); 144 | } 145 | else if (timeExpr.part(2).testHead("TimeObject") && timeExpr.part(2).part(1).isList()) 146 | { 147 | if (timeExpr.part(2).part(1).length() >= 1) 148 | local_tm.tm_hour = timeExpr.part(2).part(1).part(1).asInt(); 149 | if (timeExpr.part(2).part(1).length() >= 2) 150 | local_tm.tm_min = timeExpr.part(2).part(1).part(2).asInt(); 151 | if (timeExpr.part(2).part(1).length() >= 3) 152 | local_tm.tm_sec = timeExpr.part(2).part(1).part(3).asInt(); 153 | } 154 | for (int i = 2; i <= timeExpr.length(); i++) 155 | { 156 | if (timeExpr.part(i).isReal() || timeExpr.part(i).isInteger()) 157 | { 158 | timeZone = timeExpr.part(i).asDouble(); 159 | break; 160 | } 161 | if (timeExpr.part(i).isRule() && timeExpr.part(i).part(1).testSymbol("TimeZone")) 162 | { 163 | timeZone = timeExpr.part(i).part(2).asDouble(); 164 | break; 165 | } 166 | } 167 | offset = 60 * timeZone; 168 | timeStamp = mktime(&local_tm); 169 | } 170 | if (timeStamp && name && email) 171 | git_signature_new(&sig_, name, email, timeStamp, offset); 172 | } 173 | else if (e.isInteger()) 174 | *this = Signature(GitLinkRepository(e)); 175 | if (sig_ == NULL) 176 | *this = Signature(); 177 | } 178 | 179 | Signature::Signature(const git_signature* signature) 180 | { 181 | git_signature_dup(&sig_, signature); 182 | } 183 | 184 | Signature& Signature::operator=(const Signature& signature) 185 | { 186 | if (sig_) 187 | git_signature_free(sig_); 188 | sig_ = NULL; 189 | if (signature.sig_) 190 | git_signature_dup(&sig_, signature.sig_); 191 | return *this; 192 | } 193 | 194 | void Signature::writeAssociation(MLINK lnk) const 195 | { 196 | MLHelper helper(lnk); 197 | writeAssociation(helper); 198 | } 199 | 200 | void Signature::writeAssociation(MLHelper& helper) const 201 | { 202 | if (sig_) 203 | { 204 | helper.beginFunction("Association"); 205 | helper.putRule("Name", sig_->name); 206 | helper.putRule("Email", sig_->email); 207 | helper.putRule("TimeStamp", sig_->when); 208 | helper.endFunction(); 209 | } 210 | else 211 | helper.putSymbol("$Failed"); 212 | } 213 | -------------------------------------------------------------------------------- /src/re_build.wl: -------------------------------------------------------------------------------- 1 | (* ::Package:: *) 2 | 3 | (* ::Title:: *) 4 | (*This file is largely obsolete.*) 5 | (*It was used by Jenkins builds*) 6 | 7 | 8 | (* ::Section:: *) 9 | (*dependencies*) 10 | 11 | 12 | Needs["CCompilerDriver`"] 13 | 14 | 15 | (* ::Section:: *) 16 | (*gather build data from environment*) 17 | 18 | 19 | (* 20 | This section is jenkins-specific for the moment and will be separated from the logic 21 | of the build script as these pieces get abstracted out to a generalized build framework. 22 | This initial implementation will act as boilerplating for the moment. re_build.wl will get 23 | folded into build.wl as soon as this is done. 24 | *) 25 | 26 | env = Association[GetEnvironment[]]; 27 | ws = env["WORKSPACE"]; 28 | buildPlatform = env["BUILD_PLATFORM"]; 29 | creationID = env["CREATIONID"]; 30 | job = env["JOB_NAME"]; 31 | filesDir = FileNameJoin[{ws, "Files"}]; 32 | compilerBin = env["COMPILER_BIN"]; 33 | compilerHome = env["COMPILER_HOME"]; 34 | 35 | (* infer targetID from JOB_NAME *) 36 | componentName = StringSplit[job, "."][[2]]; 37 | targetID = StringSplit[job, "."][[3]]; 38 | 39 | 40 | 41 | (* ::Section:: *) 42 | (*component-specific values*) 43 | 44 | 45 | base = FileNameJoin[{ws, componentName}]; 46 | src = FileNames["*.cpp", FileNameJoin[{base, "src"}], Infinity]; 47 | srcDirs = Select[FileNames["*", FileNameJoin[{base, "src"}]], DirectoryQ]; 48 | cmp = FileNameJoin[{ws, "Components"}]; 49 | includeDirs = {FileNameJoin[{cmp, "libgit2", "0.28.3", "Source", "include"}]}; 50 | compileOpts = ""; 51 | 52 | 53 | libDirs = Switch[targetID, 54 | "Windows"|"Windows-x86-64", { 55 | FileNameJoin[{cmp, "libgit2", "0.28.3", targetID, "vc140"}], 56 | FileNameJoin[{cmp, "LIBSSH2", "1.9.0", targetID, "vc141", "lib"}] 57 | }, 58 | "MacOSX-x86-64", { 59 | FileNameJoin[{cmp, "libgit2", "0.28.3", targetID, "libcxx-min10.12"}], 60 | FileNameJoin[{cmp, "OpenSSL", "1.1.1c", targetID, "libcxx-min10.12", "lib"}], 61 | FileNameJoin[{cmp, "LIBSSH2", "1.9.0", targetID, "libcxx-min10.12", "lib"}] 62 | }, 63 | "Linux"|"Linux-x86-64", { 64 | FileNameJoin[{cmp, "libgit2", "0.28.3", targetID, "scientific6-gcc7.3"}], 65 | FileNameJoin[{cmp, "OpenSSL", "1.1.1c", targetID, "scientific6-gcc4.8", "lib"}], 66 | FileNameJoin[{cmp, "LIBSSH2", "1.9.0", targetID, "scientific6-gcc4.8", "lib"}] 67 | }, 68 | _, {} 69 | ]; 70 | 71 | 72 | libDirsOldSSL = Switch[targetID, 73 | "Windows"|"Windows-x86-64", { 74 | FileNameJoin[{cmp, "libgit2", "0.28.3", targetID, "vc140.ssl100"}], 75 | FileNameJoin[{cmp, "LIBSSH2", "1.8.0", targetID, "vc141", "lib"}] 76 | }, 77 | "MacOSX-x86-64", { 78 | FileNameJoin[{cmp, "libgit2", "0.28.3", targetID, "libcxx-min10.12.ssl100"}], 79 | FileNameJoin[{cmp, "OpenSSL", "1.0.2s", targetID, "libcxx-min10.9", "lib"}], 80 | FileNameJoin[{cmp, "LIBSSH2", "1.8.0", targetID, "libcxx-min10.9", "lib"}] 81 | }, 82 | "Linux"|"Linux-x86-64", { 83 | FileNameJoin[{cmp, "libgit2", "0.28.3", targetID, "scientific6-gcc7.3.ssl100"}], 84 | FileNameJoin[{cmp, "OpenSSL", "1.0.2s", targetID, "scientific6-gcc4.8", "lib"}], 85 | FileNameJoin[{cmp, "LIBSSH2", "1.8.0", targetID, "scientific6-gcc4.8", "lib"}] 86 | }, 87 | _, {} 88 | ]; 89 | 90 | 91 | compileOpts = Switch[$OperatingSystem, 92 | "Windows", "/MT /EHsc", 93 | "MacOSX", "-std=c++14 -stdlib=libc++ -mmacosx-version-min=10.9 -framework Security", 94 | "Unix", "-Wno-deprecated -std=c++14"]; 95 | linkerOpts = Switch[$OperatingSystem, 96 | "MacOSX", {"-install_name", "@rpath/gitLink.dylib", "-rpath", "@loader_path"}, 97 | "Unix", {"-rpath='$ORIGIN'"}, 98 | "Windows", {"/NODEFAULTLIB:msvcrt"}, 99 | _, ""]; 100 | oslibs = Switch[$OperatingSystem, 101 | "Windows", {"advapi32", "ole32", "rpcrt4", "shlwapi", "user32", "winhttp", "crypt32", "libssh2"}, 102 | "MacOSX", {"z", "iconv", "crypto", "ssh2"}, 103 | "Unix", {"z", "rt", "pthread", "ssh2", "ssl"} 104 | ]; 105 | defines = {Switch[$OperatingSystem, 106 | "Windows", "WIN", 107 | "MacOSX", "MAC", 108 | "Unix", "UNIX"]}; 109 | If[StringMatchQ[targetID, "*64*"], AppendTo[defines, "SIXTYFOURBIT"]]; 110 | 111 | 112 | destDir = FileNameJoin[{filesDir, "GitLink", "LibraryResources", targetID}]; 113 | If[!DirectoryQ[destDir], CreateDirectory[destDir]]; 114 | 115 | 116 | (* ::Section:: *) 117 | (*build library*) 118 | 119 | 120 | lib = CreateLibrary[src, "gitLink", 121 | "TargetDirectory"->destDir, 122 | "TargetSystemID"->targetID, 123 | "Language"->"C++", 124 | "CompileOptions"->compileOpts, 125 | "CompilerName"->compilerBin, 126 | "CompilerInstallation"->compilerHome, 127 | "Defines"->defines, 128 | "LinkerOptions"->linkerOpts, 129 | "IncludeDirectories"->Flatten[{includeDirs, srcDirs}], 130 | "LibraryDirectories"->libDirs, 131 | "Libraries"->Prepend[oslibs, "git2"], 132 | "ShellOutputFunction"->Print, 133 | "ShellCommandFunction"->Print 134 | ]; 135 | 136 | (* we should probably terminate if the compile didn't succeed *) 137 | If[lib === $Failed, 138 | Print["### ERROR: No library produced. Terminating build... ###"]; 139 | Exit[1] 140 | ]; 141 | 142 | lib_ssl100 = CreateLibrary[src, "gitLink_ssl100", 143 | "TargetDirectory"->destDir, 144 | "TargetSystemID"->targetID, 145 | "Language"->"C++", 146 | "CompileOptions"->compileOpts, 147 | "CompilerName"->compilerBin, 148 | "CompilerInstallation"->compilerHome, 149 | "Defines"->defines, 150 | "LinkerOptions"->linkerOpts, 151 | "IncludeDirectories"->Flatten[{includeDirs, srcDirs}], 152 | "LibraryDirectories"->libDirsOldSSL, 153 | "Libraries"->Prepend[oslibs, "git2"], 154 | "ShellOutputFunction"->Print, 155 | "ShellCommandFunction"->Print 156 | ]; 157 | 158 | (* we should probably terminate if the compile didn't succeed *) 159 | If[lib_ssl100 === $Failed, 160 | Print["### ERROR: No gitLink_ssl100 library produced. Terminating build... ###"]; 161 | Exit[1] 162 | ]; 163 | 164 | 165 | (* ::Section:: *) 166 | (*produce artifact*) 167 | 168 | 169 | (* 170 | Using CreateArchive only for zip where we don't care about file permissions. 171 | *) 172 | 173 | nativeCreateArchive[src_String, dest_String]:=Module[{res}, 174 | res = RunProcess[{"tar", "czf", dest, src}]; 175 | Return[res]; 176 | ]; 177 | 178 | arcName = "Files"; 179 | 180 | arcFormat = If[$OperatingSystem === "Windows", 181 | "zip", 182 | "tar.gz" 183 | ]; 184 | 185 | pack := If[arcFormat === "zip", 186 | CreateArchive, 187 | nativeCreateArchive 188 | ]; 189 | 190 | arc = arcName<>"."<>arcFormat; 191 | 192 | SetDirectory[filesDir]; 193 | 194 | archived = pack[componentName, FileNameJoin[{ws,arc}]]; 195 | 196 | ResetDirectory[]; 197 | 198 | If[archived === $Failed, 199 | Print["### ERROR: No archive produced. Terminating build... ###"]; 200 | Exit[1] 201 | ]; 202 | 203 | 204 | (* ::Section:: *) 205 | (*generate RE metadata*) 206 | 207 | 208 | Export[FileNameJoin[{ws,"Build.ini"}], 209 | { 210 | "# Build.ini", 211 | "[job]", 212 | "name: "<>job, 213 | "url: "<>env["JOB_URL"], 214 | "[build]", 215 | "creationid: "<>creationID, 216 | "number: "<>env["BUILD_NUMBER"], 217 | "url: "<>env["BUILD_URL"], 218 | "[artifact]", 219 | "name: "<>arc 220 | }, 221 | {"Text", "Lines"} 222 | ] 223 | -------------------------------------------------------------------------------- /src/classes/MLExpr.cpp: -------------------------------------------------------------------------------- 1 | #include "mathlink.h" 2 | #include "MLExpr.h" 3 | #include "git2.h" 4 | #include "WolframLibrary.h" 5 | #include "MLHelper.h" 6 | 7 | MLExpr::MLExpr(MLINK lnk) 8 | : str_(NULL) 9 | , len_(0) 10 | , loopbackLink_(NULL) 11 | { 12 | int err; 13 | if (lnk) 14 | { 15 | loopbackLink_ = MLLoopbackOpen(MLLinkEnvironment(lnk), &err); 16 | MLTransferExpression(loopbackLink_, lnk); 17 | } 18 | } 19 | 20 | MLExpr::MLExpr(MLEnvironment mle, ConstructType type, const char* str) 21 | : str_(NULL) 22 | , len_(0) 23 | { 24 | int err; 25 | loopbackLink_ = MLLoopbackOpen(mle, &err); 26 | switch(type) 27 | { 28 | case eConstructEmptyFunction: 29 | MLPutFunction(loopbackLink_, str, 0); 30 | break; 31 | case eConstructSymbol: 32 | MLPutSymbol(loopbackLink_, str); 33 | break; 34 | case eConstructString: 35 | MLPutUTF8String(loopbackLink_, (const unsigned char*)str, (int)strlen(str)); 36 | break; 37 | default: 38 | MLClose(loopbackLink_); 39 | loopbackLink_ = NULL; 40 | break; 41 | } 42 | } 43 | 44 | MLExpr::MLExpr(const MLExpr& expr) 45 | : str_(NULL) 46 | , loopbackLink_(NULL) 47 | , len_(0) 48 | { 49 | int err; 50 | if (expr.loopbackLink_ == NULL) 51 | return; 52 | loopbackLink_ = MLLoopbackOpen(MLLinkEnvironment(expr.loopbackLink_), &err); 53 | MLAutoMark mark(expr.loopbackLink_, true); 54 | MLTransferExpression(loopbackLink_, expr.loopbackLink_); 55 | } 56 | 57 | MLExpr::MLExpr(MLExpr&& expr) 58 | : str_(expr.str_) 59 | , loopbackLink_(expr.loopbackLink_) 60 | , len_(expr.len_) 61 | { 62 | expr.str_ = NULL; 63 | expr.loopbackLink_ = NULL; 64 | expr.len_ = 0; 65 | } 66 | 67 | MLExpr& MLExpr::operator=(const MLExpr& expr) 68 | { 69 | if (str_) 70 | MLReleaseUTF8String(loopbackLink_, (const unsigned char*)str_, len_); 71 | if (loopbackLink_) 72 | MLClose(loopbackLink_); 73 | loopbackLink_ = NULL; 74 | 75 | str_ = NULL; 76 | len_ = 0; 77 | 78 | if (expr.loopbackLink_) 79 | { 80 | MLAutoMark mark(expr.loopbackLink_, true); 81 | int err; 82 | loopbackLink_ = MLLoopbackOpen(MLLinkEnvironment(expr.loopbackLink_), &err); 83 | MLTransferExpression(loopbackLink_, expr.loopbackLink_); 84 | } 85 | return *this; 86 | } 87 | 88 | MLExpr& MLExpr::operator=(MLExpr&& expr) 89 | { 90 | str_ = expr.str_; expr.str_ = NULL; 91 | loopbackLink_ = expr.loopbackLink_; expr.loopbackLink_ = NULL; 92 | len_ = expr.len_; expr.len_ = 0; 93 | return *this; 94 | } 95 | 96 | MLINK MLExpr::initializeLink(MLEnvironment env) 97 | { 98 | int err; 99 | return (loopbackLink_ = MLLoopbackOpen(env, &err)); 100 | } 101 | 102 | void MLExpr::putToLink(MLINK lnk) const 103 | { 104 | MLAutoMark mark(loopbackLink_, true); 105 | MLTransferExpression(lnk, loopbackLink_); 106 | } 107 | 108 | MLINK MLExpr::putToLoopbackLink() const 109 | { 110 | int err; 111 | MLINK loopback = MLLoopbackOpen(MLLinkEnvironment(loopbackLink_), &err); 112 | MLAutoMark mark(loopbackLink_, true); 113 | MLTransferExpression(loopback, loopbackLink_); 114 | return loopback; 115 | } 116 | 117 | void MLExpr::append(const MLExpr& expr) 118 | { 119 | if (!isFunction()) 120 | return; // should assert or something 121 | int err; 122 | MLINK oldLoopback = loopbackLink_; 123 | loopbackLink_ = MLLoopbackOpen(MLLinkEnvironment(oldLoopback), &err); 124 | 125 | int argCount; 126 | MLGetNext(oldLoopback); 127 | MLGetArgCount(oldLoopback, &argCount); 128 | 129 | MLPutType(loopbackLink_, MLTKFUNC); 130 | MLPutArgCount(loopbackLink_, argCount + 1); 131 | 132 | for (int i = 0; i <= argCount; i++) 133 | MLTransferExpression(loopbackLink_, oldLoopback); 134 | expr.putToLink(loopbackLink_); 135 | 136 | MLClose(oldLoopback); 137 | } 138 | 139 | bool MLExpr::testString(const char* str) const 140 | { 141 | if (loopbackLink_ == NULL) 142 | return false; 143 | MLAutoMark mark(loopbackLink_, true); 144 | if (MLGetNext(loopbackLink_) == MLTKSTR) 145 | { 146 | MLString linkStr(loopbackLink_); 147 | return (strcmp(linkStr, str) == 0); 148 | } 149 | return false; 150 | } 151 | 152 | bool MLExpr::testSymbol(const char* sym) const 153 | { 154 | if (loopbackLink_ == NULL) 155 | return false; 156 | MLAutoMark mark(loopbackLink_, true); 157 | if (MLGetNext(loopbackLink_) == MLTKSYM) 158 | { 159 | MLString linkStr(loopbackLink_); 160 | return (strcmp(linkStr, sym) == 0); 161 | } 162 | return false; 163 | } 164 | 165 | bool MLExpr::testHead(const char* sym) const 166 | { 167 | if (loopbackLink_ == NULL) 168 | return false; 169 | { 170 | MLAutoMark mark(loopbackLink_, true); 171 | if (MLGetNext(loopbackLink_) != MLTKFUNC) 172 | return false; 173 | } 174 | return part(0).testSymbol(sym); 175 | } 176 | 177 | MLExpr MLExpr::part(int i) const 178 | { 179 | int argCount; 180 | MLAutoMark mark(loopbackLink_, true); 181 | MLGetNext(loopbackLink_); 182 | MLGetArgCount(loopbackLink_, &argCount); 183 | 184 | for (int index = 0; index < i; index++) 185 | { 186 | MLExpr drainExpr(loopbackLink_); 187 | } 188 | return MLExpr(loopbackLink_); 189 | } 190 | 191 | int MLExpr::asInt() const 192 | { 193 | MLAutoMark mark(loopbackLink_, true); 194 | int i; 195 | return (MLGetInteger(loopbackLink_, &i) == 0) ? 0 : i; 196 | } 197 | 198 | mint MLExpr::asMint() const 199 | { 200 | MLAutoMark mark(loopbackLink_, true); 201 | mint i; 202 | return (MLGetMint(loopbackLink_, &i) == 0) ? 0 : i; 203 | } 204 | 205 | double MLExpr::asDouble() const 206 | { 207 | MLAutoMark mark(loopbackLink_, true); 208 | double d; 209 | return (MLGetDouble(loopbackLink_, &d) == 0) ? 0. : d; 210 | } 211 | 212 | const char* MLExpr::asString() const 213 | { 214 | int unused; 215 | if (!str_) 216 | { 217 | MLAutoMark mark(loopbackLink_, true); 218 | MLGetUTF8String(loopbackLink_, (const unsigned char**) &str_, &len_, &unused); 219 | } 220 | return str_; 221 | } 222 | 223 | const git_oid* MLExpr::asOid() const 224 | { 225 | static git_oid oid; 226 | const char* str; 227 | if (!str_ && isString()) 228 | asString(); 229 | if (!str_ && testHead("GitObject") && part(1).isString()) 230 | { 231 | MLAutoMark mark(loopbackLink_, true); 232 | int argCount; 233 | int unused; 234 | MLTestHead(loopbackLink_, "GitObject", &argCount); 235 | MLGetUTF8String(loopbackLink_, (const unsigned char**) &str_, &len_, &unused); 236 | } 237 | return (str_ != NULL && git_oid_fromstr(&oid, str_) == 0) ? &oid : NULL; 238 | } 239 | 240 | int MLExpr::length() const 241 | { 242 | if (loopbackLink_ == NULL) 243 | return 0; 244 | MLAutoMark mark(loopbackLink_, true); 245 | int len; 246 | MLGetNext(loopbackLink_); 247 | MLGetArgCount(loopbackLink_, &len); 248 | return len; 249 | } 250 | 251 | bool MLExpr::isInteger() const 252 | { 253 | if (loopbackLink_ == NULL) 254 | return false; 255 | MLAutoMark mark(loopbackLink_, true); 256 | return (MLGetNext(loopbackLink_) == MLTKINT); 257 | } 258 | 259 | bool MLExpr::isReal() const 260 | { 261 | if (loopbackLink_ == NULL) 262 | return false; 263 | MLAutoMark mark(loopbackLink_, true); 264 | return (MLGetNext(loopbackLink_) == MLTKREAL); 265 | } 266 | 267 | bool MLExpr::isSymbol() const 268 | { 269 | if (loopbackLink_ == NULL) 270 | return false; 271 | MLAutoMark mark(loopbackLink_, true); 272 | return (MLGetNext(loopbackLink_) == MLTKSYM); 273 | } 274 | 275 | bool MLExpr::isString() const 276 | { 277 | if (loopbackLink_ == NULL) 278 | return false; 279 | MLAutoMark mark(loopbackLink_, true); 280 | return (MLGetNext(loopbackLink_) == MLTKSTR); 281 | } 282 | 283 | bool MLExpr::isFunction() const 284 | { 285 | if (loopbackLink_ == NULL) 286 | return false; 287 | MLAutoMark mark(loopbackLink_, true); 288 | return (MLGetNext(loopbackLink_) == MLTKFUNC); 289 | } 290 | 291 | bool MLExpr::contains(const char* str) const 292 | { 293 | if (isString()) 294 | return testString(str); 295 | if (isSymbol()) 296 | return testSymbol(str); 297 | if (isFunction()) 298 | { 299 | for (int i = 1; i <= length(); i++) 300 | if (part(i).contains(str)) 301 | return true; 302 | } 303 | return false; 304 | } 305 | 306 | MLExpr MLExpr::lookupKey(const char* str) const 307 | { 308 | if (!testHead("Association")) 309 | return MLExpr(NULL); 310 | for (int i = 1; i <= length(); i++) 311 | { 312 | if (!part(i).isRule()) 313 | continue; 314 | if (part(i).part(1).testString(str)) 315 | return part(i).part(2); 316 | } 317 | return MLExpr(NULL); 318 | } 319 | -------------------------------------------------------------------------------- /src/interface/RefInterface.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include "mathlink.h" 10 | #include "WolframLibrary.h" 11 | #include "git2.h" 12 | #include "RepoInterface.h" 13 | #include "CommitInterface.h" 14 | #include "GitLinkRepository.h" 15 | #include "GitLinkCommit.h" 16 | #include "Message.h" 17 | #include "Signature.h" 18 | 19 | 20 | EXTERN_C DLLEXPORT int GitCreateBranch(WolframLibraryData libData, MLINK lnk) 21 | { 22 | long argCount; 23 | MLCheckFunction(lnk, "List", &argCount); 24 | 25 | GitLinkRepository repo(lnk); 26 | MLString branchName(lnk); 27 | GitLinkCommit commit(repo, lnk); 28 | MLExpr forceIt(lnk); 29 | 30 | if (commit.createBranch(branchName, forceIt.testSymbol("True"))) 31 | MLPutSymbol(lnk, "True"); 32 | else 33 | { 34 | commit.mlHandleError(libData, "GitCreateBranch"); 35 | MLPutSymbol(lnk, "False"); 36 | } 37 | 38 | return LIBRARY_NO_ERROR; 39 | } 40 | 41 | EXTERN_C DLLEXPORT int GitDeleteBranch(WolframLibraryData libData, MLINK lnk) 42 | { 43 | long argCount; 44 | MLCheckFunction(lnk, "List", &argCount); 45 | 46 | GitLinkRepository repo(lnk); 47 | MLString branchName(lnk); 48 | MLExpr forceIt(lnk); 49 | MLExpr remoteBranch(lnk); 50 | git_reference* branchRef = NULL; 51 | const char* err = NULL; 52 | 53 | // FIXME: force is unimplemented 54 | if (!repo.isValid()) 55 | err = Message::BadRepo; 56 | else if (git_branch_lookup(&branchRef, repo.repo(), branchName, remoteBranch.asBool() ? GIT_BRANCH_REMOTE : GIT_BRANCH_LOCAL) != 0) 57 | err = remoteBranch.asBool() ? Message::NoRemoteBranch : Message::NoLocalBranch; 58 | else if (git_branch_delete(branchRef)) 59 | err = Message::GitOperationFailed; 60 | 61 | if (branchRef) 62 | git_reference_free(branchRef); 63 | 64 | if (err) 65 | MLHandleError(libData, "GitDeleteBranch", err, (err == Message::GitOperationFailed) ? giterr_last()->message : branchName.str()); 66 | 67 | MLPutSymbol(lnk, (err == NULL) ? "Null" : "$Failed"); 68 | 69 | return LIBRARY_NO_ERROR; 70 | } 71 | 72 | EXTERN_C DLLEXPORT int GitMoveBranch(WolframLibraryData libData, MLINK lnk) 73 | { 74 | long argCount; 75 | MLCheckFunction(lnk, "List", &argCount); 76 | 77 | GitLinkRepository repo(lnk); 78 | MLString branchName(lnk); 79 | GitLinkCommit dest(repo, lnk); 80 | GitLinkCommit source(repo, lnk); 81 | git_reference* branchRef; 82 | std::string fullRefName("refs/heads/"); 83 | 84 | fullRefName += branchName; 85 | 86 | if (!repo.isValid()) 87 | { 88 | MLHandleError(libData, "GitMoveBranch", Message::BadRepo); 89 | MLPutSymbol(lnk, "False"); 90 | } 91 | else if (git_branch_lookup(&branchRef, repo.repo(), branchName, GIT_BRANCH_LOCAL) != 0) 92 | { 93 | MLHandleError(libData, "GitMoveBranch", Message::NoLocalBranch, branchName); 94 | MLPutSymbol(lnk, "False"); 95 | } 96 | else 97 | { 98 | int result; 99 | git_reference_free(branchRef); 100 | if (source.isValid()) 101 | result = git_reference_create_matching(&branchRef, repo.repo(), fullRefName.c_str(), 102 | dest.oid(), true, source.oid(), "GitLink: move branch"); 103 | else 104 | result = git_reference_create(&branchRef, repo.repo(), fullRefName.c_str(), 105 | dest.oid(), true, "GitLink: move branch"); 106 | 107 | if (result == 0) 108 | { 109 | git_reference_free(branchRef); 110 | MLPutSymbol(lnk, "True"); 111 | } 112 | else 113 | MLPutSymbol(lnk, "False"); 114 | } 115 | 116 | return LIBRARY_NO_ERROR; 117 | } 118 | 119 | EXTERN_C DLLEXPORT int GitUpstreamBranch(WolframLibraryData libData, MLINK lnk) 120 | { 121 | long argCount; 122 | MLCheckFunction(lnk, "List", &argCount); 123 | 124 | GitLinkRepository repo(lnk); 125 | MLString branchName(lnk); 126 | 127 | git_reference* branchRef = NULL; 128 | git_reference* upstreamBranchRef = NULL; 129 | const char* err = NULL; 130 | 131 | if (!repo.isValid()) 132 | err = Message::BadRepo; 133 | else if (git_branch_lookup(&branchRef, repo.repo(), branchName, GIT_BRANCH_LOCAL) != 0) 134 | err = Message::NoLocalBranch; 135 | 136 | if (err) 137 | { 138 | MLHandleError(libData, "GitUpstreamBranch", err); 139 | MLPutSymbol(lnk, "$Failed"); 140 | return LIBRARY_NO_ERROR; 141 | } 142 | 143 | int result = git_branch_upstream(&upstreamBranchRef, branchRef); 144 | if (result == GIT_ENOTFOUND) 145 | MLPutSymbol(lnk, "None"); 146 | else if (result == 0) 147 | { 148 | const char* upstreamBranchName; 149 | git_branch_name(&upstreamBranchName, upstreamBranchRef); 150 | MLPutUTF8String(lnk, (const unsigned char*) upstreamBranchName, (int) strlen(upstreamBranchName)); 151 | } 152 | else 153 | { 154 | MLHandleError(libData, "GitUpstreamBranch", Message::UpstreamFailed, branchName); 155 | MLPutSymbol(lnk, "$Failed"); 156 | } 157 | 158 | if (upstreamBranchRef) 159 | git_reference_free(upstreamBranchRef); 160 | if (branchRef) 161 | git_reference_free(branchRef); 162 | 163 | return LIBRARY_NO_ERROR; 164 | } 165 | 166 | EXTERN_C DLLEXPORT int GitSetUpstreamBranch(WolframLibraryData libData, MLINK lnk) 167 | { 168 | long argCount; 169 | MLCheckFunction(lnk, "List", &argCount); 170 | 171 | GitLinkRepository repo(lnk); 172 | MLString branchName(lnk); 173 | MLString upstreamBranch(lnk); 174 | 175 | git_reference* branchRef = NULL; 176 | const char* err = NULL; 177 | 178 | if (!repo.isValid()) 179 | err = Message::BadRepo; 180 | else if (git_branch_lookup(&branchRef, repo.repo(), branchName, GIT_BRANCH_LOCAL) != 0) 181 | err = Message::NoLocalBranch; 182 | else if (git_branch_set_upstream(branchRef, upstreamBranch) != 0) 183 | err = Message::SetUpstreamFailed; 184 | 185 | if (branchRef) 186 | git_reference_free(branchRef); 187 | 188 | MLHandleError(libData, "GitSetUpstreamBranch", err, branchName, upstreamBranch); 189 | MLPutSymbol(lnk, (err == NULL) ? "True" : "False"); 190 | 191 | return LIBRARY_NO_ERROR; 192 | } 193 | 194 | EXTERN_C DLLEXPORT int GitCreateTag(WolframLibraryData libData, MLINK lnk) 195 | { 196 | long argCount; 197 | MLCheckFunction(lnk, "List", &argCount); 198 | 199 | GitLinkRepository repo(lnk); 200 | MLString tagName(lnk); 201 | GitLinkCommit commit(repo, lnk); 202 | MLExpr logMessage(lnk); 203 | MLExpr forceIt(lnk); 204 | Signature signature(repo, lnk); 205 | bool force = forceIt.testSymbol("True"); 206 | git_oid oid; 207 | bool success; 208 | 209 | if (!commit.isValid() && (git_oid_iszero(commit.oid()) || !logMessage.isString())) 210 | success = false; // do nothing 211 | else if (!logMessage.isString()) 212 | success = !git_tag_create_lightweight(&oid, repo.repo(), tagName, commit.object(), force); 213 | else 214 | { 215 | git_object* obj; 216 | if (commit.object() != NULL) 217 | git_object_dup(&obj, commit.object()); 218 | else 219 | git_object_lookup(&obj, repo.repo(), commit.oid(), GIT_OBJ_ANY); 220 | success = !git_tag_create(&oid, repo.repo(), tagName, obj, signature, logMessage.asString(), force); 221 | git_object_free(obj); 222 | } 223 | 224 | MLHelper helper(lnk); 225 | if (success) 226 | helper.putGitObject(oid, repo); 227 | else 228 | { 229 | if (giterr_last() && giterr_last()->message) 230 | MLHandleError(libData, "GitCreateTag", Message::GitOperationFailed, giterr_last()->message); 231 | helper.putSymbol("$Failed"); 232 | } 233 | 234 | return LIBRARY_NO_ERROR; 235 | } 236 | 237 | EXTERN_C DLLEXPORT int GitDeleteTag(WolframLibraryData libData, MLINK lnk) 238 | { 239 | long argCount; 240 | MLCheckFunction(lnk, "List", &argCount); 241 | 242 | GitLinkRepository repo(lnk); 243 | MLString tagName(lnk); 244 | const char* err = NULL; 245 | 246 | // FIXME: force is unimplemented 247 | if (!repo.isValid()) 248 | err = Message::BadRepo; 249 | else if (git_tag_delete(repo.repo(), tagName)) 250 | err = Message::GitOperationFailed; 251 | 252 | if (err) 253 | MLHandleError(libData, "GitDeleteTag", err, (err == Message::GitOperationFailed) ? giterr_last()->message : NULL); 254 | 255 | MLPutSymbol(lnk, (err == NULL) ? "Null" : "$Failed"); 256 | 257 | return LIBRARY_NO_ERROR; 258 | } 259 | 260 | EXTERN_C DLLEXPORT int GitType(WolframLibraryData libData, MLINK lnk) 261 | { 262 | long argCount; 263 | MLCheckFunction(lnk, "List", &argCount); 264 | 265 | GitLinkRepository repo(lnk); 266 | MLString sha(lnk); 267 | git_oid oid; 268 | git_object* object; 269 | git_otype otype = GIT_OBJ_BAD; 270 | 271 | if (repo.isValid() && !git_oid_fromstr(&oid, sha) && !git_object_lookup(&object, repo.repo(), &oid, GIT_OBJ_ANY)) 272 | { 273 | otype = git_object_type(object); 274 | git_object_free(object); 275 | } 276 | 277 | if (strcmp(OtypeToString(otype), "None") == 0) 278 | MLPutSymbol(lnk, "None"); 279 | else 280 | MLPutString(lnk, OtypeToString(otype)); 281 | return LIBRARY_NO_ERROR; 282 | } 283 | 284 | EXTERN_C DLLEXPORT int ToGitObject(WolframLibraryData libData, MLINK lnk) 285 | { 286 | long argCount; 287 | MLCheckFunction(lnk, "List", &argCount); 288 | 289 | GitLinkRepository repo(lnk); 290 | MLString name(lnk); 291 | 292 | GitLinkCommit commit(repo, name); 293 | git_oid oid; 294 | git_object* object = NULL; 295 | 296 | if (!repo.isValid()) 297 | MLPutSymbol(lnk, "$Failed"); 298 | else if (commit.isValid()) 299 | commit.write(lnk); 300 | else 301 | { 302 | object = (git_object*) commit.tag(); 303 | if (object == NULL && !git_oid_fromstrp(&oid, name)) 304 | git_object_lookup(&object, repo.repo(), &oid, GIT_OBJ_ANY); 305 | if (object != NULL) 306 | { 307 | char sha[GIT_OID_HEXSZ+1]; 308 | MLHelper helper(lnk); 309 | git_oid_tostr(sha, GIT_OID_HEXSZ + 1, git_object_id(object)); 310 | helper.beginFunction("GitObject"); 311 | helper.putString(sha); 312 | helper.putRepo(repo); 313 | if (object != (git_object*) commit.tag()) 314 | git_object_free(object); 315 | } 316 | else 317 | MLPutSymbol(lnk, "$Failed"); 318 | } 319 | 320 | return LIBRARY_NO_ERROR; 321 | } 322 | -------------------------------------------------------------------------------- /src/classes/GitTree.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | #include "mathlink.h" 13 | #include "WolframLibrary.h" 14 | #include "git2.h" 15 | #include "GitLinkRepository.h" 16 | #include "GitLinkCommit.h" 17 | #include "GitTree.h" 18 | 19 | #include "Message.h" 20 | #include "MLHelper.h" 21 | 22 | typedef std::unordered_map TreeEntryMap; 23 | 24 | static bool git_tree_entry_equal(const git_tree_entry* a, const git_tree_entry* b) 25 | { 26 | return git_oid_equal(git_tree_entry_id(a), git_tree_entry_id(b)) && 27 | git_tree_entry_type(a) == git_tree_entry_type(b) && 28 | git_tree_entry_filemode(a) == git_tree_entry_filemode(b); 29 | } 30 | 31 | 32 | 33 | GitTree::GitTree(const GitLinkRepository& repo, git_index* index) 34 | : repo_(repo.key()) 35 | { 36 | if (!git_index_write_tree_to(&oid_, index, repo.repo())) 37 | git_tree_lookup(&tree_, repo.repo(), &oid_); 38 | } 39 | 40 | GitTree::GitTree(const GitLinkRepository& repo, const char* reference) 41 | : repo_(repo.key()) 42 | , tree_(GitLinkCommit(repo, reference).copyTree()) 43 | { 44 | if (tree_ == NULL) 45 | { 46 | GitLinkCommit commit = GitLinkCommit(repo, reference); 47 | propagateError(commit); 48 | } 49 | } 50 | 51 | GitTree::GitTree(const MLExpr& expr) 52 | : repo_(expr) 53 | { 54 | MLExpr e = expr; 55 | 56 | if (e.testSymbol("Automatic") && repo_.isValid()) 57 | { 58 | git_index* index; 59 | if (!git_repository_index(&index, repo_.repo())) 60 | { 61 | if (!git_index_write_tree(&oid_, index)) 62 | git_tree_lookup(&tree_, repo_.repo(), &oid_); 63 | 64 | git_index_free(index); 65 | } 66 | return; 67 | } 68 | if (e.testHead("GitObject") && e.length() == 2 && e.part(1).isString()) 69 | e = e.part(1); 70 | if (repo_.isValid() && e.isString()) 71 | { 72 | const char* sha = e.asString(); 73 | if (git_oid_fromstr(&oid_, sha) != 0) 74 | errCode_ = Message::BadSHA; 75 | else if (git_tree_lookup(&tree_, repo_.repo(), &oid_) != 0) 76 | errCode_ = Message::NoTree; 77 | } 78 | } 79 | 80 | GitTree::~GitTree() 81 | { 82 | if (tree_) 83 | git_tree_free(tree_); 84 | } 85 | 86 | void GitTree::write(MLINK lnk) const 87 | { 88 | MLHelper helper(lnk); 89 | if (tree_ != NULL) 90 | helper.putGitObject(oid_, repo_); 91 | else 92 | helper.putSymbol("$Failed"); 93 | } 94 | 95 | void GitTree::writeContents(MLINK lnk, int depth) const 96 | { 97 | if (tree_ != NULL) 98 | { 99 | MLHelper helper(lnk); 100 | helper_ = &helper; 101 | helper.beginList(); 102 | 103 | depth_ = depth; 104 | git_tree_walk(tree_, GIT_TREEWALK_PRE, GitTree::writeTreeEntry_, (void*)this); 105 | depth_ = 1; 106 | helper_ = NULL; 107 | } 108 | else 109 | MLPutSymbol(lnk, "$Failed"); 110 | } 111 | 112 | int GitTree::writeTreeEntry_(const char* root, const git_tree_entry* entry, void* payload) 113 | { 114 | const GitTree* tree = (const GitTree*) payload; 115 | MLHelper* helper = tree->helper_; 116 | 117 | // Expand out subtrees if requested 118 | // git_tree_walk() would do this for us automatically if we returned 0 from this 119 | // callback. But the problem is that we wouldn't know when we were done expanding 120 | // a tree, so we can't track depth. I.e., one could implement depth == 1 or depth == MAX_INT, 121 | // but depth == 2 would be very challenging. So, instead, we just create a new tree walker 122 | // on the subtree. 123 | if (tree->depth_ > 1 && git_tree_entry_type(entry) == GIT_OBJ_TREE) 124 | { 125 | size_t oldRootSize = tree->root_.size(); 126 | if (!tree->root_.empty()) 127 | tree->root_ += '/'; 128 | tree->root_ += git_tree_entry_name(entry); 129 | git_tree* subtree; 130 | git_tree_lookup(&subtree, tree->repo_.repo(), git_tree_entry_id(entry)); 131 | tree->depth_--; 132 | git_tree_walk(subtree, GIT_TREEWALK_PRE, GitTree::writeTreeEntry_, payload); 133 | tree->depth_++; 134 | git_tree_free(subtree); 135 | tree->root_.resize(oldRootSize); 136 | return 1; 137 | } 138 | 139 | helper->beginFunction("Association"); 140 | 141 | helper->putRule("Type"); 142 | if (strcmp(OtypeToString(git_tree_entry_type(entry)), "None") == 0) 143 | helper->putSymbol("None"); 144 | else 145 | helper->putString(OtypeToString(git_tree_entry_type(entry))); 146 | 147 | helper->putRule("Object", *git_tree_entry_id(entry), tree->repo_); 148 | 149 | helper->putRule("Root"); 150 | helper->putString(tree->root_); 151 | 152 | helper->putRule("Name"); 153 | helper->putString(git_tree_entry_name(entry)); 154 | 155 | helper->putRule("FileMode"); 156 | switch (git_tree_entry_filemode(entry)) 157 | { 158 | case GIT_FILEMODE_TREE: helper->putString("Tree"); break; 159 | case GIT_FILEMODE_BLOB: helper->putString("Blob"); break; 160 | case GIT_FILEMODE_BLOB_EXECUTABLE: helper->putString("BlobExecutable"); break; 161 | case GIT_FILEMODE_LINK: helper->putString("Link"); break; 162 | case GIT_FILEMODE_COMMIT: helper->putString("Commit"); break; 163 | default: helper->putString("Unknown"); break; 164 | } 165 | 166 | helper->endFunction(); 167 | return 1; 168 | } 169 | 170 | PathSet GitTree::getDiffPaths(const GitTree& theirTree) const 171 | { 172 | PathSet files; 173 | PathSet disjointFiles; // this is required to work around a libgit2 bug 174 | TreeEntryMap ourTreeEntries; 175 | TreeEntryMap theirTreeEntries; 176 | 177 | git_tree_walk(tree_, GIT_TREEWALK_PRE, GitTree::addTreeEntryToMap_, (void*) &ourTreeEntries); 178 | git_tree_walk(theirTree.tree_, GIT_TREEWALK_PRE, GitTree::addTreeEntryToMap_, (void*) &theirTreeEntries); 179 | 180 | for (auto& entry : ourTreeEntries) 181 | { 182 | const std::string& path = entry.first; 183 | if (theirTreeEntries.count(path) && git_tree_entry_equal(entry.second, theirTreeEntries[path])) 184 | { 185 | git_tree_entry_free(theirTreeEntries[path]); 186 | theirTreeEntries[path] = NULL; 187 | } 188 | else 189 | { 190 | files.insert(path); 191 | if (!theirTreeEntries.count(path)) 192 | disjointFiles.insert(path); 193 | } 194 | git_tree_entry_free(entry.second); 195 | entry.second = NULL; 196 | } 197 | 198 | for (auto& entry : theirTreeEntries) 199 | { 200 | if (entry.second != NULL) 201 | { 202 | files.insert(entry.first); 203 | git_tree_entry_free(entry.second); 204 | entry.second = NULL; 205 | if (!ourTreeEntries.count(entry.first)) 206 | disjointFiles.insert(entry.first); 207 | } 208 | } 209 | 210 | // What a royal pain in the ass. git_checkout_options' GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH 211 | // option will fail to peel and walk subtrees if they don't show up elswhere. 212 | // cf. src/checkout.c: checkout_action_wd_only(). So we need to make sure that the 213 | // parent directories show up only if necessary. I.e., only if they were on one side 214 | // of the diff, but not both. 215 | PathSet parentDirectories; 216 | for (const auto& file : disjointFiles) 217 | { 218 | std::string path = file; 219 | for (int separatorPos = path.rfind('/'); separatorPos != std::string::npos; separatorPos = path.rfind('/')) 220 | { 221 | bool found = false; 222 | path = path.substr(0, separatorPos); 223 | for (const auto& diffFile : files) 224 | { 225 | if (!diffFile.compare(0, path.size(), path) && !disjointFiles.count(diffFile)) 226 | { 227 | found = true; 228 | break; 229 | } 230 | } 231 | if (!found) 232 | parentDirectories.insert(path); 233 | } 234 | } 235 | for (const auto& directory : parentDirectories) 236 | files.insert(directory); 237 | 238 | return files; 239 | } 240 | 241 | int GitTree::addTreeEntryToMap_(const char* root, const git_tree_entry* entry, void* payload) 242 | { 243 | TreeEntryMap* map = (TreeEntryMap*) payload; 244 | 245 | if (git_tree_entry_type(entry) == GIT_OBJ_TREE) 246 | return 0; 247 | 248 | std::string path(root); 249 | if (!path.empty() && path.back() != '/') 250 | path += "/"; 251 | path += git_tree_entry_name(entry); 252 | 253 | git_tree_entry* dupEntry; 254 | git_tree_entry_dup(&dupEntry, entry); 255 | 256 | (*map)[path] = dupEntry; 257 | return 0; 258 | } 259 | 260 | int GitTree::resetIndexToTreeEntry(git_index* index, const char* filename) const 261 | { 262 | // First, see if the file exists in the tree. If not, remove it from the index 263 | git_tree_entry* treeEntry; 264 | int result = git_tree_entry_bypath(&treeEntry, tree_, filename); 265 | if (result == GIT_ENOTFOUND) 266 | return git_index_remove_bypath(index, filename); 267 | else if (result != 0) 268 | return result; 269 | 270 | // Get a copy of the old index entry if we can find it. There are several fields which aren't 271 | // obvious how to fill, so let's copy them from the old one. Might be some stuff to fix here. 272 | git_index_entry newIndexEntry; 273 | const git_index_entry* oldIndexEntry = git_index_get_bypath(index, filename, GIT_INDEX_STAGE_ANY); 274 | if (oldIndexEntry) 275 | { 276 | memcpy(&newIndexEntry, oldIndexEntry, sizeof(newIndexEntry)); 277 | } 278 | else 279 | { 280 | memset((void*)&newIndexEntry, 0, sizeof(newIndexEntry)); 281 | newIndexEntry.path = filename; 282 | if (strlen(filename) > GIT_IDXENTRY_NAMEMASK) 283 | newIndexEntry.flags = GIT_IDXENTRY_NAMEMASK; 284 | else 285 | newIndexEntry.flags |= (GIT_IDXENTRY_NAMEMASK & strlen(filename)); 286 | } 287 | 288 | git_blob* blob; 289 | git_blob_lookup(&blob, repo_.repo(), git_tree_entry_id(treeEntry)); 290 | 291 | // Copy to relevant fields of index entry from tree, blob info 292 | newIndexEntry.mode = git_tree_entry_filemode_raw(treeEntry); 293 | git_oid_cpy(&newIndexEntry.id, git_tree_entry_id(treeEntry)); 294 | newIndexEntry.file_size = git_blob_rawsize(blob); 295 | GIT_IDXENTRY_STAGE_SET(&newIndexEntry, 0); 296 | 297 | result = git_index_add(index, &newIndexEntry); 298 | 299 | git_blob_free(blob); 300 | git_tree_entry_free(treeEntry); 301 | 302 | return result; 303 | } 304 | -------------------------------------------------------------------------------- /src/classes/GitLinkCommit.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * gitLink 3 | * 4 | * Created by John Fultz on 6/18/14. 5 | * Copyright (c) 2014 Wolfram Research. All rights reserved. 6 | * 7 | */ 8 | 9 | #include 10 | 11 | #include "mathlink.h" 12 | #include "WolframLibrary.h" 13 | #include "git2.h" 14 | #include "GitLinkRepository.h" 15 | #include "GitLinkCommit.h" 16 | #include "GitTree.h" 17 | #include "Signature.h" 18 | 19 | #include "Message.h" 20 | #include "MLHelper.h" 21 | #include "RepoInterface.h" 22 | 23 | 24 | GitLinkCommit::GitLinkCommit(const GitLinkRepository& repo, const MLExpr& expr) 25 | : repo_(repo) 26 | , valid_(false) 27 | , notSpec_(false) 28 | { 29 | MLExpr currentExpr = expr; 30 | memset(oid_.id, 0, GIT_OID_RAWSZ); 31 | while (currentExpr.testHead("Except") && currentExpr.length() == 1) 32 | { 33 | notSpec_ = !notSpec_; 34 | currentExpr = currentExpr.part(1); 35 | continue; 36 | } 37 | if (currentExpr.testHead("GitObject") && currentExpr.length() == 2 && currentExpr.part(1).isString()) 38 | currentExpr = currentExpr.part(1); 39 | if (repo.isValid() && currentExpr.isString()) 40 | { 41 | git_object* obj; 42 | if (git_revparse_single(&obj, repo.repo(), currentExpr.asString()) == 0) 43 | { 44 | git_oid_cpy(&oid_, git_object_id(obj)); 45 | switch (git_object_type(obj)) 46 | { 47 | case GIT_OBJ_TAG: 48 | { 49 | git_object* peeledObj; 50 | git_object_dup((git_object**) &tag_, obj); 51 | if (!git_tag_peel(&peeledObj, tag_)) 52 | { 53 | valid_ = (git_object_type(peeledObj) == GIT_OBJ_COMMIT); 54 | git_oid_cpy(&oid_, git_object_id(peeledObj)); 55 | git_object_free(peeledObj); 56 | } 57 | break; 58 | } 59 | 60 | case GIT_OBJ_COMMIT: valid_ = true; break; 61 | default: break; 62 | } 63 | git_object_free(obj); 64 | } 65 | } 66 | 67 | if (!valid_) 68 | errCode_ = repo.isValid() ? Message::BadCommitish : Message::BadRepo; 69 | } 70 | 71 | GitLinkCommit::GitLinkCommit(const GitLinkRepository& repo, const char* refName) 72 | : repo_(repo) 73 | , valid_(false) 74 | , notSpec_(false) 75 | { 76 | git_object* obj; 77 | memset(oid_.id, 0, GIT_OID_RAWSZ); 78 | if (repo.isValid() && git_revparse_single(&obj, repo.repo(), refName) == 0) 79 | { 80 | git_oid_cpy(&oid_, git_object_id(obj)); 81 | switch (git_object_type(obj)) 82 | { 83 | case GIT_OBJ_TAG: 84 | { 85 | git_object* peeledObj; 86 | git_object_dup((git_object**) &tag_, obj); 87 | if (!git_tag_peel(&peeledObj, tag_)) 88 | { 89 | valid_ = (git_object_type(peeledObj) == GIT_OBJ_COMMIT); 90 | git_oid_cpy(&oid_, git_object_id(peeledObj)); 91 | git_object_free(peeledObj); 92 | } 93 | break; 94 | } 95 | 96 | case GIT_OBJ_COMMIT: valid_ = true; break; 97 | default: break; 98 | } 99 | git_object_free(obj); 100 | } 101 | 102 | if (!valid_) 103 | errCode_ = repo.isValid() ? Message::BadCommitish : Message::BadRepo; 104 | } 105 | 106 | GitLinkCommit::GitLinkCommit(const GitLinkRepository& repo, const git_oid* oid) 107 | : repo_(repo) 108 | , valid_(true) 109 | , notSpec_(false) 110 | { 111 | git_oid_cpy(&oid_, oid); 112 | commit(); // does validity check 113 | } 114 | 115 | GitLinkCommit::GitLinkCommit(const GitLinkRepository& repo, const GitTree& tree, const GitLinkCommitDeque& parents, 116 | const git_signature* author, const git_signature* committer, const char* message) 117 | : repo_(repo) 118 | , valid_(false) 119 | , notSpec_(false) 120 | { 121 | memset(oid_.id, 0, GIT_OID_RAWSZ); 122 | if (committer == NULL) 123 | committer = repo.committer(); 124 | if (author == NULL) 125 | author = committer; 126 | 127 | if (!repo.isValid()) 128 | errCode_ = Message::BadRepo; 129 | else if (!tree.isValid()) 130 | errCode_ = Message::NoTree; 131 | else if (!message) 132 | errCode_ = Message::NoMessage; 133 | else if (committer == NULL) 134 | errCode_ = Message::NoDefaultUserName; 135 | else if (!parents.isValid()) 136 | errCode_ = Message::NoParent; 137 | else 138 | { 139 | if (!git_commit_create(&oid_, repo.repo(), NULL, author, committer, 140 | NULL, message, tree, parents.size(), parents.commits())) 141 | valid_ = true; 142 | else 143 | errCode_ = Message::GitCommitError; 144 | } 145 | } 146 | 147 | GitLinkCommit::GitLinkCommit(const GitLinkCommit& commit) 148 | : repo_(commit.repo_) 149 | , valid_(commit.valid_) 150 | , notSpec_(commit.notSpec_) 151 | { 152 | errCode_ = commit.errCode_; 153 | git_oid_cpy(&oid_, &commit.oid_); 154 | } 155 | 156 | GitLinkCommit::~GitLinkCommit() 157 | { 158 | if (commit_) 159 | git_commit_free(commit_); 160 | if (tag_) 161 | git_tag_free(tag_); 162 | } 163 | 164 | bool GitLinkCommit::operator==(GitLinkCommit& c) 165 | { 166 | commit(); 167 | c.commit(); 168 | if (!isValid() || !c.isValid()) 169 | return false; 170 | return git_oid_cmp(oid(), c.oid()) == 0; 171 | } 172 | 173 | void GitLinkCommit::writeProperties(MLINK lnk) 174 | { 175 | MLHelper helper(lnk); 176 | const git_commit* theCommit = commit(); 177 | 178 | 179 | if (!tag_ && (!isValid() || theCommit == NULL)) 180 | { 181 | helper.putString(Message::BadCommitish); 182 | return; 183 | } 184 | 185 | helper.beginFunction("Association"); 186 | 187 | if (tag_) 188 | { 189 | Signature tagger(git_tag_tagger(tag_)); 190 | helper.putRule("Type"); 191 | helper.putString("Tag"); 192 | helper.putRule("TagName", git_tag_name(tag_)); 193 | helper.putRule("TagCommitter", tagger); 194 | helper.putRule("TagMessage", git_tag_message(tag_)); 195 | helper.putRule("TagSHA", *git_tag_id(tag_)); 196 | helper.putRule("TagTarget", *git_tag_target_id(tag_)); 197 | helper.putRule("TagTargetType", OtypeToString(git_tag_target_type(tag_))); 198 | } 199 | if (!isValid() || theCommit == NULL) 200 | return; 201 | 202 | if (!tag_) 203 | { 204 | helper.putRule("Type"); 205 | helper.putString("Commit"); 206 | } 207 | 208 | helper.putRule("Parents"); 209 | helper.beginList(); 210 | for (int i = 0; i < git_commit_parentcount(theCommit); i++) 211 | helper.putGitObject(*git_commit_parent_id(theCommit, i), repo_); 212 | helper.endList(); 213 | 214 | Signature author(git_commit_author(theCommit)); 215 | Signature committer(git_commit_committer(theCommit)); 216 | 217 | helper.putRule("Tree"); 218 | helper.putGitObject(*git_commit_tree_id(theCommit), repo_); 219 | helper.putRule("Author", author); 220 | helper.putRule("Committer", committer); 221 | helper.putRule("SHA", *git_commit_id(theCommit)); 222 | helper.putRule("Message", git_commit_message_raw(theCommit)); 223 | helper.putRule("Repo"); 224 | helper.putRepo(repo_); 225 | } 226 | 227 | void GitLinkCommit::write(MLINK lnk) const 228 | { 229 | char buf[GIT_OID_HEXSZ + 1]; 230 | if (valid_) 231 | { 232 | MLHelper helper(lnk); 233 | helper.putGitObject((tag_ == NULL) ? oid_ : *git_tag_id(tag_), repo_); 234 | } 235 | else 236 | MLPutSymbol(lnk, "$Failed"); 237 | } 238 | 239 | void GitLinkCommit::writeSHA(MLINK lnk) const 240 | { 241 | char buf[GIT_OID_HEXSZ + 1]; 242 | if (valid_) 243 | { 244 | git_oid_tostr(buf, GIT_OID_HEXSZ + 1, &oid_); 245 | MLPutString(lnk, buf); 246 | } 247 | else 248 | MLPutSymbol(lnk, "$Failed"); 249 | } 250 | 251 | git_commit* GitLinkCommit::commit() 252 | { 253 | if (commit_) 254 | return commit_; 255 | if (!isValid()) 256 | return NULL; 257 | if (git_commit_lookup(&commit_, repo_.repo(), &oid_) || commit_ == NULL) 258 | { 259 | valid_ = false; 260 | return NULL; 261 | } 262 | return commit_; 263 | } 264 | 265 | bool GitLinkCommit::createBranch(const char* branchName, bool force) 266 | { 267 | // no need to set error...the constructor already set it in this case 268 | if (!isValid()) 269 | return false; 270 | 271 | errCode_ = errCodeParam_ = NULL; 272 | git_reference* ref; 273 | 274 | const git_signature* committer = repo_.committer(); 275 | if (committer == NULL) 276 | { 277 | errCode_ = Message::NoDefaultUserName; 278 | return false; 279 | } 280 | int err = git_branch_create(&ref, repo_.repo(), branchName, commit(), force); 281 | if (err == GIT_EINVALIDSPEC) 282 | { 283 | errCode_ = Message::InvalidSpec; 284 | errCodeParam_ = strdup(branchName); 285 | } 286 | else if (err == GIT_EEXISTS) 287 | { 288 | errCode_ = Message::RefExists; 289 | errCodeParam_ = strdup(branchName); 290 | } 291 | else if (err != 0) 292 | { 293 | errCode_ = Message::BranchNotCreated; 294 | errCodeParam_ = strdup(giterr_last()->message); 295 | } 296 | 297 | if (!err) 298 | git_reference_free(ref); 299 | return (err == 0); 300 | } 301 | 302 | int GitLinkCommit::parentCount() 303 | { 304 | const git_commit* theCommit = commit(); 305 | 306 | if (!isValid() || theCommit == NULL) 307 | return 0; 308 | return git_commit_parentcount(theCommit); 309 | } 310 | 311 | git_tree* GitLinkCommit::copyTree() 312 | { 313 | const git_commit* theCommit = commit(); 314 | if (!isValid() || theCommit == NULL) 315 | return NULL; 316 | git_tree* tree; 317 | if (!git_commit_tree(&tree, theCommit)) 318 | return tree; 319 | return NULL; 320 | } 321 | 322 | GitLinkCommitDeque::GitLinkCommitDeque() 323 | : std::deque() 324 | , isValid_(true) 325 | { 326 | 327 | } 328 | 329 | GitLinkCommitDeque::GitLinkCommitDeque(const GitLinkCommit& commit) 330 | : std::deque(1, commit) 331 | , isValid_(true) 332 | { 333 | 334 | } 335 | 336 | GitLinkCommitDeque::GitLinkCommitDeque(const GitLinkRepository& repo, MLExpr expr) 337 | : std::deque() 338 | , isValid_(true) 339 | { 340 | if (expr.isList()) 341 | { 342 | for (int i = 1; i <= expr.length(); i++) 343 | push_back(GitLinkCommit(repo, expr.part(i))); 344 | } 345 | else if (expr.isString()) 346 | { 347 | push_back(GitLinkCommit(repo, expr)); 348 | } 349 | 350 | for (GitLinkCommit c : *this) 351 | { 352 | c.commit(); 353 | isValid_ = isValid_ && c.isValid(); 354 | } 355 | if (!isValid_) 356 | errCode_ = Message::BadCommitish; 357 | } 358 | 359 | GitLinkCommitDeque& GitLinkCommitDeque::operator=(const GitLinkCommitDeque& theDeque) 360 | { 361 | clear(); 362 | for (const GitLinkCommit& c : theDeque) 363 | push_back(c); 364 | isValid_ = theDeque.isValid_; 365 | return *this; 366 | } 367 | 368 | const git_commit** GitLinkCommitDeque::commits() const 369 | { 370 | if (commits_.empty()) 371 | { 372 | for (const GitLinkCommit& c : *this) 373 | commits_.push_back(const_cast(c).commit()); 374 | } 375 | return &commits_[0]; 376 | } 377 | -------------------------------------------------------------------------------- /src/classes/MLHelper.cpp: -------------------------------------------------------------------------------- 1 | #include "mathlink.h" 2 | #include "git2.h" 3 | #include "WolframLibrary.h" 4 | #include "MLHelper.h" 5 | #include "GitLinkRepository.h" 6 | #include "Signature.h" 7 | #include 8 | 9 | MLHelper::MLHelper(MLINK lnk) : 10 | lnk_(lnk), unfinishedRule_(false) 11 | { 12 | tmpLinks_.push_front(lnk); 13 | argCounts_.push_front(0); 14 | unfinishedRule_.push_front(false); 15 | } 16 | 17 | MLHelper::MLHelper(MLEnvironment env, MLExpr& expr) : 18 | lnk_(expr.initializeLink(env)), unfinishedRule_(false) 19 | { 20 | tmpLinks_.push_front(lnk_); 21 | argCounts_.push_front(0); 22 | unfinishedRule_.push_front(false); 23 | } 24 | 25 | MLHelper::~MLHelper() 26 | { 27 | endAllFunctions(); 28 | } 29 | 30 | void MLHelper::processAndIgnore(WolframLibraryData libData) 31 | { 32 | endAllFunctions(); 33 | libData->processWSLINK(lnk_); 34 | 35 | while (true) 36 | { 37 | switch(MLNextPacket(lnk_)) 38 | { 39 | case ILLEGALPKT: break; 40 | case RETURNPKT: MLNewPacket(lnk_); break; 41 | default: MLNewPacket(lnk_); continue; 42 | } 43 | break; 44 | } 45 | } 46 | 47 | void MLHelper::beginFunction(const char* head) 48 | { 49 | int err; 50 | tmpLinks_.push_front(MLLoopbackOpen(MLLinkEnvironment(lnk_), &err)); 51 | argCounts_.push_front(0); 52 | unfinishedRule_.push_front(false); 53 | MLPutSymbol(tmpLinks_.front(), head); 54 | } 55 | 56 | void MLHelper::beginFunction(const MLExpr& head) 57 | { 58 | int err; 59 | tmpLinks_.push_front(MLLoopbackOpen(MLLinkEnvironment(lnk_), &err)); 60 | argCounts_.push_front(0); 61 | unfinishedRule_.push_front(false); 62 | head.putToLink(tmpLinks_.front()); 63 | } 64 | 65 | void MLHelper::endFunction() 66 | { 67 | MLINK loopbackLink = tmpLinks_.front(); 68 | int argCount = argCounts_.front(); 69 | tmpLinks_.pop_front(); 70 | argCounts_.pop_front(); 71 | unfinishedRule_.pop_front(); 72 | 73 | MLINK destLink = tmpLinks_.front(); 74 | MLPutNext(destLink, MLTKFUNC); 75 | MLPutArgCount(destLink, argCount); 76 | MLTransferExpression(destLink, loopbackLink); 77 | for (int i = 0; i < argCount; i++) 78 | MLTransferExpression(destLink, loopbackLink); 79 | MLClose(loopbackLink); 80 | incrementArgumentCount_(); 81 | } 82 | 83 | void MLHelper::endAllFunctions() 84 | { 85 | while (tmpLinks_.front() != lnk_) 86 | endFunction(); 87 | } 88 | 89 | void MLHelper::putString(const char* value) 90 | { 91 | MLPutUTF8String(tmpLinks_.front(), (const unsigned char*)value, (int)strlen(value)); 92 | incrementArgumentCount_(); 93 | } 94 | 95 | void MLHelper::putSymbol(const char* value) 96 | { 97 | MLPutSymbol(tmpLinks_.front(), value); 98 | incrementArgumentCount_(); 99 | } 100 | 101 | void MLHelper::putMint(mint value) 102 | { 103 | MLPutMint(tmpLinks_.front(), value); 104 | incrementArgumentCount_(); 105 | } 106 | 107 | void MLHelper::putInt(int value) 108 | { 109 | MLPutInteger(tmpLinks_.front(), value); 110 | incrementArgumentCount_(); 111 | } 112 | 113 | void MLHelper::putOid(const git_oid& value) 114 | { 115 | char buf[GIT_OID_HEXSZ + 1]; 116 | git_oid_tostr(buf, GIT_OID_HEXSZ + 1, &value); 117 | MLPutString(tmpLinks_.front(), buf); 118 | incrementArgumentCount_(); 119 | } 120 | 121 | void MLHelper::putRepo(const GitLinkRepository& repo) 122 | { 123 | MLINK lnk = tmpLinks_.front(); 124 | MLPutFunction(lnk, "GitRepo", 1); 125 | repo.writeProperties(lnk, true); 126 | incrementArgumentCount_(); 127 | } 128 | 129 | void MLHelper::putGitObject(const git_oid& value, const GitLinkRepository& repo) 130 | { 131 | beginFunction("GitObject"); 132 | putOid(value); 133 | putRepo(repo); 134 | endFunction(); 135 | } 136 | 137 | void MLHelper::putExpr(const MLExpr& expr) 138 | { 139 | MLINK lnk = tmpLinks_.front(); 140 | expr.putToLink(lnk); 141 | incrementArgumentCount_(); 142 | } 143 | 144 | void MLHelper::putMessage(const char* symbol, const char* tag) 145 | { 146 | beginFunction("MessageName"); 147 | putSymbol(symbol); 148 | putString(tag); 149 | endFunction(); 150 | } 151 | 152 | void MLHelper::putBlobUTF8String(const git_blob* blob) 153 | { 154 | MLINK lnk = tmpLinks_.front(); 155 | MLPutUTF8String(lnk, (const unsigned char*)git_blob_rawcontent(blob), git_blob_rawsize(blob)); 156 | incrementArgumentCount_(); 157 | } 158 | 159 | void MLHelper::putBlobByteString(const git_blob* blob) 160 | { 161 | MLINK lnk = tmpLinks_.front(); 162 | MLPutByteString(lnk, (const unsigned char*)git_blob_rawcontent(blob), git_blob_rawsize(blob)); 163 | incrementArgumentCount_(); 164 | } 165 | 166 | 167 | void MLHelper::putRule(const char* key) 168 | { 169 | MLINK lnk = tmpLinks_.front(); 170 | MLPutFunction(lnk, "Rule", 2); 171 | MLPutString(lnk, key); 172 | argCounts_.front()++; 173 | unfinishedRule_.front() = true; 174 | } 175 | 176 | void MLHelper::putRule(const char* key, int value) 177 | { 178 | MLINK lnk = tmpLinks_.front(); 179 | MLPutFunction(lnk, "Rule", 2); 180 | MLPutString(lnk, key); 181 | MLPutSymbol(lnk, value ? "True" : "False"); 182 | incrementArgumentCount_(); 183 | } 184 | 185 | void MLHelper::putRule(const char* key, double value) 186 | { 187 | MLINK lnk = tmpLinks_.front(); 188 | MLPutFunction(lnk, "Rule", 2); 189 | MLPutString(lnk, key); 190 | MLPutDouble(lnk, value); 191 | incrementArgumentCount_(); 192 | } 193 | 194 | void MLHelper::putRule(const char* key, const MLExpr& expr) 195 | { 196 | MLINK lnk = tmpLinks_.front(); 197 | MLPutFunction(lnk, "Rule", 2); 198 | MLPutString(lnk, key); 199 | expr.putToLink(lnk); 200 | incrementArgumentCount_(); 201 | } 202 | 203 | void MLHelper::putRule(const char* key, const git_time& value) 204 | { 205 | struct tm* tmPtr = localtime((time_t*)&value.time); 206 | MLINK lnk = tmpLinks_.front(); 207 | MLPutFunction(lnk, "Rule", 2); 208 | MLPutString(lnk, key); 209 | MLPutFunction(lnk, "DateObject", 2); 210 | MLPutFunction(lnk, "List", 6); 211 | MLPutInteger(lnk, tmPtr->tm_year + 1900); 212 | MLPutInteger(lnk, tmPtr->tm_mon + 1); 213 | MLPutInteger(lnk, tmPtr->tm_mday); 214 | MLPutInteger(lnk, tmPtr->tm_hour); 215 | MLPutInteger(lnk, tmPtr->tm_min); 216 | MLPutInteger(lnk, tmPtr->tm_sec); 217 | MLPutFunction(lnk, "Rule", 2); 218 | MLPutSymbol(lnk, "TimeZone"); 219 | MLPutReal(lnk, (double)value.offset / 60.); 220 | incrementArgumentCount_(); 221 | } 222 | 223 | void MLHelper::putRule(const char* key, const char* value, const char* symbolFallback) 224 | { 225 | MLINK lnk = tmpLinks_.front(); 226 | MLPutFunction(lnk, "Rule", 2); 227 | MLPutUTF8String(lnk, (const unsigned char*)key, (int)strlen(key)); 228 | if (value == NULL) 229 | MLPutSymbol(lnk, symbolFallback); 230 | else 231 | MLPutUTF8String(lnk, (const unsigned char*)value, (int)strlen(value)); 232 | incrementArgumentCount_(); 233 | } 234 | 235 | void MLHelper::putRule(const char* key, const git_oid& value) 236 | { 237 | putRule(key); 238 | putOid(value); 239 | } 240 | 241 | void MLHelper::putRule(const char* key, const git_oid& value, const GitLinkRepository& repo) 242 | { 243 | putRule(key); 244 | putGitObject(value, repo); 245 | } 246 | 247 | void MLHelper::putRule(const char* key, git_repository_state_t value) 248 | { 249 | MLINK lnk = tmpLinks_.front(); 250 | MLPutFunction(lnk, "Rule", 2); 251 | MLPutString(lnk, key); 252 | 253 | const char* state; 254 | switch (value) 255 | { 256 | case GIT_REPOSITORY_STATE_MERGE: 257 | state = "Merge"; 258 | break; 259 | case GIT_REPOSITORY_STATE_REVERT: 260 | state = "Revert"; 261 | break; 262 | case GIT_REPOSITORY_STATE_CHERRYPICK: 263 | state = "CherryPick"; 264 | break; 265 | case GIT_REPOSITORY_STATE_BISECT: 266 | state = "Bisect"; 267 | break; 268 | case GIT_REPOSITORY_STATE_REBASE: 269 | state = "Rebase"; 270 | break; 271 | case GIT_REPOSITORY_STATE_REBASE_INTERACTIVE: 272 | state = "RebaseInteractive"; 273 | break; 274 | case GIT_REPOSITORY_STATE_REBASE_MERGE: 275 | state = "RebaseMerge"; 276 | break; 277 | case GIT_REPOSITORY_STATE_APPLY_MAILBOX: 278 | state = "ApplyMailbox"; 279 | break; 280 | case GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE: 281 | state = "ApplyMailboxOrRebase"; 282 | break; 283 | default: 284 | state = "None"; 285 | break; 286 | } 287 | MLPutString(lnk, state); 288 | incrementArgumentCount_(); 289 | } 290 | 291 | void MLHelper::putRule(const char* key, const Signature& value) 292 | { 293 | putRule(key); 294 | value.writeAssociation(*this); 295 | } 296 | 297 | std::string MLGetCPPString(MLINK lnk) 298 | { 299 | const unsigned char* bytes; 300 | std::string str; 301 | int len, unused; 302 | MLGetUTF8String(lnk, &bytes, &len, &unused); 303 | str.assign((const char*) bytes, len); 304 | MLReleaseUTF8String(lnk, bytes, len); 305 | return str; 306 | } 307 | 308 | void MLHandleError(WolframLibraryData libData, const char* functionName, const char* messageName, const char* param, const char* param2) 309 | { 310 | if (messageName == NULL) 311 | return; 312 | 313 | MLINK lnk = libData->getMathLink(libData); 314 | MLPutFunction(lnk, "EvaluatePacket", 1); 315 | MLPutFunction(lnk, "Message", (param == NULL) ? 1 : ((param2 == NULL) ? 2 : 3)); 316 | MLPutFunction(lnk, "MessageName", 2); 317 | MLPutSymbol(lnk, functionName); 318 | MLPutString(lnk, messageName); 319 | if (param) 320 | { 321 | MLPutString(lnk, param); 322 | if (param2) 323 | MLPutString(lnk, param2); 324 | } 325 | libData->processWSLINK(lnk); 326 | while (true) 327 | { 328 | switch(MLNextPacket(lnk)) 329 | { 330 | case ILLEGALPKT: return; 331 | case RETURNPKT: MLNewPacket(lnk); return; 332 | default: MLNewPacket(lnk); continue; 333 | } 334 | } 335 | } 336 | 337 | MLExpr MLToExpr(WolframLibraryData libData, const MLExpr& expr) 338 | { 339 | MLINK lnk = libData->getMathLink(libData); 340 | MLHelper helper(lnk); 341 | 342 | helper.beginFunction("EvaluatePacket"); 343 | helper.putExpr(expr); 344 | helper.endFunction(); 345 | libData->processWSLINK(lnk); 346 | 347 | while (true) 348 | { 349 | switch(MLNextPacket(lnk)) 350 | { 351 | case ILLEGALPKT: return MLExpr(); 352 | case RETURNPKT: return MLExpr(lnk); 353 | default: MLNewPacket(lnk); continue; 354 | } 355 | } 356 | } 357 | 358 | std::string MLToLower(WolframLibraryData libData, const std::string& str) 359 | { 360 | std::string result; 361 | 362 | MLINK lnk = libData->getMathLink(libData); 363 | MLPutFunction(lnk, "EvaluatePacket", 1); 364 | MLPutFunction(lnk, "ToLowerCase", 1); 365 | MLPutUTF8String(lnk, (const unsigned char*) str.c_str(), str.size()); 366 | libData->processWSLINK(lnk); 367 | while (true) 368 | { 369 | switch(MLNextPacket(lnk)) 370 | { 371 | case ILLEGALPKT: return std::string(); 372 | case RETURNPKT: return MLGetCPPString(lnk); 373 | default: MLNewPacket(lnk); continue; 374 | } 375 | } 376 | } 377 | 378 | const char* OtypeToString(git_otype otype) 379 | { 380 | switch(otype) 381 | { 382 | case GIT_OBJ_COMMIT: return "Commit"; 383 | case GIT_OBJ_TREE: return "Tree"; 384 | case GIT_OBJ_BLOB: return "Blob"; 385 | case GIT_OBJ_TAG: return "Tag"; 386 | case GIT_OBJ_OFS_DELTA: return "OffsetDelta"; 387 | case GIT_OBJ_REF_DELTA: return "ObjectDelta"; 388 | default: return "None"; 389 | } 390 | } --------------------------------------------------------------------------------