* Supply a way to copy command data (including the config) so its safe to
[vng:vng.git] / src / commands / Diff.cpp
1 /*
2  * This file is part of the vng project
3  * Copyright (C) 2009 Thomas Zander <tzander@trolltech.com>
4  * Copyright (C) 2002-2004 David Roundy
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 #include "Diff.h"
21 #include "../CommandLineParser.h"
22 #include "../Logger.h"
23 #include "../GitRunner.h"
24 #include "../hunks/ChangeSet.h"
25 #include "WhatsNew.h"
26 #include "Changes.h"
27
28 #include <QDebug>
29
30 static const CommandLineOption options[] = {
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     {"--match PATTERN", "select patches matching PATTERN"},
39     {"-p, --patches REGEXP", "select patches matching REGEXP"},
40     // {"-t, --tags=REGEXP", "select tags matching REGEXP"},
41     // {"--reverse", "show changes in reverse order"},
42     CommandLineLastOption
43 };
44
45 Diff::Diff()
46     : AbstractCommand("diff")
47 {
48     CommandLineParser::addOptionDefinitions(options);
49     CommandLineParser::setArgumentDefinition("diff [FILE or DIRECTORY]" );
50 }
51
52 AbstractCommand::ReturnCodes Diff::run()
53 {
54     if (! checkInRepository())
55         return NotInRepo;
56     CommandLineParser *args = CommandLineParser::instance();
57
58     QStringList arguments;
59     arguments << QLatin1String("diff-tree") << QLatin1String("-p");
60
61     bool parsedArguments = false;
62     if (args->arguments().count() > 1) {
63         foreach (Branch branch, m_config.allBranches()) { // check if our argument is a named branch.
64             QString branchName = branch.branchName();
65             if (branchName.endsWith(QLatin1String("/HEAD")))
66                 continue;
67             bool first = true;
68             int index = -1;
69             foreach (QString arg, args->arguments()) {
70                 index++;
71                 if (first) {
72                     first = false; // skip command, args for this command are next.
73                     continue;
74                 }
75                 if (branchName == arg || (branchName.endsWith(arg) && branchName[branchName.length() - arg.length() - 1].unicode() == '/')) {
76                     // its a named branch!
77                     arguments << branch.commitTreeIsmSha1() << QLatin1String("HEAD");
78                     parsedArguments = true;
79                     break;
80                 }
81             }
82             if (parsedArguments)
83                 break;
84         }
85
86         if (!parsedArguments) { // check if our arguments are files on the filesystem.
87             QStringList files;
88             QStringList revisions;
89
90             QString currentDir = QDir::current().absolutePath();
91             moveToRoot(CheckFileSystem);
92             foreach (QString arg, rebasedArguments()) {
93                 QFile file (arg);
94                 if (file.exists()) {
95                     files << arg;
96                 } else {
97                     revisions << arg;
98                 }
99             }
100             QDir::setCurrent(currentDir);
101             if (revisions.count() > 2) {
102                 Logger::error() << "Vng Failed: unknown files or revisions passed";
103                 return InvalidOptions;
104             }
105 //qDebug() << "  revisions" << revisions << "files" << files;
106             // TODO if files have been passed we can't handle it with the diff-tree, so this will fail
107
108             // split our arguments in files and revisions
109             if (!revisions.isEmpty()) {
110                 arguments << revisions;
111                 if (revisions.count() == 1)
112                     arguments << QLatin1String("HEAD");
113                 if (! files.isEmpty())
114                     arguments << QLatin1String("--");
115             }
116             arguments << files;
117             parsedArguments = true;
118         }
119     }
120
121     const QStringList options = args->options();
122     // always use 'whatsnew' unless the user passes a patch options
123     if (!parsedArguments && !(options.contains(QLatin1String("to-match"))
124         || options.contains(QLatin1String("to-patch"))
125         || options.contains(QLatin1String("from-patch"))
126         || options.contains(QLatin1String("from-match"))
127         || options.contains(QLatin1String("last"))
128         || options.contains(QLatin1String("match")))) {
129         WhatsNew wn;
130         wn.pullConfigDataFrom(this);
131         return wn.printWhatsNew(true, false);
132     }
133
134     if (shouldUsePager())
135         Logger::startPager();
136
137     if (!parsedArguments) {
138         QString revisions;
139         Changes changes;
140         ReturnCodes rc = changes.printChangeList(true, revisions);
141         if (rc != Ok)
142             return rc;
143         arguments << revisions.split(QLatin1Char(' '));
144     }
145
146     QProcess git;
147     GitRunner runner(git, arguments);
148     ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput, GitRunner::FailureAccepted);
149     if (rc == AbstractCommand::GitFailed) { // wrong args, most likely
150         Logger::error() << "Vng failed: failed to understand arguments\n";
151         return InvalidOptions;
152     }
153     if (rc) {
154         Logger::error() << "Vng failed: Unknown reason\n";
155         return rc;
156     }
157
158     QTextStream &out = Logger::standardOut();
159     foreach (File file, ChangeSet::readGitDiff(git)) {
160         file.outputWhatsChanged(out, m_config, false, true);
161     }
162     Logger::stopPager();
163
164     return Ok;
165 }
166
167 QString Diff::argumentDescription() const
168 {
169     return QLatin1String("[FILE or DIRECTORY]");
170 }
171
172 QString Diff::commandDescription() const
173 {
174     return QLatin1String("Diff can be used to create a diff between two versions which are in your\n"
175         "repository.  Specifying just --from-patch will get you a diff against\n"
176         "your working copy.  If you give diff no version arguments, it gives\n"
177         "you the same information as whatsnew except that the patch is\n"
178         "formatted as the output of a diff command\n");
179 }