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"
23 #include <CommandLineParser.h>
24 #include <CommitsCursor.h>
26 #include <Interview.h>
27 #include <GitRunner.h>
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."},
56 AmendRecord::AmendRecord()
57 : AbstractCommand("Amend-Record")
59 CommandLineParser::addOptionDefinitions(options
);
62 AbstractCommand::ReturnCodes
AmendRecord::run()
66 Recovery() : switchedBranch(false), changedHead(false) {}
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
88 if (! checkInRepository())
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
;
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())
108 acceptedCommit
= commit
;
110 //qDebug() << "going to amend" << acceptedCommit.commitTreeIsmSha1();
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
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();
129 if (rc
!= UserCancelled
)
130 Logger::error() << "Vng failed; internal error (record1/" << rc
<< ")\n";
133 if (amendedData
.sha1().isEmpty() && !editMessage
)
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.
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.
146 // TODO find the current branch. Possibly using git-for-each-ref refs/heads/
147 recovery
.oldBranch
= "master";
152 // TODO should I replace this with various low-level commands? (checkout-index etc)
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.
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
167 QStringList arguments
;
168 arguments
<< "cherry-pick" << amendedData
.sha1();
169 GitRunner
runner(git
, arguments
);
170 rc
= runner
.start(GitRunner::WaitUntilFinished
);
172 Logger::error() << "Vng failed: merging the selected changes failed, possibly a merge conflict.\n";
178 QStringList arguments
;
179 arguments
<< "reset" << (amendedData
.sha1().isEmpty() ? "HEAD^" : "HEAD^^");
180 GitRunner
runner(git
, arguments
);
181 rc
= runner
.start(GitRunner::WaitUntilFinished
);
183 Logger::error() << "Vng failed; internal error (reset" << rc
<< ")\n";
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();
195 Logger::error() << "Vng failed; internal error (record2/" << rc
<< ")\n";
200 newPatch
.setPatchName(acceptedCommit
.logMessage());
201 newPatch
.setRecordAll(true);
205 arguments
<< "rebase" << "--onto" << branch
<< acceptedCommit
.commitTreeIsmSha1() << recovery
.oldBranch
;
206 runner
.setArguments(arguments
);
207 rc
= runner
.start(GitRunner::WaitUntilFinished
);
209 Logger::error() << "Vng failed: replaying unchanged patches failed, possibly a merge conflict.\n";
213 // TODO cherry-pick the leftData commit
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"
228 "WARNINGS: You should ONLY use amend-record on patches which only exist in a\n"
229 "single repository!\n";