aboutsummaryrefslogtreecommitdiff
path: root/bpkg/drop.cxx
blob: e2fd45530871aa6406a55be1b09d733b56173757 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// file      : bpkg/drop.cxx -*- C++ -*-
// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
// license   : MIT; see accompanying LICENSE file

#include <bpkg/drop>

#include <map>
#include <iostream>   // cout

#include <butl/utility> // reverse_iterate()

#include <bpkg/types>
#include <bpkg/package>
#include <bpkg/package-odb>
#include <bpkg/utility>
#include <bpkg/database>
#include <bpkg/diagnostics>
#include <bpkg/satisfaction>
#include <bpkg/manifest-utility>

#include <bpkg/common-options>

#include <bpkg/pkg-purge>
#include <bpkg/pkg-disfigure>

using namespace std;
using namespace butl;

namespace bpkg
{
  using package_map = map<string, shared_ptr<selected_package>>;

  static void
  collect_dependent (database& db,
                     package_map& m,
                     const shared_ptr<selected_package>& p,
                     bool w)
  {
    using query = query<package_dependent>;

    bool found (false);

    for (auto& pd: db.query<package_dependent> (query::name == p->name))
    {
      string& dn (pd.name);

      if (m.find (dn) == m.end ())
      {
        shared_ptr<selected_package> dp (db.load<selected_package> (dn));
        m.emplace (move (dn), dp);

        collect_dependent (db, m, dp, w);

        if (w)
          warn << "dependent package " << dp->name << " to be dropped as well";

        found = true;
      }
    }

    if (w && found)
      info << "because dropping " << p->name;
  }

  int
  drop (const drop_options& o, cli::scanner& args)
  {
    tracer trace ("drop");

    const dir_path& c (o.directory ());
    level4 ([&]{trace << "configuration: " << c;});

    if (!args.more ())
      fail << "package name argument expected" <<
        info << "run 'bpkg help drop' for more information";

    database db (open (c, trace));

    // Note that the session spans all our transactions. The idea here is
    // that selected_package objects in the satisfied_packages list below
    // will be cached in this session. When subsequent transactions modify
    // any of these objects, they will modify the cached instance, which
    // means our list will always "see" their updated state.
    //
    // @@ Revise.
    //
    session s;

    // Assemble the list of packages we will need to drop. Comparing pointers
    // is valid because of the session above.
    //
    package_map pkgs;
    vector<string> names;
    {
      transaction t (db.begin ());

      // The first step is to load all the packages specified by the user.
      //
      while (args.more ())
      {
        string n (args.next ());
        level4 ([&]{trace << "package " << n;});

        shared_ptr<selected_package> p (db.find<selected_package> (n));

        if (p == nullptr)
          fail << "package " << n << " does not exist in configuration " << c;

        if (p->state == package_state::broken)
          fail << "unable to drop broken package " << n <<
            info << "use 'pkg-purge --force' to remove";

        if (pkgs.emplace (n, move (p)).second)
          names.push_back (move (n));
      }

      // The next step is to see if there are any dependents that are not
      // already on the list. We will have to drop those as well.
      //
      for (const string& n: names)
      {
        const shared_ptr<selected_package>& p (pkgs[n]);

        // Unconfigured package cannot have any dependents.
        //
        if (p->state != package_state::configured)
          continue;

        collect_dependent (db, pkgs, p, !o.drop_dependent ());
      }

      // If we've found dependents, ask the user to confirm.
      //
      if (!o.drop_dependent () && names.size () != pkgs.size ())
      {
        if (o.yes ())
          fail << "refusing to drop dependent packages with just --yes" <<
            info << "specify --drop-dependent to confirm";

        if (!yn_prompt ("drop dependent packages? [y/N]", 'n'))
          return 1;
      }

      t.commit ();
    }

    return 0;
  }
}