/src/Git/GitRepositoryCommand.cpp

https://github.com/murank/TortoiseGitMod · C++ · 191 lines · 119 code · 36 blank · 36 comment · 13 complexity · f5e229de9d7cf85e49efb2d9e55e9a01 MD5 · raw file

  1. // TortoiseGit - a Windows shell extension for easy version control
  2. // Copyright (C) 2008-2011 - TortoiseGit
  3. // This program is free software; you can redistribute it and/or
  4. // modify it under the terms of the GNU General Public License
  5. // as published by the Free Software Foundation; either version 2
  6. // of the License, or (at your option) any later version.
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU General Public License for more details.
  11. // You should have received a copy of the GNU General Public License
  12. // along with this program; if not, write to the Free Software Foundation,
  13. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  14. //
  15. #include "stdafx.h"
  16. #include "GitRepositoryCommand.h"
  17. #include "CommandRunner.h"
  18. #include "Utilities.h"
  19. GitRepositoryCommand::GitRepositoryCommand(const CString& root, int encoding)
  20. : GitRepository(root, encoding)
  21. {
  22. }
  23. GitRepositoryCommand::~GitRepositoryCommand()
  24. {
  25. }
  26. static git_status_type GetMoreImportantStatus(git_status_type status1, git_status_type status2)
  27. {
  28. return std::max(status1, status2);
  29. }
  30. static git_status_type GetStatusFromStatusCode(TCHAR code)
  31. {
  32. /*
  33. Meanings of the status code
  34. ' ' = unmodified
  35. M = modified
  36. A = added
  37. D = deleted
  38. R = renamed
  39. C = copied
  40. U = updated but unmerged
  41. ? = untracked
  42. ! = ignored
  43. */
  44. switch(code) {
  45. case _T(' '):
  46. return git_status_type_unmodified;
  47. case _T('M'):
  48. return git_status_type_modified;
  49. case _T('A'):
  50. return git_status_type_added;
  51. case _T('D'):
  52. return git_status_type_deleted;
  53. case _T('R'):
  54. return git_status_type_renamed;
  55. case _T('C'):
  56. return git_status_type_copied;
  57. case _T('U'):
  58. return git_status_type_unmerged;
  59. case _T('?'):
  60. return git_status_type_untracked;
  61. case _T('!'):
  62. return git_status_type_ignored;
  63. default:
  64. throw std::invalid_argument("invalid git status");
  65. }
  66. }
  67. static git_status_type ParseStatus(const CString& line)
  68. {
  69. if(line.GetLength() < 3) {
  70. throw std::invalid_argument("invalid git status");
  71. }
  72. /*
  73. status is represented as follows:
  74. XY PATH
  75. X: status of index
  76. Y: status of work tree
  77. */
  78. TCHAR indexStatusCode = line[0];
  79. TCHAR worktreeStatusCode = line[1];
  80. git_status_type indexStatus = GetStatusFromStatusCode(indexStatusCode);
  81. git_status_type worktreeStatus = GetStatusFromStatusCode(worktreeStatusCode);
  82. if((worktreeStatus==git_status_type_added) && (indexStatus==git_status_type_added)) {
  83. // treat as a special case of conflict
  84. return git_status_type_unmerged;
  85. }
  86. return GetMoreImportantStatus(indexStatus, worktreeStatus);
  87. }
  88. static bool IsStatusRenamed(const CString& line)
  89. {
  90. return ((line[0]==_T('R')) || (line[1]==_T('R')));
  91. }
  92. static git_status_type MergeGitStatuses(const std::vector<CString>& statuses)
  93. {
  94. git_status_type status = git_status_type_none;
  95. for(std::vector<CString>::const_iterator it=statuses.begin(); it!=statuses.end(); ++it) {
  96. git_status_type newStatus = git_status_type_none;
  97. try {
  98. newStatus = ParseStatus(*it);
  99. if(IsStatusRenamed(*it)) {
  100. // the string after a 'renamed' file represents an original filename
  101. ++it;
  102. }
  103. } catch(std::invalid_argument&) {
  104. // do nothing
  105. }
  106. status = GetMoreImportantStatus(status, newStatus);
  107. }
  108. return status;
  109. }
  110. git_status_type GitRepositoryCommand::GetStatus(const CString& path) const
  111. {
  112. std::vector<CString> statuses = GetStatusStrings(path);
  113. if(statuses.empty()) {
  114. if(IsEmptyDir(path)) {
  115. return git_status_type_untracked;
  116. } else {
  117. return git_status_type_unmodified;
  118. }
  119. }
  120. return MergeGitStatuses(statuses);
  121. }
  122. static CString EscapeCommand(CString str)
  123. {
  124. str.Replace(_T("\\"), _T("/"));
  125. return str;
  126. }
  127. static CString BuildGitStatusCommand(const CString& relPath)
  128. {
  129. CString command;
  130. command.Format(CString(_T("git.exe status --porcelain --ignored -z \"%s\"")), EscapeCommand(relPath));
  131. return command;
  132. }
  133. std::vector<CString> GitRepositoryCommand::GetStatusStrings(const CString& path) const
  134. {
  135. assert(StartsWith(path, GetProjectRoot()) && "the path must be in a git repository");
  136. if(!StartsWith(path, GetProjectRoot())) {
  137. throw std::invalid_argument("the path must be in a git repository");
  138. }
  139. CString command = BuildGitStatusCommand(GetRelativePath(path));
  140. std::vector<CString> ret;
  141. if(Run(command, ret)) {
  142. return std::vector<CString>();
  143. }
  144. return ret;
  145. }
  146. int GitRepositoryCommand::Run(const CString& command, std::vector<CString>& out) const
  147. {
  148. CommandRunner runner;
  149. return runner.Run(command, GetProjectRoot(), GetEncoding(), out);
  150. }
  151. bool GitRepositoryCommand::IsEmptyDir(const CString& path) const
  152. {
  153. return (PathIsDirectoryEmpty(path)!=FALSE);
  154. }