branchdialog.cpp 15 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8
9
10
11
12
13
14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
23
24
25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
29

30
#include "branchdialog.h"
Tobias Hunger's avatar
Tobias Hunger committed
31
#include "branchadddialog.h"
Petar Perisin's avatar
Petar Perisin committed
32
#include "branchcheckoutdialog.h"
33
34
#include "branchmodel.h"
#include "gitclient.h"
35
#include "gitplugin.h"
Petar Perisin's avatar
Petar Perisin committed
36
#include "gitutils.h"
37
#include "ui_branchdialog.h"
38
39
#include "stashdialog.h" // Label helpers

40
#include <utils/qtcassert.h>
41
#include <utils/execmenu.h>
42
#include <vcsbase/vcsbaseoutputwindow.h>
43
#include <coreplugin/documentmanager.h>
44

45
#include <QAction>
46
47
#include <QItemSelectionModel>
#include <QMessageBox>
Petar Perisin's avatar
Petar Perisin committed
48
#include <QList>
49
#include <QMenu>
50

51
#include <QDebug>
52

53
namespace Git {
Tobias Hunger's avatar
Tobias Hunger committed
54
namespace Internal {
55

56
57
58
BranchDialog::BranchDialog(QWidget *parent) :
    QDialog(parent),
    m_ui(new Ui::BranchDialog),
Tobias Hunger's avatar
Tobias Hunger committed
59
    m_model(new BranchModel(GitPlugin::instance()->gitClient(), this))
60
{
61
    setModal(false);
Friedemann Kleint's avatar
Friedemann Kleint committed
62
    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
63
    setAttribute(Qt::WA_DeleteOnClose, true); // Do not update unnecessarily
Friedemann Kleint's avatar
Friedemann Kleint committed
64

65
    m_ui->setupUi(this);
66

Tobias Hunger's avatar
Tobias Hunger committed
67
68
69
70
    connect(m_ui->refreshButton, SIGNAL(clicked()), this, SLOT(refresh()));
    connect(m_ui->addButton, SIGNAL(clicked()), this, SLOT(add()));
    connect(m_ui->checkoutButton, SIGNAL(clicked()), this, SLOT(checkout()));
    connect(m_ui->removeButton, SIGNAL(clicked()), this, SLOT(remove()));
Petar Perisin's avatar
Petar Perisin committed
71
    connect(m_ui->renameButton, SIGNAL(clicked()), this, SLOT(rename()));
Tobias Hunger's avatar
Tobias Hunger committed
72
73
    connect(m_ui->diffButton, SIGNAL(clicked()), this, SLOT(diff()));
    connect(m_ui->logButton, SIGNAL(clicked()), this, SLOT(log()));
74
    connect(m_ui->resetButton, SIGNAL(clicked()), this, SLOT(reset()));
Petar Perisin's avatar
Petar Perisin committed
75
76
    connect(m_ui->mergeButton, SIGNAL(clicked()), this, SLOT(merge()));
    connect(m_ui->rebaseButton, SIGNAL(clicked()), this, SLOT(rebase()));
77
    connect(m_ui->cherryPickButton, SIGNAL(clicked()), this, SLOT(cherryPick()));
Orgad Shaneh's avatar
Orgad Shaneh committed
78
    connect(m_ui->trackButton, SIGNAL(clicked()), this, SLOT(setRemoteTracking()));
79

Tobias Hunger's avatar
Tobias Hunger committed
80
    m_ui->branchView->setModel(m_model);
81

Tobias Hunger's avatar
Tobias Hunger committed
82
83
    connect(m_ui->branchView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
            this, SLOT(enableButtons()));
84

Tobias Hunger's avatar
Tobias Hunger committed
85
    enableButtons();
86
87
88
89
90
91
92
93
94
95
}

BranchDialog::~BranchDialog()
{
    delete m_ui;
}

void BranchDialog::refresh(const QString &repository, bool force)
{
    if (m_repository == repository && !force)
Tobias Hunger's avatar
Tobias Hunger committed
96
97
        return;

98
99
    m_repository = repository;
    m_ui->repositoryLabel->setText(StashDialog::msgRepositoryLabel(m_repository));
Tobias Hunger's avatar
Tobias Hunger committed
100
101
    QString errorMessage;
    if (!m_model->refresh(m_repository, &errorMessage))
hjk's avatar
hjk committed
102
        VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
103

Tobias Hunger's avatar
Tobias Hunger committed
104
    m_ui->branchView->expandAll();
105
106
}

107
108
109
110
111
112
void BranchDialog::refreshIfSame(const QString &repository)
{
    if (m_repository == repository)
        refresh();
}

Tobias Hunger's avatar
Tobias Hunger committed
113
void BranchDialog::enableButtons()
114
{
Tobias Hunger's avatar
Tobias Hunger committed
115
    QModelIndex idx = selectedIndex();
Orgad Shaneh's avatar
Orgad Shaneh committed
116
    QModelIndex currentBranch = m_model->currentBranch();
Tobias Hunger's avatar
Tobias Hunger committed
117
    const bool hasSelection = idx.isValid();
Orgad Shaneh's avatar
Orgad Shaneh committed
118
    const bool currentSelected = hasSelection && idx == currentBranch;
Tobias Hunger's avatar
Tobias Hunger committed
119
120
    const bool isLocal = m_model->isLocal(idx);
    const bool isLeaf = m_model->isLeaf(idx);
121
    const bool isTag = m_model->isTag(idx);
122
    const bool hasActions = hasSelection && isLeaf;
Orgad Shaneh's avatar
Orgad Shaneh committed
123
    const bool currentLocal = m_model->isLocal(currentBranch);
124

125
126
    m_ui->removeButton->setEnabled(hasActions && !currentSelected && (isLocal || isTag));
    m_ui->renameButton->setEnabled(hasActions && (isLocal || isTag));
127
128
129
130
    m_ui->logButton->setEnabled(hasActions);
    m_ui->diffButton->setEnabled(hasActions);
    m_ui->checkoutButton->setEnabled(hasActions && !currentSelected);
    m_ui->rebaseButton->setEnabled(hasActions && !currentSelected);
131
    m_ui->resetButton->setEnabled(hasActions && currentLocal && !currentSelected);
132
    m_ui->mergeButton->setEnabled(hasActions && !currentSelected);
133
    m_ui->cherryPickButton->setEnabled(hasActions && !currentSelected);
Orgad Shaneh's avatar
Orgad Shaneh committed
134
    m_ui->trackButton->setEnabled(hasActions && currentLocal && !currentSelected && !isTag);
135
136
}

Tobias Hunger's avatar
Tobias Hunger committed
137
void BranchDialog::refresh()
138
139
{
    refresh(m_repository, true);
140
141
}

Tobias Hunger's avatar
Tobias Hunger committed
142
void BranchDialog::add()
143
{
144
    QModelIndex trackedIndex = selectedIndex();
145
    QString trackedBranch = m_model->fullName(trackedIndex);
Tobias Hunger's avatar
Tobias Hunger committed
146
    if (trackedBranch.isEmpty()) {
147
        trackedIndex = m_model->currentBranch();
148
        trackedBranch = m_model->fullName(trackedIndex);
Tobias Hunger's avatar
Tobias Hunger committed
149
    }
150
    const bool isLocal = m_model->isLocal(trackedIndex);
Orgad Shaneh's avatar
Orgad Shaneh committed
151
    const bool isTag = m_model->isTag(trackedIndex);
152

Tobias Hunger's avatar
Tobias Hunger committed
153
    QStringList localNames = m_model->localBranchNames();
154

155
156
157
158
159
160
161
162
163
164
    QString suggestedName;
    if (!isTag) {
        QString suggestedNameBase;
        suggestedNameBase = trackedBranch.mid(trackedBranch.lastIndexOf(QLatin1Char('/')) + 1);
        suggestedName = suggestedNameBase;
        int i = 2;
        while (localNames.contains(suggestedName)) {
            suggestedName = suggestedNameBase + QString::number(i);
            ++i;
        }
Tobias Hunger's avatar
Tobias Hunger committed
165
    }
166

167
    BranchAddDialog branchAddDialog(localNames, true, this);
Tobias Hunger's avatar
Tobias Hunger committed
168
    branchAddDialog.setBranchName(suggestedName);
Orgad Shaneh's avatar
Orgad Shaneh committed
169
    branchAddDialog.setTrackedBranchName(isTag ? QString() : trackedBranch, !isLocal);
Tobias Hunger's avatar
Tobias Hunger committed
170

171
    if (branchAddDialog.exec() == QDialog::Accepted) {
172
        QModelIndex idx = m_model->addBranch(branchAddDialog.branchName(), branchAddDialog.track(), trackedIndex);
173
174
        if (!idx.isValid())
            return;
Tobias Hunger's avatar
Tobias Hunger committed
175
176
177
178
        m_ui->branchView->selectionModel()->select(idx, QItemSelectionModel::Clear
                                                        | QItemSelectionModel::Select
                                                        | QItemSelectionModel::Current);
        m_ui->branchView->scrollTo(idx);
179
180
181
        if (QMessageBox::question(this, tr("Checkout"), tr("Checkout branch?"),
                                  QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)
            checkout();
182
183
184
    }
}

Tobias Hunger's avatar
Tobias Hunger committed
185
void BranchDialog::checkout()
186
{
187
188
    if (!Core::DocumentManager::saveAllModifiedDocuments())
        return;
Tobias Hunger's avatar
Tobias Hunger committed
189
    QModelIndex idx = selectedIndex();
190

191
192
    const QString currentBranch = m_model->fullName(m_model->currentBranch());
    const QString nextBranch = m_model->fullName(idx);
Petar Perisin's avatar
Petar Perisin committed
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
    const QString popMessageStart = QCoreApplication::applicationName() +
            QLatin1String(" ") + nextBranch + QLatin1String("-AutoStash ");

    BranchCheckoutDialog branchCheckoutDialog(this, currentBranch, nextBranch);
    GitClient *gitClient = GitPlugin::instance()->gitClient();

    if (gitClient->gitStatus(m_repository, StatusMode(NoUntracked | NoSubmodules)) != GitClient::StatusChanged)
        branchCheckoutDialog.foundNoLocalChanges();

    QList<Stash> stashes;
    gitClient->synchronousStashList(m_repository, &stashes);
    foreach (const Stash &stash, stashes) {
        if (stash.message.startsWith(popMessageStart)) {
            branchCheckoutDialog.foundStashForNextBranch();
            break;
        }
    }

    if (!branchCheckoutDialog.hasLocalChanges() &&
        !branchCheckoutDialog.hasStashForNextBranch()) {
        // No local changes and no Auto Stash - no need to open dialog
        m_model->checkoutBranch(idx);
215
    } else if (branchCheckoutDialog.exec() == QDialog::Accepted) {
Petar Perisin's avatar
Petar Perisin committed
216

217
        if (branchCheckoutDialog.makeStashOfCurrentBranch()) {
Orgad Shaneh's avatar
Orgad Shaneh committed
218
219
            if (gitClient->synchronousStash(m_repository,
                           currentBranch + QLatin1String("-AutoStash")).isEmpty()) {
220
221
222
223
                return;
            }
        } else if (branchCheckoutDialog.moveLocalChangesToNextBranch()) {
            if (!gitClient->beginStashScope(m_repository, QLatin1String("Checkout"), NoPrompt))
224
                return;
Petar Perisin's avatar
Petar Perisin committed
225
        } else if (branchCheckoutDialog.discardLocalChanges()) {
226
227
            if (!gitClient->synchronousReset(m_repository))
                return;
Petar Perisin's avatar
Petar Perisin committed
228
229
230
231
232
233
234
235
236
237
238
239
240
        }

        m_model->checkoutBranch(idx);

        QString stashName;
        gitClient->synchronousStashList(m_repository, &stashes);
        foreach (const Stash &stash, stashes) {
            if (stash.message.startsWith(popMessageStart)) {
                stashName = stash.name;
                break;
            }
        }

241
        if (branchCheckoutDialog.moveLocalChangesToNextBranch())
242
            gitClient->endStashScope(m_repository);
Petar Perisin's avatar
Petar Perisin committed
243
        else if (branchCheckoutDialog.popStashOfNextBranch())
244
            gitClient->synchronousStashRestore(m_repository, stashName, true);
Petar Perisin's avatar
Petar Perisin committed
245
    }
Tobias Hunger's avatar
Tobias Hunger committed
246
    enableButtons();
247
248
}

Tobias Hunger's avatar
Tobias Hunger committed
249
250
/* Prompt to delete a local branch and do so. */
void BranchDialog::remove()
251
{
Tobias Hunger's avatar
Tobias Hunger committed
252
    QModelIndex selected = selectedIndex();
253
    QTC_CHECK(selected != m_model->currentBranch()); // otherwise the button would not be enabled!
Tobias Hunger's avatar
Tobias Hunger committed
254

255
    QString branchName = m_model->fullName(selected);
Tobias Hunger's avatar
Tobias Hunger committed
256
    if (branchName.isEmpty())
257
        return;
Tobias Hunger's avatar
Tobias Hunger committed
258

259
260
    const bool isTag = m_model->isTag(selected);
    const bool wasMerged = isTag ? true : m_model->branchIsMerged(selected);
Friedemann Kleint's avatar
Friedemann Kleint committed
261
    QString message;
262
    if (isTag)
263
        message = tr("Would you like to delete the tag \"%1\"?").arg(branchName);
Friedemann Kleint's avatar
Friedemann Kleint committed
264
    else if (wasMerged)
265
        message = tr("Would you like to delete the branch \"%1\"?").arg(branchName);
266
    else
267
        message = tr("Would you like to delete the <b>unmerged</b> branch \"%1\"?").arg(branchName);
268
269
270
271
272
273
274
275
276

    if (QMessageBox::question(this, isTag ? tr("Delete Tag") : tr("Delete Branch"),
                              message, QMessageBox::Yes | QMessageBox::No,
                              wasMerged ? QMessageBox::Yes : QMessageBox::No) == QMessageBox::Yes) {
        if (isTag)
            m_model->removeTag(selected);
        else
            m_model->removeBranch(selected);
    }
277
278
}

Petar Perisin's avatar
Petar Perisin committed
279
280
281
282
void BranchDialog::rename()
{
    QModelIndex selected = selectedIndex();
    QTC_CHECK(selected != m_model->currentBranch()); // otherwise the button would not be enabled!
283
284
    const bool isTag = m_model->isTag(selected);
    QTC_CHECK(m_model->isLocal(selected) || isTag);
Petar Perisin's avatar
Petar Perisin committed
285

286
    QString oldName = m_model->fullName(selected);
287
288
289
    QStringList localNames;
    if (!isTag)
        localNames = m_model->localBranchNames();
Petar Perisin's avatar
Petar Perisin committed
290

291
    BranchAddDialog branchAddDialog(localNames, false, this);
292
293
294
    if (isTag)
        branchAddDialog.setWindowTitle(tr("Rename Tag"));
    branchAddDialog.setBranchName(oldName);
Orgad Shaneh's avatar
Orgad Shaneh committed
295
    branchAddDialog.setTrackedBranchName(QString(), false);
Petar Perisin's avatar
Petar Perisin committed
296

Orgad Shaneh's avatar
Orgad Shaneh committed
297
    branchAddDialog.exec();
Petar Perisin's avatar
Petar Perisin committed
298

299
    if (branchAddDialog.result() == QDialog::Accepted) {
300
        if (branchAddDialog.branchName() == oldName)
Petar Perisin's avatar
Petar Perisin committed
301
            return;
302
303
304
305
        if (isTag)
            m_model->renameTag(oldName, branchAddDialog.branchName());
        else
            m_model->renameBranch(oldName, branchAddDialog.branchName());
Petar Perisin's avatar
Petar Perisin committed
306
307
308
309
310
        refresh();
    }
    enableButtons();
}

Tobias Hunger's avatar
Tobias Hunger committed
311
void BranchDialog::diff()
312
{
313
    QString fullName = m_model->fullName(selectedIndex(), true);
314
    if (fullName.isEmpty())
315
        return;
316
    // Do not pass working dir by reference since it might change
jkobus's avatar
jkobus committed
317
    GitPlugin::instance()->gitClient()->diffBranch(QString(m_repository), fullName);
318
319
}

Tobias Hunger's avatar
Tobias Hunger committed
320
void BranchDialog::log()
321
{
322
    QString branchName = m_model->fullName(selectedIndex(), true);
Tobias Hunger's avatar
Tobias Hunger committed
323
    if (branchName.isEmpty())
324
        return;
325
    // Do not pass working dir by reference since it might change
326
    GitPlugin::instance()->gitClient()->log(QString(m_repository), QString(), false, QStringList(branchName));
327
328
}

329
330
void BranchDialog::reset()
{
331
332
    QString currentName = m_model->fullName(m_model->currentBranch());
    QString branchName = m_model->fullName(selectedIndex());
333
334
335
    if (currentName.isEmpty() || branchName.isEmpty())
        return;

336
    if (QMessageBox::question(this, tr("Git Reset"), tr("Hard reset branch \"%1\" to \"%2\"?")
337
338
339
340
341
342
343
344
                              .arg(currentName).arg(branchName),
                              QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) {
        GitPlugin::instance()->gitClient()->reset(QString(m_repository), QLatin1String("--hard"),
                                                  branchName);

    }
}

Petar Perisin's avatar
Petar Perisin committed
345
346
void BranchDialog::merge()
{
347
348
    if (!Core::DocumentManager::saveAllModifiedDocuments())
        return;
Petar Perisin's avatar
Petar Perisin committed
349
    QModelIndex idx = selectedIndex();
Orgad Shaneh's avatar
Orgad Shaneh committed
350
    QTC_CHECK(idx != m_model->currentBranch()); // otherwise the button would not be enabled!
Petar Perisin's avatar
Petar Perisin committed
351

352
    const QString branch = m_model->fullName(idx, true);
Orgad Shaneh's avatar
Orgad Shaneh committed
353
    GitClient *client = GitPlugin::instance()->gitClient();
354
355
356
357
358
359
360
361
362
363
    bool allowFastForward = true;
    if (client->isFastForwardMerge(m_repository, branch)) {
        QMenu popup;
        QAction *fastForward = popup.addAction(tr("Fast-Forward"));
        popup.addAction(tr("No Fast-Forward"));
        QAction *chosen = Utils::execMenuAtWidget(&popup, m_ui->mergeButton);
        if (!chosen)
            return;
        allowFastForward = (chosen == fastForward);
    }
Orgad Shaneh's avatar
Orgad Shaneh committed
364
    if (client->beginStashScope(m_repository, QLatin1String("merge"), AllowUnstashed))
365
        client->synchronousMerge(m_repository, branch, allowFastForward);
Petar Perisin's avatar
Petar Perisin committed
366
367
368
369
}

void BranchDialog::rebase()
{
370
371
    if (!Core::DocumentManager::saveAllModifiedDocuments())
        return;
Petar Perisin's avatar
Petar Perisin committed
372
    QModelIndex idx = selectedIndex();
Orgad Shaneh's avatar
Orgad Shaneh committed
373
    QTC_CHECK(idx != m_model->currentBranch()); // otherwise the button would not be enabled!
Petar Perisin's avatar
Petar Perisin committed
374

375
    const QString baseBranch = m_model->fullName(idx, true);
Orgad Shaneh's avatar
Orgad Shaneh committed
376
377
    GitClient *client = GitPlugin::instance()->gitClient();
    if (client->beginStashScope(m_repository, QLatin1String("rebase")))
378
        client->rebase(m_repository, baseBranch);
Petar Perisin's avatar
Petar Perisin committed
379
380
}

381
382
void BranchDialog::cherryPick()
{
383
384
    if (!Core::DocumentManager::saveAllModifiedDocuments())
        return;
385
386
387
388
389
390
391
    QModelIndex idx = selectedIndex();
    QTC_CHECK(idx != m_model->currentBranch()); // otherwise the button would not be enabled!

    const QString branch = m_model->fullName(idx, true);
    GitPlugin::instance()->gitClient()->synchronousCherryPick(m_repository, branch);
}

Orgad Shaneh's avatar
Orgad Shaneh committed
392
393
394
395
396
void BranchDialog::setRemoteTracking()
{
    m_model->setRemoteTracking(selectedIndex());
}

Tobias Hunger's avatar
Tobias Hunger committed
397
398
399
400
401
402
403
404
QModelIndex BranchDialog::selectedIndex()
{
    QModelIndexList selected = m_ui->branchView->selectionModel()->selectedIndexes();
    if (selected.isEmpty())
        return QModelIndex();
    return selected.at(0);
}

405
406
} // namespace Internal
} // namespace Git