Make detection of empty repo also work after a git gc
[vng.git] / src / Configuration.cpp
blob8b62ecfd70fe66c3f843cd61ba9e562df33a1510
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/>.
18 #include "Configuration.h"
19 #include "Logger.h"
20 #include "GitRunner.h"
21 #include "AbstractCommand.h"
23 #include <QDebug>
24 #include <QMutexLocker>
25 #include <QProcess>
27 #ifndef Q_OS_WIN
28 #include <unistd.h>
29 #endif
31 Configuration::Configuration(const char *section)
32 : m_repoDir(QLatin1String(".")),
33 m_section(QString::fromLatin1(section)),
34 m_vngConfigRead(false),
35 m_emptyRepo(false),
36 m_fetchedBranches(false),
37 m_configRead(false)
41 bool Configuration::contains(const QString & key) const
43 const_cast<Configuration*> (this)->readVngConfig();
44 return m_options.contains(key);
47 QDir Configuration::repository() const
49 const_cast<Configuration*> (this)->readVngConfig();
50 return m_repoDir;
53 QDir Configuration::repositoryMetaDir() const
55 const_cast<Configuration*> (this)->readVngConfig();
56 return m_repoMetaDataDir;
59 void Configuration::readVngConfig()
61 if (m_vngConfigRead)
62 return;
63 m_vngConfigRead = true;
64 QObject deleterParent;
66 QDir dir = QDir::current();
67 do {
68 QDir git = dir;
69 if (git.cd(QLatin1String(".git"))) {
70 m_repoDir = dir;
71 m_repoMetaDataDir = git;
72 QDir refs(git.absoluteFilePath(QLatin1String("refs/heads")));
73 m_emptyRepo = refs.count() == 2; // only '.' and '..'
74 if (m_emptyRepo) {
75 QFile refs(git.absolutePath() + QLatin1String("/packed-refs"));
76 m_emptyRepo = !refs.exists();
78 break;
80 if (!dir.cdUp())
81 break;
82 } while(!dir.isRoot());
84 QString home = QDir::homePath();
85 QFile *config;
86 config = new QFile(home + QLatin1String("/.vng/config"), &deleterParent);
87 if (! config->exists())
88 config = new QFile(home + QLatin1String("/.darcs/defaults"), &deleterParent);
89 if (config->exists()) {
90 if (! config->open(QIODevice::ReadOnly)) {
91 Logger::error() << "Failed to open config file, is it readable?\n";
92 return;
95 char buf[1024];
96 while(true) {
97 qint64 lineLength = config->readLine(buf, sizeof(buf));
98 if (lineLength == -1)
99 break;
100 QString line = QString::fromUtf8(buf, lineLength);
101 QString option;
102 if (line.startsWith(QLatin1String("ALL ")))
103 option = line.mid(3).trimmed();
104 else if (line.length() > m_section.length() && line.startsWith(m_section))
105 option = line.mid(m_section.length()).trimmed();
106 if (! option.isEmpty()) {
107 const int index = option.indexOf(QLatin1Char(' '));
108 if (index > 0)
109 m_options.insert(option.left(index).trimmed(), option.mid(index).trimmed());
110 else
111 m_options.insert(option, QString());
114 config->close();
118 void Configuration::readConfig()
120 if (m_configRead)
121 return;
122 m_configRead = true;
124 readVngConfig();
125 QFile config(m_repoMetaDataDir.absolutePath() + QLatin1String("/config"));
126 if (! config.open(QIODevice::ReadOnly))
127 return;
129 class RepoCreator {
130 public:
131 RepoCreator() : active(false) { }
132 QString name;
133 QString repoUrl;
134 bool active;
136 void create(QList<RemoteRepo> &repos) {
137 if (active && !repoUrl.isEmpty())
138 repos.append(RemoteRepo(name, repoUrl));
139 active = false;
140 name.clear();
141 repoUrl.clear();
144 RepoCreator repoCreator;
145 class BranchCreator {
146 public:
147 BranchCreator() : active(false) { }
148 QString localName;
149 QString remoteName;
150 QString repoName;
151 bool active;
153 void create(QList<TrackedBranch> &branches, const QList<RemoteRepo> &repos) {
154 if (active && !localName.isEmpty() && !repoName.isEmpty()) {
155 foreach (RemoteRepo repo, repos) {
156 if (repo.name() == repoName) {
157 branches.append(TrackedBranch(localName, remoteName, repo));
161 active = false;
162 localName.clear();
163 remoteName.clear();
164 repoName.clear();
167 BranchCreator branchCreator;
169 char bytes[1024];
170 QString defaultRepo;
171 bool vngRemotes = false;
172 while (true) {
173 qint64 lineLength = Vng::readLine(&config, bytes, 1024);
174 if (lineLength == -1)
175 break;
176 char *line = bytes;
177 while (lineLength > 0 && (line[0] == ' ' || line[0] == '\t')) {
178 ++line;
179 --lineLength;
181 if (lineLength == 0 || line[0] == '#' || line[0] == ';')
182 continue;
184 if (line[0] == '[') { // start section
185 repoCreator.create(m_remoteRepos);
186 branchCreator.create(m_trackedBranches, m_remoteRepos);
187 vngRemotes = false;
188 QString sectionLine = QString::fromAscii(line+1, lineLength-1).trimmed();
189 if (sectionLine.startsWith(QLatin1String("remote \""))) {
190 repoCreator.active = true;
191 repoCreator.name = sectionLine.mid(8, sectionLine.length()-10);
192 } else if (sectionLine.startsWith(QLatin1String("branch \""))) {
193 branchCreator.active = true;
194 branchCreator.remoteName = sectionLine.mid(8, sectionLine.length()-10);
195 } else if (sectionLine.startsWith(QLatin1String("vng \"remote\""))) {
196 vngRemotes = true;
199 else if (vngRemotes || repoCreator.active || branchCreator.active) {
200 QString variableLine = QString::fromAscii(line, lineLength-1);
201 int index = variableLine.indexOf(QLatin1Char('='));
202 if (index <= 0)
203 continue;
204 QString variable = variableLine.left(index).trimmed();
205 QString value = variableLine.mid(index+1).trimmed();
206 if (variable == QLatin1String("url") && repoCreator.active) {
207 repoCreator.repoUrl = value;
208 } else if (branchCreator.active && variable == QLatin1String("remote")) {
209 branchCreator.repoName = value;
210 } else if (branchCreator.active && variable == QLatin1String("merge")) {
211 index = value.lastIndexOf(QLatin1Char('/'));
212 if (index > 0 && value.length() > index)
213 branchCreator.localName = value.mid(index+1);
214 } else if (vngRemotes && variable == QLatin1String("default")) {
215 defaultRepo = value;
216 } else if (vngRemotes && variable == QLatin1String("url")) {
217 bool found = false;
218 foreach (const RemoteRepo &repo, m_remoteRepos) {
219 if (repo.url() == value) { // no need to add it twice
220 found = true;
221 break;
224 if (!found)
225 m_remoteRepos.append(RemoteRepo(value, value));
229 repoCreator.create(m_remoteRepos);
230 branchCreator.create(m_trackedBranches, m_remoteRepos);
232 foreach (RemoteRepo repo, m_remoteRepos) {
233 if (repo.url() == defaultRepo) {
234 repo.setIsDefault(true);
235 break;
240 QString Configuration::optionArgument(const QString &optionName, const QString &defaultValue) const
242 if (m_options.contains(optionName))
243 return m_options[optionName];
244 return defaultValue;
247 bool Configuration::colorTerm() const
249 #ifndef Q_OS_WIN
250 if (isatty(1))
251 return QString::fromLatin1(getenv("TERM")) != QLatin1String("dumb") && !Logger::hasNonColorPager();
252 #endif
253 return false;
256 bool Configuration::isEmptyRepo() const
258 const_cast<Configuration*> (this)->readVngConfig();
259 return m_emptyRepo;
262 QList<Branch> Configuration::allBranches()
264 fetchBranches();
265 QList<Branch> answer;
266 answer += m_localBranches;
267 answer += m_remoteBranches;
268 return answer;
271 QList<Branch> Configuration::branches()
273 fetchBranches();
274 return m_localBranches;
277 QList<Branch> Configuration::remoteBranches()
279 fetchBranches();
280 return m_remoteBranches;
283 void Configuration::fetchBranches()
285 if (m_fetchedBranches)
286 return;
287 m_fetchedBranches = true;
289 QProcess git;
290 QStringList arguments;
291 arguments << QLatin1String("ls-remote") << QLatin1String(".");
292 GitRunner runner(git, arguments);
293 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
294 if (rc != AbstractCommand::Ok) {
295 return;
297 char buf[1024];
298 while(true) {
299 qint64 lineLength = Vng::readLine(&git, buf, sizeof(buf));
300 if (lineLength == -1)
301 break;
302 if (lineLength > 46) { // only take stuff that is in the 'refs' dir.
303 QString name = QString::fromUtf8(buf + 46);
304 const bool remotes = name.startsWith(QLatin1String("remotes/"));
305 if (!remotes && !name.startsWith(QLatin1String("heads/")))
306 continue;
308 name = name.trimmed(); // remove linefeed
309 Branch branch(name, QString::fromLatin1(buf, 40));
310 if (name.startsWith(QLatin1String("remotes")))
311 m_remoteBranches.append(branch);
312 else
313 m_localBranches.append(branch);
317 QFile localRef(repositoryMetaDir().absolutePath()+ QLatin1String("/HEAD"));
318 if (localRef.open(QIODevice::ReadOnly)) {
319 qint64 lineLength = Vng::readLine(&localRef, buf, sizeof(buf));
320 if (lineLength >= 1) {
321 QString line = QString::fromLatin1(buf);
322 if (line.startsWith(QLatin1String("ref: refs/"))) {
323 QString head = line.mid(10, line.length()-11);
324 foreach (Branch branch, m_localBranches) {
325 if (branch.branchName() == head) {
326 branch.setIsHead(true);
327 break;
335 void Configuration::pullConfigDataFrom(const Configuration &other)
337 if (other.m_fetchedBranches) {
338 m_localBranches = other.m_localBranches;
339 m_remoteBranches = other.m_remoteBranches;
340 m_fetchedBranches = true;
342 if (other.m_configRead) {
343 m_remoteRepos = other.m_remoteRepos;
344 m_trackedBranches = other.m_trackedBranches;
345 m_configRead = true;
349 QList<TrackedBranch> Configuration::trackedBranches()
351 const_cast<Configuration*> (this)->readConfig();
352 return m_trackedBranches;
355 QList<RemoteRepo> Configuration::remotes()
357 const_cast<Configuration*> (this)->readConfig();
358 return m_remoteRepos;
361 void Configuration::addRepo(const RemoteRepo &newRepo, AddStrategy strategy)
363 RemoteRepo defaultRepo;
364 // check if its there already.
365 foreach (const RemoteRepo &repo, m_remoteRepos) {
366 if (repo.url() == newRepo.url()) {
367 if (strategy == AddNotDefault)
368 return;
369 if (repo.isDefault())
370 return;
371 } else if (repo.isDefault()) {
372 defaultRepo = repo;
375 QProcess git;
376 QStringList arguments;
377 GitRunner runner(git, arguments);
378 if (defaultRepo.isValid() && strategy == AddAsDefault) {
379 // move the default one to a normal url.
380 arguments << QLatin1String("config") << QLatin1String("--add")
381 << QLatin1String("vng.remote.url") << defaultRepo.url();
382 runner.setArguments(arguments);
383 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitUntilFinished);
384 if (rc != AbstractCommand::Ok) {
385 Logger::warn() << "Failed to store remote repository in local config; check permissions\n";
386 return;
389 // store it as default.
390 arguments.clear();
391 arguments << QLatin1String("config");
392 if (strategy == AddAsDefault)
393 arguments << QLatin1String("--replace-all") << QLatin1String("vng.remote.default");
394 else
395 arguments << QLatin1String("--add") << QLatin1String("vng.remote.url");
396 arguments << newRepo.url();
397 runner.setArguments(arguments);
398 AbstractCommand::ReturnCodes rc = runner.start(GitRunner::WaitUntilFinished);
399 if (rc != AbstractCommand::Ok) {
400 Logger::warn() << "Failed to store remote repository in local config; check permissions\n";
401 return;