Home | History | Annotate | Download | only in hg
      1 """Bring time stamps of generated checked-in files into the right order
      2 
      3 A versioned configuration file .hgtouch specifies generated files, in the
      4 syntax of make rules.
      5 
      6   output:    input1 input2
      7 
      8 In addition to the dependency syntax, #-comments are supported.
      9 """
     10 import errno
     11 import os
     12 import time
     13 
     14 def 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 
     38 def 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 
     85 def 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 
    121 def touch(ui, repo, basedir):
    122     "touch generated files that are older than their sources after an update."
    123     do_touch(ui, repo, basedir)
    124 
    125 cmdtable = {
    126     "touch": (touch,
    127               [('b', 'basedir', '', 'base dir of the tree to apply touching')],
    128               "hg touch [-b BASEDIR]")
    129 }
    130