def _parse(self): if self.changeset is not None: return self.changeset = {} maxrev = 0 if self.rev: # TODO: handle tags try: # patchset number? maxrev = int(self.rev) except ValueError: raise util.Abort( _('revision %s is not a patchset number') % self.rev) d = os.getcwd() try: os.chdir(self.path) id = None cache = 'update' if not self.ui.configbool('convert', 'cvsps.cache', True): cache = None db = cvsps.createlog(self.ui, cache=cache) db = cvsps.createchangeset( self.ui, db, fuzz=int(self.ui.config('convert', 'cvsps.fuzz', 60)), mergeto=self.ui.config('convert', 'cvsps.mergeto', None), mergefrom=self.ui.config('convert', 'cvsps.mergefrom', None)) for cs in db: if maxrev and cs.id > maxrev: break id = str(cs.id) cs.author = self.recode(cs.author) self.lastbranch[cs.branch] = id cs.comment = self.recode(cs.comment) date = util.datestr(cs.date) self.tags.update(dict.fromkeys(cs.tags, id)) files = {} for f in cs.entries: files[f.file] = "%s%s" % ('.'.join( [str(x) for x in f.revision]), ['', '(DEAD)'][f.dead]) # add current commit to set c = commit(author=cs.author, date=date, parents=[str(p.id) for p in cs.parents], desc=cs.comment, branch=cs.branch or '') self.changeset[id] = c self.files[id] = files self.heads = self.lastbranch.values() finally: os.chdir(d)
def getcommit(self, rev): certs = self.mtngetcerts(rev) return commit(author=certs["author"], date=util.datestr( util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")), desc=certs["changelog"], rev=rev, parents=self.mtnrun("parents", rev).splitlines(), branch=certs["branch"])
def getcommit(self, rev): elt = self.changes[rev] date = util.strdate(elt.get('local_date'), '%a %b %d %H:%M:%S %Z %Y') desc = elt.findtext('name') + '\n' + elt.findtext('comment', '') # etree can return unicode objects for name, comment, and author, # so recode() is used to ensure str objects are emitted. return commit(author=self.recode(elt.get('author')), date=util.datestr(date), desc=self.recode(desc).strip(), parents=self.parents[rev])
def sendemail(self, address, data): p = email.Parser.Parser() msg = p.parsestr(data) msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2") msg['To'] = address msg['From'] = self.emailfrom msg['Subject'] = 'DeliverXML' msg['Content-type'] = 'text/xml' msgtext = msg.as_string() self.ui.status(_('hgcia: sending update to %s\n') % address) mail.sendmail(self.ui, util.email(self.emailfrom), [address], msgtext)
def getcommit(self, rev): ctx = self.changectx(rev) parents = [p.hex() for p in self.parents(ctx)] if self.saverev: crev = rev else: crev = None return commit(author=ctx.user(), date=util.datestr(ctx.date()), desc=ctx.description(), rev=crev, parents=parents, branch=ctx.branch(), extra=ctx.extra(), sortkey=ctx.rev())
def _parsecatlog(self, data, rev): try: catlog = self.catlogparser.parsestr(data) # Commit date self.changes[rev].date = util.datestr( util.strdate(catlog['Standard-date'], '%Y-%m-%d %H:%M:%S')) # Commit author self.changes[rev].author = self.recode(catlog['Creator']) # Commit description self.changes[rev].summary = '\n\n'.join((catlog['Summary'], catlog.get_payload())) self.changes[rev].summary = self.recode(self.changes[rev].summary) # Commit revision origin when dealing with a branch or tag if 'Continuation-of' in catlog: self.changes[rev].continuationof = self.recode( catlog['Continuation-of']) except Exception: raise util.Abort(_('could not parse cat-log of %s') % rev)
commands.optionalrepo += ' kwdemo' # hg commands that do not act on keywords nokwcommands = ('add addremove annotate bundle export grep incoming init log' ' outgoing push tip verify convert email glog') # hg commands that trigger expansion only when writing to working dir, # not when reading filelog, and unexpand when reading from working dir restricted = 'merge kwexpand kwshrink record qrecord resolve transplant' # names of extensions using dorecord recordextensions = 'record' # date like in cvs' $Date utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S') # date like in svn's $Date svnisodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)') # date like in svn's $Id svnutcdate = lambda x: util.datestr((x[0], 0), '%Y-%m-%d %H:%M:%SZ') # make keyword tools accessible kwtools = {'templater': None, 'hgcmd': ''} def _defaultkwmaps(ui): '''Returns default keywordmaps according to keywordset configuration.''' templates = { 'Revision': '{node|short}', 'Author': '{author|user}', }
def parselogentry(orig_paths, revnum, author, date, message): """Return the parsed commit object or None, and True if the revision is a branch root. """ self.ui.debug("parsing revision %d (%d changes)\n" % (revnum, len(orig_paths))) branched = False rev = self.revid(revnum) # branch log might return entries for a parent we already have if rev in self.commits or revnum < to_revnum: return None, branched parents = [] # check whether this revision is the start of a branch or part # of a branch renaming orig_paths = sorted(orig_paths.iteritems()) root_paths = [(p, e) for p, e in orig_paths if self.module.startswith(p)] if root_paths: path, ent = root_paths[-1] if ent.copyfrom_path: branched = True newpath = ent.copyfrom_path + self.module[len(path):] # ent.copyfrom_rev may not be the actual last revision previd = self.latest(newpath, ent.copyfrom_rev) if previd is not None: prevmodule, prevnum = self.revsplit(previd)[1:] if prevnum >= self.startrev: parents = [previd] self.ui.note( _('found parent of branch %s at %d: %s\n') % (self.module, prevnum, prevmodule)) else: self.ui.debug("no copyfrom path, don't know what to do.\n") paths = [] # filter out unrelated paths for path, ent in orig_paths: if self.getrelpath(path) is None: continue paths.append((path, ent)) # Example SVN datetime. Includes microseconds. # ISO-8601 conformant # '2007-01-04T17:35:00.902377Z' date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"]) log = message and self.recode(message) or '' author = author and self.recode(author) or '' try: branch = self.module.split("/")[-1] if branch == 'trunk': branch = '' except IndexError: branch = None cset = commit(author=author, date=util.datestr(date), desc=log, parents=parents, branch=branch, rev=rev) self.commits[rev] = cset # The parents list is *shared* among self.paths and the # commit object. Both will be updated below. self.paths[rev] = (paths, cset.parents) if self.child_cset and not self.child_cset.parents: self.child_cset.parents[:] = [rev] self.child_cset = cset return cset, branched
def _parse(self, ui, path): "Prepare list of P4 filenames and revisions to import" ui.status(_('reading p4 views\n')) # read client spec or view if "/" in path: self._parse_view(path) if path.startswith("//") and path.endswith("/..."): views = {path[:-3]:""} else: views = {"//": ""} else: cmd = 'p4 -G client -o %s' % util.shellquote(path) clientspec = marshal.load(util.popen(cmd, mode='rb')) views = {} for client in clientspec: if client.startswith("View"): sview, cview = clientspec[client].split() self._parse_view(sview) if sview.endswith("...") and cview.endswith("..."): sview = sview[:-3] cview = cview[:-3] cview = cview[2:] cview = cview[cview.find("/") + 1:] views[sview] = cview # list of changes that affect our source files self.p4changes = self.p4changes.keys() self.p4changes.sort(key=int) # list with depot pathnames, longest first vieworder = views.keys() vieworder.sort(key=len, reverse=True) # handle revision limiting startrev = self.ui.config('convert', 'p4.startrev', default=0) self.p4changes = [x for x in self.p4changes if ((not startrev or int(x) >= int(startrev)) and (not self.rev or int(x) <= int(self.rev)))] # now read the full changelists to get the list of file revisions ui.status(_('collecting p4 changelists\n')) lastid = None for change in self.p4changes: cmd = "p4 -G describe -s %s" % change stdout = util.popen(cmd, mode='rb') d = marshal.load(stdout) desc = self.recode(d["desc"]) shortdesc = desc.split("\n", 1)[0] t = '%s %s' % (d["change"], repr(shortdesc)[1:-1]) ui.status(util.ellipsis(t, 80) + '\n') if lastid: parents = [lastid] else: parents = [] date = (int(d["time"]), 0) # timezone not set c = commit(author=self.recode(d["user"]), date=util.datestr(date), parents=parents, desc=desc, branch='', extra={"p4": change}) files = [] i = 0 while ("depotFile%d" % i) in d and ("rev%d" % i) in d: oldname = d["depotFile%d" % i] filename = None for v in vieworder: if oldname.startswith(v): filename = views[v] + oldname[len(v):] break if filename: files.append((filename, d["rev%d" % i])) self.depotname[filename] = oldname i += 1 self.changeset[change] = c self.files[change] = files lastid = change if lastid: self.heads = [lastid]
def createlog(ui, directory=None, root="", rlog=True, cache=None): '''Collect the CVS rlog''' # Because we store many duplicate commit log messages, reusing strings # saves a lot of memory and pickle storage space. _scache = {} def scache(s): "return a shared version of a string" return _scache.setdefault(s, s) ui.status(_('collecting CVS rlog\n')) log = [] # list of logentry objects containing the CVS state # patterns to match in CVS (r)log output, by state of use re_00 = re.compile('RCS file: (.+)$') re_01 = re.compile('cvs \\[r?log aborted\\]: (.+)$') re_02 = re.compile('cvs (r?log|server): (.+)\n$') re_03 = re.compile("(Cannot access.+CVSROOT)|" "(can't create temporary directory.+)$") re_10 = re.compile('Working file: (.+)$') re_20 = re.compile('symbolic names:') re_30 = re.compile('\t(.+): ([\\d.]+)$') re_31 = re.compile('----------------------------$') re_32 = re.compile('=======================================' '======================================$') re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$') re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);' r'(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?' r'(.*mergepoint:\s+([^;]+);)?') re_70 = re.compile('branches: (.+);$') file_added_re = re.compile(r'file [^/]+ was (initially )?added on branch') prefix = '' # leading path to strip of what we get from CVS if directory is None: # Current working directory # Get the real directory in the repository try: prefix = open(os.path.join('CVS', 'Repository')).read().strip() directory = prefix if prefix == ".": prefix = "" except IOError: raise logerror(_('not a CVS sandbox')) if prefix and not prefix.endswith(os.sep): prefix += os.sep # Use the Root file in the sandbox, if it exists try: root = open(os.path.join('CVS', 'Root')).read().strip() except IOError: pass if not root: root = os.environ.get('CVSROOT', '') # read log cache if one exists oldlog = [] date = None if cache: cachedir = os.path.expanduser('~/.hg.cvsps') if not os.path.exists(cachedir): os.mkdir(cachedir) # The cvsps cache pickle needs a uniquified name, based on the # repository location. The address may have all sort of nasties # in it, slashes, colons and such. So here we take just the # alphanumerics, concatenated in a way that does not mix up the # various components, so that # :pserver:user@server:/path # and # /pserver/user/server/path # are mapped to different cache file names. cachefile = root.split(":") + [directory, "cache"] cachefile = ['-'.join(re.findall(r'\w+', s)) for s in cachefile if s] cachefile = os.path.join(cachedir, '.'.join([s for s in cachefile if s])) if cache == 'update': try: ui.note(_('reading cvs log cache %s\n') % cachefile) oldlog = pickle.load(open(cachefile)) ui.note(_('cache has %d log entries\n') % len(oldlog)) except Exception, e: ui.note(_('error reading cache: %r\n') % e) if oldlog: date = oldlog[-1].date # last commit date as a (time,tz) tuple date = util.datestr(date, '%Y/%m/%d %H:%M:%S %1%2')
if cs.branch not in branches and cs.parents and cs.parents[0].id: ancestors[cs.branch] = (changesets[cs.parents[0].id - 1].branch, cs.parents[0].id) branches[cs.branch] = cs.id # limit by branches if opts["branches"] and (cs.branch or 'HEAD') not in opts["branches"]: continue if not off: # Note: trailing spaces on several lines here are needed to have # bug-for-bug compatibility with cvsps. ui.write('---------------------\n') ui.write('PatchSet %d \n' % cs.id) ui.write('Date: %s\n' % util.datestr(cs.date, '%Y/%m/%d %H:%M:%S %1%2')) ui.write('Author: %s\n' % cs.author) ui.write('Branch: %s\n' % (cs.branch or 'HEAD')) ui.write( 'Tag%s: %s \n' % (['', 's'][len(cs.tags) > 1], ','.join(cs.tags) or '(none)')) branchpoints = getattr(cs, 'branchpoints', None) if branchpoints: ui.write('Branchpoints: %s \n' % ', '.join(branchpoints)) if opts["parents"] and cs.parents: if len(cs.parents) > 1: ui.write('Parents: %s\n' % (','.join([str(p.id) for p in cs.parents]))) else: ui.write('Parent: %d\n' % cs.parents[0].id)
class notifier(object): '''email notification class.''' def __init__(self, ui, repo, hooktype): self.ui = ui cfg = self.ui.config('notify', 'config') if cfg: self.ui.readconfig(cfg, sections=['usersubs', 'reposubs']) self.repo = repo self.stripcount = int(self.ui.config('notify', 'strip', 0)) self.root = self.strip(self.repo.root) self.domain = self.ui.config('notify', 'domain') self.test = self.ui.configbool('notify', 'test', True) self.charsets = mail._charsets(self.ui) self.subs = self.subscribers() self.merge = self.ui.configbool('notify', 'merge', True) mapfile = self.ui.config('notify', 'style') template = (self.ui.config('notify', hooktype) or self.ui.config('notify', 'template')) self.t = cmdutil.changeset_templater(self.ui, self.repo, False, None, mapfile, False) if not mapfile and not template: template = deftemplates.get(hooktype) or single_template if template: template = templater.parsestring(template, quoted=False) self.t.use_template(template) def strip(self, path): '''strip leading slashes from local path, turn into web-safe path.''' path = util.pconvert(path) count = self.stripcount while count > 0: c = path.find('/') if c == -1: break path = path[c + 1:] count -= 1 return path def fixmail(self, addr): '''try to clean up email addresses.''' addr = util.email(addr.strip()) if self.domain: a = addr.find('@localhost') if a != -1: addr = addr[:a] if '@' not in addr: return addr + '@' + self.domain return addr def subscribers(self): '''return list of email addresses of subscribers to this repo.''' subs = set() for user, pats in self.ui.configitems('usersubs'): for pat in pats.split(','): if fnmatch.fnmatch(self.repo.root, pat.strip()): subs.add(self.fixmail(user)) for pat, users in self.ui.configitems('reposubs'): if fnmatch.fnmatch(self.repo.root, pat): for user in users.split(','): subs.add(self.fixmail(user)) return [ mail.addressencode(self.ui, s, self.charsets, self.test) for s in sorted(subs) ] def url(self, path=None): return self.ui.config('web', 'baseurl') + (path or self.root) def node(self, ctx, **props): '''format one changeset, unless it is a suppressed merge.''' if not self.merge and len(ctx.parents()) > 1: return False self.t.show(ctx, changes=ctx.changeset(), baseurl=self.ui.config('web', 'baseurl'), root=self.repo.root, webroot=self.root, **props) return True def skipsource(self, source): '''true if incoming changes from this source should be skipped.''' ok_sources = self.ui.config('notify', 'sources', 'serve').split() return source not in ok_sources def send(self, ctx, count, data): '''send message.''' p = email.Parser.Parser() try: msg = p.parsestr(data) except email.Errors.MessageParseError, inst: raise util.Abort(inst) # store sender and subject sender, subject = msg['From'], msg['Subject'] del msg['From'], msg['Subject'] if not msg.is_multipart(): # create fresh mime message from scratch # (multipart templates must take care of this themselves) headers = msg.items() payload = msg.get_payload() # for notification prefer readability over data precision msg = mail.mimeencode(self.ui, payload, self.charsets, self.test) # reinstate custom headers for k, v in headers: msg[k] = v msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2") # try to make subject line exist and be useful if not subject: if count > 1: subject = _('%s: %d new changesets') % (self.root, count) else: s = ctx.description().lstrip().split('\n', 1)[0].rstrip() subject = '%s: %s' % (self.root, s) maxsubject = int(self.ui.config('notify', 'maxsubject', 67)) if maxsubject: subject = util.ellipsis(subject, maxsubject) msg['Subject'] = mail.headencode(self.ui, subject, self.charsets, self.test) # try to make message have proper sender if not sender: sender = self.ui.config('email', 'from') or self.ui.username() if '@' not in sender or '@localhost' in sender: sender = self.fixmail(sender) msg['From'] = mail.addressencode(self.ui, sender, self.charsets, self.test) msg['X-Hg-Notification'] = 'changeset %s' % ctx if not msg['Message-Id']: msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' % (ctx, int( time.time()), hash(self.repo.root), socket.getfqdn())) msg['To'] = ', '.join(self.subs) msgtext = msg.as_string() if self.test: self.ui.write(msgtext) if not msgtext.endswith('\n'): self.ui.write('\n') else: self.ui.status( _('notify: sending %d subscribers %d changes\n') % (len(self.subs), count)) mail.sendmail(self.ui, util.email(msg['From']), self.subs, msgtext)