summaryrefslogtreecommitdiffstats
path: root/Tools/hg/hgtouch.py
blob: 5961a10ad02ac2e19bc07751d84942f8ab584f7b (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
"""Bring time stamps of generated checked-in files into the right order

A versioned configuration file .hgtouch specifies generated files, in the
syntax of make rules.

  output:    input1 input2

In addition to the dependency syntax, #-comments are supported.
"""
import errno
import os

def parse_config(repo):
    try:
        fp = repo.wfile(".hgtouch")
    except IOError, e:
        if e.errno != errno.ENOENT:
            raise
        return {}
    result = {}
    with fp:
        for line in fp:
            # strip comments
            line = line.split('#')[0].strip()
            if ':' not in line:
                continue
            outputs, inputs = line.split(':', 1)
            outputs = outputs.split()
            inputs = inputs.split()
            for o in outputs:
                try:
                    result[o].extend(inputs)
                except KeyError:
                    result[o] = inputs
    return result

def check_rule(ui, repo, modified, output, inputs):
    f_output = repo.wjoin(output)
    try:
        o_time = os.stat(f_output).st_mtime
    except OSError:
        ui.warn("Generated file %s does not exist\n" % output)
        return False
    need_touch = False
    backdate = None
    backdate_source = None
    for i in inputs:
        f_i = repo.wjoin(i)
        try:
            i_time = os.stat(f_i).st_mtime
        except OSError:
            ui.warn(".hgtouch input file %s does not exist\n" % i)
            return False
        if i in modified:
            # input is modified. Need to backdate at least to i_time
            if backdate is None or backdate > i_time:
                backdate = i_time
                backdate_source = i
            continue
        if o_time <= i_time:
            # generated file is older, touch
            need_touch = True
    if backdate is not None:
        ui.warn("Input %s for file %s locally modified\n" % (backdate_source, output))
        # set to 1s before oldest modified input
        backdate -= 1
        os.utime(f_output, (backdate, backdate))
        return False
    if need_touch:
        ui.note("Touching %s\n" % output)
        os.utime(f_output, None)
    return True

def do_touch(ui, repo):
    modified = repo.status()[0]
    dependencies = parse_config(repo)
    success = True
    # try processing all rules in topological order
    hold_back = {}
    while dependencies:
        output, inputs = dependencies.popitem()
        # check whether any of the inputs is generated
        for i in inputs:
            if i in dependencies:
                hold_back[output] = inputs
                continue
        success = check_rule(ui, repo, modified, output, inputs)
        # put back held back rules
        dependencies.update(hold_back)
        hold_back = {}
    if hold_back:
        ui.warn("Cyclic dependency involving %s\n" % (' '.join(hold_back.keys())))
        return False
    return success

def touch(ui, repo):
    "touch generated files that are older than their sources after an update."
    do_touch(ui, repo)

cmdtable = {
    "touch": (touch, [],
              "touch generated files according to the .hgtouch configuration")
}