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/>.
20 #include "CommandLineParser.h"
21 #include "GitRunner.h"
24 #include "hunks/ChangeSet.h"
25 #include "patches/Commit.h"
26 #include "patches/CommitsMatcher.h"
30 class CommitIterator
{
37 CommitIterator(QProcess
&input
, Direction direction
, int maxCommitCount
);
46 Direction m_direction
;
47 QList
<Commit
> m_commitCache
;
50 CommitsMatcher m_matcher
;
54 CommitIterator::CommitIterator(QProcess
&input
, Direction direction
, int maxCommitCount
)
55 : m_direction(direction
),
57 m_maxCommitCount(maxCommitCount
)
59 if (m_direction
== Forward
) {
60 m_current
= getNext();
62 if (maxCommitCount
== -1)
63 maxCommitCount
= 1000; // caching more sounds bad
64 while (m_commitCache
.count () < maxCommitCount
) {
65 Commit commit
= getNext();
67 m_commitCache
<< commit
;
71 m_process
.terminate();
72 if (!m_commitCache
.isEmpty())
73 m_current
= m_commitCache
.takeLast();
77 Commit
CommitIterator::next()
79 if (m_direction
== Forward
) {
80 if (m_current
.isValid())
81 m_current
= getNext();
85 if (!m_commitCache
.isEmpty())
86 m_current
= m_commitCache
.takeLast();
93 Commit
CommitIterator::current()
98 Commit
CommitIterator::getNext()
100 if (m_maxCommitCount
== 0) {
101 m_process
.terminate();
105 Commit commit
= Commit::createFromStream(&m_process
);
106 if (! commit
.isValid())
108 switch(m_matcher
.match(commit
)) {
109 case CommitsMatcher::SkipPatch
:
111 case CommitsMatcher::ShowPatch
:
114 case CommitsMatcher::Exit
:
115 m_process
.terminate();
122 static const CommandLineOption options
[] = {
123 // {"-a, --all", "answer yes to all patches"},
124 {"--to-match PATTERN", "select changes up to a patch matching PATTERN"},
125 {"--to-patch REGEXP", "select changes up to a patch matching REGEXP"},
126 // {"--to-tag REGEXP", "select changes up to a tag matching REGEXP"},
127 {"--from-match PATTERN", "select changes starting with a patch matching PATTERN"},
128 {"--from-patch REGEXP", "select changes starting with a patch matching REGEXP"},
129 // {"--from-tag REGEXP", "select changes starting with a tag matching REGEXP"},
130 {"-n, --last NUMBER", "select the last NUMBER patches"},
131 {"--match PATTERN", "select patches matching PATTERN"},
132 {"-p, --patches REGEXP", "select patches matching REGEXP"},
133 // {"-t, --tags=REGEXP", "select tags matching REGEXP"},
134 // {"--context", "give output suitable for get --context"},
135 // {"--xml-output", "generate XML formatted output"},
136 // {"--human-readable", "give human-readable output"},
137 {"-s, --summary", "summarize changes"},
138 {"--no-summary", "don't summarize changes"},
139 {"--reverse", "show changes in reverse order"},
140 {"--no-reverse", "don't show changes in reverse order"},
141 CommandLineLastOption
145 : AbstractCommand("changes")
147 CommandLineParser::addOptionDefinitions(options
);
148 CommandLineParser::setArgumentDefinition("changes [FILE or DIRECTORY]" );
151 QString
Changes::argumentDescription() const
153 return QLatin1String("[FILE or DIRECTORY]");
156 QString
Changes::commandDescription() const
158 return QLatin1String("Changes gives a changelog-style summary of the repository history.\n");
161 AbstractCommand::ReturnCodes
Changes::run()
163 if (! checkInRepository())
165 QString revisions
; // ignored
166 return printChangeList(false, revisions
);
169 AbstractCommand::ReturnCodes
Changes::printChangeList(bool tight
, QString
&revisions
)
171 CommandLineParser
*args
= CommandLineParser::instance();
174 QStringList arguments
;
175 arguments
<< QLatin1String("whatchanged") << QLatin1String("--no-abbrev") << QLatin1String("--pretty=raw");
178 if (args
->contains(QLatin1String("last"))) {
179 QString last
= args
->optionArgument(QLatin1String("last"));
181 maxPatches
= last
.toInt(&ok
);
183 Logger::warn() << "\nFailed parsing your 'last' parameter. Reading your mind failed too, so I'll just use 1\n";
184 Logger::warn().flush();
189 QList
<int> usedArguments
;
191 if (args
->arguments().count() > 1) {
192 foreach (Branch branch
, m_config
.allBranches()) {
193 QString branchName
= branch
.branchName();
194 if (branchName
.endsWith(QLatin1String("/HEAD")))
198 foreach (QString arg
, args
->arguments()) {
201 first
= false; // skip command, args for this command are next.
204 if (branchName
== arg
|| (branchName
.endsWith(arg
) && branchName
[branchName
.length() - arg
.length() - 1].unicode() == '/')) {
205 arguments
<< branchName
;
206 usedArguments
<< index
;
213 // now we have to use the rest of the arguments the user passed.
215 QStringList unknownArguments
;
216 foreach (QString arg
, args
->arguments()) {
217 if (! usedArguments
.contains(index
++)) {
219 unknownArguments
<< arg
;
223 GitRunner
runner(git
, arguments
);
224 runner
.setTimeout(-1);
225 ReturnCodes rc
= runner
.start(GitRunner::WaitForStandardOutput
);
227 Logger::error() << "Vng failed: Unknown branch or revision: ";
228 foreach(QString arg
, unknownArguments
)
229 Logger::error() << "`" << arg
<< "' ";;
230 Logger::error() << endl
;
233 if (!tight
&& shouldUsePager())
234 Logger::startPager();
236 const bool showSummery
= (m_config
.contains(QLatin1String("summary")) && !args
->contains(QLatin1String("no-summary")))
237 || args
->contains(QLatin1String("summary"));
238 QTextStream
&out
= Logger::standardOut();
239 CommitsMatcher matcher
;
240 QString firstCommit
, lastCommit
;
242 const bool reverse
= (m_config
.contains(QLatin1String("reverse")) && !args
->contains(QLatin1String("no-reverse")))
243 || args
->contains(QLatin1String("reverse"));
244 CommitIterator
iter(git
, reverse
? CommitIterator::Reverse
: CommitIterator::Forward
, maxPatches
);
246 Commit commit
= iter
.current();
247 if (! commit
.isValid())
249 if (firstCommit
.isEmpty())
250 firstCommit
= commit
.commitTreeIsmSha1();
251 lastCommit
= commit
.commitTreeIsmSha1() + QLatin1Char('^');
253 out
<< commit
.commitTime().toString() << QLatin1Char(' ');
254 m_config
.colorize(out
);
255 out
<< commit
.author();
256 m_config
.normalColor(out
);
258 if (commit
.author() != commit
.committer()) {
260 m_config
.colorize(out
);
261 out
<< commit
.committer();
262 m_config
.normalColor(out
);
265 out
<< " ID " << commit
.commitTreeIsm() << endl
;
266 out
<< commit
.logMessage();
267 if (!tight
&& (Logger::verbosity() >= Logger::Verbose
|| showSummery
)) {
268 ChangeSet cs
= commit
.changeSet();
272 for (int i
=0; i
< cs
.count(); ++i
) {
273 File file
= cs
.file(i
);
274 if (file
.oldFileName().isEmpty())
275 out
<<" A " << QString::fromUtf8(file
.fileName());
276 else if (file
.fileName().isEmpty())
277 out
<<" D " << QString::fromUtf8(file
.oldFileName());
279 out
<<" M " << QString::fromUtf8(file
.fileName());
280 if (Logger::verbosity() < Logger::Verbose
) {
281 if (file
.linesRemoved() > 0)
282 out
<< " -" << file
.linesRemoved();
283 if (file
.linesAdded() > 0)
284 out
<< " +" << file
.linesAdded();
287 if (Logger::verbosity() >= Logger::Verbose
)
288 file
.outputWhatsChanged(out
, m_config
, false, false);
293 Logger::flushPager();
294 if (! out
.device()->isWritable()) { // output cancelled; lets kill git.
302 git
.waitForFinished();
303 revisions
= firstCommit
+QLatin1Char(' ')+ lastCommit
;