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.
21 #include "SmartHandle.h"
22 #include "../Utils/CrashReport.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
36 Usage: GitWCRev Path [SrcVersionFile DstVersionFile] [-mMuUdqsFe]\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"
54 -s : if given, submodules are not checked. This increases\n\
55 the checking speed.\n"
57 -e : changes the console output encoding to Unicode\n"
60 Switches must be given in a single argument, e.g. '-nm' not '-n -m'.\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\
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\
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\
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\
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)
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)
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.
158 size_t hashlen
= strlen(GitStat
->HeadHashReadable
);
160 if ((strcmp(def
, VERDEFSHORT
) == 0))
162 char format
[1024] = { 0 };
163 char* pStart
= pBuf
+ index
+ strlen(def
);
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
)
180 // Replace the $WCxxx$ string with the actual revision number
181 char* pBuild
= pBuf
+ index
;
182 ptrdiff_t Expansion
= hashlen
- exp
- strlen(def
);
184 memmove(pBuild
, pBuild
- Expansion
, filelength
- ((pBuild
- Expansion
) - pBuf
));
185 else if (Expansion
> 0)
187 // Check for buffer overflow
188 if (maxlength
< Expansion
+ filelength
)
190 memmove(pBuild
+ Expansion
, pBuild
, filelength
- (pBuild
- pBuf
));
192 memmove(pBuild
, GitStat
->HeadHashReadable
, hashlen
);
193 filelength
+= Expansion
;
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.
204 size_t hashlen
= strlen(GitStat
->HeadHashReadable
);
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
;
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
)
226 // Replace the $WCxxx$ string with the actual revision number
227 wchar_t* pBuild
= pBuf
+ index
;
228 ptrdiff_t Expansion
= hashlen
- exp
- wcslen(def
);
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
)
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));
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.
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
);
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)
276 if (strcmp(def
, VALDEFOFFSET1
) == 0)
281 if (strcmp(def
, VALDEFOFFSET2
) == 0)
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
);
295 memmove(pBuild
, pBuild
- Expansion
, filelength
- ((pBuild
- Expansion
) - pBuf
));
296 else if (Expansion
> 0)
298 // Check for buffer overflow
299 if (maxlength
< Expansion
+ filelength
)
301 memmove(pBuild
+ Expansion
, pBuild
, filelength
- (pBuild
- pBuf
));
303 memmove(pBuild
, destbuf
, strlen(destbuf
));
304 filelength
+= Expansion
;
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.
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
;
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)
340 if (wcscmp(def
, TEXT(VALDEFOFFSET1
)) == 0)
345 if (wcscmp(def
, TEXT(VALDEFOFFSET2
)) == 0)
352 // Format the text to insert at the placeholder
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
);
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
)
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));
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*/
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.
391 // Format the text to insert at the placeholder
392 if (ttime
== USE_TIME_NOW
)
396 if (strstr(def
, "UTC"))
398 if (_gmtime64_s(&newtime
, &ttime
))
403 if (_localtime64_s(&newtime
, &ttime
))
406 char destbuf
[1024] = { 0 };
407 char* pBuild
= pBuf
+ index
;
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
);
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)
435 strcpy_s(destbuf
, "Invalid Time Format Specified");
437 _set_invalid_parameter_handler(oldHandler
);
438 Expansion
= strlen(destbuf
) - (strlen(def
) + pEnd
- pStart
+ 1);
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
448 memmove(pBuild
, pBuild
- Expansion
, filelength
- ((pBuild
- Expansion
) - pBuf
));
449 else if (Expansion
> 0)
451 // Check for buffer overflow
452 if (maxlength
< Expansion
+ filelength
)
454 memmove(pBuild
+ Expansion
, pBuild
, filelength
- (pBuild
- pBuf
));
456 memmove(pBuild
, destbuf
, strlen(destbuf
));
457 filelength
+= Expansion
;
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.
468 // Format the text to insert at the placeholder
469 if (ttime
== USE_TIME_NOW
)
473 if (wcsstr(def
, L
"UTC"))
475 if (_gmtime64_s(&newtime
, &ttime
))
480 if (_localtime64_s(&newtime
, &ttime
))
483 wchar_t destbuf
[1024];
484 wchar_t* pBuild
= pBuf
+ index
;
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
;
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)
513 wcscpy_s(destbuf
, L
"Invalid Time Format Specified");
515 _set_invalid_parameter_handler(oldHandler
);
517 Expansion
= wcslen(destbuf
) - (wcslen(def
) + pEnd
- pStart
+ 1);
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
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
)
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);
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.
548 // Look for the terminating '$' character
549 char* pBuild
= pBuf
+ index
;
550 char* pEnd
= pBuild
+ 1;
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
!= '$')
565 return false; // No split - malformed so give up.
569 // Replace $WCxxx?TrueText:FalseText$ with TrueText
570 // Remove :FalseText$
571 memmove(pSplit
, pEnd
+ 1, filelength
- (pEnd
+ 1 - pBuf
));
572 filelength
-= (pEnd
+ 1 - pSplit
);
574 size_t deflen
= strlen(def
);
575 memmove(pBuild
, pBuild
+ deflen
, filelength
- (pBuild
+ deflen
- pBuf
));
576 filelength
-= deflen
;
580 // Replace $WCxxx?TrueText:FalseText$ with FalseText
581 // Remove terminating $
582 memmove(pEnd
, pEnd
+ 1, filelength
- (pEnd
+ 1 - pBuf
));
584 // Remove $WCxxx?TrueText:
585 memmove(pBuild
, pSplit
+ 1, filelength
- (pSplit
+ 1 - pBuf
));
586 filelength
-= (pSplit
+ 1 - pBuild
);
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.
598 // Look for the terminating '$' character
599 wchar_t* pBuild
= pBuf
+ index
;
600 wchar_t* pEnd
= pBuild
+ 1;
601 while (*pEnd
!= L
'$')
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
'$')
615 return false; // No split - malformed so give up.
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));
624 size_t deflen
= wcslen(def
);
625 memmove(pBuild
, pBuild
+ deflen
, (filelength
- (pBuild
+ deflen
- pBuf
) * sizeof(wchar_t)));
626 filelength
-= (deflen
* sizeof(wchar_t));
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));
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.
649 // Replace the $WCxxx$ string with the actual value
650 char* pBuild
= pBuf
+ index
;
651 ptrdiff_t Expansion
= Value
.length() - strlen(def
);
653 memmove(pBuild
, pBuild
- Expansion
, filelength
- ((pBuild
- Expansion
) - pBuf
));
654 else if (Expansion
> 0)
656 // Check for buffer overflow
657 if (maxlength
< Expansion
+ filelength
)
659 memmove(pBuild
+ Expansion
, pBuild
, filelength
- (pBuild
- pBuf
));
661 memmove(pBuild
, Value
.c_str(), Value
.length());
662 filelength
+= Expansion
;
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.
673 // Replace the $WCxxx$ string with the actual revision number
674 wchar_t* pBuild
= pBuf
+ index
;
675 ptrdiff_t Expansion
= Value
.length() - wcslen(def
);
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
)
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));
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
;
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.
711 if (argc
== 4 || argc
== 5)
713 // GitWCRev Path Tmpl.in Tmpl.out [-params]
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)
733 if (wcschr(Params
, L
'm') != 0)
735 if (wcschr(Params
, L
'u') != 0)
736 bErrOnUnversioned
= TRUE
;
737 if (wcschr(Params
, L
'M') != 0)
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
;
760 // Bad params - abort and display help.
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
));
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);
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
;
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);
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
;
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);
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;
859 // open the file and read the contents
860 CAutoFile hFile
= CreateFile(src
, GENERIC_READ
, FILE_SHARE_READ
, nullptr, OPEN_EXISTING
, 0, 0);
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
);
872 maxlength
= filelength
+ 8192; // We might be increasing file size.
873 pBuf
= std::make_unique
<char[]>(maxlength
);
876 _tprintf(L
"Could not allocate enough memory!\n");
879 if (!ReadFile(hFile
, pBuf
.get(), (DWORD
)filelength
, &readlength
, nullptr))
881 _tprintf(L
"Could not read the file '%s'\n", src
);
884 if (readlength
!= filelength
)
886 _tprintf(L
"Could not read the file '%s' to the end!\n", src
);
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();
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
)))
911 _tprintf(L
"Working tree has uncomitted modifications!\n");
914 if (bErrOnUnversioned
&& (GitStat
.HasUnversioned
|| (bErrResursively
&& GitStat
.bHasSubmoduleUnversioned
)))
917 _tprintf(L
"Working tree has unversioned items!\n");
918 return ERR_GIT_UNVER
;
923 _tprintf(L
"HEAD is %s\n", CUnicodeUtils::StdGetUnicode(GitStat
.HeadHashReadable
).c_str());
926 _tprintf(L
"Uncommitted modifications found\n");
928 if (GitStat
.HasUnversioned
)
929 _tprintf(L
"Unversioned items found\n");
935 // now parse the file contents for version defines.
937 while (InsertRevision(VERDEF
, pBuf
.get(), index
, filelength
, maxlength
, &GitStat
));
939 while (InsertRevisionW(TEXT(VERDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, maxlength
, &GitStat
));
942 while (InsertRevision(VERDEFSHORT
, pBuf
.get(), index
, filelength
, maxlength
, &GitStat
));
944 while (InsertRevisionW(TEXT(VERDEFSHORT
), (wchar_t*)pBuf
.get(), index
, filelength
, maxlength
, &GitStat
));
947 while (InsertDate(DATEDEF
, pBuf
.get(), index
, filelength
, maxlength
, GitStat
.HeadTime
));
949 while (InsertDateW(TEXT(DATEDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, maxlength
, GitStat
.HeadTime
));
952 while (InsertDate(DATEDEFUTC
, pBuf
.get(), index
, filelength
, maxlength
, GitStat
.HeadTime
));
954 while (InsertDateW(TEXT(DATEDEFUTC
), (wchar_t*)pBuf
.get(), index
, filelength
, maxlength
, GitStat
.HeadTime
));
957 while (InsertDate(DATEWFMTDEF
, pBuf
.get(), index
, filelength
, maxlength
, GitStat
.HeadTime
));
959 while (InsertDateW(TEXT(DATEWFMTDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, maxlength
, GitStat
.HeadTime
));
961 while (InsertDate(DATEWFMTDEFUTC
, pBuf
.get(), index
, filelength
, maxlength
, GitStat
.HeadTime
));
963 while (InsertDateW(TEXT(DATEWFMTDEFUTC
), (wchar_t*)pBuf
.get(), index
, filelength
, maxlength
, GitStat
.HeadTime
));
966 while (InsertDate(NOWDEF
, pBuf
.get(), index
, filelength
, maxlength
, USE_TIME_NOW
));
968 while (InsertDateW(TEXT(NOWDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, maxlength
, USE_TIME_NOW
));
971 while (InsertDate(NOWDEFUTC
, pBuf
.get(), index
, filelength
, maxlength
, USE_TIME_NOW
));
973 while (InsertDateW(TEXT(NOWDEFUTC
), (wchar_t*)pBuf
.get(), index
, filelength
, maxlength
, USE_TIME_NOW
));
976 while (InsertDate(NOWWFMTDEF
, pBuf
.get(), index
, filelength
, maxlength
, USE_TIME_NOW
));
978 while (InsertDateW(TEXT(NOWWFMTDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, maxlength
, USE_TIME_NOW
));
981 while (InsertDate(NOWWFMTDEFUTC
, pBuf
.get(), index
, filelength
, maxlength
, USE_TIME_NOW
));
983 while (InsertDateW(TEXT(NOWWFMTDEFUTC
), (wchar_t*)pBuf
.get(), index
, filelength
, maxlength
, USE_TIME_NOW
));
986 while (InsertBoolean(MODDEF
, pBuf
.get(), index
, filelength
, GitStat
.HasMods
|| GitStat
.bHasSubmoduleNewCommits
));
988 while (InsertBooleanW(TEXT(MODDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, GitStat
.HasMods
|| GitStat
.bHasSubmoduleNewCommits
));
991 while (InsertBoolean(UNVERDEF
, pBuf
.get(), index
, filelength
, GitStat
.HasUnversioned
));
993 while (InsertBooleanW(TEXT(UNVERDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, GitStat
.HasUnversioned
));
996 while (InsertBoolean(ISTAGGED
, pBuf
.get(), index
, filelength
, GitStat
.bIsTagged
));
998 while (InsertBooleanW(TEXT(ISTAGGED
), (wchar_t*)pBuf
.get(), index
, filelength
, GitStat
.bIsTagged
));
1001 while (InsertBoolean(ISINGIT
, pBuf
.get(), index
, filelength
, GitStat
.bIsGitItem
));
1003 while (InsertBooleanW(TEXT(ISINGIT
), (wchar_t*)pBuf
.get(), index
, filelength
, GitStat
.bIsGitItem
));
1006 while (InsertBoolean(SUBDEF
, pBuf
.get(), index
, filelength
, GitStat
.bHasSubmodule
));
1008 while (InsertBooleanW(TEXT(SUBDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, GitStat
.bHasSubmodule
));
1011 while (InsertBoolean(SUBUP2DATEDEF
, pBuf
.get(), index
, filelength
, !GitStat
.bHasSubmoduleNewCommits
));
1013 while (InsertBooleanW(TEXT(SUBUP2DATEDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, !GitStat
.bHasSubmoduleNewCommits
));
1016 while (InsertBoolean(MODINSUBDEF
, pBuf
.get(), index
, filelength
, GitStat
.bHasSubmoduleMods
));
1018 while (InsertBooleanW(TEXT(MODINSUBDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, GitStat
.bHasSubmoduleMods
));
1021 while (InsertBoolean(UNVERINSUBDEF
, pBuf
.get(), index
, filelength
, GitStat
.bHasSubmoduleUnversioned
));
1023 while (InsertBooleanW(TEXT(UNVERINSUBDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, GitStat
.bHasSubmoduleUnversioned
));
1026 while (InsertBoolean(UNVERFULLDEF
, pBuf
.get(), index
, filelength
, GitStat
.HasUnversioned
|| GitStat
.bHasSubmoduleUnversioned
));
1028 while (InsertBooleanW(TEXT(UNVERFULLDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, GitStat
.HasUnversioned
|| GitStat
.bHasSubmoduleUnversioned
));
1031 while (InsertBoolean(MODFULLDEF
, pBuf
.get(), index
, filelength
, GitStat
.HasMods
|| GitStat
.bHasSubmoduleMods
|| GitStat
.bHasSubmoduleUnversioned
));
1033 while (InsertBooleanW(TEXT(MODFULLDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, GitStat
.HasMods
|| GitStat
.bHasSubmoduleMods
|| GitStat
.bHasSubmoduleUnversioned
));
1036 while (InsertBoolean(MODSFILEDEF
, pBuf
.get(), index
, filelength
, GitStat
.HasMods
));
1038 while (InsertBooleanW(TEXT(MODSFILEDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, GitStat
.HasMods
));
1041 while (InsertNumber(VALDEF
, pBuf
.get(), index
, filelength
, maxlength
, GitStat
.NumCommits
));
1043 while (InsertNumberW(TEXT(VALDEF
), (wchar_t*)pBuf
.get(), index
, filelength
, maxlength
, GitStat
.NumCommits
));
1046 while (InsertNumber(VALDEFAND
, pBuf
.get(), index
, filelength
, maxlength
, GitStat
.NumCommits
));
1048 while (InsertNumberW(TEXT(VALDEFAND
), (wchar_t*)pBuf
.get(), index
, filelength
, maxlength
, GitStat
.NumCommits
));
1051 while (InsertNumber(VALDEFOFFSET1
, pBuf
.get(), index
, filelength
, maxlength
, GitStat
.NumCommits
));
1053 while (InsertNumberW(TEXT(VALDEFOFFSET1
), (wchar_t*)pBuf
.get(), index
, filelength
, maxlength
, GitStat
.NumCommits
));
1056 while (InsertNumber(VALDEFOFFSET2
, pBuf
.get(), index
, filelength
, maxlength
, GitStat
.NumCommits
));
1058 while (InsertNumberW(TEXT(VALDEFOFFSET2
), (wchar_t*)pBuf
.get(), index
, filelength
, maxlength
, GitStat
.NumCommits
));
1061 while (InsertText(BRANCHDEF
, pBuf
.get(), index
, filelength
, maxlength
, GitStat
.CurrentBranch
));
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);
1068 _tprintf(L
"Unable to open output file '%s' for writing\n", dst
);
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
);
1083 if (readlengthExisting
!= filelengthExisting
)
1085 _tprintf(L
"Could not read the file '%s' to the end!\n", dst
);
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
);
1104 if (!SetEndOfFile(hFile
))
1106 _tprintf(L
"Could not truncate the file '%s' to the end!\n", dst
);