├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── ext ├── ReadDirectoryChanges │ ├── ReadMe.txt │ ├── download.txt │ ├── ide │ │ ├── ReadDirectoryChanges.sln │ │ ├── ReadDirectoryChanges.vcxproj │ │ ├── ReadDirectoryChanges.vcxproj.filters │ │ ├── ReadDirectoryChangesLib.vcxproj │ │ └── ReadDirectoryChangesLib.vcxproj.filters │ ├── inc │ │ ├── ReadDirectoryChanges.h │ │ └── ThreadSafeQueue.h │ └── src │ │ ├── Main.cpp │ │ ├── ReadDirectoryChanges.cpp │ │ ├── ReadDirectoryChangesPrivate.cpp │ │ ├── ReadDirectoryChangesPrivate.h │ │ ├── stdafx.cpp │ │ ├── stdafx.h │ │ └── targetver.h └── ScopedResource │ ├── download.txt │ └── inc │ ├── scope_guard.h │ └── unique_resource.h ├── ide └── GitStatusCache.sln └── src └── GitStatusCache ├── ide ├── GitStatusCache.vcxproj └── GitStatusCache.vcxproj.filters └── src ├── Cache.cpp ├── Cache.h ├── CacheInvalidator.cpp ├── CacheInvalidator.h ├── CachePrimer.cpp ├── CachePrimer.h ├── CacheStatistics.h ├── DirectoryMonitor.cpp ├── DirectoryMonitor.h ├── Git.cpp ├── Git.h ├── LogStream.cpp ├── LogStream.h ├── Logging.h ├── LoggingInitializationScope.h ├── LoggingModule.cpp ├── LoggingModule.h ├── LoggingModuleSettings.h ├── LoggingSeverities.h ├── Main.cpp ├── NamedPipeInstance.cpp ├── NamedPipeInstance.h ├── NamedPipeServer.cpp ├── NamedPipeServer.h ├── SmartPointers.h ├── StatusCache.cpp ├── StatusCache.h ├── StatusController.cpp ├── StatusController.h ├── StringConverters.h ├── stdafx.cpp ├── stdafx.h └── targetver.h /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | *.VC.opendb 81 | *.VC.db 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | 88 | # TFS 2012 Local Workspace 89 | $tf/ 90 | 91 | # Guidance Automation Toolkit 92 | *.gpState 93 | 94 | # ReSharper is a .NET coding add-in 95 | _ReSharper*/ 96 | *.[Rr]e[Ss]harper 97 | *.DotSettings.user 98 | 99 | # JustCode is a .NET coding add-in 100 | .JustCode 101 | 102 | # TeamCity is a build add-in 103 | _TeamCity* 104 | 105 | # DotCover is a Code Coverage Tool 106 | *.dotCover 107 | 108 | # NCrunch 109 | _NCrunch_* 110 | .*crunch*.local.xml 111 | 112 | # MightyMoose 113 | *.mm.* 114 | AutoTest.Net/ 115 | 116 | # Web workbench (sass) 117 | .sass-cache/ 118 | 119 | # Installshield output folder 120 | [Ee]xpress/ 121 | 122 | # DocProject is a documentation generator add-in 123 | DocProject/buildhelp/ 124 | DocProject/Help/*.HxT 125 | DocProject/Help/*.HxC 126 | DocProject/Help/*.hhc 127 | DocProject/Help/*.hhk 128 | DocProject/Help/*.hhp 129 | DocProject/Help/Html2 130 | DocProject/Help/html 131 | 132 | # Click-Once directory 133 | publish/ 134 | 135 | # Publish Web Output 136 | *.[Pp]ublish.xml 137 | *.azurePubxml 138 | # TODO: Comment the next line if you want to checkin your web deploy settings 139 | # but database connection strings (with potential passwords) will be unencrypted 140 | *.pubxml 141 | *.publishproj 142 | 143 | # NuGet Packages 144 | *.nupkg 145 | # The packages folder can be ignored because of Package Restore 146 | **/packages/* 147 | # except build/, which is used as an MSBuild target. 148 | !**/packages/build/ 149 | # Uncomment if necessary however generally it will be regenerated when needed 150 | #!**/packages/repositories.config 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Visual Studio cache files 160 | # files ending in .cache can be ignored 161 | *.[Cc]ache 162 | # but keep track of directories ending in .cache 163 | !*.[Cc]ache/ 164 | 165 | # Others 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *~ 170 | *.dbmdl 171 | *.dbproj.schemaview 172 | *.pfx 173 | *.publishsettings 174 | node_modules/ 175 | orleans.codegen.cs 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | # Node.js Tools for Visual Studio 201 | .ntvs_analysis.dat 202 | 203 | # Visual Studio 6 build log 204 | *.plg 205 | 206 | # Visual Studio 6 workspace options file 207 | *.opt -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/libgit2"] 2 | path = ext/libgit2 3 | url = https://github.com/libgit2/libgit2.git 4 | [submodule "ext/rapidjson"] 5 | path = ext/rapidjson 6 | url = https://github.com/miloyip/rapidjson.git 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Marcus Reid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-status-cache # 2 | 3 | High performance cache for git repository status. Clients can retrieve information via named pipe. 4 | 5 | Useful for scenarios that frequently access status information (ex. shell prompts like [posh-git](https://github.com/cmarcusreid/posh-git/tree/useGitStatusCache)) that don't want to pay the cost of a "git status" (significantly more expensive with large repositories) when nothing has changed. 6 | 7 | ## Clients ## 8 | 9 | - PowerShell: [git-status-cache-posh-client](https://github.com/cmarcusreid/git-status-cache-posh-client) 10 | 11 | ## Communicating with the cache ## 12 | 13 | Clients connect to the "GitStatusCache" named pipe hosted by GitStatusCache.exe. All messages sent over the pipe must be UTF-8 encoded JSON. 14 | 15 | All requests must specify "Version" and "Action". The only currently available version is 1. Should the protocol change in the future the version number will be incremented to avoid breaking existing clients. The following operations may be specified in "Action". 16 | 17 | ### GetStatus ### 18 | 19 | Retrieves current status information for the requested "Path". 20 | 21 | ##### Sample request ##### 22 | 23 | { 24 | "Path": "D:\\git-status-cache-posh-client", 25 | "Version": 1, 26 | "Action": "GetStatus" 27 | } 28 | 29 | ##### Sample response ##### 30 | 31 | { 32 | "Version": 1, 33 | "Path": "D:\\git-status-cache-posh-client", 34 | "RepoPath": "D:/git-status-cache-posh-client/.git/", 35 | "WorkingDir": "D:/git-status-cache-posh-client/", 36 | "State" : "", 37 | "Branch" : "master", 38 | "Upstream": "origin/master", 39 | "UpstreamGone": false, 40 | "AheadBy": 0, 41 | "BehindBy": 0, 42 | "IndexAdded": [], 43 | "IndexModified": [], 44 | "IndexDeleted": [], 45 | "IndexTypeChange": [], 46 | "IndexRenamed": [], 47 | "WorkingAdded": [], 48 | "WorkingModified": ["GitStatusCachePoshClient.ps1", "GitStatusCachePoshClient.psm1"], 49 | "WorkingDeleted": [], 50 | "WorkingTypeChange": [], 51 | "WorkingRenamed": [], 52 | "WorkingUnreadable": [], 53 | "Ignored": [], 54 | "Conflicted": [] 55 | "Stashes" : [{ 56 | "Name" : "stash@{0}", 57 | "Sha1Id" : "e24d59d0d03a3f680def647a7bb62f027d8671c", 58 | "Message" : "On master: Second stash!" 59 | }, { 60 | "Name" : "stash@{1}", 61 | "Sha1Id" : "0cbabd043bae55a76c3c041e6db2b129a0a4872", 62 | "Message" : "On master: My stash." 63 | } 64 | ] 65 | } 66 | 67 | ### GetCacheStatistics ### 68 | 69 | Reports information about the cache's performance. 70 | 71 | ##### Sample request ##### 72 | 73 | { 74 | "Version": 1, 75 | "Action": "GetCacheStatistics" 76 | } 77 | 78 | ##### Sample response ##### 79 | 80 | { 81 | "Version": 1, 82 | "Uptime": "01:47:20", 83 | "TotalGetStatusRequests": 541, 84 | "AverageMillisecondsInGetStatus": 12.452925, 85 | "MinimumMillisecondsInGetStatus": 0.098923, 86 | "MaximumMillisecondsInGetStatus": 213.08858, 87 | "CacheHits": 383, 88 | "CacheMisses": 156, 89 | "EffectiveCachePrimes": 26, 90 | "TotalCachePrimes": 58, 91 | "EffectiveCacheInvalidations": 175, 92 | "TotalCacheInvalidations": 662, 93 | "FullCacheInvalidations": 0 94 | } 95 | 96 | ### Shutdown ### 97 | 98 | Instructs the cache process to terminate itself. 99 | 100 | ##### Sample request ##### 101 | 102 | { 103 | "Version": 1, 104 | "Action": "Shutdown" 105 | } 106 | 107 | ##### Sample response ##### 108 | 109 | { 110 | "Version": 1, 111 | "Result": "Shutting down." 112 | } 113 | 114 | ### Reporting errors ### 115 | 116 | Any errors with the request will be reported back with a "Version" and a human-readable "Error" message. For example: 117 | 118 | { 119 | "Version": 1, 120 | "Error": "Requested 'Path' is not part of a git repository." 121 | } 122 | 123 | ## Performance ## 124 | 125 | Cost for serving a cache hit in the cache process is generally between 0.1-0.3 ms, but this metric doesn't include the overhead involved in a full request. 126 | 127 | The following measurements were taken on git repositories containing the specified file count. Each file was a text file containing a single sentence of text. Each case was run 5 times and the numbers reported below are averages. Each individual measurement was taken with a high resolution timer at 1 ms precision. 128 | 129 | ### Request from git-status-cache-posh-client ### 130 | 131 | The measurements below capture: 132 | * Serializing a request to JSON in PowerShell. 133 | * Sending the JSON request over the named pipe. 134 | * Compute the current git status (cache miss) or retrieving it from the cache (cache hit) int the cache process. 135 | * Sending the JSON response over the named pipe back to the PowerShell client. 136 | * Deserializing the JSON response into a PSCustomObject 137 | 138 | ##### No files modified ##### 139 | 140 | | | cache miss | cache hit | 141 | |-------------------------|------------|-----------| 142 | | 10 file repository | 3.0 ms | 1.0 ms | 143 | | 100 file respository | 3.0 ms | 1.0 ms | 144 | | 1,000 file respository | 5.2 ms | 1.0 ms | 145 | | 10,000 file respository | 22.4 ms | 1.0 ms | 146 | | 100,000 file repository | 176.6 ms | 1.0 ms | 147 | 148 | ##### 10 files modified ##### 149 | 150 | | | cache miss | cache hit | 151 | |-------------------------|------------|-----------| 152 | | 10 file repository | 2.8 ms | 1.0 ms | 153 | | 100 file respository | 2.8 ms | 1.0 ms | 154 | | 1,000 file respository | 4.4 ms | 1.0 ms | 155 | | 10,000 file respository | 20.6 ms | 1.0 ms | 156 | | 100,000 file repository | 176.6 ms | 1.0 ms | 157 | 158 | ### Using git-status-cache-posh-client to back posh-git ### 159 | 160 | The measurements below capture all the steps from the git-status-cache-posh-client section as well as the cost for posh-git to render the prompt. This extra step has a fixed cost of around 7-10 ms. 161 | 162 | Costs were gathered using timestamps from posh-git's debug output. Timings for posh-git without git-status-cache and git-status-cache-posh-client are included for in the "posh-git without cache" column for reference. 163 | 164 | ##### No files modified ##### 165 | 166 | | | cache miss | cache hit | posh-git without cache | 167 | |-------------------------|------------|-----------|------------------------| 168 | | 10 file repository | 11.8 ms | 8.6 ms | 57.0 ms | 169 | | 100 file respository | 11.8 ms | 8.6 ms | 62.2 ms | 170 | | 1,000 file respository | 14.2 ms | 8.6 ms | 68.2 ms | 171 | | 10,000 file respository | 30.8 ms | 8.0 ms | 133.8 ms | 172 | | 100,000 file repository | 181.6 ms | 9.6 ms | 754.4 ms | 173 | 174 | ##### 10 files modified ##### 175 | 176 | | | cache miss | cache hit | posh-git without cache | 177 | |-------------------------|------------|-----------|------------------------| 178 | | 10 file repository | 12.2 ms | 9.4 ms | 63.2 ms | 179 | | 100 file respository | 12.2 ms | 9.4 ms | 65.0 ms | 180 | | 1,000 file respository | 14.4 ms | 9.4 ms | 72.8 ms | 181 | | 10,000 file respository | 31.0 ms | 9.4 ms | 134.0 ms | 182 | | 100,000 file repository | 179.6 ms | 9.0 ms | 763.6 ms | 183 | 184 | ## Build ## 185 | 186 | Build through Visual Studio using the [solution](ide/GitStatusCache.sln) after configuring required dependencies. 187 | 188 | ### Build dependencies ### 189 | 190 | #### CMake #### 191 | 192 | Project includes libgit2 as a submodule. Initial build of libgit2 requires [CMake](http://www.cmake.org/ "CMake"). 193 | 194 | #### Boost #### 195 | 196 | Visual Studio project requires BOOST_ROOT variable be set to the location of Boost's root directory. Boost can be built using the *Simplified Build From Source* instructions on Boost's [getting started page](http://www.boost.org/doc/libs/1_58_0/more/getting_started/windows.html "getting started page"): 197 | 198 | > If you wish to build from source with Visual C++, you can use a simple build procedure described in this section. Open the command prompt and change your current directory to the Boost root directory. Then, type the following commands: 199 | > 200 | bootstrap 201 | .\b2 runtime-link=static 202 | 203 | #### libgit2 #### 204 | 205 | libgit2 is included as a submodule. To pull locally: 206 | 207 | git submodule update --init --recursive 208 | 209 | CMake is required to build. See libgit2 [build instructions](https://libgit2.github.com/docs/guides/build-and-link/ "build instructions") for details. Use the following options to generate Visual Studio project and perform initial build: 210 | 211 | cd .\ext\libgit2 212 | mkdir build 213 | cd build 214 | cmake .. -DBUILD_SHARED_LIBS=OFF -DSTATIC_CRT=ON -DTHREADSAFE=ON -DBUILD_CLAR=OFF 215 | cmake --build . --config Debug 216 | cmake --build . --config Release 217 | 218 | #### rapidjson #### 219 | 220 | [rapidjson](https://github.com/miloyip/rapidjson/ "rapidjson") headers are included as a submodule. To pull locally: 221 | 222 | git submodule update --init --recursive 223 | 224 | -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/ReadMe.txt: -------------------------------------------------------------------------------- 1 | 2 | Date: May 2010 3 | Version: 1.1 4 | Status: Beta. This code has been tested, but not exhaustively. 5 | 6 | This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" 7 | http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html 8 | 9 | To contact me, please leave a comment in the blog. 10 | 11 | This project is for Visual Studio 2010. It should build and work fine under 12 | Visual Studio 2005 or 2008, you'll just need new solution and project files. 13 | All project options were left at the default. 14 | 15 | When the application starts, it immediately starts monitoring your home 16 | directory, including children, as well as C:\, not including children. 17 | The application exits when you hit Esc. 18 | You can add a directory to the monitoring list by typing the directory 19 | name and hitting Enter. Notifications will pause while you type. 20 | 21 | This sample includes four classes and a main() function to drive them. 22 | 23 | CReadDirectoryChanges 24 | 25 | This is the public interface. It starts the worker thread and shuts it down, 26 | provides a waitable queue, and allows you to add directories. 27 | 28 | CReadChangesServer 29 | 30 | This is the server class that runs in the worker thread. One instance of this 31 | object is allocated for each instance of CReadDirectoryChanges. This class is 32 | responsible for thread startup, orderly thread shutdown, and shimming the 33 | various C-style callback functions to C++ member functions. 34 | 35 | CReadChangesRequest 36 | 37 | One instance of CReadChangesRequest is created for each monitored directory. 38 | It contains the OVERLAPPED structure, the data buffer, the completion 39 | notification function, and code to unpack the notification buffer. When 40 | a notification arrives, the data is unpacked and each change is sent to 41 | the queue in CReadDirectoryChanges. All instances of this class run in 42 | the worker thread 43 | 44 | CThreadSafeQueue 45 | 46 | Generic template class that provides a thread-safe, bounded queue that 47 | can be waited on using any of the Win32 WaitXxx functions. 48 | 49 | 50 | Implementation Notes 51 | 52 | This is a Unicode project. I haven't tried to build it single-byte, but 53 | it should work fine. 54 | 55 | The only notably missing function is RemoveDirectory. This is straightforward 56 | to implement. Create the appropriate shim and member functions in 57 | CReadChangesServer. In the member function, search m_pBlocks for the directory, 58 | call RequestTermination() on that block, and delete the block from the m_pBlocks vector. 59 | -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/download.txt: -------------------------------------------------------------------------------- 1 | http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/ide/ReadDirectoryChanges.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReadDirectoryChanges", "ReadDirectoryChanges.vcxproj", "{72A7916C-36BC-49D6-A9A9-B9FF8347BCE6}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReadDirectoryChangesLib", "ReadDirectoryChangesLib.vcxproj", "{A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Win32 = Debug|Win32 13 | Release|Win32 = Release|Win32 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {72A7916C-36BC-49D6-A9A9-B9FF8347BCE6}.Debug|Win32.ActiveCfg = Debug|Win32 17 | {72A7916C-36BC-49D6-A9A9-B9FF8347BCE6}.Debug|Win32.Build.0 = Debug|Win32 18 | {72A7916C-36BC-49D6-A9A9-B9FF8347BCE6}.Release|Win32.ActiveCfg = Release|Win32 19 | {72A7916C-36BC-49D6-A9A9-B9FF8347BCE6}.Release|Win32.Build.0 = Release|Win32 20 | {A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6}.Debug|Win32.ActiveCfg = Debug|Win32 21 | {A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6}.Debug|Win32.Build.0 = Debug|Win32 22 | {A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6}.Release|Win32.ActiveCfg = Release|Win32 23 | {A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6}.Release|Win32.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/ide/ReadDirectoryChanges.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {72A7916C-36BC-49D6-A9A9-B9FF8347BCE6} 15 | Win32Proj 16 | ReadDirectoryChanges 17 | 18 | 19 | 20 | Application 21 | true 22 | Unicode 23 | Dynamic 24 | Static 25 | v120 26 | 27 | 28 | Application 29 | false 30 | true 31 | Unicode 32 | Dynamic 33 | Static 34 | v120 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | true 48 | $(ProjectDir)\..\bin\$(ProjectName)\$(Configuration)\ 49 | $(ProjectDir)\..\build\$(ProjectName)\$(Configuration)\ 50 | 51 | 52 | false 53 | $(ProjectDir)\..\bin\$(ProjectName)\$(Configuration)\ 54 | $(ProjectDir)\..\build\$(ProjectName)\$(Configuration)\ 55 | 56 | 57 | 58 | Use 59 | Level3 60 | Disabled 61 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 62 | $(BOOST_ROOT);$(ProjectDir)\..\inc;%(AdditionalIncludeDirectories) 63 | 64 | 65 | Console 66 | true 67 | $(BOOST_ROOT)\stage\lib 68 | 69 | 70 | 71 | 72 | Level3 73 | Use 74 | MaxSpeed 75 | true 76 | true 77 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 78 | false 79 | $(BOOST_ROOT);$(ProjectDir)\..\inc;%(AdditionalIncludeDirectories) 80 | 81 | 82 | Console 83 | true 84 | true 85 | true 86 | $(BOOST_ROOT)\stage\lib 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | Create 105 | Create 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/ide/ReadDirectoryChanges.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Header Files 19 | 20 | 21 | Header Files 22 | 23 | 24 | Header Files 25 | 26 | 27 | Header Files 28 | 29 | 30 | Header Files 31 | 32 | 33 | 34 | 35 | Source Files 36 | 37 | 38 | Source Files 39 | 40 | 41 | Source Files 42 | 43 | 44 | Source Files 45 | 46 | 47 | -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/ide/ReadDirectoryChangesLib.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6} 23 | Win32Proj 24 | ReadDirectoryChanges 25 | 26 | 27 | 28 | StaticLibrary 29 | true 30 | Unicode 31 | Dynamic 32 | Static 33 | v142 34 | 35 | 36 | StaticLibrary 37 | true 38 | Unicode 39 | Dynamic 40 | Static 41 | v142 42 | 43 | 44 | StaticLibrary 45 | false 46 | true 47 | Unicode 48 | Dynamic 49 | Static 50 | v142 51 | 52 | 53 | StaticLibrary 54 | false 55 | true 56 | Unicode 57 | Dynamic 58 | Static 59 | v142 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | true 79 | $(ProjectDir)\..\bin\$(ProjectName)\$(Configuration)\ 80 | $(ProjectDir)\..\build\$(ProjectName)\$(Configuration)\ 81 | 82 | 83 | true 84 | 85 | 86 | false 87 | $(ProjectDir)\..\bin\$(ProjectName)\$(Configuration)\ 88 | $(ProjectDir)\..\build\$(ProjectName)\$(Configuration)\ 89 | 90 | 91 | false 92 | 93 | 94 | 95 | Use 96 | Level3 97 | Disabled 98 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 99 | $(BOOST_ROOT);$(ProjectDir)\..\inc;%(AdditionalIncludeDirectories) 100 | MultiThreadedDebug 101 | 102 | 103 | Console 104 | true 105 | 106 | 107 | 108 | 109 | Use 110 | Level3 111 | Disabled 112 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 113 | $(BOOST_ROOT);$(ProjectDir)\..\inc;%(AdditionalIncludeDirectories) 114 | MultiThreadedDebug 115 | 116 | 117 | Console 118 | true 119 | 120 | 121 | 122 | 123 | Level3 124 | Use 125 | MaxSpeed 126 | true 127 | true 128 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 129 | false 130 | $(BOOST_ROOT);$(ProjectDir)\..\inc;%(AdditionalIncludeDirectories) 131 | MultiThreaded 132 | 133 | 134 | Console 135 | true 136 | true 137 | true 138 | 139 | 140 | 141 | 142 | Level3 143 | Use 144 | MaxSpeed 145 | true 146 | true 147 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 148 | false 149 | $(BOOST_ROOT);$(ProjectDir)\..\inc;%(AdditionalIncludeDirectories) 150 | MultiThreaded 151 | 152 | 153 | Console 154 | true 155 | true 156 | true 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | Create 171 | Create 172 | Create 173 | Create 174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/ide/ReadDirectoryChangesLib.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | 14 | 15 | Header Files 16 | 17 | 18 | Header Files 19 | 20 | 21 | Header Files 22 | 23 | 24 | Header Files 25 | 26 | 27 | Header Files 28 | 29 | 30 | 31 | 32 | Source Files 33 | 34 | 35 | Source Files 36 | 37 | 38 | Source Files 39 | 40 | 41 | -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/inc/ReadDirectoryChanges.h: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License 3 | // 4 | // Copyright (c) 2010 James E Beveridge 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | 25 | // This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" 26 | // http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html 27 | // See ReadMe.txt for overview information. 28 | 29 | 30 | #pragma once 31 | 32 | #include "ThreadSafeQueue.h" 33 | 34 | static const DWORD FILE_ACTION_CHANGES_LOST = 0x009402006; 35 | typedef std::tuple TDirectoryChangeNotification; 36 | 37 | namespace ReadDirectoryChangesPrivate 38 | { 39 | class CReadChangesServer; 40 | } 41 | 42 | /////////////////////////////////////////////////////////////////////////// 43 | 44 | 45 | /// 46 | /// Track changes to filesystem directories and report them 47 | /// to the caller via a thread-safe queue. 48 | /// 49 | /// 50 | /// 51 | /// This sample code is based on my blog entry titled, "Understanding ReadDirectoryChangesW" 52 | /// http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html 53 | /// 54 | /// All functions in CReadDirectoryChangesServer run in 55 | /// the context of the calling thread. 56 | /// 57 | /// 58 | /// CReadDirectoryChanges changes; 59 | /// changes.AddDirectory(_T("C:\\"), false, dwNotificationFlags); 60 | /// 61 | /// const HANDLE handles[] = { hStopEvent, changes.GetWaitHandle() }; 62 | /// 63 | /// while (!bTerminate) 64 | /// { 65 | /// ::MsgWaitForMultipleObjectsEx( 66 | /// _countof(handles), 67 | /// handles, 68 | /// INFINITE, 69 | /// QS_ALLINPUT, 70 | /// MWMO_INPUTAVAILABLE | MWMO_ALERTABLE); 71 | /// switch (rc) 72 | /// { 73 | /// case WAIT_OBJECT_0 + 0: 74 | /// bTerminate = true; 75 | /// break; 76 | /// case WAIT_OBJECT_0 + 1: 77 | /// // We've received a notification in the queue. 78 | /// { 79 | /// DWORD dwAction; 80 | /// CStringW wstrFilename; 81 | /// changes.Pop(dwAction, wstrFilename); 82 | /// wprintf(L"%s %s\n", ExplainAction(dwAction), wstrFilename); 83 | /// } 84 | /// break; 85 | /// case WAIT_OBJECT_0 + _countof(handles): 86 | /// // Get and dispatch message 87 | /// break; 88 | /// case WAIT_IO_COMPLETION: 89 | /// // APC complete.No action needed. 90 | /// break; 91 | /// } 92 | /// } 93 | /// 94 | /// 95 | class CReadDirectoryChanges 96 | { 97 | public: 98 | CReadDirectoryChanges(int nMaxChanges=1000); 99 | ~CReadDirectoryChanges(); 100 | 101 | void Init(); 102 | void Terminate(); 103 | 104 | /// 105 | /// Add a new directory to be monitored. 106 | /// 107 | /// Directory to monitor. 108 | /// Token identifying this request. Will be passed back with notificaitons. 109 | /// True to also monitor subdirectories. 110 | /// The types of file system events to monitor, such as FILE_NOTIFY_CHANGE_ATTRIBUTES. 111 | /// The size of the buffer used for overlapped I/O. 112 | /// 113 | /// 114 | /// This function will make an APC call to the worker thread to issue a new 115 | /// ReadDirectoryChangesW call for the given directory with the given flags. 116 | /// 117 | /// 118 | void AddDirectory(LPCTSTR wszDirectory, UINT32 token, BOOL bWatchSubtree, DWORD dwNotifyFilter, DWORD dwBufferSize = 16384); 119 | 120 | /// 121 | /// Return a handle for the Win32 Wait... functions that will be 122 | /// signaled when there is a queue entry. 123 | /// 124 | HANDLE GetWaitHandle() { return m_Notifications.GetWaitHandle(); } 125 | 126 | bool Pop(UINT32& token, DWORD& dwAction, CStringW& wstrFilename); 127 | 128 | bool Pop(UINT32& token, DWORD& dwAction, std::wstring& wstrFilename); 129 | 130 | // "Push" is for usage by ReadChangesRequest. Not intended for external usage. 131 | void Push(UINT32 token, DWORD dwAction, CStringW& wstrFilename); 132 | 133 | // Check if the queue overflowed. If so, clear it and return true. 134 | bool CheckOverflow(); 135 | 136 | unsigned int GetThreadId() { return m_dwThreadId; } 137 | 138 | protected: 139 | ReadDirectoryChangesPrivate::CReadChangesServer* m_pServer; 140 | 141 | HANDLE m_hThread; 142 | 143 | unsigned int m_dwThreadId; 144 | 145 | CThreadSafeQueue m_Notifications; 146 | }; 147 | -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/inc/ThreadSafeQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License 3 | // 4 | // Copyright (c) 2010 James E Beveridge 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | 25 | // This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" 26 | // http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html 27 | // See ReadMe.txt for overview information. 28 | 29 | #include 30 | 31 | template 32 | class CThreadSafeQueue : protected std::list 33 | { 34 | public: 35 | CThreadSafeQueue(int nMaxCount) 36 | { 37 | m_bOverflow = false; 38 | 39 | m_hSemaphore = ::CreateSemaphore( 40 | NULL, // no security attributes 41 | 0, // initial count 42 | nMaxCount, // max count 43 | NULL); // anonymous 44 | } 45 | 46 | ~CThreadSafeQueue() 47 | { 48 | ::CloseHandle(m_hSemaphore); 49 | m_hSemaphore = NULL; 50 | } 51 | 52 | void push(C& c) 53 | { 54 | CComCritSecLock lock( m_Crit, true ); 55 | push_back( c ); 56 | lock.Unlock(); 57 | 58 | if (!::ReleaseSemaphore(m_hSemaphore, 1, NULL)) 59 | { 60 | // If the semaphore is full, then take back the entry. 61 | pop_back(); 62 | if (GetLastError() == ERROR_TOO_MANY_POSTS) 63 | { 64 | m_bOverflow = true; 65 | } 66 | } 67 | } 68 | 69 | bool pop(C& c) 70 | { 71 | CComCritSecLock lock( m_Crit, true ); 72 | 73 | // If the user calls pop() more than once after the 74 | // semaphore is signaled, then the semaphore count will 75 | // get out of sync. We fix that when the queue empties. 76 | if (empty()) 77 | { 78 | while (::WaitForSingleObject(m_hSemaphore, 0) != WAIT_TIMEOUT) 79 | 1; 80 | return false; 81 | } 82 | 83 | c = front(); 84 | pop_front(); 85 | 86 | return true; 87 | } 88 | 89 | // If overflow, use this to clear the queue. 90 | void clear() 91 | { 92 | CComCritSecLock lock( m_Crit, true ); 93 | 94 | for (DWORD i=0; i VK_HELP) 135 | { 136 | if (!gets(buf)) // End of file, usually Ctrl-Z 137 | bTerminate = true; 138 | else 139 | return true; 140 | } 141 | } 142 | 143 | ::FlushConsoleInputBuffer(hStdIn); 144 | 145 | return false; 146 | } 147 | -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/src/ReadDirectoryChanges.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License 3 | // 4 | // Copyright (c) 2010 James E Beveridge 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | 25 | // This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" 26 | // http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html 27 | // See ReadMe.txt for overview information. 28 | 29 | #include "stdafx.h" 30 | #include "ReadDirectoryChanges.h" 31 | #include "ReadDirectoryChangesPrivate.h" 32 | 33 | using namespace ReadDirectoryChangesPrivate; 34 | 35 | /////////////////////////////////////////////////////////////////////////// 36 | // CReadDirectoryChanges 37 | 38 | CReadDirectoryChanges::CReadDirectoryChanges(int nMaxCount) 39 | : m_Notifications(nMaxCount) 40 | { 41 | m_hThread = NULL; 42 | m_dwThreadId= 0; 43 | m_pServer = new CReadChangesServer(this); 44 | } 45 | 46 | CReadDirectoryChanges::~CReadDirectoryChanges() 47 | { 48 | Terminate(); 49 | delete m_pServer; 50 | } 51 | 52 | void CReadDirectoryChanges::Init() 53 | { 54 | // 55 | // Kick off the worker thread, which will be 56 | // managed by CReadChangesServer. 57 | // 58 | m_hThread = (HANDLE)_beginthreadex(NULL, 59 | 0, 60 | CReadChangesServer::ThreadStartProc, 61 | m_pServer, 62 | 0, 63 | &m_dwThreadId 64 | ); 65 | } 66 | 67 | void CReadDirectoryChanges::Terminate() 68 | { 69 | if (m_hThread) 70 | { 71 | ::QueueUserAPC(CReadChangesServer::TerminateProc, m_hThread, (ULONG_PTR)m_pServer); 72 | ::WaitForSingleObjectEx(m_hThread, 10000, true); 73 | ::CloseHandle(m_hThread); 74 | 75 | m_hThread = NULL; 76 | m_dwThreadId = 0; 77 | } 78 | } 79 | 80 | void CReadDirectoryChanges::AddDirectory(LPCTSTR szDirectory, UINT32 token, BOOL bWatchSubtree, DWORD dwNotifyFilter, DWORD dwBufferSize) 81 | { 82 | if (!m_hThread) 83 | Init(); 84 | 85 | CReadChangesRequest* pRequest = new CReadChangesRequest(m_pServer, szDirectory, bWatchSubtree, dwNotifyFilter, dwBufferSize, token); 86 | QueueUserAPC(CReadChangesServer::AddDirectoryProc, m_hThread, (ULONG_PTR)pRequest); 87 | } 88 | 89 | void CReadDirectoryChanges::Push(UINT32 token, DWORD dwAction, CStringW& wstrFilename) 90 | { 91 | m_Notifications.push( TDirectoryChangeNotification(token, dwAction, wstrFilename) ); 92 | } 93 | 94 | bool CReadDirectoryChanges::Pop(UINT32& token, DWORD& dwAction, CStringW& wstrFilename) 95 | { 96 | TDirectoryChangeNotification tuple; 97 | if (!m_Notifications.pop(tuple)) 98 | return false; 99 | 100 | token = std::get<0>(tuple); 101 | dwAction = std::get<1>(tuple); 102 | wstrFilename = std::get<2>(tuple); 103 | 104 | return true; 105 | } 106 | 107 | bool CReadDirectoryChanges::Pop(UINT32& token, DWORD& dwAction, std::wstring& wstrFilename) 108 | { 109 | CStringW filename; 110 | if (this->Pop(token, dwAction, filename)) 111 | { 112 | wstrFilename = std::wstring(filename); 113 | return true; 114 | } 115 | 116 | return false; 117 | } 118 | 119 | bool CReadDirectoryChanges::CheckOverflow() 120 | { 121 | bool b = m_Notifications.overflow(); 122 | if (b) 123 | m_Notifications.clear(); 124 | return b; 125 | } 126 | -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/src/ReadDirectoryChangesPrivate.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License 3 | // 4 | // Copyright (c) 2010 James E Beveridge 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | 25 | // This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" 26 | // http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html 27 | // See ReadMe.txt for overview information. 28 | 29 | 30 | #include "stdafx.h" 31 | #include 32 | #include "ReadDirectoryChanges.h" 33 | #include "ReadDirectoryChangesPrivate.h" 34 | 35 | 36 | // The namespace is a convenience to emphasize that these are internals 37 | // interfaces. The namespace can be safely removed if you need to. 38 | namespace ReadDirectoryChangesPrivate 39 | { 40 | 41 | /////////////////////////////////////////////////////////////////////////// 42 | // CReadChangesRequest 43 | 44 | CReadChangesRequest::CReadChangesRequest(CReadChangesServer* pServer, LPCTSTR sz, BOOL b, DWORD dw, DWORD size, UINT32 token) 45 | { 46 | m_pServer = pServer; 47 | m_dwFlags = dw; 48 | m_bChildren = b; 49 | m_wstrDirectory = sz; 50 | m_hDirectory = 0; 51 | m_token = token; 52 | 53 | ::ZeroMemory(&m_Overlapped, sizeof(OVERLAPPED)); 54 | 55 | // The hEvent member is not used when there is a completion 56 | // function, so it's ok to use it to point to the object. 57 | m_Overlapped.hEvent = this; 58 | 59 | m_Buffer.resize(size); 60 | m_BackupBuffer.resize(size); 61 | } 62 | 63 | 64 | CReadChangesRequest::~CReadChangesRequest() 65 | { 66 | // RequestTermination() must have been called successfully. 67 | _ASSERTE(m_hDirectory == NULL || m_hDirectory == INVALID_HANDLE_VALUE); 68 | } 69 | 70 | 71 | bool CReadChangesRequest::OpenDirectory() 72 | { 73 | // Allow this routine to be called redundantly. 74 | if (m_hDirectory) 75 | return true; 76 | 77 | m_hDirectory = ::CreateFile( 78 | m_wstrDirectory, // pointer to the file name 79 | FILE_LIST_DIRECTORY, // access (read/write) mode 80 | FILE_SHARE_READ // share mode 81 | | FILE_SHARE_WRITE 82 | | FILE_SHARE_DELETE, 83 | NULL, // security descriptor 84 | OPEN_EXISTING, // how to create 85 | FILE_FLAG_BACKUP_SEMANTICS // file attributes 86 | | FILE_FLAG_OVERLAPPED, 87 | NULL); // file with attributes to copy 88 | 89 | if (m_hDirectory == INVALID_HANDLE_VALUE) 90 | { 91 | return false; 92 | } 93 | 94 | return true; 95 | } 96 | 97 | void CReadChangesRequest::BeginRead() 98 | { 99 | DWORD dwBytes=0; 100 | 101 | // This call needs to be reissued after every APC. 102 | BOOL success = ::ReadDirectoryChangesW( 103 | m_hDirectory, // handle to directory 104 | &m_Buffer[0], // read results buffer 105 | m_Buffer.size(), // length of buffer 106 | m_bChildren, // monitoring option 107 | m_dwFlags, // filter conditions 108 | &dwBytes, // bytes returned 109 | &m_Overlapped, // overlapped buffer 110 | &NotificationCompletion); // completion routine 111 | } 112 | 113 | //static 114 | VOID CALLBACK CReadChangesRequest::NotificationCompletion( 115 | DWORD dwErrorCode, // completion code 116 | DWORD dwNumberOfBytesTransfered, // number of bytes transferred 117 | LPOVERLAPPED lpOverlapped) // I/O information buffer 118 | { 119 | CReadChangesRequest* pBlock = (CReadChangesRequest*)lpOverlapped->hEvent; 120 | 121 | if (dwErrorCode == ERROR_OPERATION_ABORTED) 122 | { 123 | ::InterlockedDecrement(&pBlock->m_pServer->m_nOutstandingRequests); 124 | delete pBlock; 125 | return; 126 | } 127 | 128 | // No bytes indicates error, but can occur with success error code. 129 | // Observed when debugging notification handler and changes were backed up. 130 | if (!dwNumberOfBytesTransfered) 131 | { 132 | _ASSERTE(dwErrorCode == ERROR_SUCCESS); 133 | if (dwErrorCode == ERROR_SUCCESS) 134 | { 135 | pBlock->BeginRead(); 136 | pBlock->NotifyEventsLost(); 137 | } 138 | 139 | return; 140 | } 141 | 142 | // Can't use sizeof(FILE_NOTIFY_INFORMATION) because 143 | // the structure is padded to 16 bytes. 144 | _ASSERTE(dwNumberOfBytesTransfered >= offsetof(FILE_NOTIFY_INFORMATION, FileName) + sizeof(WCHAR)); 145 | 146 | pBlock->BackupBuffer(dwNumberOfBytesTransfered); 147 | 148 | // Get the new read issued as fast as possible. The documentation 149 | // says that the original OVERLAPPED structure will not be used 150 | // again once the completion routine is called. 151 | pBlock->BeginRead(); 152 | 153 | pBlock->ProcessNotification(); 154 | } 155 | 156 | void CReadChangesRequest::NotifyEventsLost() 157 | { 158 | auto path = ExpandFilename(m_wstrDirectory); 159 | m_pServer->m_pBase->Push(m_token, FILE_ACTION_CHANGES_LOST, path); 160 | } 161 | 162 | void CReadChangesRequest::ProcessNotification() 163 | { 164 | char* pBase = (char*)&m_BackupBuffer[0]; 165 | 166 | for (;;) 167 | { 168 | FILE_NOTIFY_INFORMATION& fni = (FILE_NOTIFY_INFORMATION&)*pBase; 169 | 170 | CStringW wstrFilename(fni.FileName, fni.FileNameLength/sizeof(wchar_t)); 171 | 172 | // Handle a trailing backslash, such as for a root directory. 173 | auto path = boost::filesystem::path(static_cast(m_wstrDirectory)); 174 | path.append(static_cast(wstrFilename)); 175 | wstrFilename = path.c_str(); 176 | wstrFilename = ExpandFilename(wstrFilename); 177 | 178 | m_pServer->m_pBase->Push(m_token, fni.Action, wstrFilename); 179 | 180 | if (!fni.NextEntryOffset) 181 | break; 182 | pBase += fni.NextEntryOffset; 183 | }; 184 | } 185 | 186 | /*static*/ ATL::CStringW CReadChangesRequest::ExpandFilename(const ATL::CStringW& wstrFilename) 187 | { 188 | // If it could be a short filename, expand it. 189 | LPCWSTR wszFilename = PathFindFileNameW(wstrFilename); 190 | int len = lstrlenW(wszFilename); 191 | // The maximum length of an 8.3 filename is twelve, including the dot. 192 | if (len <= 12 && wcschr(wszFilename, L'~')) 193 | { 194 | // Convert to the long filename form. Unfortunately, this 195 | // does not work for deletions, so it's an imperfect fix. 196 | wchar_t wbuf[MAX_PATH]; 197 | if (::GetLongPathName(wstrFilename, wbuf, _countof(wbuf)) > 0) 198 | return ATL::CStringW{wbuf}; 199 | } 200 | 201 | return wstrFilename; 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/src/ReadDirectoryChangesPrivate.h: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License 3 | // 4 | // Copyright (c) 2010 James E Beveridge 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | 25 | // This sample code is for my blog entry titled, "Understanding ReadDirectoryChangesW" 26 | // http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw.html 27 | // See ReadMe.txt for overview information. 28 | 29 | class CReadDirectoryChanges; 30 | 31 | namespace ReadDirectoryChangesPrivate 32 | { 33 | 34 | class CReadChangesServer; 35 | 36 | /////////////////////////////////////////////////////////////////////////// 37 | 38 | // All functions in CReadChangesRequest run in the context of the worker thread. 39 | // One instance of this object is created for each call to AddDirectory(). 40 | class CReadChangesRequest 41 | { 42 | public: 43 | CReadChangesRequest(CReadChangesServer* pServer, LPCTSTR sz, BOOL b, DWORD dw, DWORD size, UINT32 token); 44 | 45 | ~CReadChangesRequest(); 46 | 47 | bool OpenDirectory(); 48 | 49 | void BeginRead(); 50 | 51 | // The dwSize is the actual number of bytes sent to the APC. 52 | void BackupBuffer(DWORD dwSize) 53 | { 54 | // We could just swap back and forth between the two 55 | // buffers, but this code is easier to understand and debug. 56 | memcpy(&m_BackupBuffer[0], &m_Buffer[0], dwSize); 57 | } 58 | 59 | void NotifyEventsLost(); 60 | 61 | void ProcessNotification(); 62 | 63 | void RequestTermination() 64 | { 65 | ::CancelIo(m_hDirectory); 66 | ::CloseHandle(m_hDirectory); 67 | m_hDirectory = nullptr; 68 | } 69 | 70 | CReadChangesServer* m_pServer; 71 | 72 | protected: 73 | 74 | static VOID CALLBACK NotificationCompletion( 75 | DWORD dwErrorCode, // completion code 76 | DWORD dwNumberOfBytesTransfered, // number of bytes transferred 77 | LPOVERLAPPED lpOverlapped); // I/O information buffer 78 | 79 | static ATL::CStringW ExpandFilename(const ATL::CStringW& wstrFilename); 80 | 81 | // Parameters from the caller for ReadDirectoryChangesW(). 82 | DWORD m_dwFlags; 83 | BOOL m_bChildren; 84 | CStringW m_wstrDirectory; 85 | 86 | // Result of calling CreateFile(). 87 | HANDLE m_hDirectory; 88 | 89 | // Required parameter for ReadDirectoryChangesW(). 90 | OVERLAPPED m_Overlapped; 91 | 92 | // Data buffer for the request. 93 | // Since the memory is allocated by malloc, it will always 94 | // be aligned as required by ReadDirectoryChangesW(). 95 | vector m_Buffer; 96 | 97 | // Double buffer strategy so that we can issue a new read 98 | // request before we process the current buffer. 99 | vector m_BackupBuffer; 100 | 101 | // Token to identify notifications resulting from this request. 102 | UINT32 m_token; 103 | }; 104 | 105 | /////////////////////////////////////////////////////////////////////////// 106 | 107 | // All functions in CReadChangesServer run in the context of the worker thread. 108 | // One instance of this object is allocated for each instance of CReadDirectoryChanges. 109 | // This class is responsible for thread startup, orderly thread shutdown, and shimming 110 | // the various C++ member functions with C-style Win32 functions. 111 | class CReadChangesServer 112 | { 113 | public: 114 | CReadChangesServer(CReadDirectoryChanges* pParent) 115 | { 116 | m_bTerminate=false; m_nOutstandingRequests=0;m_pBase=pParent; 117 | } 118 | 119 | static unsigned int WINAPI ThreadStartProc(LPVOID arg) 120 | { 121 | CReadChangesServer* pServer = (CReadChangesServer*)arg; 122 | pServer->Run(); 123 | return 0; 124 | } 125 | 126 | // Called by QueueUserAPC to start orderly shutdown. 127 | static void CALLBACK TerminateProc(__in ULONG_PTR arg) 128 | { 129 | CReadChangesServer* pServer = (CReadChangesServer*)arg; 130 | pServer->RequestTermination(); 131 | } 132 | 133 | // Called by QueueUserAPC to add another directory. 134 | static void CALLBACK AddDirectoryProc(__in ULONG_PTR arg) 135 | { 136 | CReadChangesRequest* pRequest = (CReadChangesRequest*)arg; 137 | pRequest->m_pServer->AddDirectory(pRequest); 138 | } 139 | 140 | CReadDirectoryChanges* m_pBase; 141 | 142 | volatile DWORD m_nOutstandingRequests; 143 | 144 | protected: 145 | 146 | void Run() 147 | { 148 | while (m_nOutstandingRequests || !m_bTerminate) 149 | { 150 | DWORD rc = ::SleepEx(INFINITE, true); 151 | } 152 | } 153 | 154 | void AddDirectory( CReadChangesRequest* pBlock ) 155 | { 156 | if (pBlock->OpenDirectory()) 157 | { 158 | ::InterlockedIncrement(&pBlock->m_pServer->m_nOutstandingRequests); 159 | m_pBlocks.push_back(pBlock); 160 | pBlock->BeginRead(); 161 | } 162 | else 163 | delete pBlock; 164 | } 165 | 166 | void RequestTermination() 167 | { 168 | m_bTerminate = true; 169 | 170 | for (DWORD i=0; iRequestTermination(); 174 | } 175 | 176 | m_pBlocks.clear(); 177 | } 178 | 179 | vector m_pBlocks; 180 | 181 | bool m_bTerminate; 182 | }; 183 | 184 | } 185 | -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/src/stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // ReadDirectoryChanges.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | 7 | // TODO: reference any additional headers you need in STDAFX.H 8 | // and not in this file 9 | -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/src/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #define _CRT_SECURE_NO_DEPRECATE 9 | 10 | #include "targetver.h" 11 | 12 | #include 13 | 14 | #ifndef VC_EXTRALEAN 15 | #define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers 16 | #endif 17 | 18 | #include 19 | 20 | #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | using namespace std; 31 | -------------------------------------------------------------------------------- /ext/ReadDirectoryChanges/src/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /ext/ScopedResource/download.txt: -------------------------------------------------------------------------------- 1 | http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3949.pdf -------------------------------------------------------------------------------- /ext/ScopedResource/inc/scope_guard.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmarcusreid/git-status-cache/b45659e32aa3b833d190f23bb95233f489ac9e9c/ext/ScopedResource/inc/scope_guard.h -------------------------------------------------------------------------------- /ext/ScopedResource/inc/unique_resource.h: -------------------------------------------------------------------------------- 1 | #ifndef UNIQUE_RESOURCE_H_ 2 | #define UNIQUE_RESOURCE_H_ 3 | 4 | namespace std{ namespace experimental{ 5 | 6 | enum class invoke_it { once, again }; 7 | 8 | template 9 | class unique_resource_t { 10 | R resource; 11 | D deleter; 12 | bool execute_on_destruction; // exposition only 13 | unique_resource_t& operator=(unique_resource_t const &) = delete; 14 | unique_resource_t(unique_resource_t const &) = delete; // no copies! 15 | 16 | public: 17 | // construction 18 | explicit unique_resource_t(R && resource, D && deleter, bool shouldrun = true) noexcept 19 | : resource(std::move(resource)) 20 | , deleter(std::move(deleter)) 21 | , execute_on_destruction{ shouldrun } 22 | { } 23 | 24 | // move 25 | unique_resource_t(unique_resource_t &&other) noexcept 26 | : resource(std::move(other.resource)) 27 | , deleter(std::move(other.deleter)) 28 | , execute_on_destruction{ other.execute_on_destruction } 29 | { 30 | other.release(); 31 | } 32 | 33 | unique_resource_t& operator=(unique_resource_t &&other) noexcept 34 | { 35 | this->invoke(invoke_it::once); 36 | deleter = std::move(other.deleter); 37 | resource = std::move(other.resource); 38 | execute_on_destruction = other.execute_on_destruction; 39 | other.release(); 40 | return *this; 41 | } 42 | 43 | // resource release 44 | ~unique_resource_t() 45 | { 46 | this->invoke(invoke_it::once); 47 | } 48 | 49 | void invoke(invoke_it const strategy = invoke_it::once) noexcept 50 | { 51 | if (execute_on_destruction) 52 | { 53 | try 54 | { 55 | this->get_deleter()(resource); 56 | } 57 | catch (...) 58 | { 59 | } 60 | } 61 | execute_on_destruction = strategy == invoke_it::again; 62 | } 63 | 64 | R const & release() noexcept 65 | { 66 | execute_on_destruction = false; 67 | return this->get(); 68 | } 69 | 70 | void reset(R && newresource) noexcept 71 | { 72 | invoke(invoke_it::again); 73 | resource = std::move(newresource); 74 | } 75 | 76 | // resource access 77 | R& get() noexcept 78 | { 79 | return resource; 80 | } 81 | 82 | operator R const &() const noexcept 83 | { 84 | return resource; 85 | } 86 | 87 | R operator->() const noexcept 88 | { 89 | return resource; 90 | } 91 | 92 | std::add_lvalue_reference_t> operator*() const 93 | { 94 | return *resource; 95 | } 96 | 97 | // deleter access 98 | const D & get_deleter() const noexcept 99 | { 100 | return deleter; 101 | } 102 | }; 103 | 104 | //factories 105 | template 106 | unique_resource_t unique_resource(R && r, D t) noexcept 107 | { 108 | return unique_resource_t(std::move(r), std::move(t), true); 109 | } 110 | 111 | template 112 | unique_resource_t unique_resource_checked(R r, R invalid, D t) noexcept 113 | { 114 | bool shouldrun = (r != invalid); 115 | return unique_resource_t(std::move(r), std::move(t), shouldrun); 116 | } 117 | 118 | }} 119 | 120 | #endif /* UNIQUE RESOURCE H */ 121 | -------------------------------------------------------------------------------- /ide/GitStatusCache.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29613.14 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GitStatusCache", "..\src\GitStatusCache\ide\GitStatusCache.vcxproj", "{2BB7912E-5C54-4247-AFC7-EBD2026418E5}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReadDirectoryChangesLib", "..\ext\ReadDirectoryChanges\ide\ReadDirectoryChangesLib.vcxproj", "{A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Win32 = Debug|Win32 13 | Debug|x64 = Debug|x64 14 | Release|Win32 = Release|Win32 15 | Release|x64 = Release|x64 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {2BB7912E-5C54-4247-AFC7-EBD2026418E5}.Debug|Win32.ActiveCfg = Debug|Win32 19 | {2BB7912E-5C54-4247-AFC7-EBD2026418E5}.Debug|Win32.Build.0 = Debug|Win32 20 | {2BB7912E-5C54-4247-AFC7-EBD2026418E5}.Debug|x64.ActiveCfg = Debug|x64 21 | {2BB7912E-5C54-4247-AFC7-EBD2026418E5}.Debug|x64.Build.0 = Debug|x64 22 | {2BB7912E-5C54-4247-AFC7-EBD2026418E5}.Release|Win32.ActiveCfg = Release|Win32 23 | {2BB7912E-5C54-4247-AFC7-EBD2026418E5}.Release|Win32.Build.0 = Release|Win32 24 | {2BB7912E-5C54-4247-AFC7-EBD2026418E5}.Release|x64.ActiveCfg = Release|x64 25 | {2BB7912E-5C54-4247-AFC7-EBD2026418E5}.Release|x64.Build.0 = Release|x64 26 | {A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6}.Debug|Win32.ActiveCfg = Debug|Win32 27 | {A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6}.Debug|Win32.Build.0 = Debug|Win32 28 | {A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6}.Debug|x64.ActiveCfg = Debug|x64 29 | {A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6}.Debug|x64.Build.0 = Debug|x64 30 | {A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6}.Release|Win32.ActiveCfg = Release|Win32 31 | {A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6}.Release|Win32.Build.0 = Release|Win32 32 | {A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6}.Release|x64.ActiveCfg = Release|x64 33 | {A14F8B89-3DBC-4DA9-A7D7-95070AB4DCF6}.Release|x64.Build.0 = Release|x64 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {E64B675A-C858-4C56-B8AE-3B20562FED03} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /src/GitStatusCache/ide/GitStatusCache.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {2BB7912E-5C54-4247-AFC7-EBD2026418E5} 23 | Win32Proj 24 | GitStatusCache 25 | 26 | 27 | 28 | Application 29 | true 30 | v142 31 | Unicode 32 | 33 | 34 | Application 35 | true 36 | v142 37 | Unicode 38 | 39 | 40 | Application 41 | false 42 | v142 43 | true 44 | Unicode 45 | 46 | 47 | Application 48 | false 49 | v142 50 | true 51 | Unicode 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | false 71 | $(ProjectDir)\..\bin\$(Configuration)\ 72 | $(ProjectDir)\..\build\$(Configuration)\ 73 | 74 | 75 | false 76 | 77 | 78 | false 79 | $(ProjectDir)\..\bin\$(Configuration)\ 80 | $(ProjectDir)\..\build\$(Configuration)\ 81 | 82 | 83 | false 84 | 85 | 86 | 87 | Use 88 | Level3 89 | Disabled 90 | WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) 91 | true 92 | $(BOOST_ROOT);$(SolutionDir)\..\ext\libgit2\include;$(SolutionDir)\..\ext\rapidjson\include;$(SolutionDir)\..\ext\ReadDirectoryChanges\inc;$(SolutionDir)\..\ext\ScopedResource\inc;%(AdditionalIncludeDirectories) 93 | stdafx.h 94 | /Zm200 %(AdditionalOptions) 95 | MultiThreadedDebug 96 | 97 | 98 | Windows 99 | true 100 | $(BOOST_ROOT)\stage\lib;$(SolutionDir)\..\ext\libgit2\build\Debug 101 | git2.lib;crypt32.lib;rpcrt4.lib;winhttp.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 102 | 103 | 104 | 105 | 106 | Use 107 | Level3 108 | Disabled 109 | WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) 110 | true 111 | $(BOOST_ROOT);$(SolutionDir)\..\ext\libgit2\include;$(SolutionDir)\..\ext\rapidjson\include;$(SolutionDir)\..\ext\ReadDirectoryChanges\inc;$(SolutionDir)\..\ext\ScopedResource\inc;%(AdditionalIncludeDirectories) 112 | stdafx.h 113 | /Zm200 %(AdditionalOptions) 114 | MultiThreadedDebug 115 | 116 | 117 | Windows 118 | true 119 | $(BOOST_ROOT)\stage\lib;$(SolutionDir)\..\ext\libgit2\build\Debug 120 | git2.lib;crypt32.lib;rpcrt4.lib;winhttp.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 121 | 122 | 123 | 124 | 125 | Level3 126 | Use 127 | MaxSpeed 128 | true 129 | true 130 | WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) 131 | true 132 | $(BOOST_ROOT);$(SolutionDir)\..\ext\libgit2\include;$(SolutionDir)\..\ext\rapidjson\include;$(SolutionDir)\..\ext\ReadDirectoryChanges\inc;$(SolutionDir)\..\ext\ScopedResource\inc;%(AdditionalIncludeDirectories) 133 | /Zm200 %(AdditionalOptions) 134 | MultiThreaded 135 | false 136 | 137 | 138 | Windows 139 | true 140 | true 141 | true 142 | $(BOOST_ROOT)\stage\lib;$(SolutionDir)\..\ext\libgit2\build\Release 143 | git2.lib;crypt32.lib;rpcrt4.lib;winhttp.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 144 | 145 | 146 | 147 | 148 | Level3 149 | Use 150 | MaxSpeed 151 | true 152 | true 153 | WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) 154 | true 155 | $(BOOST_ROOT);$(SolutionDir)\..\ext\libgit2\include;$(SolutionDir)\..\ext\rapidjson\include;$(SolutionDir)\..\ext\ReadDirectoryChanges\inc;$(SolutionDir)\..\ext\ScopedResource\inc;%(AdditionalIncludeDirectories) 156 | /Zm200 %(AdditionalOptions) 157 | MultiThreaded 158 | false 159 | 160 | 161 | Windows 162 | true 163 | true 164 | true 165 | $(BOOST_ROOT)\stage\lib;$(SolutionDir)\..\ext\libgit2\build\Release 166 | git2.lib;crypt32.lib;rpcrt4.lib;winhttp.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | Create 206 | Create 207 | Create 208 | Create 209 | 210 | 211 | 212 | 213 | {a14f8b89-3dbc-4da9-a7d7-95070ab4dcf6} 214 | 215 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /src/GitStatusCache/ide/GitStatusCache.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | Header Files 47 | 48 | 49 | Header Files 50 | 51 | 52 | Header Files 53 | 54 | 55 | Header Files 56 | 57 | 58 | Header Files 59 | 60 | 61 | Header Files 62 | 63 | 64 | Header Files 65 | 66 | 67 | Header Files 68 | 69 | 70 | Header Files 71 | 72 | 73 | Header Files 74 | 75 | 76 | Header Files 77 | 78 | 79 | 80 | 81 | Source Files 82 | 83 | 84 | Source Files 85 | 86 | 87 | Source Files 88 | 89 | 90 | Source Files 91 | 92 | 93 | Source Files 94 | 95 | 96 | Source Files 97 | 98 | 99 | Source Files 100 | 101 | 102 | Source Files 103 | 104 | 105 | Source Files 106 | 107 | 108 | Source Files 109 | 110 | 111 | Source Files 112 | 113 | 114 | Source Files 115 | 116 | 117 | Source Files 118 | 119 | 120 | -------------------------------------------------------------------------------- /src/GitStatusCache/src/Cache.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "Cache.h" 3 | 4 | std::tuple Cache::GetStatus(const std::string& repositoryPath) 5 | { 6 | auto foundResultInCache = false; 7 | std::tuple cachedStatus; 8 | 9 | { 10 | ReadLock readLock(m_cacheMutex); 11 | auto cacheEntry = m_cache.find(repositoryPath); 12 | if (cacheEntry != m_cache.end()) 13 | { 14 | foundResultInCache = true; 15 | cachedStatus = cacheEntry->second; 16 | } 17 | } 18 | 19 | if (foundResultInCache) 20 | { 21 | ++m_cacheHits; 22 | Log("Cache.GetStatus.CacheHit", Severity::Info) 23 | << R"(Found git status in cache. { "repositoryPath": ")" << repositoryPath << R"(" })"; 24 | return cachedStatus; 25 | } 26 | 27 | ++m_cacheMisses; 28 | Log("Cache.GetStatus.CacheMiss", Severity::Warning) 29 | << R"(Failed to find git status in cache. { "repositoryPath": ")" << repositoryPath << R"(" })"; 30 | 31 | auto status = m_git.GetStatus(repositoryPath); 32 | 33 | { 34 | WriteLock writeLock(m_cacheMutex); 35 | m_cache[repositoryPath] = status; 36 | } 37 | 38 | return status; 39 | } 40 | 41 | void Cache::PrimeCacheEntry(const std::string& repositoryPath) 42 | { 43 | ++m_cacheTotalPrimeRequests; 44 | { 45 | ReadLock readLock(m_cacheMutex); 46 | auto cacheEntry = m_cache.find(repositoryPath); 47 | if (cacheEntry != m_cache.end()) 48 | return; 49 | } 50 | 51 | ++m_cacheEffectivePrimeRequests; 52 | Log("Cache.PrimeCacheEntry", Severity::Info) 53 | << R"(Priming cache entry. { "repositoryPath": ")" << repositoryPath << R"(" })"; 54 | 55 | auto status = m_git.GetStatus(repositoryPath); 56 | 57 | { 58 | WriteLock writeLock(m_cacheMutex); 59 | m_cache[repositoryPath] = status; 60 | } 61 | } 62 | 63 | bool Cache::InvalidateCacheEntry(const std::string& repositoryPath) 64 | { 65 | ++m_cacheTotalInvalidationRequests; 66 | bool invalidatedCacheEntry = false; 67 | { 68 | UpgradableLock readLock(m_cacheMutex); 69 | auto cacheEntry = m_cache.find(repositoryPath); 70 | if (cacheEntry != m_cache.end()) 71 | { 72 | UpgradedLock writeLock(readLock); 73 | cacheEntry = m_cache.find(repositoryPath); 74 | if (cacheEntry != m_cache.end()) 75 | { 76 | m_cache.erase(cacheEntry); 77 | invalidatedCacheEntry = true; 78 | } 79 | } 80 | } 81 | 82 | if (invalidatedCacheEntry) 83 | ++m_cacheEffectiveInvalidationRequests; 84 | return invalidatedCacheEntry; 85 | } 86 | 87 | void Cache::InvalidateAllCacheEntries() 88 | { 89 | ++m_cacheInvalidateAllRequests; 90 | { 91 | WriteLock writeLock(m_cacheMutex); 92 | m_cache.clear(); 93 | } 94 | 95 | Log("Cache.InvalidateAllCacheEntries.", Severity::Warning) 96 | << R"(Invalidated all git status information in cache.)"; 97 | } 98 | 99 | CacheStatistics Cache::GetCacheStatistics() 100 | { 101 | CacheStatistics statistics; 102 | statistics.CacheHits = m_cacheHits; 103 | statistics.CacheMisses = m_cacheMisses; 104 | statistics.CacheEffectivePrimeRequests = m_cacheEffectivePrimeRequests; 105 | statistics.CacheTotalPrimeRequests = m_cacheTotalPrimeRequests; 106 | statistics.CacheEffectiveInvalidationRequests = m_cacheEffectiveInvalidationRequests; 107 | statistics.CacheTotalInvalidationRequests = m_cacheTotalInvalidationRequests; 108 | statistics.CacheInvalidateAllRequests = m_cacheInvalidateAllRequests; 109 | return statistics; 110 | } 111 | -------------------------------------------------------------------------------- /src/GitStatusCache/src/Cache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Git.h" 3 | #include "CacheStatistics.h" 4 | 5 | /** 6 | * Simple cache that retrieves and stores git status information. 7 | * This class is thread-safe. 8 | */ 9 | class Cache : boost::noncopyable 10 | { 11 | private: 12 | using ReadLock = boost::shared_lock; 13 | using WriteLock = boost::unique_lock; 14 | using UpgradableLock = boost::upgrade_lock; 15 | using UpgradedLock = boost::upgrade_to_unique_lock; 16 | 17 | Git m_git; 18 | std::unordered_map> m_cache; 19 | boost::shared_mutex m_cacheMutex; 20 | 21 | std::atomic m_cacheHits = 0; 22 | std::atomic m_cacheMisses = 0; 23 | std::atomic m_cacheEffectivePrimeRequests = 0; 24 | std::atomic m_cacheTotalPrimeRequests = 0; 25 | std::atomic m_cacheEffectiveInvalidationRequests = 0; 26 | std::atomic m_cacheTotalInvalidationRequests = 0; 27 | std::atomic m_cacheInvalidateAllRequests = 0; 28 | 29 | public: 30 | /** 31 | * Retrieves current git status for repository at provided path. 32 | * Returns from cache if present, otherwise queries git and adds to cache. 33 | */ 34 | std::tuple GetStatus(const std::string& repositoryPath); 35 | 36 | /** 37 | * Computes status and loads cache entry if it's not already present. 38 | */ 39 | void PrimeCacheEntry(const std::string& repositoryPath); 40 | 41 | /** 42 | * Invalidates cached git status for repository at provided path. 43 | */ 44 | bool InvalidateCacheEntry(const std::string& repositoryPath); 45 | 46 | /** 47 | * Invalidates all cached git status information. 48 | */ 49 | void InvalidateAllCacheEntries(); 50 | 51 | /** 52 | * Returns information about cache's performance. 53 | */ 54 | CacheStatistics GetCacheStatistics(); 55 | }; -------------------------------------------------------------------------------- /src/GitStatusCache/src/CacheInvalidator.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "CacheInvalidator.h" 3 | #include "StringConverters.h" 4 | 5 | CacheInvalidator::CacheInvalidator(const std::shared_ptr& cache) 6 | : m_cache(cache) 7 | , m_cachePrimer(m_cache) 8 | { 9 | m_directoryMonitor = std::make_unique( 10 | [this](DirectoryMonitor::Token token, const boost::filesystem::path& path, DirectoryMonitor::FileAction action) 11 | { 12 | this->OnFileChanged(token, path, action); 13 | }, 14 | [this] { m_cache->InvalidateAllCacheEntries(); }); 15 | } 16 | 17 | void CacheInvalidator::MonitorRepositoryDirectories(const Git::Status& status) 18 | { 19 | auto workingDirectory = status.WorkingDirectory; 20 | if (!workingDirectory.empty()) 21 | { 22 | auto token = m_directoryMonitor->AddDirectory(ConvertToUnicode(workingDirectory)); 23 | WriteLock writeLock(m_tokensToRepositoriesMutex); 24 | m_tokensToRepositories[token] = status.RepositoryPath; 25 | } 26 | 27 | auto repositoryPath = status.RepositoryPath; 28 | if (!repositoryPath.empty()) 29 | { 30 | if (workingDirectory.empty() || repositoryPath.find(workingDirectory) != 0) 31 | { 32 | auto token = m_directoryMonitor->AddDirectory(ConvertToUnicode(repositoryPath)); 33 | WriteLock writeLock(m_tokensToRepositoriesMutex); 34 | m_tokensToRepositories[token] = status.RepositoryPath; 35 | } 36 | } 37 | } 38 | 39 | void CacheInvalidator::OnFileChanged(DirectoryMonitor::Token token, const boost::filesystem::path& path, DirectoryMonitor::FileAction action) 40 | { 41 | if (CacheInvalidator::ShouldIgnoreFileChange(path)) 42 | { 43 | Log("CacheInvalidator.OnFileChanged.IgnoringFileChange", Severity::Spam) 44 | << R"(Ignoring file change. { "filePath": ")" << path.c_str() << R"(" })"; 45 | return; 46 | } 47 | 48 | std::string repositoryPath; 49 | { 50 | ReadLock readLock(m_tokensToRepositoriesMutex); 51 | auto iterator = m_tokensToRepositories.find(token); 52 | if (iterator == m_tokensToRepositories.end()) 53 | { 54 | Log("CacheInvalidator.OnFileChanged.FailedToFindToken", Severity::Error) 55 | << R"(Failed to find token to repository mapping. { "token": )" << token << R"(" })"; 56 | throw std::logic_error("Failed to find token to repository mapping."); 57 | } 58 | repositoryPath = iterator->second; 59 | } 60 | 61 | auto invalidatedEntry = m_cache->InvalidateCacheEntry(repositoryPath); 62 | if (invalidatedEntry) 63 | { 64 | Log("CacheInvalidator.OnFileChanged.InvalidatedCacheEntry", Severity::Info) 65 | << R"(Invalidated git status in cache for file change. { "token": )" << token 66 | << R"(, "repositoryPath": ")" << repositoryPath 67 | << R"(", "filePath": ")" << path.c_str() << R"(" })"; 68 | } 69 | 70 | m_cachePrimer.SchedulePrimingForRepositoryPathInFiveSeconds(repositoryPath); 71 | } 72 | 73 | /*static*/ bool CacheInvalidator::ShouldIgnoreFileChange(const boost::filesystem::path& path) 74 | { 75 | if (!path.has_filename()) 76 | return false; 77 | 78 | auto filename = path.filename(); 79 | return filename.wstring() == L"index.lock" || filename.wstring() == L".git"; 80 | } -------------------------------------------------------------------------------- /src/GitStatusCache/src/CacheInvalidator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "DirectoryMonitor.h" 3 | #include "Cache.h" 4 | #include "CachePrimer.h" 5 | 6 | /** 7 | * Invalidates cache entries in response to file system changes. 8 | * This class is thread-safe. 9 | */ 10 | class CacheInvalidator 11 | { 12 | private: 13 | using ReadLock = boost::shared_lock; 14 | using WriteLock = boost::unique_lock; 15 | using UpgradableLock = boost::upgrade_lock; 16 | using UpgradedLock = boost::upgrade_to_unique_lock; 17 | 18 | std::shared_ptr m_cache; 19 | CachePrimer m_cachePrimer; 20 | 21 | std::unique_ptr m_directoryMonitor; 22 | std::unordered_map m_tokensToRepositories; 23 | boost::shared_mutex m_tokensToRepositoriesMutex; 24 | 25 | /** 26 | * Checks if the file change can be safely ignored. 27 | */ 28 | static bool ShouldIgnoreFileChange(const boost::filesystem::path& path); 29 | 30 | /** 31 | * Handles file change notifications by invalidating cache entries and scheduling priming. 32 | */ 33 | void OnFileChanged(DirectoryMonitor::Token token, const boost::filesystem::path& path, DirectoryMonitor::FileAction action); 34 | 35 | public: 36 | CacheInvalidator(const std::shared_ptr& cache); 37 | 38 | /** 39 | * Registers working directory and repository directory for file change monitoring. 40 | */ 41 | void MonitorRepositoryDirectories(const Git::Status& status); 42 | }; -------------------------------------------------------------------------------- /src/GitStatusCache/src/CachePrimer.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "CachePrimer.h" 3 | #include 4 | #include 5 | 6 | CachePrimer::CachePrimer(const std::shared_ptr& cache) 7 | : m_cache(cache) 8 | , m_stopPrimingThread(MakeUniqueHandle(INVALID_HANDLE_VALUE)) 9 | , m_primingService() 10 | , m_primingTimer(m_primingService) 11 | { 12 | auto stopPrimingThread = ::CreateEvent( 13 | nullptr /*lpEventAttributes*/, 14 | true /*manualReset*/, 15 | false /*bInitialState*/, 16 | nullptr /*lpName*/); 17 | if (stopPrimingThread == nullptr) 18 | { 19 | Log("CachePrimer.StartingPrimingThread.CreateEventFailed", Severity::Error) 20 | << "Failed to create event to signal thread on exit."; 21 | throw std::runtime_error("CreateEvent failed unexpectedly."); 22 | } 23 | m_stopPrimingThread = MakeUniqueHandle(stopPrimingThread); 24 | 25 | Log("CachePrimer.StartingPrimingThread", Severity::Spam) 26 | << "Attempting to start background thread for cache priming."; 27 | m_primingThread = std::thread(&CachePrimer::WaitForPrimingTimerExpiration, this); 28 | } 29 | 30 | CachePrimer::~CachePrimer() 31 | { 32 | Log("CachePrimer.Shutdown.StoppingPrimingThread", Severity::Spam) 33 | << R"(Shutting down cache priming thread. { "threadId": 0x)" << std::hex << m_primingThread.get_id() << " }"; 34 | 35 | ::SetEvent(m_stopPrimingThread); 36 | { 37 | WriteLock writeLock(m_primingMutex); 38 | m_primingTimer.cancel(); 39 | } 40 | m_primingThread.join(); 41 | } 42 | 43 | void CachePrimer::OnPrimingTimerExpiration(boost::system::error_code errorCode) 44 | { 45 | if (errorCode.value() != 0) 46 | return; 47 | 48 | std::unordered_set repositoriesToPrime; 49 | { 50 | WriteLock writeLock(m_primingMutex); 51 | m_repositoriesToPrime.swap(repositoriesToPrime); 52 | } 53 | 54 | if (!repositoriesToPrime.empty()) 55 | { 56 | for (auto repositoryPath : repositoriesToPrime) 57 | m_cache->PrimeCacheEntry(repositoryPath); 58 | } 59 | 60 | this->SchedulePrimingInSixtySeconds(); 61 | } 62 | 63 | void CachePrimer::WaitForPrimingTimerExpiration() 64 | { 65 | Log("CachePrimer.WaitForPrimingTimerExpiration.Start", Severity::Verbose) << "Thread for cache priming started."; 66 | 67 | this->SchedulePrimingInSixtySeconds(); 68 | do 69 | { 70 | m_primingService.run(); 71 | } while (::WaitForSingleObject(m_stopPrimingThread, 5) != WAIT_OBJECT_0); 72 | 73 | Log("CachePrimer.WaitForPrimingTimerExpiration.Stop", Severity::Verbose) << "Thread for cache priming stopping."; 74 | } 75 | 76 | void CachePrimer::SchedulePrimingForRepositoryPathInFiveSeconds(const std::string& repositoryPath) 77 | { 78 | WriteLock writeLock(m_primingMutex); 79 | m_repositoriesToPrime.insert(repositoryPath); 80 | if (m_primingTimer.expires_from_now(boost::posix_time::seconds(5))) 81 | m_primingTimer.async_wait([this](boost::system::error_code errorCode) { this->OnPrimingTimerExpiration(errorCode); }); 82 | } 83 | 84 | void CachePrimer::SchedulePrimingInSixtySeconds() 85 | { 86 | WriteLock writeLock(m_primingMutex); 87 | m_primingTimer.expires_from_now(boost::posix_time::seconds(60)); 88 | m_primingTimer.async_wait([this](boost::system::error_code errorCode) { this->OnPrimingTimerExpiration(errorCode); }); 89 | } -------------------------------------------------------------------------------- /src/GitStatusCache/src/CachePrimer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Cache.h" 3 | #include 4 | #include 5 | 6 | /** 7 | * Actively updates invalidated cache entries to reduce cache misses on client requests. 8 | * This class is thread-safe. 9 | */ 10 | class CachePrimer : boost::noncopyable 11 | { 12 | private: 13 | using ReadLock = boost::shared_lock; 14 | using WriteLock = boost::unique_lock; 15 | using UpgradableLock = boost::upgrade_lock; 16 | using UpgradedLock = boost::upgrade_to_unique_lock; 17 | 18 | std::shared_ptr m_cache; 19 | 20 | UniqueHandle m_stopPrimingThread; 21 | std::thread m_primingThread; 22 | std::unordered_set m_repositoriesToPrime; 23 | boost::asio::io_service m_primingService; 24 | boost::asio::deadline_timer m_primingTimer; 25 | boost::shared_mutex m_primingMutex; 26 | 27 | /** 28 | * Primes cache by computing status for scheduled repositories. 29 | */ 30 | void OnPrimingTimerExpiration(boost::system::error_code errorCode); 31 | 32 | /** 33 | * Reserves thread for priming operations until cache shuts down. 34 | */ 35 | void WaitForPrimingTimerExpiration(); 36 | 37 | public: 38 | CachePrimer(const std::shared_ptr& cache); 39 | ~CachePrimer(); 40 | 41 | /** 42 | * Cancels any currently scheduled priming and reschedules for five seconds in the future. 43 | * Called repeatedly on file changes to refresh status for repositories five seconds after 44 | * a wave file change events (ex. a build) subsides. 45 | */ 46 | void SchedulePrimingForRepositoryPathInFiveSeconds(const std::string& repositoryPath); 47 | 48 | /** 49 | * Schedules cache priming for sixty seconds in the future. 50 | */ 51 | void SchedulePrimingInSixtySeconds(); 52 | }; -------------------------------------------------------------------------------- /src/GitStatusCache/src/CacheStatistics.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct CacheStatistics 4 | { 5 | uint64_t CacheHits = 0; 6 | uint64_t CacheMisses = 0; 7 | uint64_t CacheEffectivePrimeRequests = 0; 8 | uint64_t CacheTotalPrimeRequests = 0; 9 | uint64_t CacheEffectiveInvalidationRequests = 0; 10 | uint64_t CacheTotalInvalidationRequests = 0; 11 | uint64_t CacheInvalidateAllRequests = 0; 12 | }; -------------------------------------------------------------------------------- /src/GitStatusCache/src/DirectoryMonitor.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "DirectoryMonitor.h" 3 | 4 | void DirectoryMonitor::WaitForNotifications() 5 | { 6 | Log("DirectoryMonitor.WaitForNotifications.Start", Severity::Verbose) << "Thread for handling notifications started."; 7 | 8 | const HANDLE handles[] = { m_stopNotificationThread, m_readDirectoryChanges.GetWaitHandle() }; 9 | 10 | bool shouldTerminate = false; 11 | while (!shouldTerminate) 12 | { 13 | auto alertable = true; 14 | DWORD waitResult = ::WaitForMultipleObjectsEx(_countof(handles), handles, false /*bWaitAll*/, INFINITE, true /*bAlertable*/); 15 | 16 | if (waitResult == WAIT_OBJECT_0) 17 | { 18 | shouldTerminate = true; 19 | Log("DirectoryMonitor.WaitForNotifications.Stop", Severity::Verbose) << "Thread for handling notifications stopping."; 20 | } 21 | else if (waitResult == WAIT_OBJECT_0 + 1) 22 | { 23 | if (m_readDirectoryChanges.CheckOverflow()) 24 | { 25 | Log("DirectoryMonitor.Notification.Overflow", Severity::Warning) 26 | << "Change notification queue overflowed. Notifications were lost."; 27 | if (m_onEventsLostCallback != nullptr) 28 | m_onEventsLostCallback(); 29 | } 30 | else 31 | { 32 | Token token; 33 | DWORD action; 34 | std::wstring path; 35 | m_readDirectoryChanges.Pop(token, action, path); 36 | 37 | bool shouldCallOnChangeCallback = true; 38 | auto fileAction = DirectoryMonitor::FileAction::Unknown; 39 | switch (action) 40 | { 41 | default: 42 | Log("DirectoryMonitor.Notification.Unknown", Severity::Warning) 43 | << R"(Unknown notification for file. { "token": )" << token << R"(, "path": ")" << path << R"(" })"; 44 | break; 45 | case FILE_ACTION_ADDED: 46 | Log("DirectoryMonitor.Notification.Add", Severity::Spam) 47 | << R"(Added file. { "token": )" << token << R"(, "path": ")" << path << R"(" })"; 48 | fileAction = DirectoryMonitor::FileAction::Added; 49 | break; 50 | case FILE_ACTION_REMOVED: 51 | Log("DirectoryMonitor.Notification.Remove", Severity::Spam) 52 | << R"(Removed file. { "token": )" << token << R"(, "path": ")" << path << R"(" })"; 53 | fileAction = DirectoryMonitor::FileAction::Removed; 54 | break; 55 | case FILE_ACTION_MODIFIED: 56 | Log("DirectoryMonitor.Notification.Modified", Severity::Spam) 57 | << R"(Modified file. { "token": )" << token << R"(, "path": ")" << path << R"(" })"; 58 | fileAction = DirectoryMonitor::FileAction::Modified; 59 | break; 60 | case FILE_ACTION_RENAMED_OLD_NAME: 61 | Log("DirectoryMonitor.Notification.RenamedFrom", Severity::Spam) 62 | << R"(Renamed file. { "token": )" << token << R"(, "oldPath": ")" << path << R"(" })"; 63 | fileAction = DirectoryMonitor::FileAction::RenamedFrom; 64 | break; 65 | case FILE_ACTION_RENAMED_NEW_NAME: 66 | Log("DirectoryMonitor.Notification.RenamedTo", Severity::Spam) 67 | << R"(Renamed file. { "token": )" << token << R"(, "newPath": ")" << path << R"(" })"; 68 | fileAction = DirectoryMonitor::FileAction::RenamedTo; 69 | break; 70 | case FILE_ACTION_CHANGES_LOST: 71 | Log("DirectoryMonitor.Notification.EventsLost", Severity::Warning) 72 | << R"(Notifications lost. { "token": )" << token << R"(, "path": ")" << path << R"(" })"; 73 | if (m_onEventsLostCallback != nullptr) 74 | m_onEventsLostCallback(); 75 | shouldCallOnChangeCallback = false; 76 | break; 77 | } 78 | 79 | if (m_onChangeCallback != nullptr && shouldCallOnChangeCallback) 80 | m_onChangeCallback(token, boost::filesystem::path(path), fileAction); 81 | } 82 | } 83 | } 84 | } 85 | 86 | DirectoryMonitor::DirectoryMonitor(const OnChangeCallback& onChangeCallback, const OnEventsLostCallback& onEventsLostCallback) : 87 | m_onChangeCallback(onChangeCallback), 88 | m_onEventsLostCallback(onEventsLostCallback) 89 | { 90 | } 91 | 92 | DirectoryMonitor::~DirectoryMonitor() 93 | { 94 | Log("DirectoryMonitor.ShutDown", Severity::Verbose) << "Stopping directory monitor."; 95 | m_readDirectoryChanges.Terminate(); 96 | 97 | if (m_stopNotificationThread != INVALID_HANDLE_VALUE) 98 | { 99 | Log("DirectoryMonitor.ShutDown.StoppingBackgroundThread", Severity::Spam) 100 | << R"(Shutting down notification handling thread. { "threadId": 0x)" << std::hex << m_notificationThread.get_id() << " }"; 101 | ::SetEvent(m_stopNotificationThread); 102 | m_notificationThread.join(); 103 | ::CloseHandle(m_stopNotificationThread); 104 | } 105 | } 106 | 107 | DirectoryMonitor::Token DirectoryMonitor::AddDirectory(const std::wstring& directory) 108 | { 109 | Token token; 110 | { 111 | boost::unique_lock lock(m_directoriesMutex); 112 | auto iterator = m_directories.find(directory); 113 | if (iterator != m_directories.end()) 114 | return iterator->second; 115 | 116 | static Token nextToken = 0; 117 | token = nextToken++; 118 | m_directories[directory] = token; 119 | } 120 | 121 | Log("DirectoryMonitor.AddDirectory", Severity::Info) 122 | << R"(Registering directory for change notifications. { "token": )" << token << R"(, "path": ")" << directory << R"(" })"; 123 | 124 | auto notificationFlags = 125 | FILE_NOTIFY_CHANGE_LAST_WRITE 126 | | FILE_NOTIFY_CHANGE_CREATION 127 | | FILE_NOTIFY_CHANGE_FILE_NAME 128 | | FILE_NOTIFY_CHANGE_DIR_NAME 129 | | FILE_NOTIFY_CHANGE_SIZE; 130 | m_readDirectoryChanges.AddDirectory(directory.c_str(), token, true /*bWatchSubtree*/, notificationFlags); 131 | 132 | static std::once_flag flag; 133 | std::call_once(flag, [this]() 134 | { 135 | Log("DirectoryMonitor.StartingBackgroundThread", Severity::Spam) 136 | << "Attempting to start background thread for handling notifications."; 137 | 138 | auto stopNotificationThread = ::CreateEvent( 139 | nullptr /*lpEventAttributes*/, 140 | true /*manualReset*/, 141 | false /*bInitialState*/, 142 | nullptr /*lpName*/); 143 | if (stopNotificationThread == nullptr) 144 | { 145 | Log("DirectoryMonitor.StartingBackgroundThread.CreateEventFailed", Severity::Error) 146 | << "Failed to create event to signal thread on exit."; 147 | throw std::runtime_error("CreateEvent failed unexpectedly."); 148 | } 149 | m_stopNotificationThread = stopNotificationThread; 150 | 151 | m_notificationThread = std::thread(&DirectoryMonitor::WaitForNotifications, this); 152 | }); 153 | 154 | return token; 155 | } -------------------------------------------------------------------------------- /src/GitStatusCache/src/DirectoryMonitor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * Monitors directories for changes and provides notifications by callback. 8 | */ 9 | class DirectoryMonitor : boost::noncopyable 10 | { 11 | public: 12 | /** 13 | * Action that triggered change notification. 14 | */ 15 | enum FileAction 16 | { 17 | Unknown, 18 | Added, 19 | Removed, 20 | Modified, 21 | RenamedFrom, 22 | RenamedTo 23 | }; 24 | 25 | /** 26 | * Identifies AddDirectory call. Passed back with file change notifications. 27 | */ 28 | using Token = uint32_t; 29 | 30 | /** 31 | * Callback for change notifications. Provides path and change action. 32 | */ 33 | using OnChangeCallback = std::function; 34 | 35 | /** 36 | * Callback for events lost notification. Notifications may be lost if changes 37 | * occur more rapidly than they are processed. 38 | */ 39 | using OnEventsLostCallback = std::function; 40 | 41 | private: 42 | HANDLE m_stopNotificationThread = INVALID_HANDLE_VALUE; 43 | std::thread m_notificationThread; 44 | 45 | OnChangeCallback m_onChangeCallback; 46 | OnEventsLostCallback m_onEventsLostCallback; 47 | 48 | CReadDirectoryChanges m_readDirectoryChanges; 49 | 50 | std::unordered_map m_directories; 51 | boost::shared_mutex m_directoriesMutex; 52 | 53 | void WaitForNotifications(); 54 | 55 | public: 56 | /** 57 | * Constructor. Callbacks will always be invoked on the same thread. 58 | * @param onChangeCallback Callback for change notification. 59 | * @param onEventsLostCallback Callback for lost events notification. 60 | */ 61 | DirectoryMonitor(const OnChangeCallback& onChangeCallback, const OnEventsLostCallback& onEventsLostCallback); 62 | ~DirectoryMonitor(); 63 | 64 | /** 65 | * Registers a directory for change notifications. 66 | * This method is thread-safe. 67 | */ 68 | Token AddDirectory(const std::wstring& directory); 69 | }; -------------------------------------------------------------------------------- /src/GitStatusCache/src/Git.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "Git.h" 3 | #include "StringConverters.h" 4 | #include 5 | #include 6 | #include 7 | 8 | std::string ReadFirstLineInFile(const boost::filesystem::path& path) 9 | { 10 | if (!boost::filesystem::exists(path)) 11 | return std::string(); 12 | 13 | auto fileStream = std::ifstream(path.c_str()); 14 | if (!fileStream.good()) 15 | return std::string(); 16 | 17 | std::string firstLine; 18 | std::getline(fileStream, firstLine); 19 | return firstLine; 20 | } 21 | 22 | std::string ConvertErrorCodeToString(git_error_code errorCode) 23 | { 24 | switch (errorCode) 25 | { 26 | default: 27 | return "Unknown"; 28 | case GIT_OK: 29 | return "Success"; 30 | case GIT_ERROR: 31 | return "Generic Error"; 32 | case GIT_ENOTFOUND: 33 | return "Requested object could not be found"; 34 | case GIT_EEXISTS: 35 | return "Object exists preventing operation"; 36 | case GIT_EAMBIGUOUS: 37 | return "More than one object matches"; 38 | case GIT_EBUFS: 39 | return "Output buffer too short"; 40 | case GIT_EUSER: 41 | return "User generated error"; 42 | case GIT_EBAREREPO: 43 | return "Operation not allowed on bare repository"; 44 | case GIT_EUNBORNBRANCH: 45 | return "HEAD refers to branch with no commits"; 46 | case GIT_EUNMERGED: 47 | return "Merge in progress prevented operation"; 48 | case GIT_ENONFASTFORWARD: 49 | return "Reference not fast-forwardable"; 50 | case GIT_EINVALIDSPEC: 51 | return "Invalid name/ref spec"; 52 | case GIT_ECONFLICT: 53 | return "Checkout conflicts prevented operation"; 54 | case GIT_ELOCKED: 55 | return "Lock file prevented operation"; 56 | case GIT_EMODIFIED: 57 | return "Reference value does not match expected"; 58 | case GIT_EAUTH: 59 | return "Authentication error"; 60 | case GIT_ECERTIFICATE: 61 | return "Invalid certificate"; 62 | case GIT_EAPPLIED: 63 | return "Patch or merge already applied"; 64 | case GIT_EPEEL: 65 | return "Peel operation is not possible"; 66 | case GIT_EEOF: 67 | return "Unexpected end of file"; 68 | case GIT_EINVALID: 69 | return "Invalid operation or input"; 70 | case GIT_EUNCOMMITTED: 71 | return "Prevented due to uncommitted changes"; 72 | case GIT_PASSTHROUGH: 73 | return "Passthrough"; 74 | case GIT_ITEROVER: 75 | return "Iteration complete"; 76 | } 77 | } 78 | 79 | Git::Git() 80 | { 81 | git_libgit2_init(); 82 | } 83 | 84 | Git::~Git() 85 | { 86 | git_libgit2_shutdown(); 87 | } 88 | 89 | bool Git::DiscoverRepository(Git::Status& status, const std::string& path) 90 | { 91 | status.RepositoryPath = std::string(); 92 | 93 | auto repositoryPath = MakeUniqueGitBuffer(git_buf{ 0 }); 94 | auto result = git_repository_discover( 95 | &repositoryPath.get(), 96 | path.c_str(), 97 | true /*across_fs*/, 98 | nullptr /*ceiling_dirs*/); 99 | 100 | if (result != GIT_OK) 101 | { 102 | auto lastError = giterr_last(); 103 | Log("Git.GetRefStatus.FailedToDiscoverRepository", Severity::Warning) 104 | << R"(Failed to open repository. { "path: ")" << path 105 | << R"(", "result": ")" << ConvertErrorCodeToString(static_cast(result)) 106 | << R"(", "lastError": ")" << (lastError == nullptr ? "null" : lastError->message) << R"(" })"; 107 | return false; 108 | } 109 | 110 | status.RepositoryPath = std::string(repositoryPath.get().ptr, repositoryPath.get().size); 111 | return true; 112 | } 113 | 114 | bool Git::GetWorkingDirectory(Git::Status& status, UniqueGitRepository& repository) 115 | { 116 | status.WorkingDirectory = std::string(); 117 | 118 | auto path = git_repository_workdir(repository.get()); 119 | if (path == NULL) 120 | { 121 | auto lastError = giterr_last(); 122 | Log("Git.GetWorkingDirectory.FailedToFindWorkingDirectory", Severity::Warning) 123 | << R"(Failed to find working directory for repository. { "repositoryPath": ")" << status.RepositoryPath 124 | << R"(", "lastError": ")" << (lastError == nullptr ? "null" : lastError->message) << R"(" })"; 125 | return false; 126 | } 127 | 128 | status.WorkingDirectory = std::string(path); 129 | return true; 130 | } 131 | 132 | bool Git::GetRepositoryState(Git::Status& status, UniqueGitRepository& repository) 133 | { 134 | status.State = std::string(); 135 | 136 | auto state = git_repository_state(repository.get()); 137 | switch (state) 138 | { 139 | default: 140 | Log("Git.GetRepositoryState.UnknownResult", Severity::Error) 141 | << R"(Encountered unknown repository state. { "repositoryPath": ")" << status.RepositoryPath 142 | << R"(", "state": ")" << state << R"(" })"; 143 | return false; 144 | case GIT_REPOSITORY_STATE_NONE: 145 | if (git_repository_head_detached(repository.get())) 146 | status.State = std::string("DETACHED"); 147 | return true; 148 | case GIT_REPOSITORY_STATE_MERGE: 149 | status.State = std::string("MERGING"); 150 | return true; 151 | case GIT_REPOSITORY_STATE_REVERT: 152 | status.State = std::string("REVERTING"); 153 | return true; 154 | case GIT_REPOSITORY_STATE_CHERRYPICK: 155 | status.State = std::string("CHERRY-PICKING"); 156 | return true; 157 | case GIT_REPOSITORY_STATE_BISECT: 158 | status.State = std::string("BISECTING"); 159 | return true; 160 | case GIT_REPOSITORY_STATE_REBASE: 161 | status.State = std::string("REBASE"); 162 | return true; 163 | case GIT_REPOSITORY_STATE_REBASE_INTERACTIVE: 164 | status.State = std::string("REBASE-i"); 165 | return true; 166 | case GIT_REPOSITORY_STATE_REBASE_MERGE: 167 | status.State = std::string("REBASE-m"); 168 | return true; 169 | case GIT_REPOSITORY_STATE_APPLY_MAILBOX: 170 | status.State = std::string("AM"); 171 | return true; 172 | case GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE: 173 | status.State = std::string("AM/REBASE"); 174 | return true; 175 | } 176 | } 177 | 178 | bool Git::SetBranchToCurrentCommit(Git::Status& status) 179 | { 180 | auto path = boost::filesystem::path(ConvertToUnicode(status.RepositoryPath)); 181 | path.append(L"HEAD"); 182 | auto commit = ReadFirstLineInFile(path); 183 | 184 | if (!commit.empty()) 185 | { 186 | status.Branch = commit.substr(0, 7); 187 | return true; 188 | } 189 | 190 | return false; 191 | } 192 | 193 | bool Git::SetBranchFromHeadName(Git::Status& status, const boost::filesystem::path& path) 194 | { 195 | auto headName = ReadFirstLineInFile(path); 196 | const auto patternToStrip = std::string("refs/heads/"); 197 | if (headName.size() > patternToStrip.size() && headName.find(patternToStrip) == 0) 198 | { 199 | status.Branch = headName.substr(patternToStrip.size()); 200 | return true; 201 | } 202 | 203 | return false; 204 | } 205 | 206 | bool Git::SetBranchFromRebaseApplyHeadName(Git::Status& status) 207 | { 208 | auto path = boost::filesystem::path(ConvertToUnicode(status.RepositoryPath)); 209 | path.append(L"rebase-apply/head-name"); 210 | return SetBranchFromHeadName(status, path); 211 | } 212 | 213 | bool Git::SetBranchFromRebaseMergeHeadName(Git::Status& status) 214 | { 215 | auto path = boost::filesystem::path(ConvertToUnicode(status.RepositoryPath)); 216 | path.append(L"rebase-merge/head-name"); 217 | return SetBranchFromHeadName(status, path); 218 | } 219 | 220 | bool Git::GetRefStatus(Git::Status& status, UniqueGitRepository& repository) 221 | { 222 | status.Branch = std::string(); 223 | status.Upstream = std::string(); 224 | status.UpstreamGone = false; 225 | status.AheadBy = 0; 226 | status.BehindBy = 0; 227 | 228 | auto head = MakeUniqueGitReference(nullptr); 229 | auto result = git_repository_head(&head.get(), repository.get()); 230 | if (result == GIT_EUNBORNBRANCH) 231 | { 232 | Log("Git.GetRefStatus.UnbornBranch", Severity::Verbose) 233 | << R"(Current branch is unborn. { "repositoryPath": ")" << status.RepositoryPath << R"(" })"; 234 | status.Branch = std::string("UNBORN"); 235 | return true; 236 | } 237 | else if (result == GIT_ENOTFOUND) 238 | { 239 | Log("Git.GetRefStatus.HeadMissing", Severity::Warning) 240 | << R"(HEAD is missing. { "repositoryPath": ")" << status.RepositoryPath << R"(" })"; 241 | return false; 242 | } 243 | else if (result != GIT_OK) 244 | { 245 | auto lastError = giterr_last(); 246 | Log("Git.GetRefStatus.FailedToFindHead", Severity::Error) 247 | << R"(Failed to find HEAD. { "repositoryPath": ")" << status.RepositoryPath 248 | << R"(", "result": ")" << ConvertErrorCodeToString(static_cast(result)) 249 | << R"(", "lastError": ")" << (lastError == nullptr ? "null" : lastError->message) << R"(" })"; 250 | return false; 251 | } 252 | 253 | status.Branch = std::string(git_reference_shorthand(head.get())); 254 | if (status.Branch == "HEAD") 255 | { 256 | if (status.State == "DETACHED") 257 | { 258 | SetBranchToCurrentCommit(status); 259 | } 260 | else 261 | { 262 | if (!SetBranchFromRebaseApplyHeadName(status)) 263 | SetBranchFromRebaseMergeHeadName(status); 264 | } 265 | 266 | return true; 267 | } 268 | 269 | auto upstream = MakeUniqueGitReference(nullptr); 270 | result = git_branch_upstream(&upstream.get(), head.get()); 271 | if (result == GIT_ENOTFOUND) 272 | { 273 | auto upstreamBranchName = git_buf{ 0 }; 274 | auto upstreamBranchResult = git_branch_upstream_name( 275 | &upstreamBranchName, 276 | git_reference_owner(head.get()), 277 | git_reference_name(head.get())); 278 | 279 | auto canBuildUpstream = false; 280 | if (upstreamBranchResult == GIT_OK) 281 | { 282 | Log("Git.GetRefStatus.UpstreamGone", Severity::Spam) 283 | << R"(Branch has a configured upstream that is gone. { "repositoryPath": ")" << status.RepositoryPath 284 | << R"(", "localBranch": ")" << status.Branch << R"(" })"; 285 | status.UpstreamGone = true; 286 | canBuildUpstream = upstreamBranchName.ptr != nullptr && upstreamBranchName.size != 0; 287 | } 288 | 289 | if (canBuildUpstream) 290 | { 291 | const auto patternToRemove = std::string("refs/remotes/"); 292 | auto upstreamName = std::string(upstreamBranchName.ptr); 293 | auto patternPosition = upstreamName.find(patternToRemove); 294 | if (patternPosition == 0 && upstreamName.size() > patternToRemove.size()) 295 | { 296 | upstreamName.erase(patternPosition, patternToRemove.size()); 297 | } 298 | status.Upstream = upstreamName; 299 | } 300 | else 301 | { 302 | Log("Git.GetRefStatus.NoUpstream", Severity::Spam) 303 | << R"(Branch does not have a remote tracking reference. { "repositoryPath": ")" << status.RepositoryPath 304 | << R"(", "localBranch": ")" << status.Branch << R"(" })"; 305 | } 306 | 307 | return true; 308 | } 309 | else if (result != GIT_OK) 310 | { 311 | auto lastError = giterr_last(); 312 | Log("Git.GetRefStatus.FailedToFindUpstream", Severity::Error) 313 | << R"(Failed to find remote tracking reference. { "repositoryPath": ")" << status.RepositoryPath 314 | << R"(", "localBranch": ")" << status.Branch 315 | << R"(", "result": ")" << ConvertErrorCodeToString(static_cast(result)) 316 | << R"(", "lastError": ")" << (lastError == nullptr ? "null" : lastError->message) << R"(" })"; 317 | return false; 318 | } 319 | 320 | status.Upstream = std::string(git_reference_shorthand(upstream.get())); 321 | 322 | auto localTarget = git_reference_target(head.get()); 323 | if (localTarget == nullptr) 324 | { 325 | auto lastError = giterr_last(); 326 | Log("Git.GetRefStatus.FailedToRetrieveLocalBranchGitReferenceTarget", Severity::Error) 327 | << R"(Failed to retrieve git_oid for local target. { "repositoryPath": ")" << status.RepositoryPath 328 | << R"(", "localBranch": ")" << status.Branch 329 | << R"(", "upstreamBranch": ")" << status.Upstream 330 | << R"(", "lastError": ")" << (lastError == nullptr ? "null" : lastError->message) << R"(" })"; 331 | return false; 332 | } 333 | 334 | auto upstreamTarget = git_reference_target(upstream.get()); 335 | if (upstreamTarget == nullptr) 336 | { 337 | auto lastError = giterr_last(); 338 | Log("Git.GetRefStatus.FailedToRetrieveRemoteBranchGitReferenceTarget", Severity::Error) 339 | << R"(Failed to retrieve git_oid for upstream target. { "repositoryPath": ")" << status.RepositoryPath 340 | << R"(", "localBranch": ")" << status.Branch 341 | << R"(", "upstreamBranch": ")" << status.Upstream 342 | << R"(", "lastError": ")" << (lastError == nullptr ? "null" : lastError->message) << R"(" })"; 343 | return false; 344 | } 345 | 346 | size_t aheadBy, behindBy; 347 | result = git_graph_ahead_behind(&aheadBy, &behindBy, repository.get(), localTarget, upstreamTarget); 348 | if (result != GIT_OK) 349 | { 350 | auto lastError = giterr_last(); 351 | Log("Git.GetRefStatus.FailedToRetrieveAheadBehind", Severity::Error) 352 | << R"(Failed to retrieve ahead/behind information. { "repositoryPath": ")" << status.RepositoryPath 353 | << R"(", "localBranch": ")" << status.Branch 354 | << R"(", "upstreamBranch": ")" << status.Upstream 355 | << R"(", "result": ")" << ConvertErrorCodeToString(static_cast(result)) 356 | << R"(", "lastError": ")" << (lastError == nullptr ? "null" : lastError->message) << R"(" })"; 357 | return false; 358 | } 359 | 360 | status.AheadBy = aheadBy; 361 | status.BehindBy = behindBy; 362 | return true; 363 | } 364 | 365 | bool Git::GetFileStatus(Git::Status& status, UniqueGitRepository& repository) 366 | { 367 | git_status_options statusOptions = GIT_STATUS_OPTIONS_INIT; 368 | statusOptions.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; 369 | statusOptions.flags = 370 | GIT_STATUS_OPT_INCLUDE_UNTRACKED 371 | | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX 372 | | GIT_STATUS_OPT_SORT_CASE_SENSITIVELY 373 | | GIT_STATUS_OPT_EXCLUDE_SUBMODULES; 374 | 375 | auto statusList = MakeUniqueGitStatusList(nullptr); 376 | auto result = git_status_list_new(&statusList.get(), repository.get(), &statusOptions); 377 | if (result != GIT_OK) 378 | { 379 | auto lastError = giterr_last(); 380 | Log("Git.GetGitStatus.FailedToCreateStatusList", Severity::Error) 381 | << R"(Failed to create status list. { "repositoryPath": ")" << status.RepositoryPath 382 | << R"(", "result": ")" << ConvertErrorCodeToString(static_cast(result)) 383 | << R"(", "lastError": ")" << (lastError == nullptr ? "null" : lastError->message) << R"(" })"; 384 | return false; 385 | } 386 | 387 | for (auto i = size_t{ 0 }; i < git_status_list_entrycount(statusList.get()); ++i) 388 | { 389 | auto entry = git_status_byindex(statusList.get(), i); 390 | 391 | static const auto indexFlags = 392 | GIT_STATUS_INDEX_NEW 393 | | GIT_STATUS_INDEX_MODIFIED 394 | | GIT_STATUS_INDEX_DELETED 395 | | GIT_STATUS_INDEX_RENAMED 396 | | GIT_STATUS_INDEX_TYPECHANGE; 397 | if ((entry->status & indexFlags) != 0) 398 | { 399 | auto hasOldPath = entry->head_to_index->old_file.path != nullptr; 400 | auto hasNewPath = entry->head_to_index->new_file.path != nullptr; 401 | auto oldPath = hasOldPath ? entry->head_to_index->old_file.path : std::string(); 402 | auto newPath = hasNewPath ? entry->head_to_index->new_file.path : std::string(); 403 | 404 | std::string path; 405 | if (hasOldPath && hasNewPath && oldPath == newPath) 406 | path = oldPath; 407 | else 408 | path = hasOldPath ? oldPath : newPath; 409 | 410 | if ((entry->status & GIT_STATUS_INDEX_NEW) == GIT_STATUS_INDEX_NEW) 411 | status.IndexAdded.push_back(path); 412 | if ((entry->status & GIT_STATUS_INDEX_MODIFIED) == GIT_STATUS_INDEX_MODIFIED) 413 | status.IndexModified.push_back(path); 414 | if ((entry->status & GIT_STATUS_INDEX_DELETED) == GIT_STATUS_INDEX_DELETED) 415 | status.IndexDeleted.push_back(path); 416 | if ((entry->status & GIT_STATUS_INDEX_RENAMED) == GIT_STATUS_INDEX_RENAMED) 417 | status.IndexRenamed.emplace_back(std::make_pair(oldPath, newPath)); 418 | if ((entry->status & GIT_STATUS_INDEX_TYPECHANGE) == GIT_STATUS_INDEX_TYPECHANGE) 419 | status.IndexTypeChange.push_back(path); 420 | } 421 | 422 | static const auto workingFlags = 423 | GIT_STATUS_WT_NEW 424 | | GIT_STATUS_WT_MODIFIED 425 | | GIT_STATUS_WT_DELETED 426 | | GIT_STATUS_WT_TYPECHANGE 427 | | GIT_STATUS_WT_RENAMED 428 | | GIT_STATUS_WT_UNREADABLE; 429 | if ((entry->status & workingFlags) != 0) 430 | { 431 | auto hasOldPath = entry->index_to_workdir->old_file.path != nullptr; 432 | auto hasNewPath = entry->index_to_workdir->new_file.path != nullptr; 433 | auto oldPath = hasOldPath ? entry->index_to_workdir->old_file.path : std::string(); 434 | auto newPath = hasNewPath ? entry->index_to_workdir->new_file.path : std::string(); 435 | 436 | std::string path; 437 | if (hasOldPath && hasNewPath && oldPath == newPath) 438 | path = oldPath; 439 | else 440 | path = hasOldPath ? oldPath : newPath; 441 | 442 | if ((entry->status & GIT_STATUS_WT_NEW) == GIT_STATUS_WT_NEW) 443 | status.WorkingAdded.push_back(path); 444 | if ((entry->status & GIT_STATUS_WT_MODIFIED) == GIT_STATUS_WT_MODIFIED) 445 | status.WorkingModified.push_back(path); 446 | if ((entry->status & GIT_STATUS_WT_DELETED) == GIT_STATUS_WT_DELETED) 447 | status.WorkingDeleted.push_back(path); 448 | if ((entry->status & GIT_STATUS_WT_TYPECHANGE) == GIT_STATUS_WT_TYPECHANGE) 449 | status.WorkingTypeChange.push_back(path); 450 | if ((entry->status & GIT_STATUS_WT_RENAMED) == GIT_STATUS_WT_RENAMED) 451 | status.WorkingRenamed.emplace_back(std::make_pair(oldPath, newPath)); 452 | if ((entry->status & GIT_STATUS_WT_UNREADABLE) == GIT_STATUS_WT_UNREADABLE) 453 | status.WorkingUnreadable.push_back(path); 454 | } 455 | 456 | static const auto conflictIgnoreFlags = GIT_STATUS_IGNORED | GIT_STATUS_CONFLICTED; 457 | if ((entry->status & conflictIgnoreFlags) != 0) 458 | { 459 | // libgit2 reports a subset of conflicts as two separate status entries with identical paths. 460 | // One entry contains index_to_workdir and the other contains head_to_index. 461 | auto hasOldPath = false; 462 | auto hasNewPath = false; 463 | auto oldPath = std::string(); 464 | auto newPath = std::string(); 465 | if (entry->index_to_workdir != nullptr) 466 | { 467 | hasOldPath = entry->index_to_workdir->old_file.path != nullptr; 468 | hasNewPath = entry->index_to_workdir->new_file.path != nullptr; 469 | oldPath = hasOldPath ? entry->index_to_workdir->old_file.path : std::string(); 470 | newPath = hasNewPath ? entry->index_to_workdir->new_file.path : std::string(); 471 | } 472 | else if (entry->head_to_index != nullptr) 473 | { 474 | hasOldPath = entry->head_to_index->old_file.path != nullptr; 475 | hasNewPath = entry->head_to_index->new_file.path != nullptr; 476 | oldPath = hasOldPath ? entry->head_to_index->old_file.path : std::string(); 477 | newPath = hasNewPath ? entry->head_to_index->new_file.path : std::string(); 478 | } 479 | 480 | std::string path; 481 | if (hasOldPath && hasNewPath && oldPath == newPath) 482 | path = oldPath; 483 | else 484 | path = hasOldPath ? oldPath : newPath; 485 | 486 | if ((entry->status & GIT_STATUS_IGNORED) == GIT_STATUS_IGNORED) 487 | { 488 | if (std::find(status.Ignored.begin(), status.Ignored.end(), path) == status.Ignored.end()) 489 | status.Ignored.push_back(path); 490 | } 491 | if ((entry->status & GIT_STATUS_CONFLICTED) == GIT_STATUS_CONFLICTED) 492 | { 493 | if (std::find(status.Conflicted.begin(), status.Conflicted.end(), path) == status.Conflicted.end()) 494 | status.Conflicted.push_back(path); 495 | } 496 | } 497 | } 498 | 499 | return true; 500 | } 501 | 502 | bool Git::GetStashList(Status& status, UniqueGitRepository& repository) 503 | { 504 | std::vector stashes; 505 | auto result = git_stash_foreach( 506 | repository.get(), 507 | [](size_t index, const char* message, const git_oid *stash_id, void *payload) 508 | { 509 | if (payload == nullptr) 510 | return -1; 511 | 512 | char hashBuffer[40] = { 0 }; 513 | Stash stash; 514 | stash.Sha1Id = std::string(git_oid_tostr(hashBuffer, _countof(hashBuffer), stash_id)); 515 | stash.Index = index; 516 | stash.Message = std::string(message); 517 | 518 | auto payloadAsStashes = reinterpret_cast*>(payload); 519 | payloadAsStashes->emplace_back(std::move(stash)); 520 | return 0; 521 | }, 522 | &stashes); 523 | 524 | if (result != GIT_OK) 525 | { 526 | auto lastError = giterr_last(); 527 | Log("Git.GetStashList.FailedToRetrieveStashList", Severity::Error) 528 | << R"(Failed to retrieve stash list. { "repositoryPath": ")" << status.RepositoryPath 529 | << R"(", "result": ")" << ConvertErrorCodeToString(static_cast(result)) 530 | << R"(", "lastError": ")" << (lastError == nullptr ? "null" : lastError->message) << R"(" })"; 531 | return false; 532 | } 533 | 534 | status.Stashes = std::move(stashes); 535 | return true; 536 | } 537 | 538 | std::tuple Git::DiscoverRepository(const std::string& path) 539 | { 540 | Git::Status status; 541 | return Git::DiscoverRepository(status, path) 542 | ? std::make_tuple(true, std::move(status.RepositoryPath)) 543 | : std::make_tuple(false, std::string()); 544 | } 545 | 546 | std::tuple Git::GetStatus(const std::string& path) 547 | { 548 | Git::Status status; 549 | if (!Git::DiscoverRepository(status, path)) 550 | { 551 | return std::make_tuple(false, Git::Status()); 552 | } 553 | 554 | auto repository = MakeUniqueGitRepository(nullptr); 555 | auto result = git_repository_open_ext( 556 | &repository.get(), 557 | status.RepositoryPath.c_str(), 558 | GIT_REPOSITORY_OPEN_NO_SEARCH, 559 | nullptr); 560 | 561 | if (result != GIT_OK) 562 | { 563 | auto lastError = giterr_last(); 564 | Log("Git.GetGitStatus.FailedToOpenRepository", Severity::Error) 565 | << R"(Failed to open repository. { "repositoryPath": ")" << status.RepositoryPath 566 | << R"(", "result": ")" << ConvertErrorCodeToString(static_cast(result)) 567 | << R"(", "lastError": ")" << (lastError == nullptr ? "null" : lastError->message) << R"(" })"; 568 | return std::make_tuple(false, Git::Status()); 569 | } 570 | 571 | if (git_repository_is_bare(repository.get())) 572 | { 573 | Log("Git.GetGitStatus.BareRepository", Severity::Warning) 574 | << R"(Aborting due to bare repository. { "repositoryPath": ")" << status.RepositoryPath << R"(" })"; 575 | return std::make_tuple(false, Git::Status()); 576 | } 577 | 578 | Git::GetWorkingDirectory(status, repository); 579 | Git::GetRepositoryState(status, repository); 580 | Git::GetRefStatus(status, repository); 581 | Git::GetStashList(status, repository); 582 | if (!Git::GetFileStatus(status, repository)) 583 | return std::make_tuple(false, Git::Status()); 584 | 585 | return std::make_tuple(true, std::move(status)); 586 | } 587 | -------------------------------------------------------------------------------- /src/GitStatusCache/src/Git.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | /** 5 | * Performs git operations. 6 | */ 7 | class Git 8 | { 9 | public: 10 | struct Stash 11 | { 12 | uint64_t Index; 13 | std::string Sha1Id; 14 | std::string Message; 15 | }; 16 | 17 | struct Status 18 | { 19 | std::string RepositoryPath; 20 | std::string WorkingDirectory; 21 | std::string State; 22 | 23 | std::string Branch; 24 | std::string Upstream; 25 | bool UpstreamGone = false; 26 | int AheadBy = 0; 27 | int BehindBy = 0; 28 | 29 | std::vector IndexAdded; 30 | std::vector IndexModified; 31 | std::vector IndexDeleted; 32 | std::vector IndexTypeChange; 33 | std::vector> IndexRenamed; 34 | 35 | std::vector WorkingAdded; 36 | std::vector WorkingModified; 37 | std::vector WorkingDeleted; 38 | std::vector WorkingTypeChange; 39 | std::vector WorkingUnreadable; 40 | std::vector> WorkingRenamed; 41 | 42 | std::vector Ignored; 43 | std::vector Conflicted; 44 | 45 | std::vector Stashes; 46 | }; 47 | 48 | private: 49 | /** 50 | * Searches for repository containing provided path and updates status. 51 | */ 52 | bool DiscoverRepository(Status& status, const std::string& path); 53 | 54 | /** 55 | * Retrieves the repository's working directory and updates status. 56 | */ 57 | bool GetWorkingDirectory(Status& status, UniqueGitRepository& repository); 58 | 59 | /** 60 | * Retrieves current repository state based on current ongoing operation 61 | * (ex. rebase, cherry pick) and updates status. 62 | */ 63 | bool GetRepositoryState(Status& status, UniqueGitRepository& repository); 64 | 65 | /** 66 | * Attempts to set the branch in the status to the current commit. 67 | */ 68 | bool SetBranchToCurrentCommit(Status& status); 69 | 70 | /** 71 | * Attempts to set the branch in the status by parsing head-name. 72 | */ 73 | bool SetBranchFromHeadName(Status& status, const boost::filesystem::path& path); 74 | 75 | /** 76 | * Attempts to set the branch in the status using the reference from .git/rebase-apply/head-name. 77 | */ 78 | bool SetBranchFromRebaseApplyHeadName(Status& status); 79 | 80 | /** 81 | * Attempts to set the branch in the status using the reference from .git/rebase-merge/head-name. 82 | */ 83 | bool SetBranchFromRebaseMergeHeadName(Status& status); 84 | 85 | /** 86 | * Retrieves the current branch/upstream and updates status. 87 | */ 88 | bool GetRefStatus(Status& status, UniqueGitRepository& repository); 89 | 90 | /** 91 | * Retrieves file add/modify/delete statistics and updates status. 92 | */ 93 | bool GetFileStatus(Status& status, UniqueGitRepository& repository); 94 | 95 | /** 96 | * Retrieves information about stashes and updates status. 97 | */ 98 | bool GetStashList(Status& status, UniqueGitRepository& repository); 99 | 100 | public: 101 | Git(); 102 | ~Git(); 103 | 104 | /** 105 | * Searches for repository containing provided path and updates status. 106 | */ 107 | std::tuple DiscoverRepository(const std::string& path); 108 | 109 | /** 110 | * Retrieves current git status for repository at provided path. 111 | */ 112 | std::tuple GetStatus(const std::string& path); 113 | }; -------------------------------------------------------------------------------- /src/GitStatusCache/src/LogStream.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include 3 | #include 4 | #include "LogStream.h" 5 | #include "Logging.h" 6 | 7 | using namespace boost::log; 8 | 9 | namespace Logging 10 | { 11 | /*static*/ sources::severity_logger& LogStream::GetLogger() 12 | { 13 | static boost::thread_specific_ptr> s_threadLocalLogger; 14 | if (!s_threadLocalLogger.get()) 15 | { 16 | s_threadLocalLogger.reset(new sources::severity_logger()); 17 | } 18 | return *(s_threadLocalLogger.get()); 19 | } 20 | 21 | /*static*/ record LogStream::OpenRecord(std::string&& eventId, Severity severity) 22 | { 23 | if (!LoggingModule::IsEnabled()) 24 | return record{}; 25 | 26 | auto& logger = GetLogger(); 27 | auto attributeToRemove = logger.add_attribute("EventId", attributes::constant(std::move(eventId))).first; 28 | auto record = logger.open_record(::keywords::severity = severity); 29 | logger.remove_attribute(attributeToRemove); 30 | return record; 31 | } 32 | 33 | LogStream::LogStream(std::string&& eventId, Severity severity) : 34 | m_record(OpenRecord(std::move(eventId), severity)) 35 | { 36 | if (m_record) 37 | m_stream.attach_record(m_record); 38 | } 39 | 40 | LogStream::~LogStream() 41 | { 42 | if (m_record) 43 | { 44 | m_stream.flush(); 45 | auto& logger = GetLogger(); 46 | logger.push_record(std::move(m_record)); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/GitStatusCache/src/LogStream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "LoggingModule.h" 5 | #include "LoggingSeverities.h" 6 | #include "StringConverters.h" 7 | 8 | namespace Logging 9 | { 10 | class LogStream : boost::noncopyable 11 | { 12 | private: 13 | boost::log::record m_record; 14 | boost::log::basic_record_ostream m_stream; 15 | 16 | static boost::log::sources::severity_logger& GetLogger(); 17 | static boost::log::record OpenRecord(std::string&& eventId, Severity severity); 18 | 19 | public: 20 | LogStream(std::string&& eventId, Severity severity); 21 | ~LogStream(); 22 | 23 | template 24 | LogStream& operator<< (T&& value) 25 | { 26 | if (LoggingModule::IsEnabled()) 27 | m_stream << std::forward(value); 28 | return *this; 29 | } 30 | 31 | LogStream& operator<< (const std::string& value) 32 | { 33 | if (LoggingModule::IsEnabled()) 34 | { 35 | // boost logger assumes narrow characters are ASCII. 36 | m_stream << ConvertToUnicode(value); 37 | } 38 | return *this; 39 | } 40 | 41 | LogStream& operator<< (const char* value) 42 | { 43 | if (LoggingModule::IsEnabled()) 44 | { 45 | // boost logger assumes narrow characters are ASCII. 46 | m_stream << ConvertToUnicode(std::string(value)); 47 | } 48 | return *this; 49 | } 50 | }; 51 | } -------------------------------------------------------------------------------- /src/GitStatusCache/src/Logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "LoggingSeverities.h" 13 | #include "LogStream.h" 14 | 15 | #define Log(eventId, severity) LogStream(std::move(std::string(eventId)), severity) -------------------------------------------------------------------------------- /src/GitStatusCache/src/LoggingInitializationScope.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "LoggingModule.h" 4 | 5 | namespace Logging 6 | { 7 | class LoggingInitializationScope : boost::noncopyable 8 | { 9 | public: 10 | LoggingInitializationScope(LoggingModuleSettings settings) 11 | { 12 | LoggingModule::Initialize(settings); 13 | } 14 | 15 | ~LoggingInitializationScope() 16 | { 17 | LoggingModule::Uninitialize(); 18 | } 19 | }; 20 | } -------------------------------------------------------------------------------- /src/GitStatusCache/src/LoggingModule.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "LoggingModule.h" 14 | #include "LoggingSeverities.h" 15 | 16 | using namespace boost::log; 17 | 18 | namespace Logging 19 | { 20 | /*static*/ bool LoggingModule::s_isEnabled = false; 21 | 22 | /*static*/ void LoggingModule::Initialize(LoggingModuleSettings settings) 23 | { 24 | if (s_isEnabled) 25 | { 26 | throw std::logic_error("LoggingModule::Initialize called while logging is already initialized."); 27 | } 28 | 29 | if (!settings.EnableFileLogging) 30 | { 31 | LoggingModule::Uninitialize(); 32 | return; 33 | } 34 | 35 | add_common_attributes(); 36 | core::get()->add_global_attribute("ProcessName", attributes::current_process_name()); 37 | core::get()->add_global_attribute("Scope", attributes::named_scope()); 38 | 39 | auto timestamp = expressions::format_date_time("TimeStamp", "%Y-%m-%d %H:%M:%S.%f"); 40 | auto processName = expressions::attr("ProcessName"); 41 | auto processId = expressions::attr("ProcessID"); 42 | auto threadId = expressions::attr("ThreadID"); 43 | auto eventId = expressions::attr("EventId"); 44 | auto severity = expressions::attr("Severity"); 45 | 46 | std::pair charactersToEscape[3] = 47 | { 48 | std::pair{ "\r", "\\r" }, 49 | std::pair{ "\n", "\\n" }, 50 | std::pair{ "\t", "\\t" } 51 | }; 52 | auto message = expressions::char_decor(charactersToEscape)[expressions::stream << expressions::message]; 53 | 54 | auto formatter = expressions::stream 55 | << timestamp << "\t" 56 | << processName << " (" << processId << ")\t" 57 | << threadId << "\t" 58 | << severity << "\t" 59 | << eventId << "\t" 60 | << message; 61 | 62 | auto locale = boost::locale::generator()("UTF-8"); 63 | 64 | if (settings.EnableFileLogging) 65 | { 66 | boost::system::error_code error; 67 | auto tempPath = boost::filesystem::temp_directory_path(error); 68 | if (boost::system::errc::success == error.value()) 69 | { 70 | tempPath.append("GitStatusCache_%Y-%m-%d_%H-%M-%S.%N.log"); 71 | auto fileSink = add_file_log( 72 | keywords::auto_flush = true, 73 | keywords::file_name = tempPath.c_str(), 74 | keywords::rotation_size = 10 * 1024 * 1024, 75 | keywords::time_based_rotation = sinks::file::rotation_at_time_point(0, 0, 0)); 76 | fileSink->set_formatter(formatter); 77 | fileSink->imbue(locale); 78 | } 79 | } 80 | 81 | core::get()->set_filter(severity >= settings.MinimumSeverity); 82 | 83 | core::get()->set_logging_enabled(true); 84 | s_isEnabled = true; 85 | } 86 | 87 | /*static*/ void LoggingModule::Uninitialize() 88 | { 89 | core::get()->flush(); 90 | core::get()->remove_all_sinks(); 91 | core::get()->set_logging_enabled(false); 92 | } 93 | } -------------------------------------------------------------------------------- /src/GitStatusCache/src/LoggingModule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "LoggingModuleSettings.h" 4 | 5 | namespace Logging 6 | { 7 | class LoggingModule : boost::noncopyable 8 | { 9 | private: 10 | static bool s_isEnabled; 11 | 12 | LoggingModule() { } 13 | 14 | public: 15 | static void Initialize(LoggingModuleSettings settings); 16 | static void Uninitialize(); 17 | static bool IsEnabled() { return s_isEnabled; }; 18 | }; 19 | } -------------------------------------------------------------------------------- /src/GitStatusCache/src/LoggingModuleSettings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "LoggingSeverities.h" 4 | 5 | namespace Logging 6 | { 7 | struct LoggingModuleSettings 8 | { 9 | bool EnableFileLogging = false; 10 | Severity MinimumSeverity = Severity::Info; 11 | }; 12 | } -------------------------------------------------------------------------------- /src/GitStatusCache/src/LoggingSeverities.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Logging 6 | { 7 | enum Severity 8 | { 9 | Spam, 10 | Verbose, 11 | Info, 12 | Warning, 13 | Error, 14 | Critical 15 | }; 16 | 17 | inline std::ostream& operator<< (std::ostream& stream, Severity level) 18 | { 19 | static const char* strings[] = 20 | { 21 | "Spam", 22 | "Verbose", 23 | "Info", 24 | "Warning", 25 | "Error", 26 | "Critical" 27 | }; 28 | 29 | if (static_cast(level) < _countof(strings)) 30 | stream << strings[level]; 31 | else 32 | stream << static_cast(level); 33 | return stream; 34 | } 35 | } -------------------------------------------------------------------------------- /src/GitStatusCache/src/Main.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include 3 | #include "DirectoryMonitor.h" 4 | #include "LoggingModuleSettings.h" 5 | #include "LoggingInitializationScope.h" 6 | #include "NamedPipeServer.h" 7 | #include "StatusCache.h" 8 | #include "StatusController.h" 9 | 10 | using namespace boost::program_options; 11 | 12 | options_description BuildGenericOptions() 13 | { 14 | options_description generic{ "Generic options" }; 15 | generic.add_options() 16 | ("help", "Display help message"); 17 | return generic; 18 | } 19 | 20 | options_description BuildLoggingOptions(bool* enableFileLogging, bool* quiet, bool* verbose, bool* spam) 21 | { 22 | options_description logging{ "Logging options" }; 23 | logging.add_options() 24 | ("fileLogging", bool_switch(enableFileLogging), "Enables file logging.") 25 | ("quiet,q", bool_switch(quiet), "Disables all non-critical logging output.") 26 | ("verbose,v", bool_switch(verbose), "Enables verbose logging output.") 27 | ("spam,s", bool_switch(spam), "Enables spam logging output."); 28 | return logging; 29 | } 30 | 31 | void ThrowIfMutuallyExclusiveOptionsSet( 32 | const variables_map& vm, 33 | const std::string& option1, 34 | const std::string& option2, 35 | const std::string& option3) 36 | { 37 | if ( (!vm[option1].defaulted() && (!vm[option2].defaulted() || !vm[option3].defaulted())) 38 | || (!vm[option2].defaulted() && (!vm[option1].defaulted() || !vm[option3].defaulted())) 39 | || (!vm[option3].defaulted() && (!vm[option1].defaulted() || !vm[option2].defaulted()))) 40 | { 41 | throw std::logic_error(std::string("Options '") + option1 + "', '" + option2 + "', and '" + option3 + "' are mutually exclusive."); 42 | } 43 | } 44 | 45 | int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) 46 | { 47 | int argc; 48 | auto argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 49 | 50 | Logging::LoggingModuleSettings loggingSettings; 51 | bool quiet = false; 52 | bool verbose = false; 53 | bool spam = false; 54 | 55 | auto generic = BuildGenericOptions(); 56 | auto logging = BuildLoggingOptions(&loggingSettings.EnableFileLogging, &quiet, &verbose, &spam); 57 | options_description all{ "Allowed options" }; 58 | all.add(generic).add(logging); 59 | 60 | try 61 | { 62 | variables_map vm; 63 | store(wcommand_line_parser(argc, argv).options(all).run(), vm); 64 | 65 | if (vm.count("help")) 66 | { 67 | std::cout << generic << std::endl; 68 | std::cout << logging << std::endl; 69 | return 1; 70 | } 71 | 72 | ThrowIfMutuallyExclusiveOptionsSet(vm, "quiet", "verbose", "spam"); 73 | notify(vm); 74 | } 75 | catch (std::exception& e) 76 | { 77 | std::cerr << "Error: " << e.what() << std::endl; 78 | std::cout << generic << std::endl; 79 | std::cout << logging << std::endl; 80 | return -1; 81 | } 82 | 83 | if (quiet) 84 | loggingSettings.MinimumSeverity = Logging::Severity::Error; 85 | else if (verbose) 86 | loggingSettings.MinimumSeverity = Logging::Severity::Verbose; 87 | else if (spam) 88 | loggingSettings.MinimumSeverity = Logging::Severity::Spam; 89 | else 90 | loggingSettings.MinimumSeverity = Logging::Severity::Info; 91 | 92 | Logging::LoggingInitializationScope enableLogging(loggingSettings); 93 | 94 | StatusController statusController; 95 | NamedPipeServer server([&statusController](const std::string& request) { return statusController.HandleRequest(request); }); 96 | 97 | statusController.WaitForShutdownRequest(); 98 | 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /src/GitStatusCache/src/NamedPipeInstance.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "NamedPipeInstance.h" 3 | 4 | void NamedPipeInstance::OnClientRequest() 5 | { 6 | Log("NamedPipeInstance.OnClientRequest.Start", Severity::Verbose) << "Request servicing thread started."; 7 | 8 | while (true) 9 | { 10 | auto readResult = ReadRequest(); 11 | if (readResult.first != IoResult::Success) 12 | { 13 | break; 14 | } 15 | 16 | Log("NamedPipeInstance.OnClientRequest.Request", Severity::Spam) 17 | << R"(Received request from client. { "request": ")" << readResult.second << R"(" })"; 18 | 19 | auto response = m_onClientRequestCallback(readResult.second); 20 | 21 | Log("NamedPipeInstance.OnClientRequest.Response", Severity::Spam) 22 | << R"(Sending response to client. { "response": ")" << response << R"(" })"; 23 | 24 | auto writeResult = WriteResponse(response); 25 | if (writeResult != IoResult::Success) 26 | { 27 | break; 28 | } 29 | } 30 | 31 | ::FlushFileBuffers(m_pipe); 32 | ::DisconnectNamedPipe(m_pipe); 33 | m_pipe.invoke(); 34 | m_isClosed = true; 35 | 36 | Log("NamedPipeInstance.OnClientRequest.Stop", Severity::Verbose) << "Request servicing thread stopping."; 37 | } 38 | 39 | NamedPipeInstance::ReadResult NamedPipeInstance::ReadRequest() 40 | { 41 | auto requestBuffer = std::vector(BufferSize); 42 | DWORD bytesRead = 0; 43 | auto readResult = ::ReadFile( 44 | m_pipe, 45 | requestBuffer.data(), 46 | requestBuffer.capacity() * sizeof(char), 47 | &bytesRead, 48 | nullptr /*lpOverlapped*/); 49 | 50 | if (!readResult) 51 | { 52 | auto error = ::GetLastError(); 53 | if (error == ERROR_BROKEN_PIPE) 54 | { 55 | Log("NamedPipeInstance.ReadRequest.Disconnect", Severity::Verbose) 56 | << "Client disconnected. ReadFile returned ERROR_BROKEN_PIPE."; 57 | return ReadResult(IoResult::Aborted, std::string()); 58 | } 59 | else if (error == ERROR_OPERATION_ABORTED) 60 | { 61 | Log("NamedPipeInstance.ReadRequest.Aborted", Severity::Verbose) << "ReadFile returned ERROR_OPERATION_ABORTED."; 62 | return ReadResult(IoResult::Aborted, std::string()); 63 | } 64 | else 65 | { 66 | Log("NamedPipeInstance.ReadRequest.UnknownError", Severity::Error) 67 | << R"(ReadFile failed with unexpected error. { "error": )" << error << R"( })"; 68 | throw std::runtime_error("ReadFile failed unexpectedly."); 69 | return ReadResult(IoResult::Error, std::string()); 70 | } 71 | } 72 | 73 | return ReadResult(IoResult::Success, std::string(requestBuffer.data(), bytesRead / sizeof(char))); 74 | } 75 | 76 | NamedPipeInstance::IoResult NamedPipeInstance::WriteResponse(const std::string& response) 77 | { 78 | DWORD bytesWritten = 0; 79 | auto writeResult = ::WriteFile( 80 | m_pipe, 81 | response.data(), 82 | response.size() * sizeof(char), 83 | &bytesWritten, 84 | nullptr /*lpOverlapped*/); 85 | 86 | if (!writeResult) 87 | { 88 | auto error = ::GetLastError(); 89 | if (error == ERROR_BROKEN_PIPE) 90 | { 91 | Log("NamedPipeInstance.WriteResponse.Disconnect", Severity::Verbose) 92 | << "Client disconnected. WriteFile returned ERROR_BROKEN_PIPE."; 93 | return IoResult::Aborted; 94 | } 95 | else if (error == ERROR_OPERATION_ABORTED) 96 | { 97 | Log("NamedPipeInstance.WriteResponse.Aborted", Severity::Verbose) << "WriteFile returned ERROR_OPERATION_ABORTED."; 98 | return IoResult::Aborted; 99 | } 100 | else 101 | { 102 | Log("NamedPipeInstance.WriteResponse.UnknownError", Severity::Error) 103 | << R"(WriteFile failed with unexpected error. { "error": )" << error << R"( })"; 104 | throw std::runtime_error("WriteFile failed unexpectedly."); 105 | return IoResult::Error; 106 | } 107 | } 108 | 109 | return IoResult::Success; 110 | } 111 | 112 | NamedPipeInstance::NamedPipeInstance(const OnClientRequestCallback& onClientRequestCallback) 113 | : m_onClientRequestCallback(onClientRequestCallback) 114 | , m_pipe(MakeUniqueHandle(INVALID_HANDLE_VALUE)) 115 | { 116 | auto pipeMode = PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT; 117 | auto timeout = 0; 118 | auto pipe = ::CreateNamedPipe( 119 | L"\\\\.\\pipe\\GitStatusCache", 120 | PIPE_ACCESS_DUPLEX, 121 | pipeMode, 122 | PIPE_UNLIMITED_INSTANCES, 123 | BufferSize, 124 | BufferSize, 125 | timeout, 126 | nullptr); 127 | 128 | if (pipe == INVALID_HANDLE_VALUE) 129 | { 130 | Log("NamedPipeInstance.Create", Severity::Error) << "Failed to create named pipe instance."; 131 | throw std::runtime_error("Failed to create named pipe instance."); 132 | } 133 | m_pipe = MakeUniqueHandle(pipe); 134 | } 135 | 136 | NamedPipeInstance::~NamedPipeInstance() 137 | { 138 | if (m_thread.joinable()) 139 | { 140 | if (!m_isClosed) 141 | { 142 | Log("NamedPipeInstance.ShutDown.StoppingBackgroundThread", Severity::Spam) 143 | << R"(Shutting down request servicing thread. { "threadId": 0x)" << std::hex << m_thread.get_id() << " }"; 144 | ::CancelSynchronousIo(m_thread.native_handle()); 145 | } 146 | m_thread.join(); 147 | } 148 | } 149 | 150 | NamedPipeInstance::IoResult NamedPipeInstance::Connect() 151 | { 152 | auto connected = ::ConnectNamedPipe(m_pipe, nullptr /*lpOverlapped*/); 153 | if (connected || ::GetLastError() == ERROR_PIPE_CONNECTED) 154 | { 155 | Log("NamedPipeInstance.Connect.Success", Severity::Spam) << "ConnectNamedPipe succeeded."; 156 | 157 | std::call_once(m_flag, [this]() 158 | { 159 | Log("NamedPipeInstance.StartingBackgroundThread", Severity::Spam) 160 | << "Attempting to start background thread for servicing requests."; 161 | m_thread = std::thread(&NamedPipeInstance::OnClientRequest, this); 162 | }); 163 | 164 | return IoResult::Success; 165 | } 166 | 167 | auto error = GetLastError(); 168 | if (error == ERROR_OPERATION_ABORTED) 169 | { 170 | Log("NamedPipeInstance.Connect.Aborted", Severity::Verbose) << "ConnectNamedPipe returned ERROR_OPERATION_ABORTED."; 171 | return IoResult::Aborted; 172 | } 173 | 174 | Log("NamedPipeInstance.Connect.UnknownError", Severity::Error) 175 | << R"(ConnectNamedPipe failed with unexpected error. { "error": )" << error << R"( })"; 176 | throw std::runtime_error("ConnectNamedPipe failed unexpectedly."); 177 | 178 | return IoResult::Error; 179 | } 180 | -------------------------------------------------------------------------------- /src/GitStatusCache/src/NamedPipeInstance.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Dedicated pipe instance used to service requests for a single client. 5 | * Each instantiation services requests on its own thread. 6 | */ 7 | class NamedPipeInstance 8 | { 9 | public: 10 | enum IoResult 11 | { 12 | Success, 13 | Error, 14 | Aborted, 15 | }; 16 | 17 | private: 18 | using OnClientRequestCallback = std::function; 19 | using ReadResult = std::pair; 20 | const size_t BufferSize = 4096; 21 | 22 | bool m_isClosed = false; 23 | UniqueHandle m_pipe; 24 | std::thread m_thread; 25 | std::once_flag m_flag; 26 | OnClientRequestCallback m_onClientRequestCallback; 27 | 28 | void OnClientRequest(); 29 | ReadResult ReadRequest(); 30 | IoResult WriteResponse(const std::string& response); 31 | 32 | public: 33 | /** 34 | * Constructor. Callback must be thread-safe. 35 | * @param onClientRequestCallback Callback with logic to handle the request. 36 | */ 37 | NamedPipeInstance(const OnClientRequestCallback& onClientRequestCallback); 38 | ~NamedPipeInstance(); 39 | 40 | /** 41 | * Blocks until a client connects. Can be aborted with ::CancelSynchronousIo. 42 | */ 43 | IoResult Connect(); 44 | 45 | /** 46 | * Returns whether the client has disconnected. 47 | */ 48 | bool IsClosed() const { return m_isClosed; } 49 | }; 50 | -------------------------------------------------------------------------------- /src/GitStatusCache/src/NamedPipeServer.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "NamedPipeServer.h" 3 | #include "NamedPipeInstance.h" 4 | 5 | void NamedPipeServer::WaitForClientRequest() 6 | { 7 | Log("NamedPipeServer.WaitForClientRequest.Start", Severity::Verbose) << "Server thread started."; 8 | 9 | while (true) 10 | { 11 | RemoveClosedPipeInstances(); 12 | 13 | Log("NamedPipeServer.WaitForClientRequest", Severity::Verbose) << "Creating named pipe instance and waiting for client."; 14 | auto pipe = std::make_unique(m_onClientRequestCallback); 15 | auto connectResult = pipe->Connect(); 16 | m_pipeInstances.emplace_back(std::move(pipe)); 17 | 18 | if (connectResult != NamedPipeInstance::IoResult::Success) 19 | { 20 | Log("NamedPipeServer.WaitForClientRequest.Stop", Severity::Verbose) << "Server thread stopping."; 21 | break; 22 | } 23 | } 24 | } 25 | 26 | void NamedPipeServer::RemoveClosedPipeInstances() 27 | { 28 | auto originalSize = m_pipeInstances.size(); 29 | 30 | m_pipeInstances.erase( 31 | std::remove_if( 32 | m_pipeInstances.begin(), 33 | m_pipeInstances.end(), 34 | [](const std::unique_ptr& pipeInstance) { return pipeInstance->IsClosed(); }), 35 | m_pipeInstances.end()); 36 | 37 | auto newSize = m_pipeInstances.size(); 38 | if (newSize != originalSize) 39 | { 40 | Log("NamedPipeServer.RemoveClosedPipeInstances", Severity::Spam) 41 | << R"(Removed closed pipe instancs. { "instancesRemoved": )" << originalSize - newSize << " }"; 42 | } 43 | } 44 | 45 | NamedPipeServer::NamedPipeServer(const OnClientRequestCallback& onClientRequestCallback) 46 | : m_onClientRequestCallback(onClientRequestCallback) 47 | { 48 | Log("NamedPipeServer.StartingBackgroundThread", Severity::Spam) << "Attempting to start server thread."; 49 | m_serverThread = std::thread(&NamedPipeServer::WaitForClientRequest, this); 50 | } 51 | 52 | NamedPipeServer::~NamedPipeServer() 53 | { 54 | Log("NamedPipeServer.ShutDown.StoppingBackgroundThread", Severity::Spam) 55 | << R"(Shutting down server thread. { "threadId": 0x)" << std::hex << m_serverThread.get_id() << " }"; 56 | ::CancelSynchronousIo(m_serverThread.native_handle()); 57 | m_serverThread.join(); 58 | } 59 | -------------------------------------------------------------------------------- /src/GitStatusCache/src/NamedPipeServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "NamedPipeInstance.h" 4 | 5 | /** 6 | * Waits on a background thread for client connection requests. 7 | * When a new client connects the server creates a new NamedPipeInstance to 8 | * service the clients requests. 9 | */ 10 | class NamedPipeServer : boost::noncopyable 11 | { 12 | public: 13 | /** 14 | * Callback for request handling logic. Request provided in argument. 15 | * Returns response. 16 | */ 17 | using OnClientRequestCallback = std::function; 18 | 19 | private: 20 | std::thread m_serverThread; 21 | std::vector> m_pipeInstances; 22 | OnClientRequestCallback m_onClientRequestCallback; 23 | 24 | void WaitForClientRequest(); 25 | void RemoveClosedPipeInstances(); 26 | 27 | public: 28 | /** 29 | * Constructor. 30 | * @param onClientRequestCallback Callback with logic to handle the request. 31 | * Callback must be thread-safe. 32 | */ 33 | NamedPipeServer(const OnClientRequestCallback& onClientRequestCallback); 34 | ~NamedPipeServer(); 35 | }; -------------------------------------------------------------------------------- /src/GitStatusCache/src/SmartPointers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | // HANDLE 5 | using UniqueHandle = std::experimental::unique_resource_t; 6 | inline UniqueHandle MakeUniqueHandle(HANDLE handle) 7 | { 8 | return std::experimental::unique_resource_checked(handle, INVALID_HANDLE_VALUE, &::CloseHandle); 9 | } 10 | 11 | // git_buf 12 | inline void FreeGitBuf(git_buf& buffer) 13 | { 14 | git_buf_free(&buffer); 15 | } 16 | 17 | using UniqueGitBuffer = std::experimental::unique_resource_t; 18 | inline UniqueGitBuffer MakeUniqueGitBuffer(git_buf&& buffer) 19 | { 20 | return std::experimental::unique_resource(std::move(buffer), &FreeGitBuf); 21 | } 22 | 23 | // git_repository 24 | inline void FreeGitRepository(git_repository* repository) 25 | { 26 | git_repository_free(repository); 27 | } 28 | 29 | using UniqueGitRepository = std::experimental::unique_resource_t; 30 | inline UniqueGitRepository MakeUniqueGitRepository(git_repository* repository) 31 | { 32 | return std::experimental::unique_resource(std::move(repository), &FreeGitRepository); 33 | } 34 | 35 | // git_reference 36 | inline void FreeGitReference(git_reference* reference) 37 | { 38 | git_reference_free(reference); 39 | } 40 | 41 | using UniqueGitReference = std::experimental::unique_resource_t; 42 | inline UniqueGitReference MakeUniqueGitReference(git_reference* reference) 43 | { 44 | return std::experimental::unique_resource(std::move(reference), &FreeGitReference); 45 | } 46 | 47 | // git_status_list 48 | inline void FreeGitStatusList(git_status_list* statusList) 49 | { 50 | git_status_list_free(statusList); 51 | } 52 | 53 | using UniqueGitStatusList = std::experimental::unique_resource_t; 54 | inline UniqueGitStatusList MakeUniqueGitStatusList(git_status_list* statusList) 55 | { 56 | return std::experimental::unique_resource(std::move(statusList), &FreeGitStatusList); 57 | } 58 | -------------------------------------------------------------------------------- /src/GitStatusCache/src/StatusCache.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "StatusCache.h" 3 | 4 | StatusCache::StatusCache() 5 | : m_cache(std::make_shared()) 6 | , m_cacheInvalidator(m_cache) 7 | { 8 | } 9 | 10 | std::tuple StatusCache::GetStatus(const std::string& repositoryPath) 11 | { 12 | auto status = m_cache->GetStatus(repositoryPath); 13 | if (std::get<0>(status)) 14 | m_cacheInvalidator.MonitorRepositoryDirectories(std::get<1>(status)); 15 | 16 | return status; 17 | } 18 | 19 | CacheStatistics StatusCache::GetCacheStatistics() 20 | { 21 | return m_cache->GetCacheStatistics(); 22 | } -------------------------------------------------------------------------------- /src/GitStatusCache/src/StatusCache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Cache.h" 3 | #include "CacheInvalidator.h" 4 | 5 | /** 6 | * Caches git status information. This class is thread-safe. 7 | */ 8 | class StatusCache : boost::noncopyable 9 | { 10 | private: 11 | std::shared_ptr m_cache; 12 | CacheInvalidator m_cacheInvalidator; 13 | 14 | public: 15 | StatusCache(); 16 | 17 | /** 18 | * Retrieves current git status for repository at provided path. 19 | * Returns from cache if present, otherwise queries git and adds to cache. 20 | */ 21 | std::tuple GetStatus(const std::string& repositoryPath); 22 | 23 | /** 24 | * Returns information about cache's performance. 25 | */ 26 | CacheStatistics GetCacheStatistics(); 27 | }; -------------------------------------------------------------------------------- /src/GitStatusCache/src/StatusController.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "StatusController.h" 3 | #include 4 | #include 5 | 6 | StatusController::StatusController() 7 | : m_startTime(boost::posix_time::second_clock::universal_time()) 8 | , m_requestShutdown(MakeUniqueHandle(INVALID_HANDLE_VALUE)) 9 | { 10 | auto requestShutdown = ::CreateEvent( 11 | nullptr /*lpEventAttributes*/, 12 | true /*manualReset*/, 13 | false /*bInitialState*/, 14 | nullptr /*lpName*/); 15 | if (requestShutdown == nullptr) 16 | { 17 | Log("StatusController.Constructor.CreateEventFailed", Severity::Error) 18 | << "Failed to create event to signal shutdown request."; 19 | throw std::runtime_error("CreateEvent failed unexpectedly."); 20 | } 21 | m_requestShutdown = MakeUniqueHandle(requestShutdown); 22 | } 23 | 24 | StatusController::~StatusController() 25 | { 26 | } 27 | 28 | /*static*/ void StatusController::AddStringToJson(rapidjson::Writer& writer, std::string&& name, std::string&& value) 29 | { 30 | writer.String(name.c_str()); 31 | writer.String(value.c_str()); 32 | } 33 | 34 | /*static*/ void StatusController::AddBoolToJson(rapidjson::Writer& writer, std::string&& name, bool value) 35 | { 36 | writer.String(name.c_str()); 37 | writer.Bool(value); 38 | } 39 | 40 | /*static*/ void StatusController::AddUintToJson(rapidjson::Writer& writer, std::string&& name, uint32_t value) 41 | { 42 | writer.String(name.c_str()); 43 | writer.Uint(value); 44 | } 45 | 46 | /*static*/ void StatusController::AddUint64ToJson(rapidjson::Writer& writer, std::string&& name, uint64_t value) 47 | { 48 | writer.String(name.c_str()); 49 | writer.Uint64(value); 50 | } 51 | 52 | /*static*/ void StatusController::AddDoubleToJson(rapidjson::Writer& writer, std::string&& name, double value) 53 | { 54 | writer.String(name.c_str()); 55 | writer.Double(value); 56 | } 57 | 58 | /*static*/ void StatusController::AddArrayToJson(rapidjson::Writer& writer, std::string&& name, const std::vector& values) 59 | { 60 | writer.String(name.c_str()); 61 | writer.StartArray(); 62 | for (const auto& value : values) 63 | writer.String(value.c_str()); 64 | writer.EndArray(); 65 | } 66 | 67 | /*static*/ void StatusController::AddVersionToJson(rapidjson::Writer& writer) 68 | { 69 | AddUintToJson(writer, "Version", 1); 70 | } 71 | 72 | /*static*/ std::string StatusController::CreateErrorResponse(const std::string& request, std::string&& error) 73 | { 74 | Log("StatusController.FailedRequest", Severity::Warning) 75 | << R"(Failed to service request. { "error": ")" << error << R"(", "request": ")" << request << R"(" })"; 76 | 77 | rapidjson::StringBuffer buffer; 78 | rapidjson::Writer writer{buffer}; 79 | 80 | writer.StartObject(); 81 | AddVersionToJson(writer); 82 | AddStringToJson(writer, "Error", std::move(error)); 83 | writer.EndObject(); 84 | 85 | return buffer.GetString(); 86 | } 87 | 88 | void StatusController::RecordGetStatusTime(uint64_t nanosecondsInGetStatus) 89 | { 90 | WriteLock writeLock{m_getStatusStatisticsMutex}; 91 | ++m_totalGetStatusCalls; 92 | m_totalNanosecondsInGetStatus += nanosecondsInGetStatus; 93 | m_minNanosecondsInGetStatus = (std::min)(nanosecondsInGetStatus, m_minNanosecondsInGetStatus); 94 | m_maxNanosecondsInGetStatus = (std::max)(nanosecondsInGetStatus, m_maxNanosecondsInGetStatus); 95 | } 96 | 97 | std::string StatusController::GetStatus(const rapidjson::Document& document, const std::string& request) 98 | { 99 | if (!document.HasMember("Path") || !document["Path"].IsString()) 100 | { 101 | return CreateErrorResponse(request, "'Path' must be specified."); 102 | } 103 | auto path = std::string(document["Path"].GetString()); 104 | 105 | auto repositoryPath = m_git.DiscoverRepository(path); 106 | if (!std::get<0>(repositoryPath)) 107 | { 108 | return CreateErrorResponse(request, "Requested 'Path' is not part of a git repository."); 109 | } 110 | 111 | auto status = m_cache.GetStatus(std::get<1>(repositoryPath)); 112 | if (!std::get<0>(status)) 113 | { 114 | return CreateErrorResponse(request, "Failed to retrieve status of git repository at provided 'Path'."); 115 | } 116 | 117 | auto& statusToReport = std::get<1>(status); 118 | 119 | rapidjson::StringBuffer buffer; 120 | rapidjson::Writer writer{buffer}; 121 | 122 | writer.StartObject(); 123 | 124 | AddVersionToJson(writer); 125 | AddStringToJson(writer, "Path", path.c_str()); 126 | AddStringToJson(writer, "RepoPath", statusToReport.RepositoryPath.c_str()); 127 | AddStringToJson(writer, "WorkingDir", statusToReport.WorkingDirectory.c_str()); 128 | AddStringToJson(writer, "State", statusToReport.State.c_str()); 129 | AddStringToJson(writer, "Branch", statusToReport.Branch.c_str()); 130 | AddStringToJson(writer, "Upstream", statusToReport.Upstream.c_str()); 131 | AddBoolToJson(writer, "UpstreamGone", statusToReport.UpstreamGone); 132 | AddUintToJson(writer, "AheadBy", statusToReport.AheadBy); 133 | AddUintToJson(writer, "BehindBy", statusToReport.BehindBy); 134 | 135 | AddArrayToJson(writer, "IndexAdded", statusToReport.IndexAdded); 136 | AddArrayToJson(writer, "IndexModified", statusToReport.IndexModified); 137 | AddArrayToJson(writer, "IndexDeleted", statusToReport.IndexDeleted); 138 | AddArrayToJson(writer, "IndexTypeChange", statusToReport.IndexTypeChange); 139 | writer.String("IndexRenamed"); 140 | writer.StartArray(); 141 | for (const auto& value : statusToReport.IndexRenamed) 142 | { 143 | writer.StartObject(); 144 | writer.String("Old"); 145 | writer.String(value.first.c_str()); 146 | writer.String("New"); 147 | writer.String(value.second.c_str()); 148 | writer.EndObject(); 149 | } 150 | writer.EndArray(); 151 | 152 | AddArrayToJson(writer, "WorkingAdded", statusToReport.WorkingAdded); 153 | AddArrayToJson(writer, "WorkingModified", statusToReport.WorkingModified); 154 | AddArrayToJson(writer, "WorkingDeleted", statusToReport.WorkingDeleted); 155 | AddArrayToJson(writer, "WorkingTypeChange", statusToReport.WorkingTypeChange); 156 | writer.String("WorkingRenamed"); 157 | writer.StartArray(); 158 | for (const auto& value : statusToReport.WorkingRenamed) 159 | { 160 | writer.StartObject(); 161 | writer.String("Old"); 162 | writer.String(value.first.c_str()); 163 | writer.String("New"); 164 | writer.String(value.second.c_str()); 165 | writer.EndObject(); 166 | } 167 | writer.EndArray(); 168 | AddArrayToJson(writer, "WorkingUnreadable", statusToReport.WorkingUnreadable); 169 | 170 | AddArrayToJson(writer, "Ignored", statusToReport.Ignored); 171 | AddArrayToJson(writer, "Conflicted", statusToReport.Conflicted); 172 | 173 | writer.String("Stashes"); 174 | writer.StartArray(); 175 | for (const auto& value : statusToReport.Stashes) 176 | { 177 | writer.StartObject(); 178 | writer.String("Name"); 179 | std::string name = "stash@{"; 180 | name += std::to_string(value.Index); 181 | name += "}"; 182 | writer.String(name.c_str()); 183 | writer.String("Sha1Id"); 184 | writer.String(value.Sha1Id.c_str()); 185 | writer.String("Message"); 186 | writer.String(value.Message.c_str()); 187 | writer.EndObject(); 188 | } 189 | writer.EndArray(); 190 | 191 | writer.EndObject(); 192 | 193 | return buffer.GetString(); 194 | } 195 | 196 | std::string StatusController::GetCacheStatistics() 197 | { 198 | auto statistics = m_cache.GetCacheStatistics(); 199 | 200 | static const int nanosecondsPerMillisecond = 1000000; 201 | uint64_t totalGetStatusCalls; 202 | uint64_t totalNanosecondsInGetStatus; 203 | uint64_t minNanosecondsInGetStatus; 204 | uint64_t maxNanosecondsInGetStatus; 205 | { 206 | ReadLock readLock{m_getStatusStatisticsMutex}; 207 | totalGetStatusCalls = m_totalGetStatusCalls; 208 | totalNanosecondsInGetStatus = m_totalNanosecondsInGetStatus; 209 | minNanosecondsInGetStatus = m_minNanosecondsInGetStatus; 210 | maxNanosecondsInGetStatus = m_maxNanosecondsInGetStatus; 211 | } 212 | 213 | auto averageNanosecondsInGetStatus = totalGetStatusCalls != 0 ? totalNanosecondsInGetStatus / totalGetStatusCalls : 0; 214 | auto averageMillisecondsInGetStatus = static_cast(averageNanosecondsInGetStatus) / nanosecondsPerMillisecond; 215 | auto minMillisecondsInGetStatus = static_cast(minNanosecondsInGetStatus) / nanosecondsPerMillisecond; 216 | auto maxMillisecondsInGetStatus = static_cast(maxNanosecondsInGetStatus) / nanosecondsPerMillisecond; 217 | 218 | auto currentTime = boost::posix_time::second_clock::universal_time(); 219 | auto uptime = boost::posix_time::to_simple_string(currentTime - m_startTime); 220 | 221 | rapidjson::StringBuffer buffer; 222 | rapidjson::Writer writer{buffer}; 223 | 224 | writer.StartObject(); 225 | AddVersionToJson(writer); 226 | AddStringToJson(writer, "Uptime", std::move(uptime)); 227 | AddUint64ToJson(writer, "TotalGetStatusRequests", totalGetStatusCalls); 228 | AddDoubleToJson(writer, "AverageMillisecondsInGetStatus", averageMillisecondsInGetStatus); 229 | AddDoubleToJson(writer, "MinimumMillisecondsInGetStatus", minMillisecondsInGetStatus); 230 | AddDoubleToJson(writer, "MaximumMillisecondsInGetStatus", maxMillisecondsInGetStatus); 231 | AddUint64ToJson(writer, "CacheHits", statistics.CacheHits); 232 | AddUint64ToJson(writer, "CacheMisses", statistics.CacheMisses); 233 | AddUint64ToJson(writer, "EffectiveCachePrimes", statistics.CacheEffectivePrimeRequests); 234 | AddUint64ToJson(writer, "TotalCachePrimes", statistics.CacheTotalPrimeRequests); 235 | AddUint64ToJson(writer, "EffectiveCacheInvalidations", statistics.CacheEffectiveInvalidationRequests); 236 | AddUint64ToJson(writer, "TotalCacheInvalidations", statistics.CacheTotalInvalidationRequests); 237 | AddUint64ToJson(writer, "FullCacheInvalidations", statistics.CacheInvalidateAllRequests); 238 | writer.EndObject(); 239 | 240 | return buffer.GetString(); 241 | } 242 | 243 | std::string StatusController::Shutdown() 244 | { 245 | Log("StatusController.Shutdown", Severity::Info) << R"(Shutting down due to client request.")"; 246 | ::SetEvent(m_requestShutdown); 247 | 248 | rapidjson::StringBuffer buffer; 249 | rapidjson::Writer writer{ buffer }; 250 | 251 | writer.StartObject(); 252 | AddVersionToJson(writer); 253 | AddStringToJson(writer, "Result", "Shutting down."); 254 | writer.EndObject(); 255 | 256 | return buffer.GetString(); 257 | } 258 | 259 | std::string StatusController::HandleRequest(const std::string& request) 260 | { 261 | rapidjson::Document document; 262 | const auto& parser = document.Parse(request.c_str()); 263 | if (parser.HasParseError()) 264 | return CreateErrorResponse(request, "Request must be valid JSON."); 265 | 266 | if (!document.HasMember("Version") || !document["Version"].IsUint()) 267 | return CreateErrorResponse(request, "'Version' must be specified."); 268 | auto version = document["Version"].GetUint(); 269 | 270 | if (version != 1) 271 | return CreateErrorResponse(request, "Requested 'Version' unknown."); 272 | 273 | if (!document.HasMember("Action") || !document["Action"].IsString()) 274 | return CreateErrorResponse(request, "'Action' must be specified."); 275 | auto action = std::string(document["Action"].GetString()); 276 | 277 | if (boost::iequals(action, "GetStatus")) 278 | { 279 | boost::timer::cpu_timer timer; 280 | auto result = GetStatus(document, request); 281 | RecordGetStatusTime(timer.elapsed().wall); 282 | return result; 283 | } 284 | 285 | if (boost::iequals(action, "GetCacheStatistics")) 286 | return GetCacheStatistics(); 287 | 288 | if (boost::iequals(action, "Shutdown")) 289 | return Shutdown(); 290 | 291 | return CreateErrorResponse(request, "'Action' unrecognized."); 292 | } 293 | 294 | void StatusController::WaitForShutdownRequest() 295 | { 296 | ::WaitForSingleObject(m_requestShutdown, INFINITE); 297 | } -------------------------------------------------------------------------------- /src/GitStatusCache/src/StatusController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Git.h" 4 | #include "DirectoryMonitor.h" 5 | #include "StatusCache.h" 6 | #include 7 | #include 8 | #include 9 | 10 | /** 11 | * Services requests for git status information. 12 | */ 13 | class StatusController : boost::noncopyable 14 | { 15 | private: 16 | using ReadLock = boost::shared_lock; 17 | using WriteLock = boost::unique_lock; 18 | 19 | const boost::posix_time::ptime m_startTime; 20 | 21 | uint64_t m_totalNanosecondsInGetStatus = 0; 22 | uint64_t m_minNanosecondsInGetStatus = UINT64_MAX; 23 | uint64_t m_maxNanosecondsInGetStatus = 0; 24 | uint64_t m_totalGetStatusCalls = 0; 25 | boost::shared_mutex m_getStatusStatisticsMutex; 26 | 27 | Git m_git; 28 | StatusCache m_cache; 29 | UniqueHandle m_requestShutdown; 30 | 31 | /** 32 | * Adds named string to JSON response. 33 | */ 34 | static void AddStringToJson(rapidjson::Writer& writer, std::string&& name, std::string&& value); 35 | 36 | /** 37 | * Adds bool to JSON response. 38 | */ 39 | static void AddBoolToJson(rapidjson::Writer& writer, std::string&& name, bool value); 40 | 41 | /** 42 | * Adds uint32_t to JSON response. 43 | */ 44 | static void AddUintToJson(rapidjson::Writer& writer, std::string&& name, uint32_t value); 45 | 46 | /** 47 | * Adds uint64_t to JSON response. 48 | */ 49 | static void AddUint64ToJson(rapidjson::Writer& writer, std::string&& name, uint64_t value); 50 | 51 | /** 52 | * Adds double to JSON response. 53 | */ 54 | static void AddDoubleToJson(rapidjson::Writer& writer, std::string&& name, double value); 55 | 56 | /** 57 | * Adds vector of strings to JSON response. 58 | */ 59 | static void AddArrayToJson(rapidjson::Writer& writer, std::string&& name, const std::vector& value); 60 | 61 | /** 62 | * Adds version to JSON response. 63 | */ 64 | static void AddVersionToJson(rapidjson::Writer& writer); 65 | 66 | /** 67 | * Creates JSON response for errors. 68 | */ 69 | static std::string CreateErrorResponse(const std::string& request, std::string&& error); 70 | 71 | /** 72 | * Records timing datapoint for GetStatus. 73 | */ 74 | void RecordGetStatusTime(uint64_t nanosecondsInGetStatus); 75 | 76 | /** 77 | * Retrieves current git status. 78 | */ 79 | std::string GetStatus(const rapidjson::Document& document, const std::string& request); 80 | 81 | /** 82 | * Retrieves information about cache's performance. 83 | */ 84 | std::string GetCacheStatistics(); 85 | 86 | /** 87 | * Shuts down the service. 88 | */ 89 | std::string StatusController::Shutdown(); 90 | 91 | public: 92 | StatusController(); 93 | ~StatusController(); 94 | 95 | /** 96 | * Deserializes request and returns serialized response. 97 | */ 98 | std::string StatusController::HandleRequest(const std::string& request); 99 | 100 | /** 101 | * Blocks until shutdown request received. 102 | */ 103 | void WaitForShutdownRequest(); 104 | }; -------------------------------------------------------------------------------- /src/GitStatusCache/src/StringConverters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | inline std::string ConvertToUtf8(const std::wstring& unicode) 4 | { 5 | std::wstring_convert> converter; 6 | return converter.to_bytes(unicode); 7 | } 8 | 9 | inline std::wstring ConvertToUnicode(const std::string& utf8String) 10 | { 11 | std::wstring_convert> converter; 12 | return converter.from_bytes(utf8String); 13 | } -------------------------------------------------------------------------------- /src/GitStatusCache/src/stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // GitStatusCache.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | 7 | // TODO: reference any additional headers you need in STDAFX.H 8 | // and not in this file 9 | -------------------------------------------------------------------------------- /src/GitStatusCache/src/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #include "targetver.h" 9 | 10 | #define WIN32_LEAN_AND_MEAN 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #ifndef noexcept 32 | #define noexcept throw() 33 | #include 34 | #include 35 | #undef noexcept 36 | #else 37 | #include 38 | #include 39 | #endif 40 | 41 | #include 42 | #include 43 | #include 44 | 45 | #include "Logging.h" 46 | using namespace Logging; 47 | 48 | #include "SmartPointers.h" -------------------------------------------------------------------------------- /src/GitStatusCache/src/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | --------------------------------------------------------------------------------