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/>.
20 #include "CommandLineParser.h"
22 #include "GitRunner.h"
23 #include "GenericCursor.h"
24 #include "Interview.h"
32 static const CommandLineOption options
[] = {
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"},
47 : AbstractCommand("pull")
49 CommandLineParser::addOptionDefinitions(options
);
50 CommandLineParser::setArgumentDefinition("pull [Repository]" );
53 static QString
fetchSha1FromRef(QString ref
) // inline again?
57 if (localRef
.open(QIODevice::ReadOnly
)) {
58 qint64 lineLength
= Vng::readLine(&localRef
, buf
, sizeof(buf
));
60 return QString::fromLatin1(buf
, 40);
68 TrackedBranch localBranch
;
71 AbstractCommand::ReturnCodes
Pull::run()
73 if (! checkInRepository())
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));
82 foreach (const RemoteRepo
&repo
, m_config
.remotes()) {
83 if (repo
.isDefault()) {
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) {
96 foreach (const RemoteRepo
&repo
, m_config
.remotes()) {
97 if (repo
.url() == repo
.name())
98 cursor
.addDataItem(QLatin1String(" at ") + repo
.url());
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())
107 Q_ASSERT(!cursor
.selectedItems().isEmpty());
108 remoteRepo
= m_config
.remotes().at(cursor
.selectedItems().first());
110 remoteRepo
= m_config
.remotes().first();
113 Q_ASSERT(remoteRepo
.isValid());
114 Logger::debug() << "pulling from " << remoteRepo
.name() << endl
;
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
;
138 Logger::error() << "Vng failed: No repository found at `" << remoteRepo
.url() << "'\n";
142 // this gets the exported branches on the remote.
143 QList
<RemoteBranch
> remotes
;
146 qint64 lineLength
= Vng::readLine(&git
, buf
, sizeof(buf
));
147 if (lineLength
== -1)
149 if (lineLength
<= 42)
152 branch
.sha1
= QString::fromLatin1(buf
, 40);
153 branch
.ref
= QString::fromAscii(buf
+ 41, lineLength
- 42);
154 int index
= branch
.ref
.lastIndexOf(QLatin1Char('/'));
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
) {
170 RemoteBranch branch
= remoteBranch
;
171 branch
.localBranch
= localBranch
;
172 matchingBranches
<< branch
;
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())
196 foreach (int selected
, cursor
.selectedItems()) {
197 if (selected
< matchingBranches
.count()) {
198 toFetchBranches
<< matchingBranches
.at(selected
);
200 toFetchBranches
<< newBranches
.at(selected
- matchingBranches
.count());
204 toFetchBranches
<< matchingBranches
;
210 if (toFetchBranches
.isEmpty()) {
211 Logger::warn() << "No branches selected, nothing to do." << endl
;
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
);
224 Logger::error() << "Vng failed: fetching remote data didn't work\n";
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
)
248 if (ref
.open(QIODevice::WriteOnly
)) {
249 ref
.write(branch
.sha1
.toAscii());
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.
258 foreach (Branch branch
, m_config
.branches()) {
259 if (branch
.isHead()) {
264 if (!head
.isValid()) {
265 Logger::warn() << "Your workdir is not on any branch, it won't be changed\n";
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";
276 newHead
= Commit(branch
.sha1
);
280 if (! newHead
.isValid()) {
281 Logger::warn() << "Your workdir is not using a tracked branch, it won't be changed\n";
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);
297 Logger::standardOut() << buf
;
298 Logger::standardOut().flush();
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");