string fixes
[vng.git] / src / commands / AmendRecord.cpp
blob57e078a18d89983d5e3db0c1a91570f0fd387eef
1 /*
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 "AmendRecord.h"
21 #include "Record.h"
22 #include <CommandLineParser.h>
23 #include <patches/CommitsCursor.h>
24 #include <Logger.h>
25 #include <Interview.h>
26 #include <GitRunner.h>
28 #include <QDebug>
30 static const CommandLineOption options[] = {
31 {"-a, --all", "answer yes to all patches"},
32 {"-i, --interactive", "prompt user interactively"},
33 {"-m, --patch-name PATCHNAME", "name of patch"},
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 {"--match PATTERN", "select patches matching PATTERN"},
38 {"-p, --patches REGEXP", "select patches matching REGEXP"},
39 {"-e, --edit", "(re)edit the patch name"},
40 //{"-t REGEXP,--tags REGEXP", "select tags matching REGEXP"},
41 {"-A, --author EMAIL", "specify author id"},
42 //{"--logfile FILE", "give patch name and comment in file"},
43 //{"--delete-logfile", "delete the logfile when done"},
44 //{"--no-test", "don't run the test script"},
45 //{"--test", "run the test script"},
46 //{"-l, --look-for-adds", "Also look for files that are potentially pending addition"},
47 //{"--dont-look-for-adds", "Don't look for any files that could be added"},
48 //{"--posthook COMMAND", "specify command to run after this vng command."},
49 //{"--no-posthook", "Do not run posthook command."},
50 //{"--prompt-posthook", "Prompt before running posthook. [DEFAULT]"},
51 //{"--run-posthook", "Run posthook command without prompting."},
52 CommandLineLastOption
55 AmendRecord::AmendRecord()
56 : AbstractCommand("Amend-Record")
58 CommandLineParser::addOptionDefinitions(options);
61 AbstractCommand::ReturnCodes AmendRecord::run()
63 class Recovery {
64 public:
65 Recovery() : switchedBranch(false), changedHead(false) {}
66 ~Recovery() {
67 QProcess git;
68 QStringList arguments;
69 GitRunner runner(git, arguments);
70 if (switchedBranch && !oldBranch.isEmpty()) {
71 arguments << QLatin1String("reset") << QLatin1String("--hard");
72 runner.setArguments(arguments);
73 runner.start(GitRunner::WaitUntilFinished);
74 arguments.clear();
75 arguments << QLatin1String("checkout") << oldBranch;
76 runner.setArguments(arguments);
77 runner.start(GitRunner::WaitUntilFinished);
79 if (!deleteBranch.isEmpty()) {
80 arguments.clear();
81 arguments << QLatin1String("branch") << QLatin1String("-D") << deleteBranch;
82 runner.setArguments(arguments);
83 runner.start(GitRunner::WaitUntilFinished);
85 if (changedHead && !oldHead.isEmpty()) {
86 arguments.clear();
87 arguments << QLatin1String("update-ref") << QLatin1String("HEAD") << oldHead;
88 runner.setArguments(arguments);
89 runner.start(GitRunner::WaitUntilFinished);
93 QString deleteBranch; // the branch name we need to remove
94 QString oldHead; // the head before we started all this.
95 QString oldBranch;// the branch we were on before we started all this
96 bool switchedBranch;
97 bool changedHead;
99 Recovery recovery;
101 if (! checkInRepository())
102 return NotInRepo;
103 moveToRoot();
105 // create a cursor to walk our patches.
106 CommitsCursor cursor(CommitsCursor::SelectOnePatch);
107 cursor.setUseMatcher(true);
109 // use the fact that the cursor already has a commit with 'HEAD'
110 // search which branch we are on.
111 foreach (const Branch &branch, m_config.branches()) {
112 if (branch.commitTreeIsmSha1() == cursor.head().commitTreeIsmSha1()) {
113 recovery.oldBranch = branch.branchName();
114 if (recovery.oldBranch.startsWith(QLatin1String("heads/")))
115 recovery.oldBranch = recovery.oldBranch.mid(6);
116 break;
120 if (recovery.oldBranch.isEmpty()) {
121 Logger::error() << "vng Failed: Amend-record requires you to be on a named branch.\n";
122 return OtherVngError;
125 Interview interview(cursor, QLatin1String("Shall I amend this change?"));
126 interview.setUsePager(shouldUsePager());
127 if (! interview.start()) {
128 Logger::warn() << "Amend-record cancelled." << endl;
129 return Ok;
132 Commit acceptedCommit = cursor.head();
133 recovery.oldHead = acceptedCommit.commitTreeIsmSha1();
134 while (acceptedCommit.acceptance() != Vng::Accepted) {
135 acceptedCommit.commitTreeIsmSha1(); // resolve the real Sha1 internally.
136 Commit commit = acceptedCommit.previous()[0];
137 if (! commit.isValid())
138 break;
139 acceptedCommit = commit;
141 //qDebug() << "going to amend" << acceptedCommit.commitTreeIsmSha1();
142 if (dryRun())
143 return Ok;
145 if (acceptedCommit.previousCommitsCount() != 1) {
146 Logger::error() << "Vng failed: could not find proper parent of commit\n";
147 return InvalidOptions;
149 CommandLineParser *args = CommandLineParser::instance();
150 const bool editMessage = (m_config.contains(QLatin1String("edit")) && !args->contains(QLatin1String("skip")))
151 || args->contains(QLatin1String("edit"));
153 // do a normal record, passing in a dummy comment. This means we get the differences
154 Record amendedData;
155 const char* dummyPatchName = "vng amend-record temporary commit";
156 const bool all = (m_config.contains(QLatin1String("all")) && !args->contains(QLatin1String("interactive")))
157 || args->contains(QLatin1String("all"));
158 amendedData.setRecordAll(all);
159 amendedData.setPatchName(QByteArray(dummyPatchName));
160 ReturnCodes rc = amendedData.record();
161 if (rc ) {
162 if (rc != UserCancelled)
163 Logger::error() << "Vng failed; internal error (record1/" << rc << ")\n";
164 return rc;
166 if (amendedData.sha1().isEmpty() && !editMessage)
167 return Ok;
168 recovery.changedHead = true;
170 // if there is anything left in the workdir, make sure we put that in a second patch.
171 // the sha1 will just be empty if there is nothing.
172 Record leftData;
173 leftData.setPatchName(QByteArray(dummyPatchName));
174 leftData.setRecordAll(true);
175 rc = leftData.record();
176 if (rc) // this means that the rest will fail, so we have to recover and abort.
177 return rc;
179 QString branch;
180 int counter = 0;
181 while(true) {
182 // TODO should I replace this with various low-level commands? (checkout-index etc)
183 QProcess git;
184 QStringList arguments;
185 branch = QString::fromLatin1("tmp-vng-branch-%1").arg(counter++);
186 arguments << QLatin1String("checkout") << QLatin1String("-b") << branch << acceptedCommit.commitTreeIsmSha1();
187 GitRunner runner(git, arguments);
188 rc = runner.start(GitRunner::WaitUntilFinished);
189 if (rc == Ok) // if the branch already exists, it fails.
190 break;
192 recovery.deleteBranch = branch;
193 recovery.switchedBranch = true;
195 if (! amendedData.sha1().isEmpty()) { // could be empty of the user just wants to change the log message
196 QProcess git;
197 QStringList arguments;
198 arguments << QLatin1String("cherry-pick") << amendedData.sha1();
199 GitRunner runner(git, arguments);
200 rc = runner.start(GitRunner::WaitUntilFinished);
201 if (rc) {
202 Logger::error() << "Vng failed: merging the selected changes failed, possibly a merge conflict.\n";
203 return rc;
207 QProcess git;
208 QStringList arguments;
209 arguments << QLatin1String("reset") << (amendedData.sha1().isEmpty() ? QLatin1String("HEAD^") : QLatin1String("HEAD^^"));
210 GitRunner runner(git, arguments);
211 rc = runner.start(GitRunner::WaitUntilFinished);
212 if (rc) {
213 Logger::error() << "Vng failed; internal error (reset" << rc << ")\n";
214 return rc;
217 Record replacementPatch;
218 replacementPatch.setPatchName(acceptedCommit.logMessage());
219 replacementPatch.setRecordAll(true);
220 replacementPatch.setLogChatter(true);
221 replacementPatch.setAuthor(args->optionArgument(QLatin1String("author"),
222 m_config.optionArgument(QLatin1String("author"), acceptedCommit.author())));
223 replacementPatch.setEditComment(editMessage);
224 rc = replacementPatch.record();
225 if (rc) {
226 if (rc == UserCancelled)
227 return rc;
228 Logger::error() << "Vng failed; internal error (record2/" << rc << ")\n";
229 return rc;
232 Record newPatch;
233 newPatch.setPatchName(acceptedCommit.logMessage());
234 newPatch.setRecordAll(true);
235 newPatch.record();
237 arguments.clear();
238 arguments << QLatin1String("rebase") << QLatin1String("--onto") << branch
239 << acceptedCommit.commitTreeIsmSha1() << recovery.oldBranch;
240 runner.setArguments(arguments);
241 rc = runner.start(GitRunner::WaitUntilFinished);
242 if (rc) {
243 Logger::error() << "Vng failed: replaying unchanged patches failed, possibly a merge conflict.\n";
244 return rc;
246 recovery.switchedBranch = false; // we are back on our original branch now :)
248 if (! leftData.sha1().isEmpty()) {
249 arguments.clear();
250 arguments << QLatin1String("reset") << QLatin1String("--mixed") << QLatin1String("HEAD^");
251 runner.setArguments(arguments);
252 runner.start(GitRunner::WaitUntilFinished);
254 recovery.oldHead.clear();
256 return Ok;
259 QString AmendRecord::argumentDescription() const
261 return QLatin1String("<FILE or DIRECTORY>");
264 QString AmendRecord::commandDescription() const
266 return QLatin1String("Amend-record is used to replace a patch with a newer version with additional\n"
267 "changes.\n"
268 "\n"
269 "WARNINGS: You should ONLY use amend-record on patches which only exist in a\n"
270 "single repository!\n");