Find all interesting branches.
[vng:vng.git] / src / commands / Push.cpp
1 /*
2  * This file is part of the vng project
3  * Copyright (C) 2008 Thomas Zander <tzander@trolltech.com>
4  *
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.
9  *
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.
14  *
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/>.
17  */
18
19 #include "Push.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"
26
27 #include <QProcess>
28 #include <QDebug>
29 #include <QRegExp>
30
31 static const CommandLineOption options[] = {
32     // TODO :)
33     // {"--matches=PATTERN", "select patches matching PATTERN"},
34     // {"-p REGEXP, --patches=REGEXP", "select patches matching REGEXP"},
35     // {"-t REGEXP, --tags=REGEXP", "select tags matching REGEXP"},
36     // {"-a, --all", "answer yes to all patches"},
37     // {"-i, --interactive", "prompt user interactively"},
38     // {"-s, --summary", "summarize changes"},
39     // {"--no-summary", "don't summarize changes"},
40     {"--set-default", "set default repository [DEFAULT]"},
41     {"--no-set-default", "don't set default repository"},
42     CommandLineLastOption
43 };
44
45 Push::Push()
46     : AbstractCommand("push")
47 {
48     CommandLineParser::addOptionDefinitions(options);
49     CommandLineParser::setArgumentDefinition("push [Repository]" );
50 }
51
52 Vng::ReturnCodes Push::run()
53 {
54     if (! checkInRepository())
55         return Vng::NotInRepo;
56     CommandLineParser *args = CommandLineParser::instance();
57
58     /// TODO print warning when not on a branch
59
60     // Find out which remote repo to use
61     RemoteRepo remoteRepo;
62     if (args->arguments().count() > 1) {
63         remoteRepo = RemoteRepo(args->arguments().at(1), args->arguments().at(1));
64     } else {
65         foreach (const RemoteRepo &repo, m_config.remotes()) {
66             if (repo.isDefault()) {
67                 remoteRepo = repo;
68                 break;
69             }
70         }
71     }
72     if (! remoteRepo.isValid()) {
73         if (m_config.remotes().isEmpty()) {
74             Logger::error() << "Vng failed: Please specify the remote repository you want to push to\n";
75             return Vng::InvalidOptions;
76         }
77         if (m_config.remotes().size() > 1) {
78             GenericCursor cursor;
79             foreach (const RemoteRepo &repo, m_config.remotes()) {
80                 if (repo.url() == repo.name())
81                     cursor.addDataItem(QLatin1String("   at ") + repo.url());
82                 else
83                     cursor.addDataItem(QLatin1String("   '") + repo.name()
84                             + QLatin1String("` (") + repo.url() + QLatin1Char(')'));
85             }
86             Interview interview(cursor, QLatin1String("Shall I use this repository?"));
87             interview.setUsePager(shouldUsePager());
88             if (!interview.start())
89                 return Vng::Ok;
90             Q_ASSERT(!cursor.selectedItems().isEmpty());
91             remoteRepo = m_config.remotes().at(cursor.selectedItems().first());
92         } else {
93             remoteRepo = m_config.remotes().first();
94         }
95     }
96     Q_ASSERT(remoteRepo.isValid());
97
98     Logger::warn() << "Pushing to `" << remoteRepo.url() << "'\n";
99     Logger::warn().flush(); // make sure its printed before git asks for an ssh pwd.
100     if (dryRun())
101         return Vng::Ok;
102
103     Vng::ReturnCodes rc;
104     QList<RemoteRepo::RemoteContent> remotes = remoteRepo.remoteContent(&rc);
105     if (rc)
106         return rc;
107
108     /*
109        go over all local branches.
110         if they follow a branch of the remote, check if we are newer and add them to the cursor
111         if its the current branch, update the cursor message
112        if the current branch has not been found in the remotes, ask if we want to create it remotely
113     */
114     bool foundCurrentBranch = false;
115     foreach (const Branch &branch, m_config.branches()) {
116         QString branchName = branch.branchName().mid(6); // strip off 'heads/'
117         bool foundMatch = false;
118         // map local branch to branches on remote.
119         foreach (const TrackedBranch &tb, m_config.trackedBranches()) {
120             if (tb.remote() == remoteRepo && tb.localName() == branchName) {
121                 foreach (const RemoteRepo::RemoteContent &remoteContent, remotes) {
122                     if (tb.remoteName() == remoteContent.ref) {
123                         foundMatch = true;
124                         qDebug() << " branch" << branchName << "maps to " << remoteContent.ref << "on remote";
125                         //TODO add to cursor
126                         break;
127                     }
128                 }
129                 break;
130             }
131         }
132         if (foundMatch && branch.isHead()) {
133             qDebug() << "   += is current branch";
134             foundCurrentBranch = true;
135         }
136     }
137     if (!foundCurrentBranch) {
138         Branch head = m_config.head();
139         if (head.isValid())
140             qDebug() << "ask to push current branch!" << head.branchName();
141     }
142
143 /*
144     // TODO when not using --all ask the remote for all the refs it has and detect which ones we still have to push
145     // TODO use interview to ask which refs to push instead of all below
146     QProcess git;
147     QStringList arguments;
148     arguments << QLatin1String("push") << remoteRepo.url();
149     GitRunner runner(git, arguments);
150     Vng::ReturnCodes rc = runner.start(GitRunner::WaitForStandardOutput);
151     if (rc != Vng::Ok) {
152         Logger::error() << "Git push failed\n";
153         return rc;
154     }
155
156     char buf[1024];
157     while(true) { // just pipe out data
158         git.waitForReadyRead(-1);
159         qint64 readLength = git.read(buf, sizeof(buf)-1);
160         if (readLength <= 0)
161             break;
162         buf[readLength] = 0;
163         Logger::standardOut() << buf;
164     }
165     git.waitForFinished(-1);
166
167     const bool makeDefault = args->contains(QLatin1String("set-default"))
168         || (m_config.contains(QLatin1String("set-default"))
169                 && !args->contains(QLatin1String("no-set-default")));
170     m_config.addRepo(remoteRepo, makeDefault ? Configuration::AddAsDefault
171             : Configuration::AddNotDefault);
172 */
173     return Vng::Ok;
174 }
175
176 QString Push::argumentDescription() const
177 {
178     return QLatin1String("[REPOSITORY]");
179 }
180
181 QString Push::commandDescription() const
182 {
183     return QLatin1String("push is the opposite of pull. push allows you to copy changes from the\n"
184     "current repository into another repository.\n");
185 }