Keep the font size of 8 for the explorer property page
[TortoiseGit.git] / src / GitWCRev / GitWCRev.cpp
blob6c7447447c41fc094dc3daedb970bb4c0e4b2c83
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2017-2018 - TortoiseGit
4 // Copyright (C) 2003-2016 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "SmartHandle.h"
22 #include "../Utils/CrashReport.h"
24 #include <iostream>
25 #include <io.h>
26 #include <fcntl.h>
28 #include "GitWCRev.h"
29 #include "status.h"
30 #include "UnicodeUtils.h"
31 #include "../version.h"
33 // Define the help text as a multi-line macro
34 // Every line except the last must be terminated with a backslash
35 #define HelpText1 "\
36 Usage: GitWCRev Path [SrcVersionFile DstVersionFile] [-mMuUdqsFe]\n\
37 \n\
38 Params:\n\
39 Path : path to a Git working tree OR\n\
40 a directory/file inside a working tree.\n\
41 SrcVersionFile : path to a template file containing keywords.\n\
42 DstVersionFile : path to save the resulting parsed file.\n\
43 -m : if given, then GitWCRev will error if the passed\n\
44 path contains local modifications.\n\
45 -M : same as above, but recursively\n\
46 -u : if given, then GitWCRev will error if the passed\n\
47 path contains unversioned items.\n\
48 -U : same as above, but recursively\n\
49 -d : if given, then GitWCRev will only do its job if\n\
50 DstVersionFile does not exist.\n\
51 -q : if given, then GitWCRev will perform keyword\n\
52 substitution but will not show status on stdout.\n"
53 #define HelpText2 "\
54 -s : if given, submodules are not checked. This increases\n\
55 the checking speed.\n"
56 #define HelpText3 "\
57 -e : changes the console output encoding to Unicode\n"
59 #define HelpText4 "\
60 Switches must be given in a single argument, e.g. '-nm' not '-n -m'.\n\
61 \n\
62 GitWCRev reads the Git status of all files in the passed path\n\
63 including submodules. If SrcVersionFile is specified, it is scanned\n\
64 for special placeholders of the form \"$WCxxx$\".\n\
65 SrcVersionFile is then copied to DstVersionFile but the placeholders\n\
66 are replaced with information about the working tree as follows:\n\
67 \n\
68 $WCREV$ HEAD commit revision\n\
69 $WCREV=$ HEAD commit revision, truncated after the numner of chars\n\
70 provided after the =\n\
71 $WCDATE$ Date of the HEAD revision\n\
72 $WCDATE=$ Like $WCDATE$ with an added strftime format after the =\n\
73 $WCNOW$ Current system date & time\n\
74 $WCNOW=$ Like $WCNOW$ with an added strftime format after the =\n\
75 \n"
77 #define HelpText5 "\
78 The strftime format strings for $WCxxx=$ must not be longer than 1024\n\
79 characters, and must not produce output greater than 1024 characters.\n\
80 \n\
81 Placeholders of the form \"$WCxxx?TrueText:FalseText$\" are replaced with\n\
82 TrueText if the tested condition is true, and FalseText if false.\n\
83 \n\
84 $WCMODS$ True if uncommitted modifications were found\n\
85 $WCUNVER True if unversioned and unignored files were found\n\
86 $WCISTAGGED$ True if the HEAD commit is tagged\n\
87 $WCINGIT$ True if the item is versioned\n\
88 $WCLOGCOUNT$ Number of first-parent commits for the current branch\n\
89 $WCLOGCOUNT&$ Number of commits ANDed with the number after the &\n\
90 $WCLOGCOUNT+$ Number of commits added with the number after the &\n\
91 $WCLOGCOUNT-$ Number of commits subtracted with the number after the &\n\
92 $WCBRANCH$ Current branch name, SHA - 1 if head is detached\n"
94 // End of multi-line help text.
96 #define VERDEF "$WCREV$"
97 #define VERDEFSHORT "$WCREV="
98 #define DATEDEF "$WCDATE$"
99 #define DATEDEFUTC "$WCDATEUTC$"
100 #define DATEWFMTDEF "$WCDATE="
101 #define DATEWFMTDEFUTC "$WCDATEUTC="
102 #define MODDEF "$WCMODS?"
103 #define ISTAGGED "$WCISTAGGED?"
104 #define NOWDEF "$WCNOW$"
105 #define NOWDEFUTC "$WCNOWUTC$"
106 #define NOWWFMTDEF "$WCNOW="
107 #define NOWWFMTDEFUTC "$WCNOWUTC="
108 #define ISINGIT "$WCINGIT?"
109 #define UNVERDEF "$WCUNVER?"
110 #define MODSFILEDEF "$WCFILEMODS?"
111 #define SUBDEF "$WCSUBMODULE?"
112 #define SUBUP2DATEDEF "$WCSUBMODULEUP2DATE?"
113 #define MODINSUBDEF "$WCMODSINSUBMODULE?"
114 #define UNVERINSUBDEF "$WCUNVERINSUBMODULE?"
115 #define UNVERFULLDEF "$WCUNVERFULL?"
116 #define MODFULLDEF "$WCMODSFULL?"
117 #define VALDEF "$WCLOGCOUNT$"
118 #define VALDEFAND "$WCLOGCOUNT&"
119 #define VALDEFOFFSET1 "$WCLOGCOUNT-"
120 #define VALDEFOFFSET2 "$WCLOGCOUNT+"
121 #define BRANCHDEF "$WCBRANCH$"
123 // Value for apr_time_t to signify "now"
124 #define USE_TIME_NOW -2 // 0 and -1 might already be significant.
126 bool FindPlaceholder(char* def, char* pBuf, size_t& index, size_t filelength)
128 size_t deflen = strlen(def);
129 while (index + deflen <= filelength)
131 if (memcmp(pBuf + index, def, deflen) == 0)
132 return true;
133 index++;
135 return false;
137 bool FindPlaceholderW(wchar_t *def, wchar_t *pBuf, size_t & index, size_t filelength)
139 size_t deflen = wcslen(def);
140 while ((index + deflen) * sizeof(wchar_t) <= filelength)
142 if (memcmp(pBuf + index, def, deflen * sizeof(wchar_t)) == 0)
143 return true;
144 index++;
147 return false;
150 bool InsertRevision(char* def, char* pBuf, size_t& index, size_t& filelength, size_t maxlength, GitWCRev_t* GitStat)
152 // Search for first occurrence of def in the buffer, starting at index.
153 if (!FindPlaceholder(def, pBuf, index, filelength))
155 // No more matches found.
156 return false;
158 size_t hashlen = strlen(GitStat->HeadHashReadable);
159 ptrdiff_t exp = 0;
160 if ((strcmp(def, VERDEFSHORT) == 0))
162 char format[1024] = { 0 };
163 char* pStart = pBuf + index + strlen(def);
164 char* pEnd = pStart;
166 while (*pEnd != '$')
168 ++pEnd;
169 if (pEnd - pBuf >= (__int64)filelength)
170 return false; // No terminator - malformed so give up.
172 if ((pEnd - pStart) > 1024)
173 return false; // value specifier too big
174 exp = pEnd - pStart + 1;
175 memcpy(format, pStart, pEnd - pStart);
176 unsigned long number = strtoul(format, nullptr, 0);
177 if (strcmp(def, VERDEFSHORT) == 0 && number < hashlen)
178 hashlen = number;
180 // Replace the $WCxxx$ string with the actual revision number
181 char* pBuild = pBuf + index;
182 ptrdiff_t Expansion = hashlen - exp - strlen(def);
183 if (Expansion < 0)
184 memmove(pBuild, pBuild - Expansion, filelength - ((pBuild - Expansion) - pBuf));
185 else if (Expansion > 0)
187 // Check for buffer overflow
188 if (maxlength < Expansion + filelength)
189 return false;
190 memmove(pBuild + Expansion, pBuild, filelength - (pBuild - pBuf));
192 memmove(pBuild, GitStat->HeadHashReadable, hashlen);
193 filelength += Expansion;
194 return true;
196 bool InsertRevisionW(wchar_t* def, wchar_t* pBuf, size_t& index, size_t& filelength, size_t maxlength, GitWCRev_t* GitStat)
198 // Search for first occurrence of def in the buffer, starting at index.
199 if (!FindPlaceholderW(def, pBuf, index, filelength))
201 // No more matches found.
202 return false;
204 size_t hashlen = strlen(GitStat->HeadHashReadable);
205 ptrdiff_t exp = 0;
206 if ((wcscmp(def, TEXT(VERDEFSHORT)) == 0))
208 wchar_t format[1024] = { 0 };
209 wchar_t* pStart = pBuf + index + wcslen(def);
210 wchar_t* pEnd = pStart;
212 while (*pEnd != '$')
214 ++pEnd;
215 if (((__int64)(pEnd - pBuf)) * ((__int64)sizeof(wchar_t)) >= (__int64)filelength)
216 return false; // No terminator - malformed so give up.
218 if ((pEnd - pStart) > 1024)
219 return false; // Format specifier too big
220 exp = pEnd - pStart + 1;
221 memcpy(format, pStart, (pEnd - pStart) * sizeof(wchar_t));
222 unsigned long number = wcstoul(format, nullptr, 0);
223 if (wcscmp(def, TEXT(VERDEFSHORT)) == 0 && number < hashlen)
224 hashlen = number;
226 // Replace the $WCxxx$ string with the actual revision number
227 wchar_t* pBuild = pBuf + index;
228 ptrdiff_t Expansion = hashlen - exp - wcslen(def);
229 if (Expansion < 0)
230 memmove(pBuild, pBuild - Expansion, (filelength - ((pBuild - Expansion) - pBuf) * sizeof(wchar_t)));
231 else if (Expansion > 0)
233 // Check for buffer overflow
234 if (maxlength < Expansion * sizeof(wchar_t) + filelength)
235 return false;
236 memmove(pBuild + Expansion, pBuild, (filelength - (pBuild - pBuf) * sizeof(wchar_t)));
238 std::wstring hash = CUnicodeUtils::StdGetUnicode(GitStat->HeadHashReadable);
239 memmove(pBuild, hash.c_str(), hashlen * sizeof(wchar_t));
240 filelength += (Expansion * sizeof(wchar_t));
241 return true;
244 bool InsertNumber(char* def, char* pBuf, size_t& index, size_t& filelength, size_t maxlength, size_t Value)
246 // Search for first occurrence of def in the buffer, starting at index.
247 if (!FindPlaceholder(def, pBuf, index, filelength))
249 // No more matches found.
250 return false;
252 ptrdiff_t exp = 0;
253 if ((strcmp(def, VALDEFAND) == 0) || (strcmp(def, VALDEFOFFSET1) == 0) || (strcmp(def, VALDEFOFFSET2) == 0))
255 char format[1024] = { 0 };
256 char* pStart = pBuf + index + strlen(def);
257 char* pEnd = pStart;
259 while (*pEnd != '$')
261 ++pEnd;
262 if (pEnd - pBuf >= (__int64)filelength)
263 return false; // No terminator - malformed so give up.
265 if ((pEnd - pStart) > 1024)
266 return false; // value specifier too big
268 exp = pEnd - pStart + 1;
269 memcpy(format, pStart, pEnd - pStart);
270 unsigned long number = strtoul(format, NULL, 0);
271 if (strcmp(def, VALDEFAND) == 0)
273 if (Value != -1)
274 Value &= number;
276 if (strcmp(def, VALDEFOFFSET1) == 0)
278 if (Value != -1)
279 Value -= number;
281 if (strcmp(def, VALDEFOFFSET2) == 0)
283 if (Value != -1)
284 Value += number;
287 // Format the text to insert at the placeholder
288 char destbuf[1024] = { 0 };
289 sprintf_s(destbuf, "%zd", Value);
291 // Replace the $WCxxx$ string with the actual revision number
292 char* pBuild = pBuf + index;
293 ptrdiff_t Expansion = strlen(destbuf) - exp - strlen(def);
294 if (Expansion < 0)
295 memmove(pBuild, pBuild - Expansion, filelength - ((pBuild - Expansion) - pBuf));
296 else if (Expansion > 0)
298 // Check for buffer overflow
299 if (maxlength < Expansion + filelength)
300 return false;
301 memmove(pBuild + Expansion, pBuild, filelength - (pBuild - pBuf));
303 memmove(pBuild, destbuf, strlen(destbuf));
304 filelength += Expansion;
305 return true;
307 bool InsertNumberW(wchar_t* def, wchar_t* pBuf, size_t& index, size_t& filelength, size_t maxlength, size_t Value)
309 // Search for first occurrence of def in the buffer, starting at index.
310 if (!FindPlaceholderW(def, pBuf, index, filelength))
312 // No more matches found.
313 return false;
316 ptrdiff_t exp = 0;
317 if ((wcscmp(def, TEXT(VALDEFAND)) == 0) || (wcscmp(def, TEXT(VALDEFOFFSET1)) == 0) || (wcscmp(def, TEXT(VALDEFOFFSET2)) == 0))
319 wchar_t format[1024] = { 0 };
320 wchar_t* pStart = pBuf + index + wcslen(def);
321 wchar_t* pEnd = pStart;
323 while (*pEnd != '$')
325 ++pEnd;
326 if (((__int64)(pEnd - pBuf)) * ((__int64)sizeof(wchar_t)) >= (__int64)filelength)
327 return false; // No terminator - malformed so give up.
329 if ((pEnd - pStart) > 1024)
330 return false; // Format specifier too big
332 exp = pEnd - pStart + 1;
333 memcpy(format, pStart, (pEnd - pStart) * sizeof(wchar_t));
334 unsigned long number = wcstoul(format, NULL, 0);
335 if (wcscmp(def, TEXT(VALDEFAND)) == 0)
337 if (Value != -1)
338 Value &= number;
340 if (wcscmp(def, TEXT(VALDEFOFFSET1)) == 0)
342 if (Value != -1)
343 Value -= number;
345 if (wcscmp(def, TEXT(VALDEFOFFSET2)) == 0)
347 if (Value != -1)
348 Value += number;
352 // Format the text to insert at the placeholder
353 wchar_t destbuf[40];
354 swprintf_s(destbuf, L"%zd", Value);
355 // Replace the $WCxxx$ string with the actual revision number
356 wchar_t* pBuild = pBuf + index;
357 ptrdiff_t Expansion = wcslen(destbuf) - exp - wcslen(def);
358 if (Expansion < 0)
359 memmove(pBuild, pBuild - Expansion, (filelength - ((pBuild - Expansion) - pBuf) * sizeof(wchar_t)));
360 else if (Expansion > 0)
362 // Check for buffer overflow
363 if (maxlength < Expansion * sizeof(wchar_t) + filelength)
364 return false;
365 memmove(pBuild + Expansion, pBuild, (filelength - (pBuild - pBuf) * sizeof(wchar_t)));
367 memmove(pBuild, destbuf, wcslen(destbuf) * sizeof(wchar_t));
368 filelength += (Expansion * sizeof(wchar_t));
369 return true;
372 void _invalid_parameter_donothing(
373 const wchar_t* /*expression*/,
374 const wchar_t* /*function*/,
375 const wchar_t* /*file*/,
376 unsigned int /*line*/,
377 uintptr_t /*pReserved*/
380 // do nothing
383 bool InsertDate(char* def, char* pBuf, size_t& index, size_t& filelength, size_t maxlength, __time64_t ttime)
385 // Search for first occurrence of def in the buffer, starting at index.
386 if (!FindPlaceholder(def, pBuf, index, filelength))
388 // No more matches found.
389 return false;
391 // Format the text to insert at the placeholder
392 if (ttime == USE_TIME_NOW)
393 _time64(&ttime);
395 struct tm newtime;
396 if (strstr(def, "UTC"))
398 if (_gmtime64_s(&newtime, &ttime))
399 return false;
401 else
403 if (_localtime64_s(&newtime, &ttime))
404 return false;
406 char destbuf[1024] = { 0 };
407 char* pBuild = pBuf + index;
408 ptrdiff_t Expansion;
409 if ((strcmp(def,DATEWFMTDEF) == 0) || (strcmp(def,NOWWFMTDEF) == 0) || (strcmp(def,DATEWFMTDEFUTC) == 0) || (strcmp(def,NOWWFMTDEFUTC) == 0))
411 // Format the date/time according to the supplied strftime format string
412 char format[1024] = { 0 };
413 char* pStart = pBuf + index + strlen(def);
414 char* pEnd = pStart;
416 while (*pEnd != '$')
418 ++pEnd;
419 if (pEnd - pBuf >= (__int64)filelength)
420 return false; // No terminator - malformed so give up.
422 if ((pEnd - pStart) > 1024)
423 return false; // Format specifier too big
424 memcpy(format,pStart,pEnd - pStart);
426 // to avoid wcsftime aborting if the user specified an invalid time format,
427 // we set a custom invalid parameter handler that does nothing at all:
428 // that makes wcsftime do nothing and set errno to EINVAL.
429 // we restore the invalid parameter handler right after
430 _invalid_parameter_handler oldHandler = _set_invalid_parameter_handler(_invalid_parameter_donothing);
432 if (strftime(destbuf,1024,format,&newtime) == 0)
434 if (errno == EINVAL)
435 strcpy_s(destbuf, "Invalid Time Format Specified");
437 _set_invalid_parameter_handler(oldHandler);
438 Expansion = strlen(destbuf) - (strlen(def) + pEnd - pStart + 1);
440 else
442 // Format the date/time in international format as yyyy/mm/dd hh:mm:ss
443 sprintf_s(destbuf, "%04d/%02d/%02d %02d:%02d:%02d", newtime.tm_year + 1900, newtime.tm_mon + 1, newtime.tm_mday, newtime.tm_hour, newtime.tm_min, newtime.tm_sec);
444 Expansion = strlen(destbuf) - strlen(def);
446 // Replace the def string with the actual commit date
447 if (Expansion < 0)
448 memmove(pBuild, pBuild - Expansion, filelength - ((pBuild - Expansion) - pBuf));
449 else if (Expansion > 0)
451 // Check for buffer overflow
452 if (maxlength < Expansion + filelength)
453 return false;
454 memmove(pBuild + Expansion, pBuild, filelength - (pBuild - pBuf));
456 memmove(pBuild, destbuf, strlen(destbuf));
457 filelength += Expansion;
458 return true;
460 bool InsertDateW(wchar_t* def, wchar_t* pBuf, size_t& index, size_t& filelength, size_t maxlength, __time64_t ttime)
462 // Search for first occurrence of def in the buffer, starting at index.
463 if (!FindPlaceholderW(def, pBuf, index, filelength))
465 // No more matches found.
466 return false;
468 // Format the text to insert at the placeholder
469 if (ttime == USE_TIME_NOW)
470 _time64(&ttime);
472 struct tm newtime;
473 if (wcsstr(def, L"UTC"))
475 if (_gmtime64_s(&newtime, &ttime))
476 return false;
478 else
480 if (_localtime64_s(&newtime, &ttime))
481 return false;
483 wchar_t destbuf[1024];
484 wchar_t* pBuild = pBuf + index;
485 ptrdiff_t Expansion;
486 if ((wcscmp(def,TEXT(DATEWFMTDEF)) == 0) || (wcscmp(def,TEXT(NOWWFMTDEF)) == 0) ||
487 (wcscmp(def,TEXT(DATEWFMTDEFUTC)) == 0) || (wcscmp(def,TEXT(NOWWFMTDEFUTC)) == 0))
489 // Format the date/time according to the supplied strftime format string
490 wchar_t format[1024] = { 0 };
491 wchar_t* pStart = pBuf + index + wcslen(def);
492 wchar_t* pEnd = pStart;
494 while (*pEnd != '$')
496 ++pEnd;
497 if (((__int64)(pEnd - pBuf))*((__int64)sizeof(wchar_t)) >= (__int64)filelength)
498 return false; // No terminator - malformed so give up.
500 if ((pEnd - pStart) > 1024)
501 return false; // Format specifier too big
502 memcpy(format, pStart, (pEnd - pStart) * sizeof(wchar_t));
504 // to avoid wcsftime aborting if the user specified an invalid time format,
505 // we set a custom invalid parameter handler that does nothing at all:
506 // that makes wcsftime do nothing and set errno to EINVAL.
507 // we restore the invalid parameter handler right after
508 _invalid_parameter_handler oldHandler = _set_invalid_parameter_handler(_invalid_parameter_donothing);
510 if (wcsftime(destbuf,1024,format,&newtime) == 0)
512 if (errno == EINVAL)
513 wcscpy_s(destbuf, L"Invalid Time Format Specified");
515 _set_invalid_parameter_handler(oldHandler);
517 Expansion = wcslen(destbuf) - (wcslen(def) + pEnd - pStart + 1);
519 else
521 // Format the date/time in international format as yyyy/mm/dd hh:mm:ss
522 swprintf_s(destbuf, L"%04d/%02d/%02d %02d:%02d:%02d", newtime.tm_year + 1900, newtime.tm_mon + 1, newtime.tm_mday, newtime.tm_hour, newtime.tm_min, newtime.tm_sec);
523 Expansion = wcslen(destbuf) - wcslen(def);
525 // Replace the def string with the actual commit date
526 if (Expansion < 0)
527 memmove(pBuild, pBuild - Expansion, (filelength - ((pBuild - Expansion) - pBuf) * sizeof(wchar_t)));
528 else if (Expansion > 0)
530 // Check for buffer overflow
531 if (maxlength < Expansion * sizeof(wchar_t) + filelength)
532 return false;
533 memmove(pBuild + Expansion, pBuild, (filelength - (pBuild - pBuf) * sizeof(wchar_t)));
535 memmove(pBuild, destbuf, wcslen(destbuf) * sizeof(wchar_t));
536 filelength += Expansion * sizeof(wchar_t);
537 return true;
540 int InsertBoolean(char* def, char* pBuf, size_t& index, size_t& filelength, BOOL isTrue)
542 // Search for first occurrence of def in the buffer, starting at index.
543 if (!FindPlaceholder(def, pBuf, index, filelength))
545 // No more matches found.
546 return false;
548 // Look for the terminating '$' character
549 char* pBuild = pBuf + index;
550 char* pEnd = pBuild + 1;
551 while (*pEnd != '$')
553 ++pEnd;
554 if (pEnd - pBuf >= (__int64)filelength)
555 return false; // No terminator - malformed so give up.
558 // Look for the ':' dividing TrueText from FalseText
559 char *pSplit = pBuild + 1;
560 // this loop is guaranteed to terminate due to test above.
561 while (*pSplit != ':' && *pSplit != '$')
562 pSplit++;
564 if (*pSplit == '$')
565 return false; // No split - malformed so give up.
567 if (isTrue)
569 // Replace $WCxxx?TrueText:FalseText$ with TrueText
570 // Remove :FalseText$
571 memmove(pSplit, pEnd + 1, filelength - (pEnd + 1 - pBuf));
572 filelength -= (pEnd + 1 - pSplit);
573 // Remove $WCxxx?
574 size_t deflen = strlen(def);
575 memmove(pBuild, pBuild + deflen, filelength - (pBuild + deflen - pBuf));
576 filelength -= deflen;
578 else
580 // Replace $WCxxx?TrueText:FalseText$ with FalseText
581 // Remove terminating $
582 memmove(pEnd, pEnd + 1, filelength - (pEnd + 1 - pBuf));
583 filelength--;
584 // Remove $WCxxx?TrueText:
585 memmove(pBuild, pSplit + 1, filelength - (pSplit + 1 - pBuf));
586 filelength -= (pSplit + 1 - pBuild);
588 return true;
590 bool InsertBooleanW(wchar_t* def, wchar_t* pBuf, size_t& index, size_t& filelength, BOOL isTrue)
592 // Search for first occurrence of def in the buffer, starting at index.
593 if (!FindPlaceholderW(def, pBuf, index, filelength))
595 // No more matches found.
596 return false;
598 // Look for the terminating '$' character
599 wchar_t* pBuild = pBuf + index;
600 wchar_t* pEnd = pBuild + 1;
601 while (*pEnd != L'$')
603 ++pEnd;
604 if (pEnd - pBuf >= (__int64)filelength)
605 return false; // No terminator - malformed so give up.
608 // Look for the ':' dividing TrueText from FalseText
609 wchar_t *pSplit = pBuild + 1;
610 // this loop is guaranteed to terminate due to test above.
611 while (*pSplit != L':' && *pSplit != L'$')
612 pSplit++;
614 if (*pSplit == L'$')
615 return false; // No split - malformed so give up.
617 if (isTrue)
619 // Replace $WCxxx?TrueText:FalseText$ with TrueText
620 // Remove :FalseText$
621 memmove(pSplit, pEnd + 1, (filelength - (pEnd + 1 - pBuf) * sizeof(wchar_t)));
622 filelength -= ((pEnd + 1 - pSplit) * sizeof(wchar_t));
623 // Remove $WCxxx?
624 size_t deflen = wcslen(def);
625 memmove(pBuild, pBuild + deflen, (filelength - (pBuild + deflen - pBuf) * sizeof(wchar_t)));
626 filelength -= (deflen * sizeof(wchar_t));
628 else
630 // Replace $WCxxx?TrueText:FalseText$ with FalseText
631 // Remove terminating $
632 memmove(pEnd, pEnd + 1, (filelength - (pEnd + 1 - pBuf) * sizeof(wchar_t)));
633 filelength -= sizeof(wchar_t);
634 // Remove $WCxxx?TrueText:
635 memmove(pBuild, pSplit + 1, (filelength - (pSplit + 1 - pBuf) * sizeof(wchar_t)));
636 filelength -= ((pSplit + 1 - pBuild) * sizeof(wchar_t));
638 return true;
641 bool InsertText(char* def, char* pBuf, size_t& index, size_t& filelength, size_t maxlength, const std::string& Value)
643 // Search for first occurrence of def in the buffer, starting at index.
644 if (!FindPlaceholder(def, pBuf, index, filelength))
646 // No more matches found.
647 return false;
649 // Replace the $WCxxx$ string with the actual value
650 char* pBuild = pBuf + index;
651 ptrdiff_t Expansion = Value.length() - strlen(def);
652 if (Expansion < 0)
653 memmove(pBuild, pBuild - Expansion, filelength - ((pBuild - Expansion) - pBuf));
654 else if (Expansion > 0)
656 // Check for buffer overflow
657 if (maxlength < Expansion + filelength)
658 return false;
659 memmove(pBuild + Expansion, pBuild, filelength - (pBuild - pBuf));
661 memmove(pBuild, Value.c_str(), Value.length());
662 filelength += Expansion;
663 return true;
665 bool InsertTextW(wchar_t* def, wchar_t* pBuf, size_t& index, size_t& filelength, size_t maxlength, const std::string& Value)
667 // Search for first occurrence of def in the buffer, starting at index.
668 if (!FindPlaceholderW(def, pBuf, index, filelength))
670 // No more matches found.
671 return false;
673 // Replace the $WCxxx$ string with the actual revision number
674 wchar_t* pBuild = pBuf + index;
675 ptrdiff_t Expansion = Value.length() - wcslen(def);
676 if (Expansion < 0)
677 memmove(pBuild, pBuild - Expansion, (filelength - ((pBuild - Expansion) - pBuf) * sizeof(wchar_t)));
678 else if (Expansion > 0)
680 // Check for buffer overflow
681 if (maxlength < Expansion * sizeof(wchar_t) + filelength)
682 return false;
683 memmove(pBuild + Expansion, pBuild, (filelength - (pBuild - pBuf) * sizeof(wchar_t)));
685 std::wstring wValue = CUnicodeUtils::StdGetUnicode(Value);
686 memmove(pBuild, wValue.c_str(), wValue.length() * sizeof(wchar_t));
687 filelength += (Expansion * sizeof(wchar_t));
688 return true;
691 int _tmain(int argc, _TCHAR* argv[])
693 // we have three parameters
694 const TCHAR* src = nullptr;
695 const TCHAR* dst = nullptr;
696 const TCHAR* wc = nullptr;
697 BOOL bErrResursively = FALSE;
698 BOOL bErrOnMods = FALSE;
699 BOOL bErrOnUnversioned = FALSE;
700 BOOL bQuiet = FALSE;
701 GitWCRev_t GitStat;
703 SetDllDirectory(L"");
704 CCrashReportTGit crasher(L"GitWCRev " _T(APP_X64_STRING), TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, TGIT_VERBUILD, TGIT_VERDATE);
706 if (argc >= 2 && argc <= 5)
708 // WC path is always first argument.
709 wc = argv[1];
711 if (argc == 4 || argc == 5)
713 // GitWCRev Path Tmpl.in Tmpl.out [-params]
714 src = argv[2];
715 dst = argv[3];
716 if (!PathFileExists(src))
718 _tprintf(L"File '%s' does not exist\n", src);
719 return ERR_FNF; // file does not exist
722 if (argc == 3 || argc == 5)
724 // GitWCRev Path -params
725 // GitWCRev Path Tmpl.in Tmpl.out -params
726 const TCHAR* Params = argv[argc - 1];
727 if (Params[0] == L'-')
729 if (wcschr(Params, L'e') != 0)
730 _setmode(_fileno(stdout), _O_U16TEXT);
731 if (wcschr(Params, L'q') != 0)
732 bQuiet = TRUE;
733 if (wcschr(Params, L'm') != 0)
734 bErrOnMods = TRUE;
735 if (wcschr(Params, L'u') != 0)
736 bErrOnUnversioned = TRUE;
737 if (wcschr(Params, L'M') != 0)
739 bErrOnMods = TRUE;
740 bErrResursively = TRUE;
742 if (wcschr(Params, L'U') != 0)
744 bErrOnUnversioned = TRUE;
745 bErrResursively = TRUE;
747 if (wcschr(Params, L'd') != 0)
749 if (dst && PathFileExists(dst))
751 _tprintf(L"File '%s' already exists\n", dst);
752 return ERR_OUT_EXISTS;
755 if (wcschr(Params, L's') != 0)
756 GitStat.bNoSubmodules = TRUE;
758 else
760 // Bad params - abort and display help.
761 wc = nullptr;
765 if (!wc)
767 _tprintf(L"GitWCRev %d.%d.%d, Build %d - %s\n\n", TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, TGIT_VERBUILD, _T(TGIT_PLATFORM));
768 _putts(_T(HelpText1));
769 _putts(_T(HelpText2));
770 _putts(_T(HelpText3));
771 _putts(_T(HelpText4));
772 _putts(_T(HelpText5));
773 return ERR_SYNTAX;
776 DWORD reqLen = GetFullPathName(wc, 0, nullptr, nullptr);
777 auto wcfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
778 GetFullPathName(wc, reqLen, wcfullPath.get(), nullptr);
779 // GetFullPathName() sometimes returns the full path with the wrong
780 // case. This is not a problem on Windows since its filesystem is
781 // case-insensitive. But for Git that's a problem if the wrong case
782 // is inside a working copy: the git index is case sensitive.
783 // To fix the casing of the path, we use a trick:
784 // convert the path to its short form, then back to its long form.
785 // That will fix the wrong casing of the path.
786 int shortlen = GetShortPathName(wcfullPath.get(), nullptr, 0);
787 if (shortlen)
789 auto shortPath = std::make_unique<TCHAR[]>(shortlen + 1);
790 if (GetShortPathName(wcfullPath.get(), shortPath.get(), shortlen + 1))
792 reqLen = GetLongPathName(shortPath.get(), nullptr, 0);
793 wcfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
794 GetLongPathName(shortPath.get(), wcfullPath.get(), reqLen);
797 wc = wcfullPath.get();
798 std::unique_ptr<TCHAR[]> dstfullPath;
799 if (dst)
801 reqLen = GetFullPathName(dst, 0, nullptr, nullptr);
802 dstfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
803 GetFullPathName(dst, reqLen, dstfullPath.get(), nullptr);
804 shortlen = GetShortPathName(dstfullPath.get(), nullptr, 0);
805 if (shortlen)
807 auto shortPath = std::make_unique<TCHAR[]>(shortlen + 1);
808 if (GetShortPathName(dstfullPath.get(), shortPath.get(), shortlen+1))
810 reqLen = GetLongPathName(shortPath.get(), nullptr, 0);
811 dstfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
812 GetLongPathName(shortPath.get(), dstfullPath.get(), reqLen);
815 dst = dstfullPath.get();
817 std::unique_ptr<TCHAR[]> srcfullPath;
818 if (src)
820 reqLen = GetFullPathName(src, 0, nullptr, nullptr);
821 srcfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
822 GetFullPathName(src, reqLen, srcfullPath.get(), nullptr);
823 shortlen = GetShortPathName(srcfullPath.get(), nullptr, 0);
824 if (shortlen)
826 auto shortPath = std::make_unique<TCHAR[]>(shortlen + 1);
827 if (GetShortPathName(srcfullPath.get(), shortPath.get(), shortlen+1))
829 reqLen = GetLongPathName(shortPath.get(), nullptr, 0);
830 srcfullPath = std::make_unique<TCHAR[]>(reqLen + 1);
831 GetLongPathName(shortPath.get(), srcfullPath.get(), reqLen);
834 src = srcfullPath.get();
837 if (!PathFileExists(wc))
839 _tprintf(L"Directory or file '%s' does not exist\n", wc);
840 if (wcschr(wc, '\"')) // dir contains a quotation mark
842 _tprintf(L"The Path contains a quotation mark.\n");
843 _tprintf(L"this indicates a problem when calling GitWCRev from an interpreter which treats\n");
844 _tprintf(L"a backslash char specially.\n");
845 _tprintf(L"Try using double backslashes or insert a dot after the last backslash when\n");
846 _tprintf(L"calling GitWCRev\n");
847 _tprintf(L"Examples:\n");
848 _tprintf(L"GitWCRev \"path to wc\\\\\"\n");
849 _tprintf(L"GitWCRev \"path to wc\\.\"\n");
851 return ERR_FNF; // dir does not exist
853 std::unique_ptr<char[]> pBuf;
854 DWORD readlength = 0;
855 size_t filelength = 0;
856 size_t maxlength = 0;
857 if (dst)
859 // open the file and read the contents
860 CAutoFile hFile = CreateFile(src, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, 0);
861 if (!hFile)
863 _tprintf(L"Unable to open input file '%s'\n", src);
864 return ERR_OPEN; // error opening file
866 filelength = GetFileSize(hFile, nullptr);
867 if (filelength == INVALID_FILE_SIZE)
869 _tprintf(L"Could not determine file size of '%s'\n", src);
870 return ERR_READ;
872 maxlength = filelength + 8192; // We might be increasing file size.
873 pBuf = std::make_unique<char[]>(maxlength);
874 if (!pBuf)
876 _tprintf(L"Could not allocate enough memory!\n");
877 return ERR_ALLOC;
879 if (!ReadFile(hFile, pBuf.get(), (DWORD)filelength, &readlength, nullptr))
881 _tprintf(L"Could not read the file '%s'\n", src);
882 return ERR_READ;
884 if (readlength != filelength)
886 _tprintf(L"Could not read the file '%s' to the end!\n", src);
887 return ERR_READ;
891 git_libgit2_init();
893 // Now check the status of every file in the working copy
894 // and gather revision status information in GitStat.
895 int err = GetStatus(wc, GitStat);
897 git_libgit2_shutdown();
898 if (err)
899 return err;
901 if (!bQuiet)
903 char wcfull_oem[MAX_PATH] = { 0 };
904 CharToOem(wc, wcfull_oem);
905 _tprintf(L"GitWCRev: '%hs'\n", wcfull_oem);
908 if (bErrOnMods && (GitStat.HasMods || GitStat.bHasSubmoduleNewCommits || (bErrResursively && GitStat.bHasSubmoduleMods)))
910 if (!bQuiet)
911 _tprintf(L"Working tree has uncomitted modifications!\n");
912 return ERR_GIT_MODS;
914 if (bErrOnUnversioned && (GitStat.HasUnversioned || (bErrResursively && GitStat.bHasSubmoduleUnversioned)))
916 if (!bQuiet)
917 _tprintf(L"Working tree has unversioned items!\n");
918 return ERR_GIT_UNVER;
921 if (!bQuiet)
923 _tprintf(L"HEAD is %s\n", CUnicodeUtils::StdGetUnicode(GitStat.HeadHashReadable).c_str());
925 if (GitStat.HasMods)
926 _tprintf(L"Uncommitted modifications found\n");
928 if (GitStat.HasUnversioned)
929 _tprintf(L"Unversioned items found\n");
932 if (!dst)
933 return 0;
935 // now parse the file contents for version defines.
936 size_t index = 0;
937 while (InsertRevision(VERDEF, pBuf.get(), index, filelength, maxlength, &GitStat));
938 index = 0;
939 while (InsertRevisionW(TEXT(VERDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, &GitStat));
941 index = 0;
942 while (InsertRevision(VERDEFSHORT, pBuf.get(), index, filelength, maxlength, &GitStat));
943 index = 0;
944 while (InsertRevisionW(TEXT(VERDEFSHORT), (wchar_t*)pBuf.get(), index, filelength, maxlength, &GitStat));
946 index = 0;
947 while (InsertDate(DATEDEF, pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));
948 index = 0;
949 while (InsertDateW(TEXT(DATEDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));
951 index = 0;
952 while (InsertDate(DATEDEFUTC, pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));
953 index = 0;
954 while (InsertDateW(TEXT(DATEDEFUTC), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));
956 index = 0;
957 while (InsertDate(DATEWFMTDEF, pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));
958 index = 0;
959 while (InsertDateW(TEXT(DATEWFMTDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));
960 index = 0;
961 while (InsertDate(DATEWFMTDEFUTC, pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));
962 index = 0;
963 while (InsertDateW(TEXT(DATEWFMTDEFUTC), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.HeadTime));
965 index = 0;
966 while (InsertDate(NOWDEF, pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
967 index = 0;
968 while (InsertDateW(TEXT(NOWDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
970 index = 0;
971 while (InsertDate(NOWDEFUTC, pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
972 index = 0;
973 while (InsertDateW(TEXT(NOWDEFUTC), (wchar_t*)pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
975 index = 0;
976 while (InsertDate(NOWWFMTDEF, pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
977 index = 0;
978 while (InsertDateW(TEXT(NOWWFMTDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
980 index = 0;
981 while (InsertDate(NOWWFMTDEFUTC, pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
982 index = 0;
983 while (InsertDateW(TEXT(NOWWFMTDEFUTC), (wchar_t*)pBuf.get(), index, filelength, maxlength, USE_TIME_NOW));
985 index = 0;
986 while (InsertBoolean(MODDEF, pBuf.get(), index, filelength, GitStat.HasMods || GitStat.bHasSubmoduleNewCommits));
987 index = 0;
988 while (InsertBooleanW(TEXT(MODDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.HasMods || GitStat.bHasSubmoduleNewCommits));
990 index = 0;
991 while (InsertBoolean(UNVERDEF, pBuf.get(), index, filelength, GitStat.HasUnversioned));
992 index = 0;
993 while (InsertBooleanW(TEXT(UNVERDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.HasUnversioned));
995 index = 0;
996 while (InsertBoolean(ISTAGGED, pBuf.get(), index, filelength, GitStat.bIsTagged));
997 index = 0;
998 while (InsertBooleanW(TEXT(ISTAGGED), (wchar_t*)pBuf.get(), index, filelength, GitStat.bIsTagged));
1000 index = 0;
1001 while (InsertBoolean(ISINGIT, pBuf.get(), index, filelength, GitStat.bIsGitItem));
1002 index = 0;
1003 while (InsertBooleanW(TEXT(ISINGIT), (wchar_t*)pBuf.get(), index, filelength, GitStat.bIsGitItem));
1005 index = 0;
1006 while (InsertBoolean(SUBDEF, pBuf.get(), index, filelength, GitStat.bHasSubmodule));
1007 index = 0;
1008 while (InsertBooleanW(TEXT(SUBDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.bHasSubmodule));
1010 index = 0;
1011 while (InsertBoolean(SUBUP2DATEDEF, pBuf.get(), index, filelength, !GitStat.bHasSubmoduleNewCommits));
1012 index = 0;
1013 while (InsertBooleanW(TEXT(SUBUP2DATEDEF), (wchar_t*)pBuf.get(), index, filelength, !GitStat.bHasSubmoduleNewCommits));
1015 index = 0;
1016 while (InsertBoolean(MODINSUBDEF, pBuf.get(), index, filelength, GitStat.bHasSubmoduleMods));
1017 index = 0;
1018 while (InsertBooleanW(TEXT(MODINSUBDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.bHasSubmoduleMods));
1020 index = 0;
1021 while (InsertBoolean(UNVERINSUBDEF, pBuf.get(), index, filelength, GitStat.bHasSubmoduleUnversioned));
1022 index = 0;
1023 while (InsertBooleanW(TEXT(UNVERINSUBDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.bHasSubmoduleUnversioned));
1025 index = 0;
1026 while (InsertBoolean(UNVERFULLDEF, pBuf.get(), index, filelength, GitStat.HasUnversioned || GitStat.bHasSubmoduleUnversioned));
1027 index = 0;
1028 while (InsertBooleanW(TEXT(UNVERFULLDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.HasUnversioned || GitStat.bHasSubmoduleUnversioned));
1030 index = 0;
1031 while (InsertBoolean(MODFULLDEF, pBuf.get(), index, filelength, GitStat.HasMods || GitStat.bHasSubmoduleMods || GitStat.bHasSubmoduleUnversioned));
1032 index = 0;
1033 while (InsertBooleanW(TEXT(MODFULLDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.HasMods || GitStat.bHasSubmoduleMods || GitStat.bHasSubmoduleUnversioned));
1035 index = 0;
1036 while (InsertBoolean(MODSFILEDEF, pBuf.get(), index, filelength, GitStat.HasMods));
1037 index = 0;
1038 while (InsertBooleanW(TEXT(MODSFILEDEF), (wchar_t*)pBuf.get(), index, filelength, GitStat.HasMods));
1040 index = 0;
1041 while (InsertNumber(VALDEF, pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));
1042 index = 0;
1043 while (InsertNumberW(TEXT(VALDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));
1045 index = 0;
1046 while (InsertNumber(VALDEFAND, pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));
1047 index = 0;
1048 while (InsertNumberW(TEXT(VALDEFAND), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));
1050 index = 0;
1051 while (InsertNumber(VALDEFOFFSET1, pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));
1052 index = 0;
1053 while (InsertNumberW(TEXT(VALDEFOFFSET1), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));
1055 index = 0;
1056 while (InsertNumber(VALDEFOFFSET2, pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));
1057 index = 0;
1058 while (InsertNumberW(TEXT(VALDEFOFFSET2), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.NumCommits));
1060 index = 0;
1061 while (InsertText(BRANCHDEF, pBuf.get(), index, filelength, maxlength, GitStat.CurrentBranch));
1062 index = 0;
1063 while (InsertTextW(TEXT(BRANCHDEF), (wchar_t*)pBuf.get(), index, filelength, maxlength, GitStat.CurrentBranch));
1065 CAutoFile hFile = CreateFile(dst, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, 0, 0);
1066 if (!hFile)
1068 _tprintf(L"Unable to open output file '%s' for writing\n", dst);
1069 return ERR_OPEN;
1072 size_t filelengthExisting = GetFileSize(hFile, nullptr);
1073 BOOL sameFileContent = FALSE;
1074 if (filelength == filelengthExisting)
1076 DWORD readlengthExisting = 0;
1077 auto pBufExisting = std::make_unique<char[]>(filelength);
1078 if (!ReadFile(hFile, pBufExisting.get(), (DWORD)filelengthExisting, &readlengthExisting, nullptr))
1080 _tprintf(L"Could not read the file '%s'\n", dst);
1081 return ERR_READ;
1083 if (readlengthExisting != filelengthExisting)
1085 _tprintf(L"Could not read the file '%s' to the end!\n", dst);
1086 return ERR_READ;
1088 sameFileContent = (memcmp(pBuf.get(), pBufExisting.get(), filelength) == 0);
1091 // The file is only written if its contents would change.
1092 // this object prevents the timestamp from changing.
1093 if (!sameFileContent)
1095 SetFilePointer(hFile, 0, nullptr, FILE_BEGIN);
1097 WriteFile(hFile, pBuf.get(), (DWORD)filelength, &readlength, nullptr);
1098 if (readlength != filelength)
1100 _tprintf(L"Could not write the file '%s' to the end!\n", dst);
1101 return ERR_READ;
1104 if (!SetEndOfFile(hFile))
1106 _tprintf(L"Could not truncate the file '%s' to the end!\n", dst);
1107 return ERR_READ;
1111 return 0;