bump version
[vng.git] / hunks / ChangeSet.cpp
blob4eed9e0a4d0b6408885aa13dd49ae097cf19d9c2
1 /*
2 * This file is part of the vng project
3 * Copyright (C) 2008 Thomas Zander <tzander@trolltech.com>
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "ChangeSet.h"
20 #include "../GitRunner.h"
21 #include "../Logger.h"
22 #include "../AbstractCommand.h"
23 #include "../Vng.h"
25 #include <QProcess>
26 #include <QDebug>
28 ChangeSet::ChangeSet()
29 : m_changeSetOnIndex(false)
33 AbstractCommand::ReturnCodes ChangeSet::fillFromDiffFile(QIODevice &file)
35 file.open(QIODevice::ReadOnly);
37 foreach (File f, readGitDiff(file))
38 addFile(f);
40 if (Logger::verbosity() >= Logger::Debug) {
41 foreach(File f, m_files) {
42 Logger::debug() << "changes in file: " << f.fileName() << endl;
43 int i=0;
44 foreach(Hunk h, f.hunks()) {
45 Logger::debug() << " +-(" << i++ << ") @" << h.lineNumber() << "; " << h.patch().size() << " bytes\n";
46 for(int i = 0; i < h.subHunkCount(); i++) {
47 Logger::debug() << " " << i <<"/"<< h.subHunkCount() <<"; "<< h.subHunk(i).size() <<" bytes\n";
51 Logger::debug().flush();
53 return AbstractCommand::Ok;
56 AbstractCommand::ReturnCodes ChangeSet::fillFromCurrentChanges(const QStringList &paths)
58 m_changeSetOnIndex = true;
59 QDir refs(".git/refs/heads");
60 const bool emptyRepo = refs.count() == 2; // only '.' and '..'
61 if (emptyRepo) { // all files added are new, just add all.
62 QProcess git;
63 QStringList arguments;
64 arguments << "ls-files" << "-s";
65 GitRunner runner(git, arguments);
66 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
67 if (rc)
68 return rc;
69 char buf[1024];
70 while(true) {
71 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
72 if (lineLength == -1)
73 break;
74 File file;
75 file.setProtection(QString::fromAscii(buf, 6));
76 file.setSha1(QString::fromAscii(buf+7, 40));
77 file.setFileName(QByteArray(buf + 50, lineLength - 51));
78 m_files.append(file);
80 return AbstractCommand::Ok;
83 QProcess git;
84 QStringList arguments;
85 arguments << "diff-index" << "-M" << "HEAD";
86 if (! paths.isEmpty())
87 arguments << "--" << paths;
89 // for each line
90 // new file:
91 // :000000 100644 0000000000000000000000000000000000000000 8c3ae1d344f18b23c3bdde5d26658b70b03c65d9 A bar
92 // rename main.cpp => notmain.cpp
93 // :100644 100644 e58cfe72cb7a9559a0090886bea5b0ce00db6b47 477c729e5abbd701eb708df563e7e4f749b50435 R074 main.cpp notmain.cpp
94 // normal change
95 // :100644 100644 0bdd73e9ea0ba026f6796799946c4bfc9dd1b0b8 0000000000000000000000000000000000000000 M test
97 GitRunner runner(git, arguments);
98 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
99 if (rc)
100 return rc;
101 char buf[1024];
102 while(true) {
103 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
104 if (lineLength == -1)
105 break;
106 if (lineLength > 0 && buf[0] != ':') // not a diff line, ignore.
107 continue;
109 File file;
110 file.setOldProtection(QString::fromAscii(buf+1, 6));
111 file.setProtection(QString::fromAscii(buf+8, 6));
112 file.setOldSha1(QString::fromAscii(buf+15, 40));
113 file.setSha1(QString::fromAscii(buf+56, 40));
114 int offset = 98;
115 while (buf[offset] != '\t' && offset < lineLength)
116 offset++;
117 if (buf[97] == 'R') { // rename
118 int tab = offset + 1;
119 while (buf[tab] != '\t' && tab < lineLength)
120 tab++;
121 file.setOldFileName(QByteArray(buf + offset + 1, tab - offset - 1));
122 file.setFileName(QByteArray(buf + tab + 1, lineLength - tab - 2));
124 else {
125 QByteArray filename(buf + offset + 1, lineLength - offset - 2);
126 if (buf[97] != 'A') // Add
127 file.setOldFileName(filename);
128 if (buf[97] != 'D') // Delete
129 file.setFileName(filename);
131 m_files.append(file);
133 generateHunks();
135 return AbstractCommand::Ok;
138 void ChangeSet::generateHunks()
140 // TODO make the following run in another thread
141 foreach(File file, m_files) {
142 file.fetchHunks(m_changeSetOnIndex);
144 if (!file.isBinary() && file.count() == 0 && !file.hasChanged()) { // No change in file at all.
145 Logger::debug() << "file: `" << file.oldFileName() << "' => `" << file.fileName() << "'\n";
146 Logger::debug() << " +- Unchanged file, skipping\n";
147 m_files.removeAll(file);
148 QProcess git;
149 QStringList arguments;
150 arguments << "update-index" << "-q" << "--refresh" << file.fileName();
151 GitRunner runner(git, arguments);
152 runner.start(GitRunner::WaitUntilFinished);
153 continue;
156 int i=0;
157 if (Logger::verbosity() >= Logger::Debug) {
158 Logger::debug() << "file: `" << file.oldFileName() << "' => `" << file.fileName() << "'\n";
159 if (file.isBinary())
160 Logger::debug() << " +- is a binary file" << endl;
161 Logger::debug() << " +- " << file.oldProtection() << " => " << file.protection() << endl;
162 foreach(Hunk h, file.hunks()) {
163 Logger::debug() << " +-(" << i++ << ") @" << h.lineNumber() << "; " << h.patch().size() << " bytes\n";
164 for(int i = 0; i < h.subHunkCount(); i++) {
165 Logger::debug() << " " << i <<"/"<< h.subHunkCount() <<"; "<< h.subHunk(i).size() <<" bytes\n";
170 Logger::debug().flush();
173 // static
174 QList<File> ChangeSet::readGitDiff(QIODevice &git, File *fileToDiff)
176 QList<File> filesInDiff;
178 // parse the output and create objects for each hunk. (note how that can be made multi-threading)
179 // we have to have the filename in the hunk too to allow skipping a whole hunk
180 char buf[1024];
181 bool inPatch = false;
182 // a diff can be multiple files, or just the one fileToDiff. Lets keep one File object to point to the current file.
183 File file;
184 Hunk hunk;
185 while(true) {
186 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
187 if (lineLength == -1)
188 break;
189 QString line = QString::fromLocal8Bit(buf, lineLength);
191 const bool newfile = line.startsWith("--- /dev/null");
192 if (line.length() > 6 && (newfile || line.startsWith("--- a/"))) {
193 file.addHunk(hunk);
194 hunk = Hunk();
195 if (file.isValid())
196 filesInDiff << file;
197 if (fileToDiff)
198 file = File(*fileToDiff);
199 else {
200 file = File();
201 if (!newfile) {
202 QByteArray array(buf + 6, strlen(buf) - 7);
203 file.setOldFileName(array);
206 inPatch = false;
208 else if (fileToDiff == 0 && line.length() > 6 && line.startsWith("+++ b/")) {
209 QByteArray array(buf + 6, strlen(buf) - 7);
210 file.setFileName(array);
212 else if (line.length() > 5 && line.startsWith("@@ -")) {
213 file.addHunk(hunk);
214 hunk = Hunk();
215 inPatch = true;
217 else if (inPatch && line.startsWith("diff --git "))
218 inPatch = false;
219 else if (line.startsWith("Binary files a/") && line.indexOf(" differ") > 0) { // TODO does git do translations?
220 Q_ASSERT(fileToDiff);
221 fileToDiff->setBinary(true);
222 git.close();
223 return filesInDiff;
225 if (inPatch) {
226 QByteArray array(buf, lineLength);
227 hunk.addLine(array);
230 file.addHunk(hunk);
231 if (fileToDiff == 0 && file.isValid())
232 filesInDiff << file;
233 git.close();
234 return filesInDiff;
237 void ChangeSet::addFile(const File &file)
239 if (file.isValid())
240 m_files << file;
243 QList<File> ChangeSet::files() const
245 return m_files;
248 int ChangeSet::count() const
250 return m_files.count();
253 void ChangeSet::writeDiff(QIODevice &file, ChangeSet::Selection selection) const
255 file.open(QIODevice::WriteOnly | QIODevice::Truncate);
256 QDataStream diff(&file);
257 foreach(File file, m_files) {
258 bool fileHeaderWritten = false;
259 foreach(Hunk hunk, file.hunks()) {
260 if (selection == AllHunks
261 || selection == UserSelection
262 && (hunk.acceptance() == Vng::Accepted || hunk.acceptance() == Vng::MixedAcceptance)
263 || selection == InvertedUserSelection && hunk.acceptance() != Vng::Accepted) {
264 if (!fileHeaderWritten) {
265 diff.writeRawData("--- a/", 6);
266 diff.writeRawData(file.oldFileName().data(), file.oldFileName().size());
267 diff.writeRawData("\n+++ b/", 7);
268 diff.writeRawData(file.fileName().data(), file.fileName().size());
269 diff.writeRawData("\n", 1);
270 fileHeaderWritten = true;
272 QByteArray acceptedPatch;
273 if (selection == InvertedUserSelection)
274 acceptedPatch =hunk.rejectedPatch();
275 else if (selection == AllHunks) {
276 acceptedPatch = hunk.header();
277 acceptedPatch.append(hunk.patch());
279 else
280 acceptedPatch = hunk.acceptedPatch();
281 diff.writeRawData(acceptedPatch.data(), acceptedPatch.size());
285 file.close();
288 bool ChangeSet::hasAcceptedChanges() const
290 foreach(File file, files()) {
291 if (file.renameAcceptance() == Vng::Accepted && file.fileName() != file.oldFileName())
292 return true;
293 if (file.protectionAcceptance() == Vng::Accepted && file.protection() != file.oldProtection())
294 return true;
295 foreach(Hunk hunk, file.hunks()) {
296 Vng::Acceptance a = hunk.acceptance();
297 if (a == Vng::Accepted || a == Vng::MixedAcceptance)
298 return true;
301 return false;