Add move command
[vng.git] / Changes.cpp
blob7c11f221d1be67c7dad0be9140545870f1fba78d
1 /*
2 * This file is part of the vng project
3 * Copyright (C) 2008 Thomas Zander <tzander@trolltech.com>
4 * Copyright (C) 2002-2004 David Roundy
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (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, see <http://www.gnu.org/licenses/>.
19 #include "Changes.h"
20 #include "CommandLineParser.h"
21 #include "GitRunner.h"
22 #include "Logger.h"
23 #include "Vng.h"
24 #include "hunks/ChangeSet.h"
25 #include "commits/Commit.h"
27 #include <QDebug>
29 static const CommandLineOption options[] = {
30 // {"-a, --all", "answer yes to all patches"},
31 {"--to-match PATTERN", "select changes up to a patch matching PATTERN"},
32 {"--to-patch REGEXP", "select changes up to a patch matching REGEXP"},
33 // {"--to-tag REGEXP", "select changes up to a tag matching REGEXP"},
34 {"--from-match PATTERN", "select changes starting with a patch matching PATTERN"},
35 {"--from-patch REGEXP", "select changes starting with a patch matching REGEXP"},
36 // {"--from-tag REGEXP", "select changes starting with a tag matching REGEXP"},
37 {"-n, --last NUMBER", "select the last NUMBER patches"},
38 {"--matches PATTERN", "select patches matching PATTERN"},
39 {"-p, --patches REGEXP", "select patches matching REGEXP"},
40 // {"-t, --tags=REGEXP", "select tags matching REGEXP"},
41 // {"--context", "give output suitable for get --context"},
42 // {"--xml-output", "generate XML formatted output"},
43 // {"--human-readable", "give human-readable output"},
44 {"-s, --summary", "summarize changes"},
45 {"--no-summary", "don't summarize changes"},
46 // {"--reverse", "show changes in reverse order"},
47 CommandLineLastOption
50 Changes::Changes()
51 : AbstractCommand("changes")
53 CommandLineParser::addOptionDefinitions(options);
54 CommandLineParser::setArgumentDefinition("changes [FILE or DIRECTORY]" );
57 QString Changes::argumentDescription() const
59 return "[FILE or DIRECTORY]";
62 QString Changes::commandDescription() const
64 return "Gives a changelog-style summary of the repository history.\n";
67 AbstractCommand::ReturnCodes Changes::run()
69 if (! checkInRepository())
70 return NotInRepo;
71 CommandLineParser *args = CommandLineParser::instance();
73 class Matcher {
74 public:
75 enum ExpectedAction {
76 SkipPatch,
77 ShowPatch,
78 Exit
81 Matcher()
82 : useRegExp(false), useRegExpTo(false), useMatcher(false), useMatcherTo(false)
84 CommandLineParser *args = CommandLineParser::instance();
86 if (args->contains("to-match")) {
87 m_matcherTo = QStringMatcher(args->optionArgument("to-match"), Qt::CaseInsensitive);
88 useMatcherTo = true;
90 else if (args->contains("to-patch")) {
91 m_regExpTo = QRegExp(args->optionArgument("to-patch"), Qt::CaseInsensitive, QRegExp::RegExp2);
92 useRegExpTo = true;
95 m_state = BeforeFrom;
96 if (args->contains("from-match")) {
97 m_matcher = QStringMatcher(args->optionArgument("from-match"), Qt::CaseInsensitive);
98 useMatcher = true;
100 else if (args->contains("from-patch")) {
101 m_regExp = QRegExp(args->optionArgument("from-patch"), Qt::CaseInsensitive, QRegExp::RegExp2);
102 useRegExp = true;
104 else {
105 m_state = MatchingPerItem;
106 findNormalMatchers();
110 ExpectedAction match(const Commit &commit)
112 switch (m_state) {
113 case AllClear: return ShowPatch;
114 case BeforeFrom:
115 if (useMatcher && m_matcher.indexIn(commit.author()) == -1
116 && m_matcher.indexIn(commit.logMessage()) == -1
117 || useRegExp && m_regExp.indexIn(commit.author()) == -1
118 && m_regExp.indexIn(commit.logMessage()) == -1)
119 return SkipPatch;
120 m_state = MatchingPerItem;
121 findNormalMatchers();
122 return match(commit);
123 case MatchingPerItem:
124 if (useMatcher && m_matcher.indexIn(commit.author()) == -1
125 && m_matcher.indexIn(commit.logMessage()) == -1
126 || useRegExp && m_regExp.indexIn(commit.author()) == -1
127 && m_regExp.indexIn(commit.logMessage()) == -1)
128 return SkipPatch;
129 if (!useMatcherTo && !useRegExpTo)
130 return ShowPatch;
131 case SearchingForTo:
132 if (useMatcherTo && m_matcherTo.indexIn(commit.author()) == -1
133 && m_matcherTo.indexIn(commit.logMessage()) == -1
134 || useRegExpTo && m_regExpTo.indexIn(commit.author()) == -1
135 && m_regExpTo.indexIn(commit.logMessage()) == -1)
136 return ShowPatch;
137 m_state = AfterTo;
138 return ShowPatch;
139 default:
140 case AfterTo:
141 return Exit;
145 private:
146 void findNormalMatchers() {
147 CommandLineParser *args = CommandLineParser::instance();
148 if (args->contains("matches")) {
149 m_matcher = QStringMatcher(args->optionArgument("matches"), Qt::CaseInsensitive);
150 useMatcher = true;
152 else if (args->contains("patches")) {
153 m_regExp = QRegExp(args->optionArgument("patches"), Qt::CaseInsensitive, QRegExp::RegExp2);
154 useRegExp = true;
156 else if (useMatcherTo || useRegExpTo)
157 m_state = SearchingForTo;
158 else
159 m_state = AllClear;
163 enum State {
164 AllClear, // no matching to be done. All pass per definition.
165 BeforeFrom, // searching for the 'from' All fail until we find that.
166 MatchingPerItem,// Skipping / matching per item. As appropriate.
167 SearchingForTo, // Pass all until we find the 'to' match.
168 AfterTo // We are done, fail all.
170 bool useRegExp, useRegExpTo, useMatcher, useMatcherTo;
171 QRegExp m_regExp, m_regExpTo;
172 QStringMatcher m_matcher, m_matcherTo;
173 State m_state;
176 QProcess git;
177 QStringList arguments;
178 arguments << "whatchanged" << "-m" << "--pretty=raw";
180 if (args->contains("last"))
181 arguments << "-n" << args->optionArgument("last");
183 QList<int> usedArguments;
184 usedArguments << 0;
185 foreach (Branch branch, m_config.allBranches()) {
186 QString branchName = branch.branchName();
187 if (branchName.endsWith("/HEAD"))
188 continue;
189 bool first = true;
190 int index = -1;
191 foreach (QString arg, args->arguments()) {
192 index++;
193 if (first) {
194 first = false; // skip command, args for this command are next.
195 continue;
197 if (branchName.endsWith(arg) && branchName[branchName.length() - arg.length() - 1] == '/') {
198 arguments << branchName;
199 usedArguments << index;
200 break;
205 // now we have to use the rest of the arguments the user passed.
206 int index = 0;
207 foreach (QString arg, args->arguments()) {
208 if (! usedArguments.contains(index++))
209 arguments << arg;
212 GitRunner runner(git, arguments);
213 ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
214 if (rc) {
215 // TODO proper reporting
216 return rc;
218 if (shouldUsePager())
219 Logger::startPager();
221 const bool showSummery = m_config.contains("summary") && !args->contains("no-summary") || args->contains("summary");
222 QTextStream &out = Logger::standardOut();
223 Matcher matcher;
224 while(true) {
225 Commit commit = Commit::createFromStream(&git);
226 if (! commit.isValid())
227 break;
228 switch(matcher.match(commit)) {
229 case Matcher::SkipPatch: continue;
230 case Matcher::ShowPatch: break;
231 case Matcher::Exit:
232 Logger::stopPager();
233 git.waitForFinished();
234 return Ok;
237 out << commit.commitTime().toString() << " ";
238 m_config.colorize(out);
239 out << commit.author() << endl;
240 m_config.normalColor(out);
241 out << " ID " << commit.commitTreeIsm() << endl;
242 out << commit.logMessage();
243 if (Logger::verbosity() >= Logger::Verbose || showSummery) {
244 out << endl;
245 ChangeSet cs = commit.changeSet();
246 cs.generateHunks();
247 foreach (File file, cs.files()) {
248 if (file.oldFileName().isEmpty())
249 out <<" A " << file.fileName();
250 else if (file.fileName().isEmpty())
251 out <<" D " << file.oldFileName();
252 else
253 out <<" M " << file.fileName();
254 if (Logger::verbosity() < Logger::Verbose) {
255 if (file.linesRemoved() > 0)
256 out << " -" << file.linesRemoved();
257 if (file.linesAdded() > 0)
258 out << " +" << file.linesAdded();
260 out << endl;
261 if (Logger::verbosity() >= Logger::Verbose)
262 file.outputWhatsChanged(out, m_config, false, false);
265 out << endl;
266 Logger::flushPager();
268 Logger::stopPager();
269 git.waitForFinished();
270 return Ok;