set usage of the pager properly
[vng.git] / src / commands / Pull.cpp
blob9a4d116edf5eb68da564bff4e5fc91b984843bdd
1 /*
2 * This file is part of the vng project
3 * Copyright (C) 2008-2009 Thomas Zander <tzander@trolltech.com>
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "Pull.h"
20 #include "CommandLineParser.h"
21 #include "Logger.h"
22 #include "GitRunner.h"
23 #include "GenericCursor.h"
24 #include "Interview.h"
25 #include "Vng.h"
27 #include <QProcess>
28 #include <QDebug>
29 #include <QList>
30 #include <QRegExp>
32 static const CommandLineOption options[] = {
33 // TODO :)
34 // {"--matches=PATTERN", "select patches matching PATTERN"},
35 // {"-p REGEXP, --patches=REGEXP", "select patches matching REGEXP"},
36 // {"-t REGEXP, --tags=REGEXP", "select tags matching REGEXP"},
37 // {"-a, --all", "answer yes to all patches"},
38 // {"-i, --interactive", "prompt user interactively"},
39 // {"-s, --summary", "summarize changes"},
40 // {"--no-summary", "don't summarize changes"},
41 {"--set-default", "set default repository [DEFAULT]"},
42 {"--no-set-default", "don't set default repository"},
43 CommandLineLastOption
46 Pull::Pull()
47 : AbstractCommand("pull")
49 CommandLineParser::addOptionDefinitions(options);
50 CommandLineParser::setArgumentDefinition("pull [Repository]" );
53 static QString fetchSha1FromRef(QString ref) // inline again?
55 QFile localRef(ref);
56 char buf[50];
57 if (localRef.open(QIODevice::ReadOnly)) {
58 qint64 lineLength = Vng::readLine(&localRef, buf, sizeof(buf));
59 if (lineLength >= 40)
60 return QString::fromLatin1(buf, 40);
62 return QString();
65 struct RemoteBranch {
66 QString sha1;
67 QString ref;
68 TrackedBranch localBranch;
71 AbstractCommand::ReturnCodes Pull::run()
73 if (! checkInRepository())
74 return NotInRepo;
75 CommandLineParser *args = CommandLineParser::instance();
77 // Find out which remote repo to use
78 RemoteRepo remoteRepo;
79 if (args->arguments().count() > 1) {
80 remoteRepo = RemoteRepo(args->arguments().at(1), args->arguments().at(1));
81 } else {
82 foreach (const RemoteRepo &repo, m_config.remotes()) {
83 if (repo.isDefault()) {
84 remoteRepo = repo;
85 break;
89 if (! remoteRepo.isValid()) {
90 if (m_config.remotes().isEmpty()) {
91 Logger::error() << "Vng failed: Please specify the remote repository you want to pull from\n";
92 return InvalidOptions;
94 if (m_config.remotes().size() > 1) {
95 GenericCursor cursor;
96 foreach (const RemoteRepo &repo, m_config.remotes()) {
97 if (repo.url() == repo.name())
98 cursor.addDataItem(QLatin1String(" at ") + repo.url());
99 else
100 cursor.addDataItem(QLatin1String(" '") + repo.name()
101 + QLatin1String("` (") + repo.url() + QLatin1Char(')'));
103 Interview interview(cursor, QLatin1String("Shall I use this repository?"));
104 interview.setUsePager(shouldUsePager());
105 if (!interview.start())
106 return Ok;
107 Q_ASSERT(!cursor.selectedItems().isEmpty());
108 remoteRepo = m_config.remotes().at(cursor.selectedItems().first());
109 } else {
110 remoteRepo = m_config.remotes().first();
113 Q_ASSERT(remoteRepo.isValid());
114 Logger::debug() << "pulling from " << remoteRepo.name() << endl;
116 QProcess git;
117 QStringList arguments;
118 arguments << QLatin1String("ls-remote") << QLatin1String("--heads")
119 << QLatin1String("--tags") << remoteRepo.url();
120 GitRunner runner(git, arguments);
121 runner.setTimeout(5 * 60 * 1000); // network stuff, 5 min timeout.
122 ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput, GitRunner::FailureAccepted);
123 if (rc == AbstractCommand::GitFailed) {
124 if (git.exitCode() == 128) {
125 QString error = QString::fromLocal8Bit(git.readAllStandardError());
126 Logger::error() << "Vng failed: can not read the remote repo";
127 if (error.startsWith(QLatin1String("ssh:"))) {
128 Logger::error() << ", please check network or and access rights.";
129 } else if (error.indexOf(QLatin1String("does not look like a v2 bundle file"))) {
130 Logger::error() << ", it doesn't look like a valid repo.";
132 Logger::error() << "\n";
133 Logger::info() << error;
134 return rc;
137 if (rc) {
138 Logger::error() << "Vng failed: No repository found at `" << remoteRepo.url() << "'\n";
139 return rc;
142 // this gets the exported branches on the remote.
143 QList<RemoteBranch> remotes;
144 char buf[120];
145 while(true) {
146 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
147 if (lineLength == -1)
148 break;
149 if (lineLength <= 42)
150 continue;
151 RemoteBranch branch;
152 branch.sha1 = QString::fromLatin1(buf, 40);
153 branch.ref = QString::fromAscii(buf + 41, lineLength - 42);
154 int index = branch.ref.lastIndexOf(QLatin1Char('/'));
155 if (index >= 0)
156 branch.ref = branch.ref.mid(index+1);
157 remotes.append(branch);
160 QString remotesDir = m_config.repositoryMetaDir().absolutePath()
161 + QLatin1String("/refs/remotes/");
163 QList<RemoteBranch> matchingBranches;
164 QList<RemoteBranch> newBranches;
165 foreach (const RemoteBranch &remoteBranch, remotes) {
166 bool foundMatch = false;
167 foreach (const TrackedBranch &localBranch, m_config.trackedBranches()) {
168 if (localBranch.remoteName() == remoteBranch.ref) {
169 foundMatch = true;
170 RemoteBranch branch = remoteBranch;
171 branch.localBranch = localBranch;
172 matchingBranches << branch;
173 break;
176 if (!foundMatch) {
177 QString sha1 = fetchSha1FromRef(remotesDir
178 + remoteRepo.name() + QLatin1Char('/') + remoteBranch.ref);
179 if (remoteBranch.sha1 != sha1)
180 newBranches << remoteBranch;
184 QList<RemoteBranch> toFetchBranches;
185 if (matchingBranches.count() > 1 || !newBranches.isEmpty()) {
186 GenericCursor cursor(GenericCursor::ExitWhenDone);
187 foreach (const RemoteBranch &repo, matchingBranches)
188 // TODO mark current branch somehow
189 cursor.addDataItem(QLatin1String(" branch: '") + repo.ref + QLatin1Char('`'));
190 foreach (const RemoteBranch &repo, newBranches)
191 cursor.addDataItem(QLatin1String(" new branch: '") + repo.ref + QLatin1Char('`'));
192 Interview interview(cursor, QLatin1String("Update this branch?"));
193 interview.setUsePager(shouldUsePager());
194 if (!interview.start())
195 return Ok;
196 foreach (int selected, cursor.selectedItems()) {
197 if (selected < matchingBranches.count()) {
198 toFetchBranches << matchingBranches.at(selected);
199 } else {
200 toFetchBranches << newBranches.at(selected - matchingBranches.count());
203 } else {
204 toFetchBranches << matchingBranches;
207 if (dryRun())
208 return Ok;
210 if (toFetchBranches.isEmpty()) {
211 Logger::warn() << "No branches selected, nothing to do." << endl;
212 return Ok;
215 arguments.clear();
216 arguments << QLatin1String("fetch-pack") << QLatin1String("--no-progress") << remoteRepo.url();
217 foreach (const RemoteBranch &branch, toFetchBranches) {
218 arguments << QLatin1String("refs/heads/") + branch.ref;
221 runner.setArguments(arguments);
222 rc = runner.start(GitRunner::WaitUntilFinished);
223 if (rc) {
224 Logger::error() << "Vng failed: fetching remote data didn't work\n";
225 return rc;
227 const bool makeDefault = args->contains(QLatin1String("set-default"))
228 || (m_config.contains(QLatin1String("set-default"))
229 && !args->contains(QLatin1String("no-set-default")));
230 m_config.addRepo(remoteRepo, makeDefault ? Configuration::AddAsDefault
231 : Configuration::AddNotDefault);
233 // update and create remote refs
234 QString remoteDir = remotesDir + remoteRepo.name() + QLatin1Char('/');
235 foreach (const RemoteBranch &branch, toFetchBranches) {
236 QFile ref(remoteDir + branch.ref);
237 // lets open it first and compare since writing is much more expensive.
238 if (ref.open(QIODevice::ReadOnly)) {
239 qint64 lineLength = Vng::readLine(&ref, buf, sizeof(buf));
240 if (lineLength >= 40) {
241 QString currentContent = QString::fromLatin1(buf, 40);
242 if (currentContent == branch.sha1)
243 continue;
245 ref.close();
248 if (ref.open(QIODevice::WriteOnly)) {
249 ref.write(branch.sha1.toAscii());
250 ref.close();
254 // now we check the branch currently checked out.
255 // We then check if its in the tracked branches and see what the remote name is
256 // if the remote branch is changed we merge our checkout.
257 Branch head;
258 foreach (Branch branch, m_config.branches()) {
259 if (branch.isHead()) {
260 head = branch;
261 break;
264 if (!head.isValid()) {
265 Logger::warn() << "Your workdir is not on any branch, it won't be changed\n";
266 return Ok;
268 Commit newHead;
269 foreach (const RemoteBranch &branch, matchingBranches) {
270 if (branch.localBranch.isValid() && QLatin1String("heads/")
271 + branch.localBranch.localName() == head.branchName()) {
272 if (head.commitTreeIsmSha1() == branch.sha1) {
273 Logger::warn() << "Already up-to-date.\n";
274 return Ok;
276 newHead = Commit(branch.sha1);
277 break;
280 if (! newHead.isValid()) {
281 Logger::warn() << "Your workdir is not using a tracked branch, it won't be changed\n";
282 return Ok;
285 arguments.clear();
286 arguments << QLatin1String("merge") << newHead.commitTreeIsmSha1();
287 runner.setArguments(arguments);
288 Logger::standardOut().flush();
289 rc = runner.start(GitRunner::WaitForStandardOutput);
291 while(true) { // just pipe out data
292 git.waitForReadyRead(-1);
293 qint64 readLength = git.read(buf, sizeof(buf)-1);
294 if (readLength <= 0)
295 break;
296 buf[readLength] = 0;
297 Logger::standardOut() << buf;
298 Logger::standardOut().flush();
300 return rc;
303 QString Pull::argumentDescription() const
305 return QLatin1String("[REPOSITORY]");
308 QString Pull::commandDescription() const
310 return QLatin1String("Pull is used to bring changes made in another repository into the current\nn"
311 "repository (that is, either the one in the current directory, or the one\n"
312 "specified with the --repodir option). Pull allows you to bring over all or\n"
313 "some of the branches that are in that repository but not in this one. Pull\n"
314 "accepts arguments, which are URLs from which to pull, and when called\n"
315 "without an argument, pull will use the repository from which you have most\n"
316 "recently either pushed or pulled.\n");