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"
22 #include <CommandLineParser.h>
23 #include <patches/CommitsCursor.h>
25 #include <Interview.h>
26 #include <GitRunner.h>
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."},
55 AmendRecord::AmendRecord()
56 : AbstractCommand("Amend-Record")
58 CommandLineParser::addOptionDefinitions(options
);
61 AbstractCommand::ReturnCodes
AmendRecord::run()
65 Recovery() : switchedBranch(false), changedHead(false) {}
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
);
75 arguments
<< QLatin1String("checkout") << oldBranch
;
76 runner
.setArguments(arguments
);
77 runner
.start(GitRunner::WaitUntilFinished
);
79 if (!deleteBranch
.isEmpty()) {
81 arguments
<< QLatin1String("branch") << QLatin1String("-D") << deleteBranch
;
82 runner
.setArguments(arguments
);
83 runner
.start(GitRunner::WaitUntilFinished
);
85 if (changedHead
&& !oldHead
.isEmpty()) {
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
101 if (! checkInRepository())
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);
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
;
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())
139 acceptedCommit
= commit
;
141 //qDebug() << "going to amend" << acceptedCommit.commitTreeIsmSha1();
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
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();
162 if (rc
!= UserCancelled
)
163 Logger::error() << "Vng failed; internal error (record1/" << rc
<< ")\n";
166 if (amendedData
.sha1().isEmpty() && !editMessage
)
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.
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.
182 // TODO should I replace this with various low-level commands? (checkout-index etc)
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.
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
197 QStringList arguments
;
198 arguments
<< QLatin1String("cherry-pick") << amendedData
.sha1();
199 GitRunner
runner(git
, arguments
);
200 rc
= runner
.start(GitRunner::WaitUntilFinished
);
202 Logger::error() << "Vng failed: merging the selected changes failed, possibly a merge conflict.\n";
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
);
213 Logger::error() << "Vng failed; internal error (reset" << rc
<< ")\n";
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();
226 if (rc
== UserCancelled
)
228 Logger::error() << "Vng failed; internal error (record2/" << rc
<< ")\n";
233 newPatch
.setPatchName(acceptedCommit
.logMessage());
234 newPatch
.setRecordAll(true);
238 arguments
<< QLatin1String("rebase") << QLatin1String("--onto") << branch
239 << acceptedCommit
.commitTreeIsmSha1() << recovery
.oldBranch
;
240 runner
.setArguments(arguments
);
241 rc
= runner
.start(GitRunner::WaitUntilFinished
);
243 Logger::error() << "Vng failed: replaying unchanged patches failed, possibly a merge conflict.\n";
246 recovery
.switchedBranch
= false; // we are back on our original branch now :)
248 if (! leftData
.sha1().isEmpty()) {
250 arguments
<< QLatin1String("reset") << QLatin1String("--mixed") << QLatin1String("HEAD^");
251 runner
.setArguments(arguments
);
252 runner
.start(GitRunner::WaitUntilFinished
);
254 recovery
.oldHead
.clear();
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"
269 "WARNINGS: You should ONLY use amend-record on patches which only exist in a\n"
270 "single repository!\n");