| """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. |
| """ |
| from __future__ import with_statement |
| import errno |
| import os |
| import time |
| |
| 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, basedir, output, inputs): |
| """Verify that the output is newer than any of the inputs. |
| Return (status, stamp), where status is True if the update succeeded, |
| and stamp is the newest time stamp assigned to any file (might be in |
| the future). |
| |
| If basedir is nonempty, it gives a directory in which the tree is to |
| be checked. |
| """ |
| f_output = repo.wjoin(os.path.join(basedir, output)) |
| try: |
| o_time = os.stat(f_output).st_mtime |
| except OSError: |
| ui.warn("Generated file %s does not exist\n" % output) |
| return False, 0 |
| youngest = 0 # youngest dependency |
| backdate = None |
| backdate_source = None |
| for i in inputs: |
| f_i = repo.wjoin(os.path.join(basedir, 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, 0 |
| 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 |
| youngest = max(i_time, youngest) |
| 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, 0 |
| if youngest >= o_time: |
| ui.note("Touching %s\n" % output) |
| youngest += 1 |
| os.utime(f_output, (youngest, youngest)) |
| return True, youngest |
| else: |
| # Nothing to update |
| return True, 0 |
| |
| def do_touch(ui, repo, basedir): |
| if basedir: |
| if not os.path.isdir(repo.wjoin(basedir)): |
| ui.warn("Abort: basedir %r does not exist\n" % basedir) |
| return |
| modified = [] |
| else: |
| modified = repo.status()[0] |
| dependencies = parse_config(repo) |
| success = True |
| tstamp = 0 # newest time stamp assigned |
| # 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, _tstamp = check_rule(ui, repo, modified, basedir, output, inputs) |
| success = success and _success |
| tstamp = max(tstamp, _tstamp) |
| # put back held back rules |
| dependencies.update(hold_back) |
| hold_back = {} |
| now = time.time() |
| if tstamp > now: |
| # wait until real time has passed the newest time stamp, to |
| # avoid having files dated in the future |
| time.sleep(tstamp-now) |
| if hold_back: |
| ui.warn("Cyclic dependency involving %s\n" % (' '.join(hold_back.keys()))) |
| return False |
| return success |
| |
| def touch(ui, repo, basedir): |
| "touch generated files that are older than their sources after an update." |
| do_touch(ui, repo, basedir) |
| |
| cmdtable = { |
| "touch": (touch, |
| [('b', 'basedir', '', 'base dir of the tree to apply touching')], |
| "hg touch [-b BASEDIR]") |
| } |