• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Bring time stamps of generated checked-in files into the right order
2
3A versioned configuration file .hgtouch specifies generated files, in the
4syntax of make rules.
5
6  output:    input1 input2
7
8In addition to the dependency syntax, #-comments are supported.
9"""
10import errno
11import os
12import time
13
14def parse_config(repo):
15    try:
16        fp = repo.wfile(".hgtouch")
17    except IOError, e:
18        if e.errno != errno.ENOENT:
19            raise
20        return {}
21    result = {}
22    with fp:
23        for line in fp:
24            # strip comments
25            line = line.split('#')[0].strip()
26            if ':' not in line:
27                continue
28            outputs, inputs = line.split(':', 1)
29            outputs = outputs.split()
30            inputs = inputs.split()
31            for o in outputs:
32                try:
33                    result[o].extend(inputs)
34                except KeyError:
35                    result[o] = inputs
36    return result
37
38def check_rule(ui, repo, modified, basedir, output, inputs):
39    """Verify that the output is newer than any of the inputs.
40    Return (status, stamp), where status is True if the update succeeded,
41    and stamp is the newest time stamp assigned  to any file (might be in
42    the future).
43
44    If basedir is nonempty, it gives a directory in which the tree is to
45    be checked.
46    """
47    f_output = repo.wjoin(os.path.join(basedir, output))
48    try:
49        o_time = os.stat(f_output).st_mtime
50    except OSError:
51        ui.warn("Generated file %s does not exist\n" % output)
52        return False, 0
53    youngest = 0   # youngest dependency
54    backdate = None
55    backdate_source = None
56    for i in inputs:
57        f_i = repo.wjoin(os.path.join(basedir, i))
58        try:
59            i_time = os.stat(f_i).st_mtime
60        except OSError:
61            ui.warn(".hgtouch input file %s does not exist\n" % i)
62            return False, 0
63        if i in modified:
64            # input is modified. Need to backdate at least to i_time
65            if backdate is None or backdate > i_time:
66                backdate = i_time
67                backdate_source = i
68            continue
69        youngest = max(i_time, youngest)
70    if backdate is not None:
71        ui.warn("Input %s for file %s locally modified\n" % (backdate_source, output))
72        # set to 1s before oldest modified input
73        backdate -= 1
74        os.utime(f_output, (backdate, backdate))
75        return False, 0
76    if youngest >= o_time:
77        ui.note("Touching %s\n" % output)
78        youngest += 1
79        os.utime(f_output, (youngest, youngest))
80        return True, youngest
81    else:
82        # Nothing to update
83        return True, 0
84
85def do_touch(ui, repo, basedir):
86    if basedir:
87        if not os.path.isdir(repo.wjoin(basedir)):
88            ui.warn("Abort: basedir %r does not exist\n" % basedir)
89            return
90        modified = []
91    else:
92        modified = repo.status()[0]
93    dependencies = parse_config(repo)
94    success = True
95    tstamp = 0       # newest time stamp assigned
96    # try processing all rules in topological order
97    hold_back = {}
98    while dependencies:
99        output, inputs = dependencies.popitem()
100        # check whether any of the inputs is generated
101        for i in inputs:
102            if i in dependencies:
103                hold_back[output] = inputs
104                continue
105        _success, _tstamp = check_rule(ui, repo, modified, basedir, output, inputs)
106        success = success and _success
107        tstamp = max(tstamp, _tstamp)
108        # put back held back rules
109        dependencies.update(hold_back)
110        hold_back = {}
111    now = time.time()
112    if tstamp > now:
113        # wait until real time has passed the newest time stamp, to
114        # avoid having files dated in the future
115        time.sleep(tstamp-now)
116    if hold_back:
117        ui.warn("Cyclic dependency involving %s\n" % (' '.join(hold_back.keys())))
118        return False
119    return success
120
121def touch(ui, repo, basedir):
122    "touch generated files that are older than their sources after an update."
123    do_touch(ui, repo, basedir)
124
125cmdtable = {
126    "touch": (touch,
127              [('b', 'basedir', '', 'base dir of the tree to apply touching')],
128              "hg touch [-b BASEDIR]")
129}
130