Many fixes and cleanups
[vng.git] / src / commands / AmendRecord.cpp
blobc43a62cc58df52edbd23b92d7dbc49b437a468cb
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 "UnRecord.h"
22 #include "Record.h"
23 #include <CommandLineParser.h>
24 #include <CommitsCursor.h>
25 #include <Logger.h>
26 #include <Interview.h>
27 #include <GitRunner.h>
29 #include <QDebug>
31 static const CommandLineOption options[] = {
32 {"-a, --all", "answer yes to all patches"},
33 {"-i, --interactive", "prompt user interactively"},
34 {"-m, --patch-name PATCHNAME", "name of patch"},
35 {"--from-match PATTERN", "select changes starting with a patch matching PATTERN"},
36 {"--from-patch REGEXP", "select changes starting with a patch matching REGEXP"},
37 //{"--from-tag REGEXP", "select changes starting with a tag matching REGEXP"},
38 {"--match PATTERN", "select patches matching PATTERN"},
39 {"-p, --patches REGEXP", "select patches matching REGEXP"},
40 {"-e, --edit", "(re)edit the patch name"},
41 //{"-t REGEXP,--tags REGEXP", "select tags matching REGEXP"},
42 {"-A, --author EMAIL", "specify author id"},
43 //{"--logfile FILE", "give patch name and comment in file"},
44 //{"--delete-logfile", "delete the logfile when done"},
45 //{"--no-test", "don't run the test script"},
46 //{"--test", "run the test script"},
47 //{"-l, --look-for-adds", "Also look for files that are potentially pending addition"},
48 //{"--dont-look-for-adds", "Don't look for any files that could be added"},
49 //{"--posthook COMMAND", "specify command to run after this vng command."},
50 //{"--no-posthook", "Do not run posthook command."},
51 //{"--prompt-posthook", "Prompt before running posthook. [DEFAULT]"},
52 //{"--run-posthook", "Run posthook command without prompting."},
53 CommandLineLastOption
56 AmendRecord::AmendRecord()
57 : AbstractCommand("Amend-Record")
59 CommandLineParser::addOptionDefinitions(options);
62 AbstractCommand::ReturnCodes AmendRecord::run()
64 class Recovery {
65 public:
66 Recovery() : switchedBranch(false), changedHead(false) {}
67 ~Recovery() {
68 if (switchedBranch && ! oldBranch.isEmpty()) {
69 // TODO git reset --hard
70 // git checkout m_oldBranch
72 if (! oldBranch.isEmpty()) {
73 // TODO git branch -D m_oldBranch
75 if (changedHead && oldHead.isEmpty()) {
76 // TODO git update-ref HEAD m_oldHead
80 QString deleteBranch; // the branch name we need to remove
81 QString oldHead; // the head before we started all this.
82 QString oldBranch;// the branch we were on before we started all this
83 bool switchedBranch;
84 bool changedHead;
86 Recovery recovery;
88 if (! checkInRepository())
89 return NotInRepo;
90 moveToRoot();
92 CommitsCursor cursor(CommitsCursor::SelectOnePatch);
93 cursor.setUseMatcher(true);
94 Interview interview(cursor, name());
95 interview.setUsePager(shouldUsePager());
96 if (! interview.start()) {
97 Logger::warn() << "amend-record cancelled." << endl;
98 return Ok;
101 Commit acceptedCommit = cursor.head();
102 recovery.oldHead = acceptedCommit.commitTreeIsmSha1();
103 while (acceptedCommit.acceptance() != Vng::Accepted) {
104 acceptedCommit.commitTreeIsmSha1(); // resolve the real Sha1 internally.
105 Commit commit = acceptedCommit.previous()[0];
106 if (! commit.isValid())
107 break;
108 acceptedCommit = commit;
110 //qDebug() << "going to amend" << acceptedCommit.commitTreeIsmSha1();
111 if (dryRun())
112 return Ok;
114 if (acceptedCommit.previousCommitsCount() != 1) {
115 Logger::error() << "Vng failed: could not find proper parent of commit\n";
116 return InvalidOptions;
118 CommandLineParser *args = CommandLineParser::instance();
119 const bool editMessage = (m_config.contains("edit") && !args->contains("skip")) || args->contains("edit");
121 // do a normal record, passing in a dummy comment. This means we get the differences
122 Record amendedData;
123 const char* dummyPatchName = "vng amend-record temporary commit";
124 const bool all = (m_config.contains("all") && !args->contains("interactive")) || args->contains("all");
125 amendedData.setRecordAll(all);
126 amendedData.setPatchName(QByteArray(dummyPatchName));
127 ReturnCodes rc = amendedData.record();
128 if (rc ) {
129 if (rc != UserCancelled)
130 Logger::error() << "Vng failed; internal error (record1/" << rc << ")\n";
131 return rc;
133 if (amendedData.sha1().isEmpty() && !editMessage)
134 return Ok;
135 recovery.changedHead = true;
137 // if there is anything left in the workdir, make sure we put that in a second patch.
138 // the sha1 will just be empty if there is nothing.
139 Record leftData;
140 leftData.setPatchName(QByteArray(dummyPatchName));
141 leftData.setRecordAll(true);
142 rc = leftData.record();
143 if (rc) // this means that the rest will fail, so we have to recover and abort.
144 return rc;
146 // TODO find the current branch. Possibly using git-for-each-ref refs/heads/
147 recovery.oldBranch = "master";
149 QString branch;
150 int counter = 0;
151 while(true) {
152 // TODO should I replace this with various low-level commands? (checkout-index etc)
153 QProcess git;
154 QStringList arguments;
155 branch = QString("tmp-vng-branch-%1").arg(counter++);
156 arguments << "checkout" << "-b" << branch << acceptedCommit.commitTreeIsmSha1();
157 GitRunner runner(git, arguments);
158 rc = runner.start(GitRunner::WaitUntilFinished);
159 if (rc == Ok) // if the branch already exists, it fails.
160 break;
162 recovery.deleteBranch = branch;
163 recovery.switchedBranch = true;
165 if (! amendedData.sha1().isEmpty()) { // could be empty of the user just wants to change the log message
166 QProcess git;
167 QStringList arguments;
168 arguments << "cherry-pick" << amendedData.sha1();
169 GitRunner runner(git, arguments);
170 rc = runner.start(GitRunner::WaitUntilFinished);
171 if (rc) {
172 Logger::error() << "Vng failed: merging the selected changes failed, possibly a merge conflict.\n";
173 return rc;
177 QProcess git;
178 QStringList arguments;
179 arguments << "reset" << (amendedData.sha1().isEmpty() ? "HEAD^" : "HEAD^^");
180 GitRunner runner(git, arguments);
181 rc = runner.start(GitRunner::WaitUntilFinished);
182 if (rc) {
183 Logger::error() << "Vng failed; internal error (reset" << rc << ")\n";
184 return rc;
187 Record replacementPatch;
188 replacementPatch.setPatchName(acceptedCommit.logMessage());
189 replacementPatch.setRecordAll(true);
190 replacementPatch.setAuthor(args->optionArgument("author",
191 m_config.optionArgument("author", acceptedCommit.author())));
192 replacementPatch.setEditComment(editMessage);
193 rc = replacementPatch.record();
194 if (rc) {
195 Logger::error() << "Vng failed; internal error (record2/" << rc << ")\n";
196 return rc;
199 Record newPatch;
200 newPatch.setPatchName(acceptedCommit.logMessage());
201 newPatch.setRecordAll(true);
202 newPatch.record();
204 arguments.clear();
205 arguments << "rebase" << "--onto" << branch << acceptedCommit.commitTreeIsmSha1() << recovery.oldBranch;
206 runner.setArguments(arguments);
207 rc = runner.start(GitRunner::WaitUntilFinished);
208 if (rc) {
209 Logger::error() << "Vng failed: replaying unchanged patches failed, possibly a merge conflict.\n";
210 return rc;
213 // TODO cherry-pick the leftData commit
215 return Ok;
218 QString AmendRecord::argumentDescription() const
220 return "<FILE or DIRECTORY>";
223 QString AmendRecord::commandDescription() const
225 return "Amend-record is used to replace a patch with a newer version with additional\n"
226 "changes.\n"
227 "\n"
228 "WARNINGS: You should ONLY use amend-record on patches which only exist in a\n"
229 "single repository!\n";