File: VersContCvs.py

package info (click to toggle)
aap 1.072-1.1
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k, lenny
  • size: 4,976 kB
  • ctags: 2,160
  • sloc: python: 15,113; makefile: 62; sh: 13
file content (453 lines) | stat: -rw-r--r-- 16,938 bytes parent folder | download | duplicates (2)
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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
# Part of the A-A-P recipe executive: CVS access

# Copyright (C) 2002-2003 Stichting NLnet Labs
# Permission to copy and use this file is specified in the file COPYING.
# If this file is missing you can find it here: http://www.a-a-p.org/COPYING

#
# Functions to get files out of a CVS repository and put them back.
# See interface.txt for an explanation of what each function does.
#

import string
import os
import os.path

from Error import *
from Message import *
from Util import *

def cvs_command(recdict, server, url_dict, nodelist, action):
    """Handle CVS command "action".
       Return non-zero when it worked."""
    # Since CVS doesn't do locking, quite a few commands can be simplified:
    if action == "fetch":
        action = "checkout"    # "fetch" is exactly the same as "checkout"
    elif action in [ "checkin", "publish" ]:
        action = "commit"   # "checkin" and "publish" are the same as "commit"
    elif action == "unlock":
        return []           # unlocking is a no-op

    # All CVS commands require an argument to specify where the server is.
    if not server:
        # Obtain the previously used CVSROOT from CVS/Root.
        # There are several of these files that contain the same info, just use
        # the one in the current directory.
        try:
            f = open("CVS/Root")
        except StandardError, e:
            msg_extra(recdict,
                   _('Cannot open for obtaining CVSROOT: "CVS/Root"') + str(e))
        else:
            try:
                server = f.readline()
                f.close()
            except StandardError, e:
                msg_warning(recdict,
                   _('Cannot read for obtaining CVSROOT: "CVS/Root"') + str(e))
                server = ''     # in case something was read
            else:
                if server[-1] == '\n':
                    server = server[:-1]
    if server:
        serverarg = "-d" + server
    else:
        serverarg = ''

    #
    # Loop over the list of nodes and handle each separately.  This is required
    # to be able to do something useful with an error message.
    # For a "tag" command all nodes with the same tag are done at once to speed
    # it up.
    #
    failed = []

    if action == "tag":
        # Loop over todolist, taking out nodes with identical tags, until it's
        # empty.
        todolist = nodelist[:]
        while todolist:
            tag = ''
            thislist = []
            for node in todolist[:]:
                # Use the specified "tag" attribute for tagging.
                if not node.attributes.has_key("tag"):
                    msg_error(recdict, _('tag attribute missing for "%s"')
                                                           % node.short_name())
                    failed.extend(todolist)
                    failed.extend(thislist)
                    return failed
                if not tag or node.attributes["tag"] == tag:
                    thislist.append(node)
                    todolist.remove(node)
                    tag = node.attributes["tag"]
            f = cvs_tag(recdict, serverarg, tag, thislist)
            if f:
                failed.extend(f)

    else:
        for node in nodelist:
            if not cvs_command_node(recdict, serverarg, url_dict, node, action):
                failed.append(node)

    return failed


def cvs_prepare(recdict):
    """
    Prepare for using the cvs command.  Returns the program name.
    """
    # Install cvs when needed.
    from DoInstall import assert_pkg
    assert_pkg([], recdict, "cvs")

    # Create ~/.cvspass if it doesn't exist.  An empty file should be
    # sufficient for anonymous logins.
    if os.environ.get("HOME"):
        fn = os.path.expanduser("~/.cvspass")
        if not os.path.exists(fn):
            from Commands import touch_file
            touch_file(fn, 0644)

    n = get_var_val(0, recdict, "_no", "CVSCMD")
    if n:
        return n

    # Use $CVS if defined, otherwise use "cvs".
    return get_progname(recdict, "CVS", "cvs", "")


def cvs_tag(recdict, serverarg, tag, nodelist):
    """Handle CVS tag command for a list of nodes.
       Return list of nodes that failed."""
    msg_info(recdict, _('CVS tag for nodes %s')
                                % str(map(lambda x: x.short_name(), nodelist)))

    # Prepare for using CVS and get the command name.
    cvscmd = cvs_prepare(recdict)

    names = ''
    for node in nodelist:
        names = names + '"' + node.short_name() + '" '

    # TODO: check which of the nodes actually failed
    if logged_system(recdict,
                 '"%s" %s tag "%s" %s' % (cvscmd, serverarg, tag, names)) == 0:
        return []
    return nodelist


def cvs_get_repository(recdict, dir):
    """Get the first line of the CVS/Repository file in directory "dir"."""
    cvspath = ''
    fname = os.path.join(dir, "CVS/Repository")
    try:
        f = open(fname)
    except StandardError, e:
        # Only give this error when the directory exists, when it doesn't it's
        # probably the first time the files are checked out.
        if os.path.exists(os.path.join(dir, "CVS")):
            msg_warning(recdict,
                (_('Cannot open for obtaining path in module: "%s"')
                                                             % fname) + str(e))
    else:
        try:
            cvspath = f.readline()
            f.close()
        except StandardError, e:
            msg_warning(recdict,
                    (_('Cannot read for obtaining path in module: "%s"')
                                                             % fname) + str(e))
        else:
            if cvspath[-1] == '\n':
                cvspath = cvspath[:-1]
    return cvspath


def cvs_command_node(recdict, serverarg, url_dict, node, action):
    """Handle CVS command "action" for one node.
       Return non-zero when it worked."""

    msg_info(recdict, _('CVS %s for node "%s"') % (action, node.short_name()))

    # Count the number of directories in the node name.
    n = node.short_name()
    dirlevels = 0
    while n:
        prev = n
        n = os.path.dirname(n)
        if n == prev:
            break
        dirlevels = dirlevels + 1

    # A "checkout" only works reliably when in the top directory  of the
    # module.
    # "add" must be done in the current directory of the file.
    # Change to the directory where "path" + "node.name" is valid.
    if action == "checkout":
        cvspath = ''
        if url_dict.has_key("path"):
            # Use the specified "path" attribute.
            cvspath = url_dict["path"]
            dir_for_path = node.recipe_dir
        else:
            # Try to obtain the path from the CVS/Repository file.
            if os.path.isdir(os.path.join(node.absname, "CVS")):
                dir_for_path = node.absname
            else:
                dir_for_path = os.path.dirname(node.absname)

            cvspath = cvs_get_repository(recdict, dir_for_path)

        # Use node.recipe_dir and take off one part for each part in "path".
        dir = fname_fold(dir_for_path)
        path = fname_fold(cvspath)
        while path:
            if os.path.basename(dir) != os.path.basename(path):
                # This might happen when a module has an alias.
                msg_note(recdict, _('mismatch between path in cvs:// and tail of recipe directory: "%s" and "%s"') % (cvspath, dir_for_path))
                break
            ndir = os.path.dirname(dir)
            if ndir == dir:
                # This is probably an error somewhere...
                msg_error(recdict, _('path in cvs:// is longer than recipe directory: "%s" and "%s"') % (cvspath, dir_for_path))
                break
            dir = ndir
            npath = os.path.dirname(path)
            if npath == path:   # just in case: avoid getting stuck
                break
            path = npath
            if not path:
                break

            # Check that the CVS repository mentioned here is correct.  Bail
            # out here when it isn't, happens when a full path was used in
            # CVS/Repository (CVS sometimes does that for unknown reasons).
            # Also happens when a module "foo" is an alias for "bar/foo".
            # CVS/Repository then contains "bar/foo" but we must checkout
            # "foo", because the "bar" directory doesn't exist.
            p = cvs_get_repository(recdict, dir)
            if not p:
                break
            # Extra check for wrong assumptions about CVS/Repository contents.
            if fname_fold(os.path.basename(p)) != os.path.basename(path):
                msg_note(recdict, _('mismatch between contents of CVS/Repository at different levels: "%s" and "%s"') % (dir, path))
                break
    else:
        dir = os.path.dirname(node.absname)

    # Use the specified "logentry" attribute for a log message.
    # Only used for "commit" (also for add and remove).
    if url_dict.has_key("logentry"):
        logentry = url_dict["logentry"]
    elif node.attributes.has_key("logentry"):
        logentry = node.attributes["logentry"]
    else:
        logentry = get_var_val_int(recdict, "LOGENTRY")

    # Changing directory, don't return until going back!
    cwd = os.getcwd()
    if fname_equal(cwd, dir):
        cwd = ''        # we're already there, avoid a chdir()
    else:
        try:
            os.chdir(dir)
        except StandardError, e:
            msg_warning(recdict,
                      (_('Could not change to directory "%s"') % dir) + str(e))
            return 0

    msg_log(recdict, 'Cvs command in "%s"' % dir)

    node_name = node.short_name()

    tmpname = ''
    if action == "remove" and os.path.exists(node_name):
        # CVS refuses to remove a file that still exists, temporarily rename
        # it.  Careful: must always move it back when an exception is thrown!
        assert_aap_dir(recdict)
        tmpname = in_aap_dir(node_name)
        try:
            os.rename(node_name, tmpname)
        except:
            tmpname = ''

    try:
        # If the node has a "binary" attribute, give CVS the "-kb" argument for
        # an "add" action (also for commit with auto-add).
        if node.attributes.get("binary"):
            addbinarg = "-kb"
        else:
            addbinarg = ""

        ok = exec_cvs_cmd(recdict, serverarg, action, addbinarg,
                                    logentry, node_name, dirlevels = dirlevels)

        # For a remove we must commit it now, otherwise the local file will be
        # deleted when doing it later.  To be consistent, also do it for "add".
        if ok and action in [ "remove", "add" ]:
            ok = exec_cvs_cmd(recdict, serverarg, "commit", "", logentry,
                                node_name, dirlevels = dirlevels, auto_add = 0)
    finally:
        if tmpname:
            try:
                os.rename(tmpname, node_name)
            except StandardError, e:
                msg_error(recdict, (_('Could not move file "%s" back to "%s"')
                                              % (tmpname, node_name)) + str(e))

        if cwd:
            try:
                os.chdir(cwd)
            except StandardError, e:
                msg_error(recdict, (_('Could not go back to directory "%s"')
                                                               % cwd) + str(e))

    # TODO: how to check if it really worked?
    return ok


def exec_cvs_cmd(recdict, serverarg, action, addbinarg, logentry, node_name,
                                                  dirlevels = 1, auto_add = 1):
    """Execute the CVS command for "action".  Handle failure.
       For "commit" may create directories up to "dirlevels" upwards.
       When "auto_add" is non-zero and committing fails, try to add the file
       first.
       Return non-zero when it worked."""

    # Prepare for using CVS and get the command name.
    cvscmd = cvs_prepare(recdict)

    if logentry:
        logarg = '-m "%s"' % logentry
    else:
        logarg = ''

    if action == "commit":
        # If the file was never added to the repository we need to add it.
        # Since most files will exist in the repository, trying to commit and
        # handling the error is the best method.

        # Repeat this when the directory needs to be added.
        did_add_dir = 0
        while 1:
            # TODO: escaping special characters
            cmd = ('"%s" %s commit %s "%s"'
                                      % (cvscmd, serverarg, logarg, node_name))
            ok, text = redir_system_int(recdict, cmd)

            if text:
                msg_log(recdict, text)

                # If the directory for the file doesn't exist, CVS says "there
                # is no version here".  Need to create the directory first.
                # This is only done for the number of levels that were included
                # in the node name for the original directory.
                # Errors are ignored, the commit will fail later.
                if ((string.find(text, "no version here") >= 0
                             or string.find(text, "not open CVS/Entries") >= 0)
                         and not did_add_dir
                         and auto_add):
                    msg_info(recdict, _("Directory does not appear to exist in repository, adding it"))
                    commit_dir(recdict, node_name, serverarg, dirlevels, cvscmd)
                    did_add_dir = 1
                    continue

                # If the file was never in the repository CVS says "nothing
                # known about".  If it was there before "use `cvs add' to
                # create an entry".
                if ok and (string.find(text, "nothing known about") >= 0
                                         or string.find(text, "cvs add") >= 0):
                    ok = 0
            break

        if ok or not auto_add:
            return ok

        try:
            msg_info(recdict,
                   _("File does not appear to exist in repository, adding it"))
            logged_system(recdict, '"%s" %s add %s %s'
                                   % (cvscmd, serverarg, addbinarg, node_name))
        except StandardError, e:
            msg_warning(recdict, _('Adding file failed: ') + str(e))


        # TODO: escaping special characters
        return logged_system(recdict, '"%s" %s commit %s "%s"'
                      % (cvscmd, serverarg, logarg, node_name)) == 0

    # TODO: escaping special characters
    if action != "add":
        addbinarg = ""
    return logged_system(recdict, '"%s" %s %s %s "%s"'
                      % (cvscmd, serverarg, action, addbinarg, node_name)) == 0


def commit_dir(recdict, node_name, serverarg, dirlevels, cvscmd):
    """Commit to create the current directory.  If its parent is not in CVS
       either go up further."""
    cwd = os.getcwd()
    try:
        os.chdir("..")
        dirname = os.path.dirname(node_name)
        if not dirname:
            dirname = os.path.basename(cwd)

        if dirlevels > 0 and not os.path.isdir("CVS"):
            commit_dir(recdict, dirname, serverarg, dirlevels - 1, cvscmd)

        logged_system(recdict, '"%s" %s add "%s"'
                                                % (cvscmd, serverarg, dirname))
    except:
        pass
    os.chdir(cwd)


def cvs_list(recdict, name, commit_item, dirname, recursive):
    """Obtain a list of items in CVS for directory "dirname".
       Recursively entry directories if "recursive" is non-zero.
       "name" is not used, we don't access the server."""
    # We could use "cvs status" to obtain the actual entries in the repository,
    # but that is slow and the output is verbose and hard to parse.
    # Instead read the "CVS/Entries" file.  A disadvantage is that we might
    # list a file that is actually already removed from the repository if
    # another user removed it.
    fname = os.path.join(dirname, "CVS/Entries")
    try:
        f = open(fname)
    except StandardError, e:
        msg_error(recdict, (_('Cannot open "%s": ') % fname) + str(e))
        return []
    try:
        lines = f.readlines()
        f.close()
    except StandardError, e:
        msg_error(recdict, (_('Cannot read "%s": ') % fname) + str(e))
        return []

    # The format of the lines is:
    #   D/dirname////
    #   /itemname/vers/foo//
    # We only need to extract "dirname" or "itemname".
    res = []
    for line in lines:
        s = string.find(line, "/")
        if s < 0:
            continue
        s = s + 1
        e = string.find(line, "/", s)
        if e < 0:
            continue
        item = os.path.join(dirname, line[s:e])

        if line[0] == 'D' and recursive:
            res.extend(cvs_list(recdict, name, commit_item, item, 1))
        else:
            res.append(item)

    return res



# vim: set sw=4 et sts=4 tw=79 fo+=l: