File: migrate_work.cc

package info (click to toggle)
monotone 0.48-3
  • links: PTS
  • area: main
  • in suites: squeeze
  • size: 20,096 kB
  • ctags: 8,077
  • sloc: cpp: 81,000; sh: 6,402; perl: 1,241; lisp: 1,045; makefile: 655; python: 566; sql: 112; ansic: 52
file content (287 lines) | stat: -rw-r--r-- 10,309 bytes parent folder | download
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// Copyright (C) 2006 Zack Weinberg <zackw@panix.com>
//
// This program is made available under the GNU GPL version 2.0 or
// greater. See the accompanying file COPYING for details.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE.

#include "base.hh"
#include "sanity.hh"
#include "cset.hh"
#include "simplestring_xform.hh"
#include "revision.hh"
#include "file_io.hh"
#include "work.hh"
#include "transforms.hh"

#include "lexical_cast.hh"
#include <exception>

using std::string;
using std::exception;
using boost::lexical_cast;

// This file's primary entry point is workspace::migrate_format.  It is
// responsible for migrating workspace directories from metadata formats
// used by older versions of monotone.  This file also defines the other
// workspace:: functions related to metadata format.  Whenever a new
// workspace format is added, this file must be updated and a test must be
// added to tests/workspace_migration/, following the instructions in that
// file.

// Workspace metadata formats have a revision number, which is a simple
// nonnegative integer.  Any given version of monotone supports normal use
// of exactly one format, the "current" format; it also supports 'migrating'
// from all previous formats.  The current metadata format is recorded in
// this constant:
static const unsigned int current_workspace_format = 2;

// This is the oldest released version of monotone that supports the current
// format.
static const char first_version_supporting_current_format[] = "0.30";

// In a workspace, the metadata format's revision number is, notionally,
// stored in the file _MTN/format.  However, this file only appears in
// metadata formats 2 and later.  Format 1 is indicated by the _absence_
// of _MTN/format.  Format 0 is even older, and is indicated by the
// metadata directory being named "MT", not "_MTN".  All these little
// details are handled by the following two functions.  Note that
// write_format is a public interface, but get_workspace_format is not
// (the corresponding public interface is check_format, below).

static unsigned int
get_workspace_format()
{
  unsigned int format;
  bookkeeping_path f_path = bookkeeping_root / "format";
  if (!file_exists(f_path))
    {
      if (directory_exists(bookkeeping_root))
        format = 1;
      else if (directory_exists(file_path() / old_bookkeeping_root_component))
        format = 0;
      else
        E(false, origin::user, F("workspace required but not found"));
    }
  else
    {
      data f_dat;
      try
        {
          read_data(f_path, f_dat);
          format = lexical_cast<unsigned int>(remove_ws(f_dat()));
        }
      catch (exception & e)
        {
          E(false, origin::system,
            F("workspace is corrupt: %s is invalid")
            % f_path);
        }
      if (format == 1)
        {
          W(F("_MTN/format should not exist in a format 1 workspace; corrected"));
          delete_file(f_path);
        }
    }
  return format;
}

void
workspace::write_format()
{
  bookkeeping_path f_path = bookkeeping_root / "format";
  // one or other side of this conditional will always be dead code, but
  // both sides should be preserved, to document all historical formats.
  // N.B. this will _not_ do the right thing for format 0.  Which is fine.
  if (current_workspace_format <= 1)
    {
      if (file_exists(f_path))
        delete_file(f_path);
    }
  else
    {
      data f_dat(lexical_cast<string>(current_workspace_format) + "\n",
                 origin::workspace);
      write_data(f_path, f_dat);
    }
}

// This function is the public face of get_workspace_format.  It produces
// suitable error messages if the workspace's format number is not
// equal to current_workspace_format.

void
workspace::check_format()
{
  if (!workspace::found)
    return;

  unsigned int format = get_workspace_format();

  // Don't give user false expectations about format 0.
  E(format > 0, origin::system,
    F("this workspace's metadata is in format 0. to use this workspace\n"
      "with this version of monotone, you must delete it and check it\n"
      "out again (migration from format 0 is not possible).\n"
      "once you have done this, you will not be able to use the workspace\n"
      "with versions of monotone older than %s.\n"
      "we apologize for the inconvenience.")
    % first_version_supporting_current_format);

  E(format >= current_workspace_format, origin::system,
    F("to use this workspace with this version of monotone, its metadata\n"
      "must be migrated from format %d to format %d, using the command\n"
      "'%s migrate_workspace'.\n"
      "once you have done this, you will not be able to use the workspace\n"
      "with versions of monotone older than %s.")
    % format % current_workspace_format % prog_name
    % first_version_supporting_current_format);

  // keep this message in sync with the copy in migrate_format
  E(format <= current_workspace_format, origin::system,
    F("this version of monotone only understands workspace metadata\n"
      "in formats 0 through %d.  your workspace is in format %d.\n"
      "you need a newer version of monotone to use this workspace.")
    % current_workspace_format % format);
}


// Workspace migration is done incrementally.  The functions defined below
// each perform one step.  Note that they must access bookkeeping directory
// files directly, not via work.cc APIs, as those APIs expect a workspace in
// the current format.  Also, note that these functions do not have access
// to the database, lua hooks, or keys; this is because we want the
// migration command to work without options, but work.cc may not know how
// to read options from an old workspace.

static void
migrate_0_to_1()
{
  // Notionally, converting a format 0 workspace to a format 1 workspace is
  // done by renaming the bookkeeping directory from "MT" to "_MTN" and the
  // ignore file from ".mt-ignore" to ".mtn-ignore".  However, there is no
  // point in implementing this, because the first version of monotone that
  // supported workspace format 1 (0.26) also brought a database flag day
  // that invalidates the revision number cached in the bookkeeping
  // directory.  There is no programmatic way to find the new revision
  // number corresponding to what was cached.  Thus, even if we did convert
  // the workspace, it would still be unusable.

  E(false, origin::system,
    F("it is not possible to migrate from workspace format 0 to any\n"
      "later format.  you must delete this workspace and check it out\n"
      "again.  we apologize for the inconvenience."));
}

static void
migrate_1_to_2()
{
  // In format 1, the parent revision ID of the checkout is stored bare in a
  // file named _MTN/revision, and any directory tree operations are in cset
  // format in _MTN/work, which does not exist if that cset is empty (no
  // changes or only content changes).  In format 2, _MTN/revision contains
  // a serialized revision (qua revision.hh), carrying both pieces of
  // information, and _MTN/work does not exist; also, there may be more than
  // one parent revision, but we do not have to worry about that here.

  bookkeeping_path rev_path = bookkeeping_root / "revision";
  data base_rev_data; MM(base_rev_data);
  try
    {
      read_data(rev_path, base_rev_data);
    }
  catch (exception & e)
    {
      E(false, origin::system, F("workspace is corrupt: reading %s: %s")
        % rev_path % e.what());
    }
  revision_id base_rid(decode_hexenc_as<revision_id>(remove_ws(base_rev_data()),
                                                     origin::system));
  MM(base_rid);

  cset workcs;
  MM(workcs);
  bookkeeping_path workcs_path = bookkeeping_root / "work";
  bool delete_workcs = false;
  if (file_exists(workcs_path))
    {
      delete_workcs = true;
      data workcs_data; MM(workcs_data);
      try
        {
          read_data(workcs_path, workcs_data);
        }
      catch (exception & e)
        {
          E(false, origin::system,
            F("workspace is corrupt: reading %s: %s")
            % workcs_path % e.what());
        }

      read_cset(workcs_data, workcs);
    }
  else
    require_path_is_nonexistent(workcs_path,
                                F("workspace is corrupt: "
                                  "%s exists but is not a regular file")
                                % workcs_path);

  revision_t rev;
  MM(rev);
  make_revision_for_workspace(base_rid, workcs, rev);
  data rev_data;
  write_revision(rev, rev_data);
  write_data(rev_path, rev_data);
  if (delete_workcs)
    delete_file(workcs_path);
}

// This function is the public face of the migrate_X_to_X+1 functions.

void
workspace::migrate_format()
{
  unsigned int format = get_workspace_format();

  // When adding new migrations, note the organization of the first block of
  // case entries in this switch statement.  There are entries each of the
  // numbers 0 ... C-1 (where C is current_workspace_format); each calls the
  // migrate_<n>_to_<n+1> function, AND DROPS THROUGH.  Thus, when we
  // encounter a workspace in format K < C, the migrate_K_to_K+1,
  // migrate_K+1_to_K+2, ..., migrate_C-1_to_C functions will all be called.
  // The last entry drops through to the write_format() line.

  switch (format)
    {
    case 0: migrate_0_to_1();
    case 1: migrate_1_to_2();

      // We are now in the current format.
      write_format();
      break;

    case current_workspace_format:
      P(F("this workspace is in the current format, "
          "no migration is necessary."));
      break;

    default:
      I(format > current_workspace_format);
      // keep this message in sync with the copy in check_format
      E(false, origin::system,
        F("this version of monotone only understands workspace metadata\n"
          "in formats 0 through %d.  your workspace is in format %d.\n"
          "you need a newer version of monotone to use this workspace.")
        % current_workspace_format % format);
    }
}

// Local Variables:
// mode: C++
// fill-column: 76
// c-file-style: "gnu"
// indent-tabs-mode: nil
// End:
// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s: