diff -Nru reposurgeon-3.10/control reposurgeon-3.12/control --- reposurgeon-3.10/control 2013-12-01 15:12:42.000000000 +0000 +++ reposurgeon-3.12/control 2014-06-20 19:45:10.000000000 +0000 @@ -12,8 +12,6 @@ production of very high-quality conversions from Subversion to any supported DVCS. -XBS-Destinations: freecode - XBS-Gitorious-URL: https://gitorious.org/reposurgeon/ XBS-IRC-Channel: irc://chat.freenode.net/#reposurgeon @@ -28,6 +26,6 @@ XBS-Web-Extras: conversion.mk -XBS-Freecode-Tags: version control, svn, git, hg, bzr, cvs, rcs - XBS-VC-Tag-Template: %(version)s + +XBS-Validate: make check diff -Nru reposurgeon-3.10/conversion.mk reposurgeon-3.12/conversion.mk --- reposurgeon-3.10/conversion.mk 2014-02-23 04:46:00.000000000 +0000 +++ reposurgeon-3.12/conversion.mk 2014-08-11 17:52:54.000000000 +0000 @@ -7,7 +7,9 @@ # 3. Set SOURCE_VCS to svn or cvs # 4. Set TARGET_VCS to git, hg, or bzr # 5. For svn, set SVN_URL to point at the remote repository you want to convert. -# 6. For cvs, set CVS_HOST to the repo hostname and CVS_MODULE to the module. +# 6. For cvs, set CVS_HOST to the repo hostname and CVS_MODULE to the module. +# Note: for CVS hosts other than Sourceforge or Savannah you will need to +# include the path to the CVS modules directory after the hosyname. # 7. Create a $(PROJECT).lift script for your custom commands, initially empty. # 8. Run 'make stubmap' to create a stub author map. # 9. (Optional) set REPOSURGEON to point at a faster cython build of the tool @@ -26,7 +28,8 @@ TARGET_VCS = git EXTRAS = SVN_URL = svn://svn.debian.org/$(PROJECT) -CVS_HOST = $(PROJECT).cvs.sourceforge.net +CVS_HOST = cvs.sourceforge.net +#CVS_HOST = cvs.savannah.gnu.org CVS_MODULE = $(PROJECT) VERBOSITY = "verbose 1" REPOSURGEON = reposurgeon @@ -85,9 +88,10 @@ ifeq ($(SOURCE_VCS),cvs) # Mirror a CVS repo. Requires cvssync(1) from the cvs-fast-export -# distribution. You will need cvs-fast-export installed as well. +# distribution, version 1.13 or later. You will need to have cvs-fast-export +# installed as well. $(PROJECT)-mirror: - cvssync -c -o $(PROJECT)-mirror "$(CVS_HOST):/cvsroot/$(PROJECT)" $(CVS_MODULE) + cvssync -c -o $(PROJECT)-mirror "cvs://$(CVS_HOST)/$(PROJECT)#$(CVS_MODULE)" # Build the first-stage CVS stream dump from the local mirror $(PROJECT).cvs: $(PROJECT)-mirror diff -Nru reposurgeon-3.10/debian/changelog reposurgeon-3.12/debian/changelog --- reposurgeon-3.10/debian/changelog 2014-08-23 05:55:08.000000000 +0000 +++ reposurgeon-3.12/debian/changelog 2014-10-18 18:33:16.000000000 +0000 @@ -1,4 +1,10 @@ -reposurgeon (3.10-1~ppa1~t) trusty; urgency=low +reposurgeon (3.12-1~ppa1~t) trusty; urgency=medium + + * New upstream release 3.12 + + -- pi-rho Sat, 18 Oct 2014 13:29:44 -0500 + +reposurgeon (3.10-1~ppa1~uuu) UNRELEASED; urgency=low * New upstream release 3.10 diff -Nru reposurgeon-3.10/debian/changelog.old reposurgeon-3.12/debian/changelog.old --- reposurgeon-3.10/debian/changelog.old 2014-08-23 05:55:05.000000000 +0000 +++ reposurgeon-3.12/debian/changelog.old 2014-10-18 18:33:14.000000000 +0000 @@ -1,3 +1,9 @@ +reposurgeon (3.12-1~ppa1~uuu) UNRELEASED; urgency=medium + + * New upstream release 3.12 + + -- pi-rho Sat, 18 Oct 2014 13:29:44 -0500 + reposurgeon (3.10-1~ppa1~uuu) UNRELEASED; urgency=low * New upstream release 3.10 diff -Nru reposurgeon-3.10/debian/patches/001-shebang.patch reposurgeon-3.12/debian/patches/001-shebang.patch --- reposurgeon-3.10/debian/patches/001-shebang.patch 2014-08-23 05:55:05.000000000 +0000 +++ reposurgeon-3.12/debian/patches/001-shebang.patch 2014-10-18 18:33:14.000000000 +0000 @@ -11,33 +11,6 @@ # # Compare git repositories or fast-import streams for differences. # -@@ -132,7 +132,7 @@ class Baton: - class Commit(object): - __slots__ = ("repo", "text", "commit", "tree", "author", "committer", - "comment", "authordate", "commitdate", "matched", -- "parents", "mark") -+ "parents", "mark") - def __init__(self, repo, text): - self.repo = repo - self.text = text -@@ -403,7 +403,7 @@ class Repository: - "Sort by Committer-Date in case commits were generated in topo order." - self.signature.sort(key=lambda c: c.timestamp()) - def tagnames(self): -- return set(self.tags.keys()) -+ return set(self.tags.keys()) - def __str__(self): - return "<%s: %s>" % (self.path, self.signature) - def __del__(self): -@@ -682,7 +682,7 @@ if __name__ == '__main__': - sys.stderr.write("repodiffer: input and output directory or import-stream arguments are required.\n") - sys.exit(1) - else: -- (alpha, beta) = arguments[:2] -+ (alpha, beta) = arguments[:2] - if not os.path.exists(alpha): - sys.stderr.write("repodiffer: %s must exist.\n" % alpha) - sys.exit(1) --- a/reposurgeon +++ b/reposurgeon @@ -1,4 +1,4 @@ @@ -46,583 +19,3 @@ # # reposurgeon - a repository surgeon. # -@@ -15,7 +15,7 @@ import functools, filecmp - import email.message, email.parser - - # This import only works on Unixes. The intention is to enable --# Ctrl-P, Ctrl-N, and friends in Cmd. -+# Ctrl-P, Ctrl-N, and friends in Cmd. - try: - import readline - except ImportError: -@@ -181,7 +181,7 @@ vcstypes = [ - dfltignores="", # Has none - project="http://git-scm.com/", - notes="The authormap is not required, but will be used if present."), -- # -+ # - VCS(name="bzr", - subdirectory=".bzr", - exporter="bzr fast-export --no-plain %(basename)s", -@@ -225,7 +225,7 @@ bzr-orphans - dfltignores="", - project="http://mercurial.selenic.com/", - notes="The hg export-import methods are not part of stock Mercurial."), -- # Styleflags may need tweaking for round-tripping -+ # Styleflags may need tweaking for round-tripping - VCS(name="darcs", - subdirectory="_darcs", - exporter="darcs fastconvert export", -@@ -324,7 +324,7 @@ class Fatal(Exception): - - # How to write extractor classes: - # --# Clone one of the existing ones and mutate. -+# Clone one of the existing ones and mutate. - # - # Significant fact: None of the get_* methods for extracting information about - # a revision is called until after checkout has been called on that revision. -@@ -524,7 +524,7 @@ DEBUG_FILEMAP = 3 # Debug building - DEBUG_DELETE = 3 # Debug canonicalization after deletes - DEBUG_IGNORES = 3 # Debug ignore generation - DEBUG_SVNPARSE = 4 # Lower-level Subversion parsing details --DEBUG_EMAILIN = 4 # Debug event round-tripping through mailbox_{out|in} -+DEBUG_EMAILIN = 4 # Debug event round-tripping through mailbox_{out|in} - DEBUG_SHUFFLE = 4 # Debug file and directory handling - DEBUG_COMMANDS = 5 # Show commands as they are executed - DEBUG_UNITE = 5 # Debug mark assignments in merging -@@ -692,7 +692,7 @@ class RepoSurgeonEmail(email.message.Mes - Divider = 78 * "-" - __hash__ = None - def __init__(self, **kwargs): -- email.message.Message.__init__(self, **kwargs) -+ email.message.Message.__init__(self, **kwargs) - self.set_unixfrom(RepoSurgeonEmail.Divider) - @staticmethod - def readmsg(fp): -@@ -759,7 +759,7 @@ class Date(object): - return - except ValueError: - # time.strptime() throws this -- # "time data 'xxxxxx' does not match format '%Y-%m-%dT%H:%M:%S'" -+ # "time data 'xxxxxx' does not match format '%Y-%m-%dT%H:%M:%S'" - pass - # Date format not recognized - raise error("'%s' is not a valid timestamp" % text) -@@ -892,7 +892,7 @@ class Attribution(object): - if m: - return (m.group(1).strip(), m.group(2), m.group(3).strip()) - else: -- raise Fatal("malformed attribution '%s'" % line) -+ raise Fatal("malformed attribution '%s'" % line) - def __init__(self, operson=None): - self.name = self.email = self.date = None - if operson: -@@ -916,7 +916,7 @@ class Attribution(object): - self.email = mail - if timezone: - self.date.orig_tz_string = timezone -- break -+ break - def action_stamp(self): - return self.date.rfc3339() + "!" + self.email - def __eq__(self, other): -@@ -951,7 +951,7 @@ class Blob(object): - def blobfile(self, create=False): - "File where the content lives." - stem = repr(id(self)) -- parts = ("blobs", stem[:3], stem[3:6], stem[6:]) -+ parts = ("blobs", stem[:3], stem[3:6], stem[6:]) - if create: - for d in range(len(parts)-1): - partial = os.path.join(self.repo.subdir(), *parts[:d+1]) -@@ -1485,7 +1485,7 @@ class Commit(object): - successor_branches = {child.branch for child in self.children() if child.parents()[0] == self} - if len(successor_branches) == 1 and successor_branches.pop() == self.branch: - return -- return "%6d\tcommit\t%s" % (eventnum+1, self.branch) -+ return "%6d\tcommit\t%s" % (eventnum+1, self.branch) - def email_out(self, modifiers, eventnum): - "Enable do_mailbox_out() to report these." - msg = RepoSurgeonEmail() -@@ -2288,7 +2288,7 @@ class RepoStreamer: - repo.addEvent(reset) - commit.fileops.sort(key=FileOp.sortkey) - commit.fossil_id = revision -- commit.properties.update(self.extractor.get_properties(revision)) -+ commit.properties.update(self.extractor.get_properties(revision)) - commit.set_mark(self.__newmark()) - self.commit_map[revision] = commit - if debug_enable(DEBUG_EXTRACT): -@@ -2592,7 +2592,7 @@ class StreamParser: - try: - self.fp = fp - # Optimization: if we're reading from a plain file, -- # no need to clone all the blobs. -+ # no need to clone all the blobs. - if os.path.isfile(self.fp.name): - # We can't just pass the input file object here, it - # leads to bad results when fast_import is called -@@ -2699,7 +2699,7 @@ class StreamParser: - elif line.startswith("Node-action: "): - node.action = StreamParser.sd_body(line) - node.action = StreamParser.NodeAction.ActionValues.index(node.action) -- -+ - if node.action is None: - self.error("unknown action %s" \ - % node.action) -@@ -2960,7 +2960,7 @@ class StreamParser: - return 0o100755 - elif "svn:special" in node.props: - # Map to git symlink, which behaves the same way. -- # Blob contents is the path the link should resolve to. -+ # Blob contents is the path the link should resolve to. - return 0o120000 - return 0o100644 - def branchpath(self, path): -@@ -3038,7 +3038,7 @@ class StreamParser: - filemaps[revision] = filemap.snapshot() - baton.twirl() - del filemap -- self.repo.timings.append(["filemaps", time.time()]) -+ self.repo.timings.append(["filemaps", time.time()]) - baton.twirl() - # Blows up huge on large repos... - #if debug_enable(DEBUG_FILEMAP): -@@ -3063,7 +3063,7 @@ class StreamParser: - any(filemaps[copynode.revision].ls_R(node.path)): - self.gripe("inconsistently empty from set for %s" % copynode) - baton.twirl() -- self.repo.timings.append(["copysets", time.time()]) -+ self.repo.timings.append(["copysets", time.time()]) - baton.twirl() - # Build commits - # This code can eat your processor, so we make it give up -@@ -3140,7 +3140,7 @@ class StreamParser: - # Zero revision is never interesting - no operations, no - # comment, no author, it's just a start marker for a - # non-incremental dump. -- if revision == "0": -+ if revision == "0": - continue - expanded_nodes = [] - has_properties = set() -@@ -3670,7 +3670,7 @@ class StreamParser: - if debug_enable(DEBUG_TOPOLOGY): - complain("lookback for %s failed" % latest) - raise Fatal("couldn't find a branch root for the copy of %s at r%s." % (latest.path, latest.revision)) -- # We're done, add all the new commits -+ # We're done, add all the new commits - self.repo.events += newcommits - self.repo.declare_sequence_mutation() - # Report progress, and give up our scheduler slot -@@ -3684,7 +3684,7 @@ class StreamParser: - self.fileop_branchlinks.discard("trunk" + os.sep) - if self.fileop_branchlinks - self.directory_branchlinks: - self.gripe("branch links detected by file ops only: %s" % " ".join(self.fileop_branchlinks - self.directory_branchlinks)) -- self.repo.timings.append(["commits", time.time()]) -+ self.repo.timings.append(["commits", time.time()]) - if debug_enable(DEBUG_EXTRACT): - announce("at post-parsing time:") - for commit in self.repo.commits(): -@@ -3742,7 +3742,7 @@ class StreamParser: - self.branches["root"] = None - lastbranch = branch - baton.twirl() -- self.repo.timings.append(["branches", time.time()]) -+ self.repo.timings.append(["branches", time.time()]) - baton.twirl() - # ...then rebuild parent links so they follow the branches - for commit in self.repo.commits(): -@@ -3754,7 +3754,7 @@ class StreamParser: - self.branches[commit.branch] = commit - # Per-commit spinner disabled because this pass is fast - #baton.twirl() -- self.repo.timings.append(["parents", time.time()]) -+ self.repo.timings.append(["parents", time.time()]) - baton.twirl() - # The root branch is special. It wasn't made by a copy, so - # we didn't get the information to connect it to trunk in the -@@ -3804,7 +3804,7 @@ class StreamParser: - and root.branch != ("trunk" + os.sep): - self.gripe("r%s: can't connect nonempty branch %s to origin" \ - % (root.fossil_id, root.branch)) -- self.repo.timings.append(["branchlinks", time.time()]) -+ self.repo.timings.append(["branchlinks", time.time()]) - baton.twirl() - # Add links due to svn:mergeinfo properties - mergeinfo = PathMap() -@@ -3942,7 +3942,7 @@ class StreamParser: - if m and not commit.has_children(): - commit.delete(["--tagback"]) - baton.twirl() -- self.repo.timings.append(["junk", time.time()]) -+ self.repo.timings.append(["junk", time.time()]) - baton.twirl() - if debug_enable(DEBUG_EXTRACT): - announce("after cvs2svn artifact removal") -@@ -4027,7 +4027,7 @@ class StreamParser: - commit.set_branch(os.path.join("refs", "heads", - os.path.basename(commit.branch[:-1]))) - baton.twirl() -- ##self.repo.timings.append(["polishing", time.time()]) -+ ##self.repo.timings.append(["polishing", time.time()]) - baton.twirl() - if debug_enable(DEBUG_EXTRACT): - announce("after branch name mapping") -@@ -4044,7 +4044,7 @@ class StreamParser: - commit.fileops[i].op = None - commit.fileops = [fileop for fileop in commit.fileops if fileop.op is not None] - baton.twirl() -- self.repo.timings.append(["canonicalizing", time.time()]) -+ self.repo.timings.append(["canonicalizing", time.time()]) - baton.twirl() - if debug_enable(DEBUG_EXTRACT): - announce("after delete/copy canonicalization") -@@ -4065,11 +4065,11 @@ class StreamParser: - commit.remove_parent(a) - # Per-commit spinner disabled because this pass is fast - #baton.twirl() -- self.repo.timings.append(["debubbling", time.time()]) -+ self.repo.timings.append(["debubbling", time.time()]) - baton.twirl() - self.repo.renumber(baton=baton) - baton.twirl() -- self.repo.timings.append(["renumbering", time.time()]) -+ self.repo.timings.append(["renumbering", time.time()]) - self.repo.write_fossils = True - # Look for tag and branch merges that mean we may want to undo a - # tag or branch creation -@@ -4081,8 +4081,8 @@ class StreamParser: - and commit.mark not in ignore_deleteall: - self.gripe("mid-branch deleteall on %s at <%s>." % \ - (commit.branch, commit.fossil_id)) -- self.repo.timings.append(["linting", time.time()]) -- # Treat this in-core state is though it was read from an SVN repo -+ self.repo.timings.append(["linting", time.time()]) -+ # Treat this in-core state is though it was read from an SVN repo - self.repo.vcs = next(vcstype for vcstype in vcstypes if vcstype.name == "svn") - - class SubversionDumper: -@@ -4151,7 +4151,7 @@ class SubversionDumper: - #fp.write("Text-content-md5: %s\n" % hashlib.md5(content).hexdigest()) - fp.write("Text-content-sha1: %s\n" % hashlib.sha1(content).hexdigest()) - fp.write("Content-length: %d\n\n" % (len(nodeprops) + len(content))) -- fp.write(nodeprops) -+ fp.write(nodeprops) - if content: - fp.write(content) - fp.write("\n\n") -@@ -4331,7 +4331,7 @@ class SubversionDumper: - fp.write("UUID: %s\n\n" % (self.repo.uuid or uuid.uuid4())) - SubversionDumper.dump_revprops(fp, - revision=0, -- date=Date(rfc3339(time.time()))) -+ date=Date(rfc3339(time.time()))) - baton.twirl() - revision = 0 - for i in selection: -@@ -4426,7 +4426,7 @@ class SubversionDumper: - # Preserve lightweight tags, too. Ugh, O(n**2). - if event.has_children(): - for child in event.children(): -- if child.branch == event.branch: -+ if child.branch == event.branch: - break - else: - revision += 1 -@@ -4475,7 +4475,7 @@ class Repository: - if not name: - return os.path.join(self.basedir, ".rs" + repr(os.getpid())) - else: -- return os.path.join(self.basedir, ".rs" + repr(os.getpid())+ "-" + name) -+ return os.path.join(self.basedir, ".rs" + repr(os.getpid())+ "-" + name) - def makedir(self): - try: - if debug_enable(DEBUG_SHUFFLE): -@@ -5037,7 +5037,7 @@ class Repository: - # - # First op D or deleteall - # -- # Delete followed by modify undoes delete, since M carries whole files. -+ # Delete followed by modify undoes delete, since M carries whole files. - elif pair == ("D", "M"): - return (True, None, right, None, 6) - # But we have to leave deletealls in place, since they affect right ops -@@ -5066,7 +5066,7 @@ class Repository: - return (True, None, right, None, 9) - else: - # On rename followed by delete of source discard the delete -- # but user should be warned. -+ # but user should be warned. - return (False, left, None, - "delete of %s after renaming to %s?" % (right.path, left.source), -4) - # Rename followed by deleteall shouldn't be possible -@@ -5183,7 +5183,7 @@ class Repository: - event = self.events[ei] - if isinstance(event, Commit): - if delete: -- speak = "warning: commit %s to be deleted has " % event.mark -+ speak = "warning: commit %s to be deleted has " % event.mark - if '/' in event.branch and not '/heads/' in event.branch: - complain(speak + "non-head branch attribute %s" % event.branch) - if not event.alldeletes(): -@@ -5400,7 +5400,7 @@ class Repository: - for fld in ("mark", "committish"): - try: - old = getattr(event, fld) -- if old is not None: -+ if old is not None: - new = remark(old, event) - if debug_enable(DEBUG_UNITE): - announce("renumbering %s -> %s in %s.%s" % (old, new, -@@ -5755,7 +5755,7 @@ def read_repo(source, options, preferred - for line in open(".git/cvs-revisions", "rb"): - (path, rev, hashv) = line.split() - pathrev_to_hash[(path, rev)] = hashv -- # Pass 2: get git's hash to (time,person) mapping -+ # Pass 2: get git's hash to (time,person) mapping - hash_to_action = {} - stamp_set = set({}) - with popen_or_die("git log --all --format='%H %ct %ce'", "r") as fp: -@@ -5770,7 +5770,7 @@ def read_repo(source, options, preferred - else: - hash_to_action[hashv] = stamp - stamp_set.add(stamp) -- # Pass 3: build a (time,person) to commit mapping -+ # Pass 3: build a (time,person) to commit mapping - action_to_mark = {} - for commit in repo.commits(): - action_to_mark[(commit.committer.date.timestamp, commit.committer.email)] = commit -@@ -5849,7 +5849,7 @@ def rebuild_repo(repo, target, options, - except OSError: - raise Recoverable("staging directory creation failed") - -- # Try the rebuild in the empty staging directory -+ # Try the rebuild in the empty staging directory - here = os.getcwd() - try: - os.chdir(staging) -@@ -6018,7 +6018,7 @@ class RepositoryList: - "Remove a repo by name." - if self.repo and self.repo.name == name: - self.unchoose() -- self.repolist.pop(self.reponames().index(name)) -+ self.repolist.pop(self.reponames().index(name)) - def cut_conflict(self, early, late): - "Apply a graph-coloring algorithm to see if the repo can be split here." - self.cut_index = late.parent_marks().index(early.mark) -@@ -6067,9 +6067,9 @@ class RepositoryList: - if isinstance(c, Commit): - if c is t.target: - t.color = c.color -- # Front events go with early segment, they'll be copied to late one. -+ # Front events go with early segment, they'll be copied to late one. - for event in self.repo.front_events(): -- event.color = "early" -+ event.color = "early" - assert all(hasattr(x, "color") or hasattr(x, "colors") or isinstance(x, Reset) for x in self.repo) - # Resets are tricky. One may have both colors. - # Blobs can have both colors too, through references in -@@ -6112,7 +6112,7 @@ class RepositoryList: - # Options and features may need to be copied to the late fragment. - late.events = copy.copy(early.front_events()) + late.events - late.declare_sequence_mutation() -- # Add the split results to the repo list. -+ # Add the split results to the repo list. - self.repolist.append(early) - self.repolist.append(late) - self.repo.cleanup() -@@ -6394,7 +6394,7 @@ into sections of the input stream instea - % self.outfile) - self.line = self.line[:m.start(0)] + self.line[m.end(0)+1:] - self.redirected = True -- # Options -+ # Options - while True: - m = re.search(r"--\S+", self.line) - if not m: -@@ -6565,7 +6565,7 @@ into sections of the input stream instea - announce("%s <- eval_expression(), left = %s" % (value, repr(self.line))) - return value - def eval_disjunct(self, preselection): -- "Evaluate a disjunctive expression (| has lowest precedence)" -+ "Evaluate a disjunctive expression (| has lowest precedence)" - if debug_enable(DEBUG_LEXER): - announce("eval_disjunct(%s)" % self.line) - self.line = self.line.lstrip() -@@ -6585,7 +6585,7 @@ into sections of the input stream instea - announce("%s <- eval_disjunct(), left = %s" % (conjunct, repr(self.line))) - return preselection - unselected - def eval_conjunct(self, preselection): -- "Evaluate a conjunctive expression (& has higher precedence)" -+ "Evaluate a conjunctive expression (& has higher precedence)" - if debug_enable(DEBUG_LEXER): - announce("eval_conjunct(%s)" % self.line) - self.line = self.line.lstrip() -@@ -7041,7 +7041,7 @@ into sections of the input stream instea - if fileop.op == "M" and fileop.path == path: - here.append(child.mark) - here += find_successor(child, path) -- return here -+ return here - for event in self.chosen().commits(): - for fileop in event.fileops: - if fileop.op == 'M' and fileop.ref == singleton.mark: -@@ -7084,7 +7084,7 @@ into sections of the input stream instea - # when possible. - if blobs and self.chosen().inlines > 0: - for ei in range(self.selection[0], self.selection[-1]): -- event = self.chosen().events[ei] -+ event = self.chosen().events[ei] - if isinstance(event, (Commit, Tag)): - for fileop in event.fileops: - if fileop.inline is not None: -@@ -7191,7 +7191,7 @@ More ways to construct event sets: - @min() create singleton set of the least element in the argument - @max() create singleton set of the greatest element in the argument - @amp() all events if the argument set is nonempty, null set otherwise --@dsc() all (recursive) descendant commits of the argument set -+@dsc() all (recursive) descendant commits of the argument set - - You can compose sets as follows: - -@@ -7222,7 +7222,7 @@ parsed, any argument beginning with '<' - as the name of a file from which command output should be taken. Any - remaining arguments are available to the command logic. - """) -- -+ - ## - ## Command implementation begins here - ## -@@ -7448,16 +7448,16 @@ file in the blob. Supports > redirectio - parse.stdout.write("%6d blob %6s %s\n" % (i+1, event.mark," ".join(event.paths()))) - continue - if isinstance(event, Commit): -- parse.stdout.write("%6d commit %6s %s\n" % (i+1, event.mark or '-', event.branch)) -+ parse.stdout.write("%6d commit %6s %s\n" % (i+1, event.mark or '-', event.branch)) - continue - if isinstance(event, Tag): -- parse.stdout.write("%6d tag %6s %4s\n" % (i+1, event.committish, repr(event.name),)) -+ parse.stdout.write("%6d tag %6s %4s\n" % (i+1, event.committish, repr(event.name),)) - continue - if isinstance(event, Reset): -- parse.stdout.write("%6d branch %6s %s\n" % (i+1, event.committish or '-', event.ref)) -+ parse.stdout.write("%6d branch %6s %s\n" % (i+1, event.committish or '-', event.ref)) - continue - else: -- parse.stdout.write("? - %s\n" % (event,)) -+ parse.stdout.write("? - %s\n" % (event,)) - def help_profile(self): - print(""" - Enable profiling. Must be one of the initial command-line arguments, and -@@ -7531,7 +7531,7 @@ in the currently-selected repo. - complain("no repo has been chosen.") - return - elif self.selection == None: -- self.selection = self.chosen().all() -+ self.selection = self.chosen().all() - with RepoSurgeon.LineParse(line, capabilities=["stdout"]) as parse: - parse.stdout.write("%d\n" % len(self.selection)) - -@@ -7854,7 +7854,7 @@ current preserve list is displayed after - print(""" - A read command with no arguments is treated as 'read .', operating on the - current directory. -- -+ - With a directory-name argument, this command attempts to read in the - contents of a repository in any supported version-control system under - that directory. -@@ -8327,7 +8327,7 @@ With --dedos, DOS/Windows-style \r\n lin - s, - len(s) if subcount == 0 else subcount) - elif filtercmd.startswith('--replace'): -- self.sub = lambda s: s.replace(parts[1], -+ self.sub = lambda s: s.replace(parts[1], - parts[2], - len(s) if subcount == 0 else subcount) - elif filtercmd.startswith('--dedos'): -@@ -8609,7 +8609,7 @@ change in a future release. - for _, event in self.selected(Commit): - event.invalidate_pathset_cache() - if opindex == "deletes": -- event.fileops = [e for e in event.fileops if e.op != "D"] -+ event.fileops = [e for e in event.fileops if e.op != "D"] - return - for (ind, op) in enumerate(event.fileops): - if hasattr(op, "path") and getattr(op, "path") == opindex: -@@ -8743,7 +8743,7 @@ branch 'qux', the branch segments are re - complain("first element of selection is not a commit") - return - possibles = list(early.children()) -- if len(self.selection) == 1: -+ if len(self.selection) == 1: - if len(possibles) > 1: - complain("commit has multiple children, one must be specified") - return -@@ -8900,7 +8900,7 @@ type. - raise Recoverable("no such repo as %s" % name) - else: - factors.append(repo) -- if not factors or len(factors) < 2: -+ if not factors or len(factors) < 2: - raise Recoverable("unite requires repo name arguments") - self.unite(factors, parse.options) - if verbose: -@@ -9034,7 +9034,7 @@ source branch are removed. - def help_path(self): - print(""" - Rename a path in every fileop of every selected commit. The --default selection set is all commits. The first argument is interpreted as a -+default selection set is all commits. The first argument is interpreted as a - Python regular expression to match against paths; the second may contain - back-reference syntax. - -@@ -9076,7 +9076,7 @@ silently skipped and the old path fileop - drop.append(fileop) - else: - raise Recoverable("rename at %s failed, %s exists there" % (commit.id_me(), newpath)) -- else: -+ else: - actions.append((fileop, attr, newpath)) - for fileop in drop: - commit.fileops.remove(fileop) -@@ -9383,7 +9383,7 @@ fields are changed and a warning is issu - # A tag name can erfere to one of the following things: - # (1) A tag object, by name - # (2) A reset object having a name in the tags/ namespace -- # (3) The tip commit of a branch with branch fields -+ # (3) The tip commit of a branch with branch fields - # These things often occur in combination. Notably, git-fast-export - # generates for each tag object corresponding branch labels on - # some ancestor commmits - the rule for where this stops is unclear. -@@ -9544,7 +9544,7 @@ moved, no branch fields are changed. - def help_authors(self): - print(""" - Apply or dump author-map information for the specified selection --set, defaulting to all events. -+set, defaulting to all events. - - Lifts from CVS and Subversion may have only usernames local to - the repository host in committer and author IDs. DVCSes want email -@@ -9557,7 +9557,7 @@ When an authors file is applied, email a - metdata for which the local ID matches between < and @ are replaced - according to the mapping (this handles git-svn lifts). Alternatively, - if the local ID is the entire address, this is also considered a match --(this handles what git-cvsimport and cvs2git do) -+(this handles what git-cvsimport and cvs2git do) - - With the 'read' modifier, or no modifier, apply author mapping data - (from standard input or a <-redirected input file). May be useful if -@@ -10004,7 +10004,7 @@ Note that '_selection' will be a list of - raise Recoverable(str(e)) - - # -- # Version binding -+ # Version binding - # - def help_version(self): - print(""" diff -Nru reposurgeon-3.10/Makefile reposurgeon-3.12/Makefile --- reposurgeon-3.10/Makefile 2014-02-24 03:36:25.000000000 +0000 +++ reposurgeon-3.12/Makefile 2014-06-04 11:15:17.000000000 +0000 @@ -83,13 +83,14 @@ version: @echo $(VERS) -# Include W1401 in both sets when I get my pylint updated -COMMON_PYLINT = --rcfile=/dev/null --reports=n --include-ids=y -PYLINTOPTS1 = $(COMMON_PYLINT) --disable=C0103,C0111,C0301,C0302,C0322,C0324,C0321,C0323,R0201,R0902,R0903,R0904,R0911,R0912,R0913,R0914,R0915,W0108,W0141,W0142,W0212,W0233,W0603,W0511,W0611,E1101,E1103 -PYLINTOPTS2 = $(COMMON_PYLINT) --disable=C0103,C0111,C0301,W0603,W0621,E1101,E1103,R0902,R0903,R0912,R0914,R0915 +COMMON_PYLINT = --rcfile=/dev/null --reports=n \ + --msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" \ + --dummy-variables-rgx='^_' +PYLINTOPTS1 = "C0103,C0111,C0301,C0302,C0322,C0324,C0325,C0321,C0323,C1001,R0201,R0902,R0903,R0904,R0911,R0912,R0913,R0914,R0915,W0108,W0141,W0142,W0212,W0233,W0603,W0511,W0611,E1101,E1103,I0011" +PYLINTOPTS2 = "C0103,C0111,C0301,C0326,C1001,W0603,W0621,E1101,E1103,R0902,R0903,R0912,R0914,R0915" pylint: - @pylint --output-format=parseable $(PYLINTOPTS1) reposurgeon - @pylint --output-format=parseable $(PYLINTOPTS2) repodiffer + @pylint $(COMMON_PYLINT) --disable=$(PYLINTOPTS1) reposurgeon + @pylint $(COMMON_PYLINT) --disable=$(PYLINTOPTS2) repodiffer check: cd test; $(MAKE) --quiet diff -Nru reposurgeon-3.10/NEWS reposurgeon-3.12/NEWS --- reposurgeon-3.10/NEWS 2014-04-20 01:14:04.000000000 +0000 +++ reposurgeon-3.12/NEWS 2014-09-11 20:37:02.000000000 +0000 @@ -1,4 +1,14 @@ reposurgeon project news + +3.12: 2014-09-11 + Explicit svn:ignore patterns aren't recursive to lower directories; cope. + A new 'ignores' command has obtions for translation of ignore files. + The --noignores option has been retired. + +3.11: 2014-08-12 + When converting SVN, ignore explicit .gitignores created by git-svn. + (Better than letting them collide with translated svn:ignore properties.) + 3.10: 2014-04-19 Finer control over filtering with caC flags. New setfield command for tweaking object attributes from lift scripts. diff -Nru reposurgeon-3.10/repodiffer reposurgeon-3.12/repodiffer --- reposurgeon-3.10/repodiffer 2013-12-06 17:38:46.000000000 +0000 +++ reposurgeon-3.12/repodiffer 2014-05-21 21:09:24.000000000 +0000 @@ -132,7 +132,7 @@ class Commit(object): __slots__ = ("repo", "text", "commit", "tree", "author", "committer", "comment", "authordate", "commitdate", "matched", - "parents", "mark") + "parents", "mark") def __init__(self, repo, text): self.repo = repo self.text = text @@ -397,13 +397,12 @@ for commit in self.signature: if commit.commit.startswith(name) or commit.mark == name: return commit - else: - return None + return None def sort(self): "Sort by Committer-Date in case commits were generated in topo order." self.signature.sort(key=lambda c: c.timestamp()) def tagnames(self): - return set(self.tags.keys()) + return set(self.tags.keys()) def __str__(self): return "<%s: %s>" % (self.path, self.signature) def __del__(self): @@ -682,7 +681,7 @@ sys.stderr.write("repodiffer: input and output directory or import-stream arguments are required.\n") sys.exit(1) else: - (alpha, beta) = arguments[:2] + (alpha, beta) = arguments[:2] if not os.path.exists(alpha): sys.stderr.write("repodiffer: %s must exist.\n" % alpha) sys.exit(1) @@ -705,4 +704,8 @@ except KeyboardInterrupt: pass +# The following sets edit modes for GNU EMACS +# Local Variables: +# mode:python +# End: # end diff -Nru reposurgeon-3.10/reposurgeon reposurgeon-3.12/reposurgeon --- reposurgeon-3.10/reposurgeon 2014-04-20 01:14:31.000000000 +0000 +++ reposurgeon-3.12/reposurgeon 2014-09-11 20:37:19.000000000 +0000 @@ -15,13 +15,13 @@ import email.message, email.parser # This import only works on Unixes. The intention is to enable -# Ctrl-P, Ctrl-N, and friends in Cmd. +# Ctrl-P, Ctrl-N, and friends in Cmd. try: import readline except ImportError: pass -version="3.10" +version="3.12" # # This code is intended to be hackable to support for special-purpose or @@ -111,6 +111,7 @@ self.project = project self.notes = notes def __str__(self): + realignores = [line for line in self.dfltignores.split('\n') if not line.startswith("# ")] return " Name: {self.name}\n" \ " Subdirectory: {self.subdirectory}\n" \ " Exporter: {self.exporter}\n" \ @@ -123,12 +124,14 @@ " Preserve: {{{preserve}}}\n" \ " Authormap: {self.authormap}\n" \ " Ignorename: {self.ignorename}\n" \ - " Ignores: {self.dfltignores}\n" \ + " Ignores: {{{ignores}}}\n" \ " Project: {self.project}\n" \ - " Notes: {self.notes}\n".format( + " Notes: {{{notes}}}\n".format( self = self, styleflags = ", ".join(self.styleflags), - preserve = ", ".join(self.preserve)) + preserve = ", ".join(self.preserve), + ignores = " ".join(realignores).strip(), + notes = self.notes.strip()) # Most knowledge about specific version-control systems lives in the # following class list. Exception; there's a git-specific hook in the @@ -138,11 +141,12 @@ # # * Name of its characteristic subdirectory. # * Command to export from the VCS to the interchange format -# * Export-style flags. +# * Import/export style flags. # "no-nl-after-commit" = no extra NL after each commit # "nl-after-comment" = inserts an extra NL after each comment # "export-progress" = exporter generates its own progress messages, # no need for baton prompt. +# "import-defaults" = Import sets default ignores # * Flag specifying whether it handles per-commit properties on import # * Command to initialize a new repo # * Command to import from the interchange format @@ -169,7 +173,7 @@ VCS(name="git", subdirectory=".git", exporter="git fast-export --signed-tags=verbatim --tag-of-filtered-object=drop --all", - styleflags=set(), + styleflags={}, properties=False, initializer="git init --quiet", importer="git fast-import --quiet", @@ -181,7 +185,7 @@ dfltignores="", # Has none project="http://git-scm.com/", notes="The authormap is not required, but will be used if present."), - # + # VCS(name="bzr", subdirectory=".bzr", exporter="bzr fast-export --no-plain %(basename)s", @@ -196,6 +200,7 @@ project="http://bazaar.canonical.com/en/", ignorename=".bzrignore", dfltignores=""" +# A simulation of bzr default ignores, generated by reposurgeon. *.a *.o *.py[co] @@ -206,14 +211,14 @@ [#]*# __pycache__ bzr-orphans +# Simulated bzr default ignores end here """, notes="Requires the bzr-fast-import plugin."), # Export is tested and works; import is flaky. VCS(name="hg", subdirectory=".hg", exporter="hg-fast-export.py --marks /dev/null --mapping /dev/null --heads /dev/null --status /dev/null --repo .", - styleflags={"nl-after-comment", - "export-progress"}, + styleflags={"import-defaults", "nl-after-comment", "export-progress"}, properties=False, initializer="hg init", lister="hg locate", @@ -225,7 +230,7 @@ dfltignores="", project="http://mercurial.selenic.com/", notes="The hg export-import methods are not part of stock Mercurial."), - # Styleflags may need tweaking for round-tripping + # Styleflags may need tweaking for round-tripping VCS(name="darcs", subdirectory="_darcs", exporter="darcs fastconvert export", @@ -238,14 +243,113 @@ preserve=set(), authormap=None, ignorename="_darcs/prefs/boring", - dfltignores="", # Has none + dfltignores=""" +# A simulation of darcs default ignores, generated by reposurgeon. +# haskell (ghc) interfaces +*.hi +*.hi-boot +*.o-boot +# object files +*.o +*.o.cmd +# profiling haskell +*.p_hi +*.p_o +# haskell program coverage resp. profiling info +*.tix +*.prof +# fortran module files +*.mod +# linux kernel +*.ko.cmd +*.mod.c +*.tmp_versions +# *.ko files aren't boring by default because they might +# be Korean translations rather than kernel modules +# *.ko +# python, emacs, java byte code +*.py[co] +*.elc +*.class +# objects and libraries; lo and la are libtool things +*.obj +*.a +*.exe +*.so +*.lo +*.la +# compiled zsh configuration files +*.zwc +# Common LISP output files for CLISP and CMUCL +*.fas +*.fasl +*.sparcf +*.x86f +### build and packaging systems +# cabal intermediates +*.installed-pkg-config +*.setup-config +# standard cabal build dir, might not be boring for everybody +# dist +# autotools +autom4te.cache +config.log +config.status +# microsoft web expression, visual studio metadata directories +*.\\_vti_cnf +*.\\_vti_pvt +# gentoo tools +*.revdep-rebuild.* +# generated dependencies +.depend +### verion control +# darcs +_darcs +.darcsrepo +*.darcs-temp-mail +-darcs-backup[[:digit:]]+ +# gnu arch ++ +, +vssver.scc +*.swp +MT +{arch} +*.arch-ids +# bitkeeper +BitKeeper +ChangeSet +### miscellaneous +# backup files +*~ +*.bak +*.BAK +# patch originals and rejects +*.orig +*.rej +# X server +..serverauth.* +# image spam +\\# +Thumbs.db +# vi, emacs tags +tags +TAGS +# core dumps +core +# partial broken files (KIO copy operations) +*.part +# mac os finder +.DS_Store +# Simulated darcs default ignores end here +""", project="http://darcs.net/", notes="Assumes no boringfile preference has been set."), # Export is experimental and doesn't round-trip VCS(name="svn", subdirectory="locks", exporter="svnadmin dump .", - styleflags={"export-progress"}, + styleflags={"import-defaults", "export-progress"}, properties=False, initializer="svn create .", importer="svnadmin load .", @@ -254,13 +358,31 @@ preserve={"hooks"}, authormap=None, ignorename=None, - dfltignores="", # Embedded in the properties parser + dfltignores="""\ +# A simulation of Subversion default ignores, generated by reposurgeon. +*.o +*.lo +*.la +*.al +*.libs +*.so +*.so.[0-9]* +*.a +*.pyc +*.pyo +*.rej +*~ +*.#* +.*.swp +.DS_store +# Simulated Subversion default ignores end here +""", project="http://subversion.apache.org/", notes="Run from the repository, not a checkout directory."), VCS(name="cvs", - subdirectory="CVS", - exporter="find . -name '*,v' -print | cvs-fast-export -k --reposurgeon", - styleflags={"export-progress"}, + subdirectory="Attic", + exporter="find . -name '*,v' -print | cvs-fast-export --reposurgeon", + styleflags={"import-defaults", "export-progress"}, properties=False, initializer=None, importer=None, @@ -270,6 +392,7 @@ authormap=None, ignorename=None, dfltignores="""\ +# A simulation of cvs default ignores, generated by reposurgeon. tags TAGS .make.state @@ -296,6 +419,7 @@ *.elc *.ln core +# Simulated cvs default ignores end here """, project="http://www.catb.org/~esr/cvs-fast-export", notes="Requires cvs-fast-export."), @@ -324,7 +448,7 @@ # How to write extractor classes: # -# Clone one of the existing ones and mutate. +# Clone one of the existing ones and mutate. # # Significant fact: None of the get_* methods for extracting information about # a revision is called until after checkout has been called on that revision. @@ -524,7 +648,7 @@ DEBUG_DELETE = 3 # Debug canonicalization after deletes DEBUG_IGNORES = 3 # Debug ignore generation DEBUG_SVNPARSE = 4 # Lower-level Subversion parsing details -DEBUG_EMAILIN = 4 # Debug event round-tripping through mailbox_{out|in} +DEBUG_EMAILIN = 4 # Debug event round-tripping through mailbox_{out|in} DEBUG_SHUFFLE = 4 # Debug file and directory handling DEBUG_COMMANDS = 5 # Show commands as they are executed DEBUG_UNITE = 5 # Debug mark assignments in merging @@ -692,7 +816,7 @@ Divider = 78 * "-" __hash__ = None def __init__(self, **kwargs): - email.message.Message.__init__(self, **kwargs) + email.message.Message.__init__(self, **kwargs) self.set_unixfrom(RepoSurgeonEmail.Divider) @staticmethod def readmsg(fp): @@ -759,7 +883,7 @@ return except ValueError: # time.strptime() throws this - # "time data 'xxxxxx' does not match format '%Y-%m-%dT%H:%M:%S'" + # "time data 'xxxxxx' does not match format '%Y-%m-%dT%H:%M:%S'" pass # Date format not recognized raise error("'%s' is not a valid timestamp" % text) @@ -892,7 +1016,7 @@ if m: return (m.group(1).strip(), m.group(2), m.group(3).strip()) else: - raise Fatal("malformed attribution '%s'" % line) + raise Fatal("malformed attribution '%s'" % line) def __init__(self, operson=None): self.name = self.email = self.date = None if operson: @@ -916,7 +1040,7 @@ self.email = mail if timezone: self.date.orig_tz_string = timezone - break + break def action_stamp(self): return self.date.rfc3339() + "!" + self.email def __eq__(self, other): @@ -951,7 +1075,7 @@ def blobfile(self, create=False): "File where the content lives." stem = repr(id(self)) - parts = ("blobs", stem[:3], stem[3:6], stem[6:]) + parts = ("blobs", stem[:3], stem[3:6], stem[6:]) if create: for d in range(len(parts)-1): partial = os.path.join(self.repo.subdir(), *parts[:d+1]) @@ -1017,6 +1141,7 @@ os.link(self.blobfile(), c.blobfile(create=True)) return c def dump(self, vcs=None, options=None, realized=None, internals=None): + pacify_pylint(options) pacify_pylint(realized) pacify_pylint(internals) if self.hasfile() and not os.path.exists(self.blobfile()): @@ -1025,12 +1150,6 @@ content = self.get_content() if vcs is None and self.repo.vcs and self.repo.vcs.importer: vcs = self.repo.vcs - # Ugh. This is where we mess with ignore syntax translation - if options is None or not '--noignores' in options: - if vcs and self.repo.vcs and len(self.pathlist) == 1 and self.pathlist[0].endswith(".gitignore"): - if vcs.name == "hg" and self.repo.vcs.name != "hg": - if not content.startswith("syntax: glob\n"): - content = "syntax: glob\n" + content return "blob\nmark %s\ndata %d\n%s\n" % (self.mark, len(content), content) def __str__(self): return self.dump() @@ -1246,22 +1365,6 @@ self.path = None self.ref = None self.inline = None - def path_remap_in(self): - "Hack the fileop's basename to map it to git conventions." - # Ignore file names from non-git VCSes need to get - # mapped to .gitignore, because we have to - # have some way to recognize what they are - # in order to remap the name properly on - # export. - if self.vcs is not None: - if os.path.basename(self.path) == self.vcs.ignorename: - self.path = os.path.join(os.path.dirname(self.path), ".gitignore") - def path_remap_out(self, path, vcs): - "Hack the fileop's basename to map it to a target VCS's conventions." - if vcs is not None and vcs.ignorename is not None: - if os.path.basename(path) == ".gitignore": - return os.path.join(os.path.dirname(path), vcs.ignorename) - return path def setOp(self, op): self.op = op @staticmethod @@ -1277,12 +1380,10 @@ def construct(self, *opargs): if opargs[0] == "M": (self.op, self.mode, self.ref, self.path) = opargs - self.path_remap_in() if isinstance(self.mode, int): self.mode = "%06o" % self.mode elif opargs[0] == "D": (self.op, self.path) = opargs - self.path_remap_in() elif opargs[0] == "N": (self.op, self.ref, self.committish) = opargs elif opargs[0] in ("R", "C"): @@ -1299,7 +1400,6 @@ (self.op, self.mode, self.ref, self.path) = m.groups() if self.path[0] == '"' and self.path[-1] == '"': self.path = self.path[1:-1] - self.path_remap_in() elif opline[0] == "N": try: opline = opline.replace("'", r"\'") @@ -1310,7 +1410,6 @@ (self.op, self.path) = ("D", opline[2:].strip()) if self.path[0] == '"' and self.path[-1] == '"': self.path = self.path[1:-1] - self.path_remap_in() elif opline[0] in ("R", "C"): try: opline = opline.replace("'", r"\'") @@ -1348,7 +1447,7 @@ if len(self.path.split()) > 1: parts.extend(('"', self.path, '"')) else: - parts.append(self.path_remap_out(self.path, vcs)) + parts.append(self.path) if self.ref == 'inline': parts.append("\ndata %d\n%s" % (len(self.inline), self.inline)) elif self.op == "N": @@ -1360,11 +1459,9 @@ if len(self.path.split()) > 1: parts.extend(('"', self.path, '"')) else: - parts.append(self.path_remap_out(self.path, vcs)) + parts.append(self.path) elif self.op in ("R", "C"): - parts = ['%s "%s" "%s"' % (self.op, - self.path_remap_out(self.source, vcs), - self.path_remap_out(self.target, vcs))] + parts = ['%s "%s" "%s"' % (self.op, self.source, self.target)] elif self.op == "deleteall": parts = [self.op] else: @@ -1485,7 +1582,7 @@ successor_branches = {child.branch for child in self.children() if child.parents()[0] == self} if len(successor_branches) == 1 and successor_branches.pop() == self.branch: return - return "%6d\tcommit\t%s" % (eventnum+1, self.branch) + return "%6d\tcommit\t%s" % (eventnum+1, self.branch) def email_out(self, modifiers, eventnum): "Enable do_mailbox_out() to report these." msg = RepoSurgeonEmail() @@ -1657,7 +1754,8 @@ newparent = mark else: newparent = self.repo.objfind(mark) - assert(newparent) + if not newparent: + raise Fatal("ill-formed stream: cannot resolve %s" % mark) self._parent_nodes.append(newparent) newparent._child_nodes.append(self) self.repo.invalidate_manifests() @@ -1829,8 +1927,10 @@ # Finishing touches: new_ops.sort(key=FileOp.sortkey) self._pathset = None - def alldeletes(self, killset={"D", "deleteall"}): + def alldeletes(self, killset=None): "Is this an all-deletes commit?" + if killset is None: + killset = {"D", "deleteall"} return all(fileop.op in killset for fileop in self.fileops) def checkout(self, directory=None): "Make a directory with links to files in a specified checkout." @@ -2288,7 +2388,7 @@ repo.addEvent(reset) commit.fileops.sort(key=FileOp.sortkey) commit.fossil_id = revision - commit.properties.update(self.extractor.get_properties(revision)) + commit.properties.update(self.extractor.get_properties(revision)) commit.set_mark(self.__newmark()) self.commit_map[revision] = commit if debug_enable(DEBUG_EXTRACT): @@ -2395,25 +2495,6 @@ "svn:needs-lock", "svn:eol-style", # Don't want to suppress, but cvs2svn floods these. } - # These are the default patterns globally ignored by Subversion. - SubversionDefaultIgnores = """\ -# A simulation of Subversion default ignores, generated by reposurgeon. -*.o -*.lo -*.la -*.al -.libs -*.so -*.so.[0-9]* -*.a -*.pyc -*.pyo -*.rej -*~ -.#* -.*.swp -.DS_store -""" cvs2svn_tag_re = re.compile("This commit was manufactured by cvs2svn to create tag.*'([^']*)'") cvs2svn_branch_re = re.compile("This commit was manufactured by cvs2svn to create branch.*'([^']*)'") SplitSep = '.' @@ -2592,7 +2673,7 @@ try: self.fp = fp # Optimization: if we're reading from a plain file, - # no need to clone all the blobs. + # no need to clone all the blobs. if os.path.isfile(self.fp.name): # We can't just pass the input file object here, it # leads to bad results when fast_import is called @@ -2699,7 +2780,7 @@ elif line.startswith("Node-action: "): node.action = StreamParser.sd_body(line) node.action = StreamParser.NodeAction.ActionValues.index(node.action) - + if node.action is None: self.error("unknown action %s" \ % node.action) @@ -2960,7 +3041,7 @@ return 0o100755 elif "svn:special" in node.props: # Map to git symlink, which behaves the same way. - # Blob contents is the path the link should resolve to. + # Blob contents is the path the link should resolve to. return 0o120000 return 0o100644 def branchpath(self, path): @@ -3038,7 +3119,7 @@ filemaps[revision] = filemap.snapshot() baton.twirl() del filemap - self.repo.timings.append(["filemaps", time.time()]) + self.repo.timings.append(["filemaps", time.time()]) baton.twirl() # Blows up huge on large repos... #if debug_enable(DEBUG_FILEMAP): @@ -3063,7 +3144,7 @@ any(filemaps[copynode.revision].ls_R(node.path)): self.gripe("inconsistently empty from set for %s" % copynode) baton.twirl() - self.repo.timings.append(["copysets", time.time()]) + self.repo.timings.append(["copysets", time.time()]) baton.twirl() # Build commits # This code can eat your processor, so we make it give up @@ -3140,7 +3221,7 @@ # Zero revision is never interesting - no operations, no # comment, no author, it's just a start marker for a # non-incremental dump. - if revision == "0": + if revision == "0": continue expanded_nodes = [] has_properties = set() @@ -3186,10 +3267,7 @@ if node.action in (SD_ADD, SD_CHANGE): if node.path in self.branches: if not node.props: node.props = {} - if "--noignores" in options: - startwith = "" - else: - startwith = StreamParser.SubversionDefaultIgnores + startwith = next(vcs.dfltignores for vcs in vcstypes if vcs.name == "svn") try: ignore = startwith + \ "# The contents of the svn:ignore" \ @@ -3331,6 +3409,16 @@ # turn into fileops. ignore = node.props.get("svn:ignore") if ignore is not None: + # svn:ignore properties are nonrecursive + # to lower directories, but .gitignore + # patterns are recursive. Thus we need to + # anchor the translated pattern with + # leading / in order to render the + # Subversion behavior accurately. + ignore = re.sub("\n(?!#)", "\n/", "\n" + ignore) + ignore = ignore[1:] + if ignore.endswith("/"): + ignore = ignore[:-1] blob = Blob(self.repo) blob.set_content(ignore) newnode = StreamParser.NodeAction() @@ -3466,6 +3554,19 @@ perms = oldperms = 0o100644 if node.props is not None: perms = self.node_permissions(node) + new_content = (node.blob is not None) + # Ignore and complain about explicit .gitignores + # created, e.g, by git-svn. In an ideal world we + # would merge these with svn:ignore properties. but + # this would be hairy and bug-prone. So we give + # the user a heads-up and expect these to be + # merged by hand. + if new_content \ + and not node.generated \ + and node.path.endswith(".gitignore"): + self.gripe("r%s~%s: user-created .gitignore ignored." \ + % (node.revision, node.path)) + continue # This ugly nasty guard is critically important. # We need to generate a modify if: # 1. There is new content. @@ -3478,7 +3579,6 @@ # 4. The permissions for this path have changed; # we need to generate a modify with an old mark # but new permissions. - new_content = (node.blob is not None) generated_file_copy = node.generated subversion_file_copy = (node.from_hash is not None) permissions_changed = (perms != oldperms) @@ -3497,14 +3597,6 @@ elif debug_enable(DEBUG_EXTRACT): announce("r%s~%s: unmodified" % (node.revision, node.path)) self.permissions[node.path] = perms - # If it is a non-generated .gitignore, odds are - # somebody was getting jiggy with git-svn - # behind Subversion's back. Warn about this. - if new_content \ - and not node.generated \ - and node.path.endswith(".gitignore"): - self.gripe("r%s~%s: user-created gitignore." \ - % (node.revision, node.path)) # These are directory actions. elif node.action in (SD_DELETE, SD_REPLACE): if debug_enable(DEBUG_EXTRACT): @@ -3670,7 +3762,7 @@ if debug_enable(DEBUG_TOPOLOGY): complain("lookback for %s failed" % latest) raise Fatal("couldn't find a branch root for the copy of %s at r%s." % (latest.path, latest.revision)) - # We're done, add all the new commits + # We're done, add all the new commits self.repo.events += newcommits self.repo.declare_sequence_mutation() # Report progress, and give up our scheduler slot @@ -3684,7 +3776,7 @@ self.fileop_branchlinks.discard("trunk" + os.sep) if self.fileop_branchlinks - self.directory_branchlinks: self.gripe("branch links detected by file ops only: %s" % " ".join(self.fileop_branchlinks - self.directory_branchlinks)) - self.repo.timings.append(["commits", time.time()]) + self.repo.timings.append(["commits", time.time()]) if debug_enable(DEBUG_EXTRACT): announce("at post-parsing time:") for commit in self.repo.commits(): @@ -3742,7 +3834,7 @@ self.branches["root"] = None lastbranch = branch baton.twirl() - self.repo.timings.append(["branches", time.time()]) + self.repo.timings.append(["branches", time.time()]) baton.twirl() # ...then rebuild parent links so they follow the branches for commit in self.repo.commits(): @@ -3754,7 +3846,7 @@ self.branches[commit.branch] = commit # Per-commit spinner disabled because this pass is fast #baton.twirl() - self.repo.timings.append(["parents", time.time()]) + self.repo.timings.append(["parents", time.time()]) baton.twirl() # The root branch is special. It wasn't made by a copy, so # we didn't get the information to connect it to trunk in the @@ -3804,7 +3896,7 @@ and root.branch != ("trunk" + os.sep): self.gripe("r%s: can't connect nonempty branch %s to origin" \ % (root.fossil_id, root.branch)) - self.repo.timings.append(["branchlinks", time.time()]) + self.repo.timings.append(["branchlinks", time.time()]) baton.twirl() # Add links due to svn:mergeinfo properties mergeinfo = PathMap() @@ -3942,7 +4034,7 @@ if m and not commit.has_children(): commit.delete(["--tagback"]) baton.twirl() - self.repo.timings.append(["junk", time.time()]) + self.repo.timings.append(["junk", time.time()]) baton.twirl() if debug_enable(DEBUG_EXTRACT): announce("after cvs2svn artifact removal") @@ -4027,7 +4119,7 @@ commit.set_branch(os.path.join("refs", "heads", os.path.basename(commit.branch[:-1]))) baton.twirl() - ##self.repo.timings.append(["polishing", time.time()]) + ##self.repo.timings.append(["polishing", time.time()]) baton.twirl() if debug_enable(DEBUG_EXTRACT): announce("after branch name mapping") @@ -4044,7 +4136,7 @@ commit.fileops[i].op = None commit.fileops = [fileop for fileop in commit.fileops if fileop.op is not None] baton.twirl() - self.repo.timings.append(["canonicalizing", time.time()]) + self.repo.timings.append(["canonicalizing", time.time()]) baton.twirl() if debug_enable(DEBUG_EXTRACT): announce("after delete/copy canonicalization") @@ -4065,11 +4157,11 @@ commit.remove_parent(a) # Per-commit spinner disabled because this pass is fast #baton.twirl() - self.repo.timings.append(["debubbling", time.time()]) + self.repo.timings.append(["debubbling", time.time()]) baton.twirl() self.repo.renumber(baton=baton) baton.twirl() - self.repo.timings.append(["renumbering", time.time()]) + self.repo.timings.append(["renumbering", time.time()]) self.repo.write_fossils = True # Look for tag and branch merges that mean we may want to undo a # tag or branch creation @@ -4081,8 +4173,8 @@ and commit.mark not in ignore_deleteall: self.gripe("mid-branch deleteall on %s at <%s>." % \ (commit.branch, commit.fossil_id)) - self.repo.timings.append(["linting", time.time()]) - # Treat this in-core state is though it was read from an SVN repo + self.repo.timings.append(["linting", time.time()]) + # Treat this in-core state is though it was read from an SVN repo self.repo.vcs = next(vcstype for vcstype in vcstypes if vcstype.name == "svn") class SubversionDumper: @@ -4151,7 +4243,7 @@ #fp.write("Text-content-md5: %s\n" % hashlib.md5(content).hexdigest()) fp.write("Text-content-sha1: %s\n" % hashlib.sha1(content).hexdigest()) fp.write("Content-length: %d\n\n" % (len(nodeprops) + len(content))) - fp.write(nodeprops) + fp.write(nodeprops) if content: fp.write(content) fp.write("\n\n") @@ -4331,7 +4423,7 @@ fp.write("UUID: %s\n\n" % (self.repo.uuid or uuid.uuid4())) SubversionDumper.dump_revprops(fp, revision=0, - date=Date(rfc3339(time.time()))) + date=Date(rfc3339(time.time()))) baton.twirl() revision = 0 for i in selection: @@ -4426,7 +4518,7 @@ # Preserve lightweight tags, too. Ugh, O(n**2). if event.has_children(): for child in event.children(): - if child.branch == event.branch: + if child.branch == event.branch: break else: revision += 1 @@ -4475,7 +4567,7 @@ if not name: return os.path.join(self.basedir, ".rs" + repr(os.getpid())) else: - return os.path.join(self.basedir, ".rs" + repr(os.getpid())+ "-" + name) + return os.path.join(self.basedir, ".rs" + repr(os.getpid())+ "-" + name) def makedir(self): try: if debug_enable(DEBUG_SHUFFLE): @@ -4884,7 +4976,8 @@ if isinstance(event, Commit): for fileop in event.fileops: if fileop.op == 'M': - implied.add(self.find(fileop.ref)) + if fileop.ref != "inline": + implied.add(self.find(fileop.ref)) for tag in event.attachments: implied.add(self.find(tag.committish)) selection = list(implied) @@ -5037,7 +5130,7 @@ # # First op D or deleteall # - # Delete followed by modify undoes delete, since M carries whole files. + # Delete followed by modify undoes delete, since M carries whole files. elif pair == ("D", "M"): return (True, None, right, None, 6) # But we have to leave deletealls in place, since they affect right ops @@ -5066,7 +5159,7 @@ return (True, None, right, None, 9) else: # On rename followed by delete of source discard the delete - # but user should be warned. + # but user should be warned. return (False, left, None, "delete of %s after renaming to %s?" % (right.path, left.source), -4) # Rename followed by deleteall shouldn't be possible @@ -5183,7 +5276,7 @@ event = self.events[ei] if isinstance(event, Commit): if delete: - speak = "warning: commit %s to be deleted has " % event.mark + speak = "warning: commit %s to be deleted has " % event.mark if '/' in event.branch and not '/heads/' in event.branch: complain(speak + "non-head branch attribute %s" % event.branch) if not event.alldeletes(): @@ -5400,7 +5493,7 @@ for fld in ("mark", "committish"): try: old = getattr(event, fld) - if old is not None: + if old is not None: new = remark(old, event) if debug_enable(DEBUG_UNITE): announce("renumbering %s -> %s in %s.%s" % (old, new, @@ -5542,6 +5635,7 @@ ancestors = commit.parents() while ancestors: backto = [] + # pylint: disable=useless-else-on-loop for ancestor in ancestors: # This is potential trouble if the file was renamed # down one side of a merge bubble but not the other. @@ -5560,6 +5654,7 @@ "Make rename sequences from matched delete-modify pairs." # TODO: Actually use this somewhere... rename_count = 0 + # pylint: disable=unpacking-non-sequence for commit in self.commits(): renames = [] for (d, op) in enumerate(commit.fileops): @@ -5755,7 +5850,7 @@ for line in open(".git/cvs-revisions", "rb"): (path, rev, hashv) = line.split() pathrev_to_hash[(path, rev)] = hashv - # Pass 2: get git's hash to (time,person) mapping + # Pass 2: get git's hash to (time,person) mapping hash_to_action = {} stamp_set = set({}) with popen_or_die("git log --all --format='%H %ct %ce'", "r") as fp: @@ -5770,7 +5865,7 @@ else: hash_to_action[hashv] = stamp stamp_set.add(stamp) - # Pass 3: build a (time,person) to commit mapping + # Pass 3: build a (time,person) to commit mapping action_to_mark = {} for commit in repo.commits(): action_to_mark[(commit.committer.date.timestamp, commit.committer.email)] = commit @@ -5849,7 +5944,7 @@ except OSError: raise Recoverable("staging directory creation failed") - # Try the rebuild in the empty staging directory + # Try the rebuild in the empty staging directory here = os.getcwd() try: os.chdir(staging) @@ -6018,7 +6113,7 @@ "Remove a repo by name." if self.repo and self.repo.name == name: self.unchoose() - self.repolist.pop(self.reponames().index(name)) + self.repolist.pop(self.reponames().index(name)) def cut_conflict(self, early, late): "Apply a graph-coloring algorithm to see if the repo can be split here." self.cut_index = late.parent_marks().index(early.mark) @@ -6067,9 +6162,9 @@ if isinstance(c, Commit): if c is t.target: t.color = c.color - # Front events go with early segment, they'll be copied to late one. + # Front events go with early segment, they'll be copied to late one. for event in self.repo.front_events(): - event.color = "early" + event.color = "early" assert all(hasattr(x, "color") or hasattr(x, "colors") or isinstance(x, Reset) for x in self.repo) # Resets are tricky. One may have both colors. # Blobs can have both colors too, through references in @@ -6112,7 +6207,7 @@ # Options and features may need to be copied to the late fragment. late.events = copy.copy(early.front_events()) + late.events late.declare_sequence_mutation() - # Add the split results to the repo list. + # Add the split results to the repo list. self.repolist.append(early) self.repolist.append(late) self.repo.cleanup() @@ -6394,7 +6489,7 @@ % self.outfile) self.line = self.line[:m.start(0)] + self.line[m.end(0)+1:] self.redirected = True - # Options + # Options while True: m = re.search(r"--\S+", self.line) if not m: @@ -6424,6 +6519,7 @@ self.echo = 0 self.prompt = "reposurgeon% " self.preferred = None + self.ignorename = None self.selection = [] self.line = "" self.history = [] @@ -6431,6 +6527,7 @@ self.definitions = {} self.profile_log = None self.capture = None + self.start_time = time.time() for option in dict(RepoSurgeon.OptionFlags): global_options[option] = False global_options['svn_branchify'] = ['trunk', 'tags/*', 'branches/*', '*'] @@ -6565,7 +6662,7 @@ announce("%s <- eval_expression(), left = %s" % (value, repr(self.line))) return value def eval_disjunct(self, preselection): - "Evaluate a disjunctive expression (| has lowest precedence)" + "Evaluate a disjunctive expression (| has lowest precedence)" if debug_enable(DEBUG_LEXER): announce("eval_disjunct(%s)" % self.line) self.line = self.line.lstrip() @@ -6585,7 +6682,7 @@ announce("%s <- eval_disjunct(), left = %s" % (conjunct, repr(self.line))) return preselection - unselected def eval_conjunct(self, preselection): - "Evaluate a conjunctive expression (& has higher precedence)" + "Evaluate a conjunctive expression (& has higher precedence)" if debug_enable(DEBUG_LEXER): announce("eval_conjunct(%s)" % self.line) self.line = self.line.lstrip() @@ -7041,7 +7138,7 @@ if fileop.op == "M" and fileop.path == path: here.append(child.mark) here += find_successor(child, path) - return here + return here for event in self.chosen().commits(): for fileop in event.fileops: if fileop.op == 'M' and fileop.ref == singleton.mark: @@ -7084,7 +7181,7 @@ # when possible. if blobs and self.chosen().inlines > 0: for ei in range(self.selection[0], self.selection[-1]): - event = self.chosen().events[ei] + event = self.chosen().events[ei] if isinstance(event, (Commit, Tag)): for fileop in event.fileops: if fileop.inline is not None: @@ -7191,7 +7288,7 @@ @min() create singleton set of the least element in the argument @max() create singleton set of the greatest element in the argument @amp() all events if the argument set is nonempty, null set otherwise -@dsc() all (recursive) descendant commits of the argument set +@dsc() all (recursive) descendant commits of the argument set You can compose sets as follows: @@ -7222,7 +7319,7 @@ as the name of a file from which command output should be taken. Any remaining arguments are available to the command logic. """) - + ## ## Command implementation begins here ## @@ -7448,16 +7545,16 @@ parse.stdout.write("%6d blob %6s %s\n" % (i+1, event.mark," ".join(event.paths()))) continue if isinstance(event, Commit): - parse.stdout.write("%6d commit %6s %s\n" % (i+1, event.mark or '-', event.branch)) + parse.stdout.write("%6d commit %6s %s\n" % (i+1, event.mark or '-', event.branch)) continue if isinstance(event, Tag): - parse.stdout.write("%6d tag %6s %4s\n" % (i+1, event.committish, repr(event.name),)) + parse.stdout.write("%6d tag %6s %4s\n" % (i+1, event.committish, repr(event.name),)) continue if isinstance(event, Reset): - parse.stdout.write("%6d branch %6s %s\n" % (i+1, event.committish or '-', event.ref)) + parse.stdout.write("%6d branch %6s %s\n" % (i+1, event.committish or '-', event.ref)) continue else: - parse.stdout.write("? - %s\n" % (event,)) + parse.stdout.write("? - %s\n" % (event,)) def help_profile(self): print(""" Enable profiling. Must be one of the initial command-line arguments, and @@ -7531,7 +7628,7 @@ complain("no repo has been chosen.") return elif self.selection == None: - self.selection = self.chosen().all() + self.selection = self.chosen().all() with RepoSurgeon.LineParse(line, capabilities=["stdout"]) as parse: parse.stdout.write("%d\n" % len(self.selection)) @@ -7854,7 +7951,7 @@ print(""" A read command with no arguments is treated as 'read .', operating on the current directory. - + With a directory-name argument, this command attempts to read in the contents of a repository in any supported version-control system under that directory. @@ -8327,7 +8424,7 @@ s, len(s) if subcount == 0 else subcount) elif filtercmd.startswith('--replace'): - self.sub = lambda s: s.replace(parts[1], + self.sub = lambda s: s.replace(parts[1], parts[2], len(s) if subcount == 0 else subcount) elif filtercmd.startswith('--dedos'): @@ -8609,7 +8706,7 @@ for _, event in self.selected(Commit): event.invalidate_pathset_cache() if opindex == "deletes": - event.fileops = [e for e in event.fileops if e.op != "D"] + event.fileops = [e for e in event.fileops if e.op != "D"] return for (ind, op) in enumerate(event.fileops): if hasattr(op, "path") and getattr(op, "path") == opindex: @@ -8743,7 +8840,7 @@ complain("first element of selection is not a commit") return possibles = list(early.children()) - if len(self.selection) == 1: + if len(self.selection) == 1: if len(possibles) > 1: complain("commit has multiple children, one must be specified") return @@ -8900,7 +8997,7 @@ raise Recoverable("no such repo as %s" % name) else: factors.append(repo) - if not factors or len(factors) < 2: + if not factors or len(factors) < 2: raise Recoverable("unite requires repo name arguments") self.unite(factors, parse.options) if verbose: @@ -9034,7 +9131,7 @@ def help_path(self): print(""" Rename a path in every fileop of every selected commit. The -default selection set is all commits. The first argument is interpreted as a +default selection set is all commits. The first argument is interpreted as a Python regular expression to match against paths; the second may contain back-reference syntax. @@ -9076,7 +9173,7 @@ drop.append(fileop) else: raise Recoverable("rename at %s failed, %s exists there" % (commit.id_me(), newpath)) - else: + else: actions.append((fileop, attr, newpath)) for fileop in drop: commit.fileops.remove(fileop) @@ -9383,7 +9480,7 @@ # A tag name can erfere to one of the following things: # (1) A tag object, by name # (2) A reset object having a name in the tags/ namespace - # (3) The tip commit of a branch with branch fields + # (3) The tip commit of a branch with branch fields # These things often occur in combination. Notably, git-fast-export # generates for each tag object corresponding branch labels on # some ancestor commmits - the rule for where this stops is unclear. @@ -9538,13 +9635,108 @@ else: raise Recoverable("unknown verb '%s' in reset command." % verb) + def help_ignores(self): + print("""Intelligent handling of ignore-pattern files. +This command fails if no repository has been selected or no preferred write +type has been set for the repository. It does not take a selection set. + +If the rename modifier is present, this command attempts to rename all +ignore-pattern files to whatever is appropriate for the preferred type +- e.g. .gitignore for git, .hgignore for hg, etc. This option does not +cause any translation of the ignore files it renames. + +If the translate modifier is present, syntax translation of each ignore +file is attempted. At present, the only transformation the code knows +is to prepend a 'syntax: glob' header if the preferred type is hg. + +If the defaults modifier is present, the command attempts to prepend +these default patterns to all ignore files. If no ignore file is +created by the first commit, it will be modified to create one +containing the defaults. This command will error out on prefer types +that have no default ignore patterns (git and hg, in particular). It +will also error out when it knows the import tool has already set +default patterns. +""") + def do_ignores(self, line): + "Manipulate ignore patterns in the repo." + if self.chosen() is None: + complain("no repo has been chosen.") + return + repo = self.chosen() + if self.preferred and not self.ignorename: + self.ignorename = self.preferred.ignorename + if not self.preferred: + raise Recoverable("preferred repository type has not been set") + def isignore(blob): + return len(blob.pathlist) == 1 \ + and blob.pathlist[0].endswith(self.ignorename) + if 'defaults' in line: + if "import-defaults" in self.preferred.styleflags: + raise Recoverable("importer already set default ignores") + elif not self.preferred.dfltignores: + raise Recoverable("no default ignores in %s" % self.preferred.name) + else: + changecount = 0 + # Modify existing ignore files + for (_, blob) in repo.iterevents(indices=None, types=(Blob,)): + if isignore(blob): + blob.set_content(self.preferred.dfltignores \ + + blob.get_content()) + changecount += 1 + # Create an early ignore file if required. + # Don't move this before the modification pass! + earliest = repo.earliest_commit() + if not [fileop for fileop in earliest.fileops if fileop.op == "M" and fileop.path.endswith(self.ignorename)]: + blob = Blob(repo) + blob.pathlist.append(self.ignorename) + blob.set_content(self.preferred.dfltignores) + blob.mark = ":insert" + repo.events.insert(repo.index(earliest), blob) + repo.declare_sequence_mutation() + newop = FileOp() + newop.construct("M", 0o100664, ":insert", self.ignorename) + earliest.fileops.append(newop) + repo.renumber() + announce("initial %s created." % self.ignorename) + announce("%d %s blobs modified." % (changecount, self.ignorename)) + if 'rename' in line: + changecount = 0 + for (_, event) in repo.iterevents(indices=None, types=(Commit,)): + for fileop in event.fileops: + for attr in ("path", "source", "target"): + if hasattr(fileop, attr): + oldpath = getattr(fileop, "path") + if oldpath and oldpath.endswith(self.ignorename): + newpath = os.path.join(os.path.dirname(oldpath), + self.preferred.ignorename) + setattr(fileop, attr, newpath) + changecount += 1 + if fileop.op == "M": + blob = repo.objfind(fileop.ref) + if blob.pathlist[0] == oldpath: + blob.pathlist[0] = newpath + announce("%d ignore files renamed (%s -> %s)." + % (changecount, + self.ignorename, + self.preferred.ignorename)) + self.ignorename = self.preferred.ignorename + if 'translate' in line: + changecount = 0 + for (_, blob) in repo.iterevents(indices=None, types=(Blob,)): + if isignore(blob): + if self.preferred.name == "hg": + if not blob.get_content().startswith("syntax: glob\n"): + blob.set_content("syntax: glob\n" + blob.get_content()) + changecount += 1 + announce("%d %s blobs modified." % (changecount, self.ignorename)) + # # Artifact removal # def help_authors(self): print(""" Apply or dump author-map information for the specified selection -set, defaulting to all events. +set, defaulting to all events. Lifts from CVS and Subversion may have only usernames local to the repository host in committer and author IDs. DVCSes want email @@ -9557,7 +9749,7 @@ metdata for which the local ID matches between < and @ are replaced according to the mapping (this handles git-svn lifts). Alternatively, if the local ID is the entire address, this is also considered a match -(this handles what git-cvsimport and cvs2git do) +(this handles what git-cvsimport and cvs2git do) With the 'read' modifier, or no modifier, apply author mapping data (from standard input or a <-redirected input file). May be useful if @@ -10004,7 +10196,7 @@ raise Recoverable(str(e)) # - # Version binding + # Version binding # def help_version(self): print(""" @@ -10031,6 +10223,20 @@ raise Fatal("major version mismatch, aborting.") elif verbose > 0: announce("version check passed.") + + # + # Exiting (in case EOT has been rebound) + # + def help_exit(self): + print(""" +Exit the program cleanly, emitting a goodbye message. + +Typing EOT (usually Ctrl-D) will exit quietly. +""") + def do_exit(self, _line): + announce("exiting, elapsed time %d sec." % (time.time() - self.start_time)) + sys.exit(0) + # # Running unit tests (undocumented) # @@ -10092,6 +10298,11 @@ else: cProfile.run('interactive()') else: + # A minor concession to people used to GNU conventions. + # Makes "reposurgeon --help" and "reposurgeon --version" + # work as expected. + if arg.startswith("--"): + arg = arg[2:] # Call the base method so RecoverableExceptions # won't be caught; we want them to abort scripting. cmd.Cmd.onecmd(interpreter, interpreter.precmd(arg)) diff -Nru reposurgeon-3.10/reposurgeon.xml reposurgeon-3.12/reposurgeon.xml --- reposurgeon-3.10/reposurgeon.xml 2014-04-17 21:53:30.000000000 +0000 +++ reposurgeon-3.12/reposurgeon.xml 2014-09-11 13:22:08.000000000 +0000 @@ -113,11 +113,14 @@ interactive command interpreter or in batch mode to execute commands given as arguments on the reposurgeon invocation line. The only differences between these modes are (1) the interactive -one begins by turning on the 'verbose 1' option, and (2) in batch mode -all errors (including normally recoverable errors in selection-set -syntax) are fatal. Also, in interactive mode, Ctrl-P and Ctrl-N will -be available to scroll through your command history and tab completion -of command keywords is available. +one begins by turning on the 'verbose 1' option, (2) in batch mode all +errors (including normally recoverable errors in selection-set syntax) +are fatal, and (3) each command-line argument beginning with +-- has that stripped off (which, in particular means +that --help and --version will work as expected). Also, in interactive +mode, Ctrl-P and Ctrl-N will be available to scroll through your +command history and tab completion of command keywords is +available. A git-fast-import stream consists of a sequence of commands which must be executed in the specified sequence to build the repo; to @@ -128,6 +131,19 @@ sequence numbers so reposurgeon will know which events to modify or delete. +For all the details of event types and semantics, see the +git-fast-import1 +manual page; the rest of this paragraph is a quick start for the +impatient. Most events in a stream are commits describing revision states of the +repository; these group together under a single change comment one or +more fileops (file operations), which +usually point to blobs that are +revision states of individual files. A fileop may also be a delete +operation indicating that a specified previously-existing file was +deleted as part of the version commit; there are a couple of other +special fileop types of lesser importance. + Commands to reposurgeon consist of a command keyword, sometimes preceded by a selection set, sometimes followed by whitespace-separated arguments. It is often possible to @@ -166,6 +182,9 @@ :46 tip ;; Display the branch tip that owns commit :46. @dsc(:55) list ;; Display all commits with ancestry tracing to :55 + +@min([.gitignore]) remove .gitignore delete + ;; Remove the first .gitignore fileop in the repo. @@ -945,6 +964,14 @@ The default is to squash forward, modifying children; but see the list of policy modifiers below for how to change this. + +It is easy to get the bounds of a squash command wrong, with +confusing and destructive results. Beware thinking you can squash on a +selection set to merge all commits except the last one into the last +one; what you will actually do is to merge all of them to the first +commit after the selected set. + + Normally, any tag pointing to a combined commit will also be pushed forward. But see the list of policy modifiers below for how to change this. @@ -1128,7 +1155,7 @@ coalesce - [--debug}|--coalesce] + [--debug}|--changelog] [timefuzz] @@ -1325,9 +1352,8 @@ for author names only. With --replace, the behavior is like --regexp but the expressions are -not interpreted as regular expressions. (This is slightly faster). - - +not interpreted as regular expressions. (This is slightly +faster). With --dedos, DOS/Windows-style \r\n line terminators are replaced with \n. @@ -1663,6 +1689,35 @@ With no modifiers, this command strips blobs. + +ignores + [rename]. + [translate]. + [defaults]. + + +Intelligent handling of ignore-pattern files. +This command fails if no repository has been selected or no preferred write +type has been set for the repository. It does not take a selection set. + +If the rename modifier is present, this command attempts to rename all +ignore-pattern files to whatever is appropriate for the preferred type +- e.g. .gitignore for git, .hgignore for hg, etc. This option does not +cause any translation of the ignore files it renames. + +If the translate modifier is present, syntax translation of each ignore +file is attempted. At present, the only transformation the code knows +is to prepend a 'syntax: glob' header if the preferred type is hg. + +If the defaults modifier is present, the command attempts to prepend +these default patterns to all ignore files. If no ignore file is +created by the first commit, it will be modified to create one +containing the defaults. This command will error out on prefer types +that have no default ignore patterns (git and hg, in particular). It +will also error out when it knows the import tool has already set +default patterns. + + @@ -2305,6 +2360,15 @@ Mainly of interest to developers trying to speed up the program. + +exit + + +Exit, reporting the time. Included here because, while EOT will +also cleanly exit the interpreter, this command reports elapsed time +since start. + + @@ -2342,11 +2406,6 @@ it. - ---noignores -Do not generate a simulation of Subversion ignores in -the root node. - These modifiers can go anywhere in any order on the read command @@ -2429,10 +2488,11 @@ cvs2svn are incorporated into the internal map used for reference-lifting, then discarded. -Per-directory svn:ignore properties become .gitignore files; if -you're somehow managed to have an actual .gitignore file in a -Subversion directory with an svn:ignore property, the file will get -stepped on. +Per-directory svn:ignore properties become .gitignore files. +Actual .gitignore files in a Subversion directory are presumed to have +been created by git-svn users separately from native Subversion ignore +properties and discarded with a warning. It is up to the user to merge +the content of such files into the target repository by hand. svn:mergeinfo properties are interpreted. Any svn:mergeinfo property on a revision A with a merge source range ending in revision B produces @@ -2764,7 +2824,6 @@ When writing to an hg repository from any other kind, reposurgeon prepends to the output .hgignore a "syntax: glob" line. - TRANSLATION STYLE