From a6d5f2891dc353ebe5d9d8598790a6674c174eec Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 19 Jul 2009 15:24:51 -0400 Subject: [PATCH] Added --allow-empty to "be commit" Previously many backends would silently add an empty commit. Not very useful. When the new --allow-empty flag and related allow_empty options are false, every versioning backend is guaranteed to raise the EmptyCommit exception in the case of an attempted empty commit. --- becommands/commit.py | 15 +++++++++++--- libbe/arch.py | 19 +++++++++--------- libbe/bzr.py | 18 ++++++++++++++--- libbe/cmdutil.py | 6 ++++-- libbe/darcs.py | 29 +++++++++++++------------- libbe/git.py | 15 +++++++++++--- libbe/hg.py | 11 +++++++--- libbe/rcs.py | 48 +++++++++++++++++++++++++++++++++----------- test_usage.sh | 5 +++++ 9 files changed, 117 insertions(+), 49 deletions(-) diff --git a/becommands/commit.py b/becommands/commit.py index bda51c4..4f3bdbd 100644 --- a/becommands/commit.py +++ b/becommands/commit.py @@ -14,7 +14,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """Commit the currently pending changes to the repository""" -from libbe import cmdutil, bugdir, editor +from libbe import cmdutil, bugdir, editor, rcs import sys __desc__ = __doc__ @@ -49,13 +49,22 @@ def execute(args, manipulate_encodings=True): body = editor.editor_string("Please enter your commit message above") else: body = bd.rcs.get_file_contents(options.body, allow_no_rcs=True) - revision = bd.rcs.commit(summary, body=body) - print "Committed %s" % revision + try: + revision = bd.rcs.commit(summary, body=body, + allow_empty=options.allow_empty) + except rcs.EmptyCommit, e: + print e + return 1 + else: + print "Committed %s" % revision def get_parser(): parser = cmdutil.CmdOptionParser("be commit COMMENT") parser.add_option("-b", "--body", metavar="FILE", dest="body", help='Provide a detailed body for the commit message. In the special case that FILE == "EDITOR", spawn an editor to enter the body text (in which case you cannot use stdin for the summary)', default=None) + parser.add_option("-a", "--allow-empty", dest="allow_empty", + help="Allow empty commits", + default=False, action="store_true") return parser longhelp=""" diff --git a/libbe/arch.py b/libbe/arch.py index 30983e7..2f45aa9 100644 --- a/libbe/arch.py +++ b/libbe/arch.py @@ -260,16 +260,17 @@ class Arch(RCS): else: status,output,error = \ self._u_invoke_client("get", revision,directory) - def _rcs_commit(self, commitfile): + def _rcs_commit(self, commitfile, allow_empty=False): + if allow_empty == False: + # arch applies empty commits without complaining, so check first + status,output,error = self._u_invoke_client("changes",expect=(0,1)) + if status == 0: + raise rcs.EmptyCommit() summary,body = self._u_parse_commitfile(commitfile) - #status,output,error = self._invoke_client("make-log") - if body == None: - status,output,error \ - = self._u_invoke_client("commit","--summary",summary) - else: - status,output,error \ - = self._u_invoke_client("commit","--summary",summary, - "--log-message",body) + args = ["commit", "--summary", summary] + if body != None: + args.extend(["--log-message",body]) + status,output,error = self._u_invoke_client(*args) revision = None revline = re.compile("[*] committed (.*)") match = revline.search(output) diff --git a/libbe/bzr.py b/libbe/bzr.py index fcbd6ac..b33292c 100644 --- a/libbe/bzr.py +++ b/libbe/bzr.py @@ -71,9 +71,21 @@ class Bzr(RCS): else: self._u_invoke_client("branch", "--revision", revision, ".", directory) - def _rcs_commit(self, commitfile): - status,output,error = self._u_invoke_client("commit", "--unchanged", - "--file", commitfile) + def _rcs_commit(self, commitfile, allow_empty=False): + args = ["commit", "--file", commitfile] + if allow_empty == True: + args.append("--unchanged") + status,output,error = self._u_invoke_client(*args) + else: + kwargs = {"expect":(0,3)} + status,output,error = self._u_invoke_client(*args, **kwargs) + if status != 0: + strings = ["ERROR: no changes to commit.", # bzr 1.3.1 + "ERROR: No changes to commit."] # bzr 1.15.1 + if self._u_any_in_string(strings, error) == True: + raise rcs.EmptyCommit() + else: + raise rcs.CommandError(args, status, error) revision = None revline = re.compile("Committed revision (.*)[.]") match = revline.search(error) diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index bba3e0e..853a75a 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -73,8 +73,10 @@ def get_command(command_name): def execute(cmd, args): enc = encoding.get_encoding() cmd = get_command(cmd) - cmd.execute([a.decode(enc) for a in args]) - return 0 + ret = cmd.execute([a.decode(enc) for a in args]) + if ret == None: + ret = 0 + return ret def help(cmd=None, parser=None): if cmd != None: diff --git a/libbe/darcs.py b/libbe/darcs.py index 1beb45d..e7132c0 100644 --- a/libbe/darcs.py +++ b/libbe/darcs.py @@ -131,24 +131,25 @@ class Darcs(RCS): RCS._rcs_duplicate_repo(self, directory, revision) else: self._u_invoke_client("put", "--to-patch", revision, directory) - def _rcs_commit(self, commitfile): + def _rcs_commit(self, commitfile, allow_empty=False): id = self.get_user_id() if '@' not in id: id = "%s <%s@invalid.com>" % (id, id) - # Darcs doesn't like commitfiles without trailing endlines. - f = codecs.open(commitfile, 'r', self.encoding) - contents = f.read() - f.close() - if contents[-1] != '\n': - f = codecs.open(commitfile, 'a', self.encoding) - f.write('\n') - f.close() - status,output,error = self._u_invoke_client('record', '--all', - '--author', id, - '--logfile', commitfile) + args = ['record', '--all', '--author', id, '--logfile', commitfile] + status,output,error = self._u_invoke_client(*args) + empty_strings = ["No changes!"] revision = None - - revline = re.compile("Finished recording patch '(.*)'") + if self._u_any_in_string(empty_strings, output) == True: + if allow_empty == False: + raise rcs.EmptyCommit() + else: # we need a extra call to get the current revision + args = ["changes", "--last=1", "--xml"] + status,output,error = self._u_invoke_client(*args) + revline = re.compile("[ \t]*(.*)") + # note that darcs does _not_ make an empty revision. + # this returns the last non-empty revision id... + else: + revline = re.compile("Finished recording patch '(.*)'") match = revline.search(output) assert match != None, output+error assert len(match.groups()) == 1 diff --git a/libbe/git.py b/libbe/git.py index 4a91d44..2f9ffa9 100644 --- a/libbe/git.py +++ b/libbe/git.py @@ -93,9 +93,18 @@ class Git(RCS): #self._u_invoke_client("archive", revision, directory) # makes tarball self._u_invoke_client("clone", "--no-checkout",".",directory) self._u_invoke_client("checkout", revision, directory=directory) - def _rcs_commit(self, commitfile): - status,output,error = self._u_invoke_client('commit', '-a', - '-F', commitfile) + def _rcs_commit(self, commitfile, allow_empty=False): + args = ['commit', '--all', '--file', commitfile] + if allow_empty == True: + args.append("--allow-empty") + status,output,error = self._u_invoke_client(*args) + else: + kwargs = {"expect":(0,1)} + status,output,error = self._u_invoke_client(*args, **kwargs) + strings = ["nothing to commit", + "nothing added to commit"] + if self._u_any_in_string(strings, output) == True: + raise rcs.EmptyCommit() revision = None revline = re.compile("(.*) (.*)[:\]] (.*)") match = revline.search(output) diff --git a/libbe/hg.py b/libbe/hg.py index c301948..a20eeb5 100644 --- a/libbe/hg.py +++ b/libbe/hg.py @@ -58,7 +58,7 @@ class Hg(RCS): def _rcs_add(self, path): self._u_invoke_client("add", path) def _rcs_remove(self, path): - self._u_invoke_client("rm", path) + self._u_invoke_client("rm", "--force", path) def _rcs_update(self, path): pass def _rcs_get_file_contents(self, path, revision=None, binary=False): @@ -73,8 +73,13 @@ class Hg(RCS): return RCS._rcs_duplicate_repo(self, directory, revision) else: self._u_invoke_client("archive", "--rev", revision, directory) - def _rcs_commit(self, commitfile): - self._u_invoke_client('commit', '--logfile', commitfile) + def _rcs_commit(self, commitfile, allow_empty=False): + args = ['commit', '--logfile', commitfile] + status,output,error = self._u_invoke_client(*args) + if allow_empty == False: + strings = ["nothing changed"] + if self._u_any_in_string(strings, output) == True: + raise rcs.EmptyCommit() status,output,error = self._u_invoke_client('identify') revision = None revline = re.compile("(.*) tip") diff --git a/libbe/rcs.py b/libbe/rcs.py index 7138d01..3bf8c9d 100644 --- a/libbe/rcs.py +++ b/libbe/rcs.py @@ -61,10 +61,13 @@ def installed_rcs(): class CommandError(Exception): - def __init__(self, err_str, status): - Exception.__init__(self, "Command failed (%d): %s" % (status, err_str)) - self.err_str = err_str + def __init__(self, command, status, err_str): + strerror = ["Command failed (%d):\n %s\n" % (status, err_str), + "while executing\n %s" % command] + Exception.__init__(self, "\n".join(strerror)) + self.command = command self.status = status + self.err_str = err_str class SettingIDnotSupported(NotImplementedError): pass @@ -86,6 +89,10 @@ class NoSuchFile(Exception): path = os.path.abspath(os.path.join(root, pathname)) Exception.__init__(self, "No such file: %s" % path) +class EmptyCommit(Exception): + def __init__(self): + Exception.__init__(self, "No changes to commit") + def new(): return RCS() @@ -197,11 +204,14 @@ class RCS(object): dir specifies a directory to create the duplicate in. """ shutil.copytree(self.rootdir, directory, True) - def _rcs_commit(self, commitfile): + def _rcs_commit(self, commitfile, allow_empty=False): """ Commit the current working directory, using the contents of commitfile as the comment. Return the name of the old revision (or None if commits are not supported). + + If allow_empty == False, raise EmptyCommit if there are no + changes to commit. """ return None def installed(self): @@ -364,22 +374,25 @@ class RCS(object): shutil.rmtree(self._duplicateBasedir) self._duplicateBasedir = None self._duplicateDirname = None - def commit(self, summary, body=None): + def commit(self, summary, body=None, allow_empty=False): """ Commit the current working directory, with a commit message string summary and body. Return the name of the old revision (or None if versioning is not supported). + + If allow_empty == False (the default), raise EmptyCommit if + there are no changes to commit. """ - summary = summary.strip() + summary = summary.strip()+'\n' if body is not None: - summary += '\n\n' + body.strip() + '\n' + summary += '\n' + body.strip() + '\n' descriptor, filename = tempfile.mkstemp() revision = None try: temp_file = os.fdopen(descriptor, 'wb') temp_file.write(summary) temp_file.flush() - revision = self._rcs_commit(filename) + revision = self._rcs_commit(filename, allow_empty=allow_empty) temp_file.close() finally: os.remove(filename) @@ -388,7 +401,20 @@ class RCS(object): pass def postcommit(self, directory): pass + def _u_any_in_string(self, list, string): + """ + Return True if any of the strings in list are in string. + Otherwise return False. + """ + for list_string in list: + if list_string in string: + return True + return False def _u_invoke(self, args, stdin=None, expect=(0,), cwd=None): + """ + expect should be a tuple of allowed exit codes. cwd should be + the directory from which the command will be executed. + """ if cwd == None: cwd = self.rootdir if self.verboseInvoke == True: @@ -401,15 +427,13 @@ class RCS(object): q = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True, cwd=cwd) except OSError, e : - strerror = "%s\nwhile executing %s" % (e.args[1], args) - raise CommandError(strerror, e.args[0]) + raise CommandError(args, e.args[0], e) output, error = q.communicate(input=stdin) status = q.wait() if self.verboseInvoke == True: print >> sys.stderr, "%d\n%s%s" % (status, output, error) if status not in expect: - strerror = "%s\nwhile executing %s\n%s" % (args[1], args, error) - raise CommandError(strerror, status) + raise CommandError(args, status, error) return status, output, error def _u_invoke_client(self, *args, **kwargs): directory = kwargs.get('directory',None) diff --git a/test_usage.sh b/test_usage.sh index 48d572d..13be2ff 100755 --- a/test_usage.sh +++ b/test_usage.sh @@ -18,6 +18,8 @@ set -v # verbose, echo commands to stdout exec 6>&2 # save stderr to file descriptor 6 exec 2>&1 # fd 2 now writes to stdout +ONLY_TEST_COMMIT="true" + if [ $# -gt 1 ] then echo "usage: test_usage.sh [RCS]" @@ -130,6 +132,9 @@ BUGC=`echo "$OUT" | sed -n 's/Created bug with ID //p'` be comment $BUGC "The ants go marching..." be show --xml $BUGC | be comment --xml ${BUG}:2 - be remove $BUG # decide that you don't like that bug after all +be commit "You can even commit using BE" +be commit --allow-empty "And you can add empty commits if you like" +be commit "But this will fail" || echo "Failed" cd / rm -rf $TESTDIR -- 2.26.2