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"
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
))
40 if (Logger::verbosity() >= Logger::Debug
) {
41 foreach(File f
, m_files
) {
42 Logger::debug() << "changes in file: " << f
.fileName() << endl
;
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.
63 QStringList arguments
;
64 arguments
<< "ls-files" << "-s";
65 GitRunner
runner(git
, arguments
);
66 AbstractCommand::ReturnCodes rc
= runner
.start(GitRunner::WaitForStandardOutput
);
71 qint64 lineLength
= Vng::readLine(&git
, buf
, sizeof(buf
));
75 file
.setProtection(QString::fromAscii(buf
, 6));
76 file
.setSha1(QString::fromAscii(buf
+7, 40));
77 file
.setFileName(QByteArray(buf
+ 50, lineLength
- 51));
80 return AbstractCommand::Ok
;
84 QStringList arguments
;
85 arguments
<< "diff-index" << "-M" << "HEAD";
86 if (! paths
.isEmpty())
87 arguments
<< "--" << paths
;
91 // :000000 100644 0000000000000000000000000000000000000000 8c3ae1d344f18b23c3bdde5d26658b70b03c65d9 A bar
92 // rename main.cpp => notmain.cpp
93 // :100644 100644 e58cfe72cb7a9559a0090886bea5b0ce00db6b47 477c729e5abbd701eb708df563e7e4f749b50435 R074 main.cpp notmain.cpp
95 // :100644 100644 0bdd73e9ea0ba026f6796799946c4bfc9dd1b0b8 0000000000000000000000000000000000000000 M test
97 GitRunner
runner(git
, arguments
);
98 AbstractCommand::ReturnCodes rc
= runner
.start(GitRunner::WaitForStandardOutput
);
103 qint64 lineLength
= Vng::readLine(&git
, buf
, sizeof(buf
));
104 if (lineLength
== -1)
106 if (lineLength
> 0 && buf
[0] != ':') // not a diff line, ignore.
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));
115 while (buf
[offset
] != '\t' && offset
< lineLength
)
117 if (buf
[97] == 'R') { // rename
118 int tab
= offset
+ 1;
119 while (buf
[tab
] != '\t' && tab
< lineLength
)
121 file
.setOldFileName(QByteArray(buf
+ offset
+ 1, tab
- offset
- 1));
122 file
.setFileName(QByteArray(buf
+ tab
+ 1, lineLength
- tab
- 2));
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
);
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
);
149 QStringList arguments
;
150 arguments
<< "update-index" << "-q" << "--refresh" << file
.fileName();
151 GitRunner
runner(git
, arguments
);
152 runner
.start(GitRunner::WaitUntilFinished
);
157 if (Logger::verbosity() >= Logger::Debug
) {
158 Logger::debug() << "file: `" << file
.oldFileName() << "' => `" << file
.fileName() << "'\n";
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();
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
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.
186 qint64 lineLength
= Vng::readLine(&git
, buf
, sizeof(buf
));
187 if (lineLength
== -1)
189 QString line
= QString::fromLocal8Bit(buf
, lineLength
);
191 const bool newfile
= line
.startsWith("--- /dev/null");
192 if (line
.length() > 6 && (newfile
|| line
.startsWith("--- a/"))) {
198 file
= File(*fileToDiff
);
202 QByteArray
array(buf
+ 6, strlen(buf
) - 7);
203 file
.setOldFileName(array
);
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("@@ -")) {
217 else if (inPatch
&& line
.startsWith("diff --git "))
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);
226 QByteArray
array(buf
, lineLength
);
231 if (fileToDiff
== 0 && file
.isValid())
237 void ChangeSet::addFile(const File
&file
)
243 QList
<File
> ChangeSet::files() const
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());
280 acceptedPatch
= hunk
.acceptedPatch();
281 diff
.writeRawData(acceptedPatch
.data(), acceptedPatch
.size());
288 bool ChangeSet::hasAcceptedChanges() const
290 foreach(File file
, files()) {
291 if (file
.renameAcceptance() == Vng::Accepted
&& file
.fileName() != file
.oldFileName())
293 if (file
.protectionAcceptance() == Vng::Accepted
&& file
.protection() != file
.oldProtection())
295 foreach(Hunk hunk
, file
.hunks()) {
296 Vng::Acceptance a
= hunk
.acceptance();
297 if (a
== Vng::Accepted
|| a
== Vng::MixedAcceptance
)