├── .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 |
--------------------------------------------------------------------------------