Split UnRecord::run() into a protected run() and a public unRecord() to make sure...
[vng.git] / src / commands / UnRecord.cpp
blob869823adb749e0cdfda104e1eeaae21087cb1545
1 /*
2 * This file is part of the vng project
3 * Copyright (C) 2008 Thomas Zander <tzander@trolltech.com>
4 * Copyright (C) 2003-2005 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 "UnRecord.h"
21 #include "CommandLineParser.h"
22 #include "Interview.h"
23 #include "GitRunner.h"
24 #include "Logger.h"
25 #include "hunks/ChangeSet.h"
26 #include "patches/Commit.h"
27 #include "patches/CommitsCursor.h"
29 #include <QDebug>
31 static const CommandLineOption options[] = {
32 {"--from-match PATTERN", "select changes starting with a patch matching PATTERN"},
33 {"--from-patch REGEXP", "select changes starting with a patch matching REGEXP"},
34 //{"--from-tag REGEXP", "select changes starting with a tag matching REGEXP"},
35 {"-n, --last NUMBER", "select the last NUMBER patches"},
36 {"-a, --all", "select all patches till the first branchpoint found"},
37 {"--match PATTERN", "select patches matching PATTERN"},
38 {"-p, --patches REGEXP", "select patches matching REGEXP"},
39 //{"-t REGEXP,--tags REGEXP", "select tags matching REGEXP"},
40 CommandLineLastOption
43 UnRecord::UnRecord()
44 :AbstractCommand("unrecord"),
45 m_all(false),
46 m_last(-1)
48 CommandLineParser::addOptionDefinitions(options);
51 QString UnRecord::argumentDescription() const
53 return QString();
56 QString UnRecord::commandDescription() const
58 return "UnRecord does the opposite of record in that it makes the changes from\n"
59 "patches active changes again which you may record or revert later. The\n"
60 "working copy itself will not change.\n";
63 AbstractCommand::ReturnCodes UnRecord::run()
65 CommandLineParser *args = CommandLineParser::instance();
66 m_all = args->contains("all");
67 if (args->contains("last")) {
68 int count = args->optionArgument("last").toInt();
69 if (count <= 0) {
70 Logger::error() << "need a positive number for `last'\n";
71 return InvalidOptions;
73 m_last = count;
75 return unRecord();
78 AbstractCommand::ReturnCodes UnRecord::unRecord()
80 if (! checkInRepository())
81 return NotInRepo;
82 if (m_config.isEmptyRepo()) {
83 Logger::error() << "Vng failed: Can not run unrecord on a repo without recorded pathes\n";
84 return Disabled;
87 Commit acceptedCommit = m_unrecordCommit;
88 int unrecordCount = 0;
89 bool oneAutoAcceptedPatch = false;
90 if (acceptedCommit.isValid()) {
91 unrecordCount = 1;
92 } else {
93 if (m_all) {
94 Commit head("HEAD");
95 Commit commit = head.firstCommitInBranch();
96 if (! commit.isValid()) {
97 Logger::error() << "Vng failed: Could not find the branch point, are you sure you are on a branch?\n";
98 return OtherVngError;
100 acceptedCommit = commit;
101 while(true) {
102 unrecordCount++;
103 if (commit == head)
104 break;
105 commit = commit.next();
106 oneAutoAcceptedPatch = true;
109 else { // use a cursor
110 CommitsCursor cursor(CommitsCursor::SelectRange);
111 cursor.setUseMatcher(true);
112 if (m_last > 0) {
113 for (int i = 0; i < m_last && cursor.isValid(); i++) {
114 cursor.setResponse(true);
115 cursor.forward();
118 else {
119 Interview interview(cursor, name());
120 interview.setUsePager(shouldUsePager());
121 if (! interview.start()) {
122 Logger::warn() << "unrecord cancelled." << endl;
123 return Ok;
127 Commit commit = cursor.head();
128 while (true) {
129 if (commit.acceptance() == Vng::Rejected) // can't use this one.
130 break;
131 else if (commit.acceptance() == Vng::Accepted)
132 acceptedCommit = commit;
133 if (commit.previousCommitsCount() == 0) // at first commit.
134 break;
135 if (commit.acceptance() == Vng::Undecided) {
136 if (acceptedCommit.isValid()) // already found a 'yes'.
137 break;
138 oneAutoAcceptedPatch = true;
140 commit = commit.previous()[0];
141 if (unrecordCount >= cursor.oldestCommitAltered())
142 break;
143 unrecordCount++;
145 acceptedCommit = commit;
149 if (unrecordCount == 0 || !acceptedCommit.isValid()) {
150 Logger::warn() << "Ok, if you don't want to unrecord anything, that's fine!\n";
151 return Ok;
153 if (oneAutoAcceptedPatch) {
154 QString answer = Interview::ask(QString("Do you really want to unrecord %1 patches? ").arg(unrecordCount));
155 if (! (answer.startsWith("y") || answer.startsWith("Y")))
156 return Ok;
158 QProcess git;
159 QStringList arguments;
160 arguments << "diff-index" << acceptedCommit.commitTreeIsm(); // get all the affected files.
161 GitRunner runner(git, arguments);
162 ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
163 if (rc) {
164 Logger::error() << "Vng failed: Internal error" << endl;
165 return rc;
167 Commit commit = Commit::createFromStream(&git);
169 QStringList addFilesList; // list of files that we have to git-add since they were not known in this revision.
170 QStringList resetFileList; // list of files that we need to git-reset to make the index notice their diffs
171 ChangeSet changeSet = commit.changeSet();
172 for (int i=0; i < changeSet.count(); ++i) {
173 File file = changeSet.file(i);
174 if (file.oldFileName().isEmpty())
175 addFilesList << file.fileName();
176 else if (! file.fileName().isEmpty()) // ignore removed files, git will notice this
177 addFilesList << file.fileName();
180 if (dryRun())
181 return Ok;
183 arguments.clear();
184 arguments << "update-ref" << "HEAD" << acceptedCommit.commitTreeIsm();
185 runner.setArguments(arguments);
186 rc = runner.start(GitRunner::WaitUntilFinished);
187 if (rc != Ok) {
188 Logger::error() << "Failed to update the ref, sorry.\n";
189 return rc;
192 // revert the index for modified files
193 arguments.clear();
194 arguments << "reset" << "--mixed" << "-q" << "HEAD" << "--";
195 arguments << resetFileList;
196 runner.setArguments(arguments);
197 runner.start(GitRunner::WaitUntilFinished);
199 // add all added files.
200 arguments.clear();
201 arguments << "add" << "--force" << "--ignore-errors" << "--";
202 arguments << addFilesList;
203 runner.setArguments(arguments);
204 runner.start(GitRunner::WaitUntilFinished);
206 return Ok;