summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2023-10-08 13:13:24 -0400
committerJesse Luehrs <doy@tozt.net>2023-10-08 13:23:36 -0400
commitda9ca02b185d6cc95e41187a641fe3d0c77b6799 (patch)
treedef0b6bd4eabeef3efeb5a4939bfe4090a6276af
parent7b49108ed72f290c12d97e8b7c56bc20e3c47344 (diff)
downloadconf-da9ca02b185d6cc95e41187a641fe3d0c77b6799.tar.gz
conf-da9ca02b185d6cc95e41187a641fe3d0c77b6799.zip
remove some no longer used config
-rw-r--r--abook/.abook/abookrc39
-rwxr-xr-xlocal/.bin/clean-vim-undo13
-rw-r--r--local/.bin/git/git-imerge4135
-rwxr-xr-xlocal/.bin/hornet/twitch9
-rwxr-xr-xlocal/.bin/nopaste37
-rwxr-xr-xlocal/.bin/pick-music6
-rwxr-xr-xlocal/.bin/rand-music13
-rwxr-xr-xlocal/.bin/ringtone20
-rwxr-xr-xlocal/.bin/tozt/learn_spam38
-rwxr-xr-xlocal/.bin/update-addressbook276
-rw-r--r--mpd/.config/mpd/mpd.conf5
-rwxr-xr-xmpd/.services/available/mpd/log/run4
-rwxr-xr-xmpd/.services/available/mpd/run3
-rw-r--r--mpd/Makefile6
-rw-r--r--ncmpcpp/.config/ncmpcpp/bindings591
-rw-r--r--packages.hornet4
-rw-r--r--packages.mail2
-rw-r--r--packages.mz-doy11
-rw-r--r--packages.partofme2
-rw-r--r--packages.root1
-rw-r--r--packages.tozt2
-rw-r--r--screen/.screenrc28
22 files changed, 0 insertions, 5235 deletions
diff --git a/abook/.abook/abookrc b/abook/.abook/abookrc
deleted file mode 100644
index 671efa9..0000000
--- a/abook/.abook/abookrc
+++ /dev/null
@@ -1,39 +0,0 @@
-field aim = AIM
-field irc = IRC, list
-field twitter = Twitter
-field skype = Skype
-field birthday = Birthday, date
-
-field 2address = Address
-field 2address2 = Address2
-field 2city = City
-field 2state = State/Province
-field 2zip = "ZIP/Postal Code"
-field 2country = Country
-
-field textphone = Text
-
-view Contact = name, email, birthday, nick
-view Phone = mobile, textphone, phone, workphone
-view Internet = irc, aim, skype, twitter, url
-view Address = address, address2, city, state, zip, country
-view Address2 = 2address, 2address2, 2city, 2state, 2zip, 2country
-view Other = notes
-
-set index_format=" {name:23} {email:34} {mobile:12|phone|workphone} {birthday:10} {nick}"
-
-set preserve_fields=all
-set show_all_emails=false
-set www_command=firefox
-set address_style=us
-set sort_field=name
-
-set use_colors=true
-set color_header_bg=default
-set color_header_fg=green
-set color_footer_bg=default
-set color_footer_fg=green
-set color_list_header_bg=default
-set color_list_highlight_bg=cyan
-set color_list_even_fg=yellow
-set color_list_odd_fg=yellow
diff --git a/local/.bin/clean-vim-undo b/local/.bin/clean-vim-undo
deleted file mode 100755
index efcd3aa..0000000
--- a/local/.bin/clean-vim-undo
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/env perl
-use strict;
-use warnings;
-
-my $undodir = "$ENV{HOME}/.cache/vim/undo";
-opendir my $undo, $undodir
- or die "can't open $undodir: $!";
-
-for my $undofile (readdir($undo)) {
- next unless -f "$undodir/$undofile";
- (my $file = $undofile) =~ s{%}{/}g;
- unlink "$undodir/$undofile" unless -e $file && $file =~ m{^/};
-}
diff --git a/local/.bin/git/git-imerge b/local/.bin/git/git-imerge
deleted file mode 100644
index b903539..0000000
--- a/local/.bin/git/git-imerge
+++ /dev/null
@@ -1,4135 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2012-2013 Michael Haggerty <mhagger@alum.mit.edu>
-#
-# This file is part of git-imerge.
-#
-# git-imerge is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation, either version 2 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see
-# <http://www.gnu.org/licenses/>.
-
-r"""Git incremental merge
-
-Perform the merge between two branches incrementally. If conflicts
-are encountered, figure out exactly which pairs of commits conflict,
-and present the user with one pairwise conflict at a time for
-resolution.
-
-Multiple incremental merges can be in progress at the same time. Each
-incremental merge has a name, and its progress is recorded in the Git
-repository as references under 'refs/imerge/NAME'.
-
-An incremental merge can be interrupted and resumed arbitrarily, or
-even pushed to a server to allow somebody else to work on it.
-
-
-Instructions:
-
-To start an incremental merge or rebase, use one of the following
-commands:
-
- git-imerge merge BRANCH
- Analogous to "git merge BRANCH"
-
- git-imerge rebase BRANCH
- Analogous to "git rebase BRANCH"
-
- git-imerge drop [commit | commit1..commit2]
- Drop the specified commit(s) from the current branch
-
- git-imerge revert [commit | commit1..commit2]
- Revert the specified commits by adding new commits that
- reverse their effects
-
- git-imerge start --name=NAME --goal=GOAL BRANCH
- Start a general imerge
-
-Then the tool will present conflicts to you one at a time, similar to
-"git rebase --incremental". Resolve each conflict, and then
-
- git add FILE...
- git-imerge continue
-
-You can view your progress at any time with
-
- git-imerge diagram
-
-When you have resolved all of the conflicts, simplify and record the
-result by typing
-
- git-imerge finish
-
-To get more help about any git-imerge subcommand, type
-
- git-imerge SUBCOMMAND --help
-
-"""
-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import locale
-import sys
-import re
-import subprocess
-from subprocess import CalledProcessError
-from subprocess import check_call
-import itertools
-import argparse
-from io import StringIO
-import json
-import os
-
-
-PREFERRED_ENCODING = locale.getpreferredencoding()
-
-
-# Define check_output() for ourselves, including decoding of the
-# output into PREFERRED_ENCODING:
-def check_output(*popenargs, **kwargs):
- if 'stdout' in kwargs:
- raise ValueError('stdout argument not allowed, it will be overridden.')
- process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
- output = communicate(process)[0]
- retcode = process.poll()
- if retcode:
- cmd = kwargs.get("args")
- if cmd is None:
- cmd = popenargs[0]
- # We don't store output in the CalledProcessError because
- # the "output" keyword parameter was not supported in
- # Python 2.6:
- raise CalledProcessError(retcode, cmd)
- return output
-
-
-STATE_VERSION = (1, 3, 0)
-
-ZEROS = '0' * 40
-
-ALLOWED_GOALS = [
- 'full',
- 'rebase',
- 'rebase-with-history',
- 'border',
- 'border-with-history',
- 'border-with-history2',
- 'merge',
- 'drop',
- 'revert',
- ]
-DEFAULT_GOAL = 'merge'
-
-
-class Failure(Exception):
- """An exception that indicates a normal failure of the script.
-
- Failures are reported at top level via sys.exit(str(e)) rather
- than via a Python stack dump."""
-
- pass
-
-
-class AnsiColor:
- BLACK = '\033[0;30m'
- RED = '\033[0;31m'
- GREEN = '\033[0;32m'
- YELLOW = '\033[0;33m'
- BLUE = '\033[0;34m'
- MAGENTA = '\033[0;35m'
- CYAN = '\033[0;36m'
- B_GRAY = '\033[0;37m'
- D_GRAY = '\033[1;30m'
- B_RED = '\033[1;31m'
- B_GREEN = '\033[1;32m'
- B_YELLOW = '\033[1;33m'
- B_BLUE = '\033[1;34m'
- B_MAGENTA = '\033[1;35m'
- B_CYAN = '\033[1;36m'
- WHITE = '\033[1;37m'
- END = '\033[0m'
-
- @classmethod
- def disable(cls):
- cls.BLACK = ''
- cls.RED = ''
- cls.GREEN = ''
- cls.YELLOW = ''
- cls.BLUE = ''
- cls.MAGENTA = ''
- cls.CYAN = ''
- cls.B_GRAY = ''
- cls.D_GRAY = ''
- cls.B_RED = ''
- cls.B_GREEN = ''
- cls.B_YELLOW = ''
- cls.B_BLUE = ''
- cls.B_MAGENTA = ''
- cls.B_CYAN = ''
- cls.WHITE = ''
- cls.END = ''
-
-
-def iter_neighbors(iterable):
- """For an iterable (x0, x1, x2, ...) generate [(x0,x1), (x1,x2), ...]."""
-
- i = iter(iterable)
-
- try:
- last = next(i)
- except StopIteration:
- return
-
- for x in i:
- yield (last, x)
- last = x
-
-
-def find_first_false(f, lo, hi):
- """Return the smallest i in lo <= i < hi for which f(i) returns False using bisection.
-
- If there is no such i, return hi.
-
- """
-
- # Loop invariant: f(i) returns True for i < lo; f(i) returns False
- # for i >= hi.
-
- while lo < hi:
- mid = (lo + hi) // 2
- if f(mid):
- lo = mid + 1
- else:
- hi = mid
-
- return lo
-
-
-def call_silently(cmd):
- try:
- NULL = open(os.devnull, 'w')
- except (IOError, AttributeError):
- NULL = subprocess.PIPE
-
- p = subprocess.Popen(cmd, stdout=NULL, stderr=NULL)
- p.communicate()
- retcode = p.wait()
- if retcode:
- raise CalledProcessError(retcode, cmd)
-
-
-def communicate(process, input=None):
- """Return decoded output from process."""
- if input is not None:
- input = input.encode(PREFERRED_ENCODING)
-
- output, error = process.communicate(input)
-
- output = None if output is None else output.decode(PREFERRED_ENCODING)
- error = None if error is None else error.decode(PREFERRED_ENCODING)
-
- return (output, error)
-
-
-if sys.hexversion < 0x03000000:
- # In Python 2.x, os.environ keys and values must be byte
- # strings:
- def env_encode(s):
- """Encode unicode keys or values for use in os.environ."""
-
- return s.encode(PREFERRED_ENCODING)
-
-else:
- # In Python 3.x, os.environ keys and values must be unicode
- # strings:
- def env_encode(s):
- """Use unicode keys or values unchanged in os.environ."""
-
- return s
-
-
-class UncleanWorkTreeError(Failure):
- pass
-
-
-class AutomaticMergeFailed(Exception):
- def __init__(self, commit1, commit2):
- Exception.__init__(
- self, 'Automatic merge of %s and %s failed' % (commit1, commit2,)
- )
- self.commit1, self.commit2 = commit1, commit2
-
-
-class InvalidBranchNameError(Failure):
- pass
-
-
-class NotFirstParentAncestorError(Failure):
- def __init__(self, commit1, commit2):
- Failure.__init__(
- self,
- 'Commit "%s" is not a first-parent ancestor of "%s"'
- % (commit1, commit2),
- )
-
-
-class NonlinearAncestryError(Failure):
- def __init__(self, commit1, commit2):
- Failure.__init__(
- self,
- 'The history "%s..%s" is not linear'
- % (commit1, commit2),
- )
-
-
-class NothingToDoError(Failure):
- def __init__(self, src_tip, dst_tip):
- Failure.__init__(
- self,
- 'There are no commits on "%s" that are not already in "%s"'
- % (src_tip, dst_tip),
- )
-
-
-class GitTemporaryHead(object):
- """A context manager that records the current HEAD state then restores it.
-
- This should only be used when the working copy is clean. message
- is used for the reflog.
-
- """
-
- def __init__(self, git, message):
- self.git = git
- self.message = message
-
- def __enter__(self):
- self.head_name = self.git.get_head_refname()
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- if self.head_name:
- try:
- self.git.restore_head(self.head_name, self.message)
- except CalledProcessError as e:
- raise Failure(
- 'Could not restore HEAD to %r!: %s\n'
- % (self.head_name, e.message,)
- )
-
- return False
-
-
-class GitRepository(object):
- BRANCH_PREFIX = 'refs/heads/'
-
- MERGE_STATE_REFNAME_RE = re.compile(
- r"""
- ^
- refs\/imerge\/
- (?P<name>.+)
- \/state
- $
- """,
- re.VERBOSE,
- )
-
- def __init__(self):
- self.git_dir_cache = None
-
- def git_dir(self):
- if self.git_dir_cache is None:
- self.git_dir_cache = check_output(
- ['git', 'rev-parse', '--git-dir']
- ).rstrip('\n')
-
- return self.git_dir_cache
-
- def check_imerge_name_format(self, name):
- """Check that name is a valid imerge name."""
-
- try:
- call_silently(
- ['git', 'check-ref-format', 'refs/imerge/%s' % (name,)]
- )
- except CalledProcessError:
- raise Failure('Name %r is not a valid refname component!' % (name,))
-
- def check_branch_name_format(self, name):
- """Check that name is a valid branch name."""
-
- try:
- call_silently(
- ['git', 'check-ref-format', 'refs/heads/%s' % (name,)]
- )
- except CalledProcessError:
- raise InvalidBranchNameError('Name %r is not a valid branch name!' % (name,))
-
- def iter_existing_imerge_names(self):
- """Iterate over the names of existing MergeStates in this repo."""
-
- for line in check_output(['git', 'for-each-ref', 'refs/imerge']).splitlines():
- (sha1, type, refname) = line.split()
- if type == 'blob':
- m = GitRepository.MERGE_STATE_REFNAME_RE.match(refname)
- if m:
- yield m.group('name')
-
- def set_default_imerge_name(self, name):
- """Set the default merge to the specified one.
-
- name can be None to cause the default to be cleared."""
-
- if name is None:
- try:
- check_call(['git', 'config', '--unset', 'imerge.default'])
- except CalledProcessError as e:
- if e.returncode == 5:
- # Value was not set
- pass
- else:
- raise
- else:
- check_call(['git', 'config', 'imerge.default', name])
-
- def get_default_imerge_name(self):
- """Get the name of the default merge, or None if it is currently unset."""
-
- try:
- return check_output(['git', 'config', 'imerge.default']).rstrip()
- except CalledProcessError:
- return None
-
- def get_default_edit(self):
- """Should '--edit' be used when committing intermediate user merges?
-
- When 'git imerge continue' or 'git imerge record' finds a user
- merge that can be committed, should it (by default) ask the user
- to edit the commit message? This behavior can be configured via
- 'imerge.editmergemessages'. If it is not configured, return False.
-
- Please note that this function is only used to choose the default
- value. It can be overridden on the command line using '--edit' or
- '--no-edit'.
-
- """
-
- try:
- return {'true' : True, 'false' : False}[
- check_output(
- ['git', 'config', '--bool', 'imerge.editmergemessages']
- ).rstrip()
- ]
- except CalledProcessError:
- return False
-
- def unstaged_changes(self):
- """Return True iff there are unstaged changes in the working copy"""
-
- try:
- check_call(['git', 'diff-files', '--quiet', '--ignore-submodules'])
- return False
- except CalledProcessError:
- return True
-
- def uncommitted_changes(self):
- """Return True iff the index contains uncommitted changes."""
-
- try:
- check_call([
- 'git', 'diff-index', '--cached', '--quiet',
- '--ignore-submodules', 'HEAD', '--',
- ])
- return False
- except CalledProcessError:
- return True
-
- def get_commit_sha1(self, arg):
- """Convert arg into a SHA1 and verify that it refers to a commit.
-
- If not, raise ValueError."""
-
- try:
- return self.rev_parse('%s^{commit}' % (arg,))
- except CalledProcessError:
- raise ValueError('%r does not refer to a valid git commit' % (arg,))
-
- def refresh_index(self):
- process = subprocess.Popen(
- ['git', 'update-index', '-q', '--ignore-submodules', '--refresh'],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- )
- out, err = communicate(process)
- retcode = process.poll()
- if retcode:
- raise UncleanWorkTreeError(err.rstrip() or out.rstrip())
-
- def verify_imerge_name_available(self, name):
- self.check_imerge_name_format(name)
- if check_output(['git', 'for-each-ref', 'refs/imerge/%s' % (name,)]):
- raise Failure('Name %r is already in use!' % (name,))
-
- def check_imerge_exists(self, name):
- """Verify that a MergeState with the given name exists.
-
- Just check for the existence, readability, and compatible
- version of the 'state' reference. If the reference doesn't
- exist, just return False. If it exists but is unusable for
- some other reason, raise an exception."""
-
- self.check_imerge_name_format(name)
- state_refname = 'refs/imerge/%s/state' % (name,)
- for line in check_output(['git', 'for-each-ref', state_refname]).splitlines():
- (sha1, type, refname) = line.split()
- if refname == state_refname and type == 'blob':
- self.read_imerge_state_dict(name)
- # If that didn't throw an exception:
- return True
- else:
- return False
-
- def read_imerge_state_dict(self, name):
- state_string = check_output(
- ['git', 'cat-file', 'blob', 'refs/imerge/%s/state' % (name,)],
- )
- state = json.loads(state_string)
-
- # Convert state['version'] to a tuple of integers, and verify
- # that it is compatible with this version of the script:
- version = tuple(int(i) for i in state['version'].split('.'))
- if version[0] != STATE_VERSION[0] or version[1] > STATE_VERSION[1]:
- raise Failure(
- 'The format of imerge %s (%s) is not compatible with this script version.'
- % (name, state['version'],)
- )
- state['version'] = version
-
- return state
-
- def read_imerge_state(self, name):
- """Read the state associated with the specified imerge.
-
- Return the tuple
-
- (state_dict, {(i1, i2) : (sha1, source), ...})
-
- , where source is 'auto' or 'manual'. Validity is checked only
- lightly.
-
- """
-
- merge_ref_re = re.compile(
- r"""
- ^
- refs\/imerge\/
- """ + re.escape(name) + r"""
- \/(?P<source>auto|manual)\/
- (?P<i1>0|[1-9][0-9]*)
- \-
- (?P<i2>0|[1-9][0-9]*)
- $
- """,
- re.VERBOSE,
- )
-
- state_ref_re = re.compile(
- r"""
- ^
- refs\/imerge\/
- """ + re.escape(name) + r"""
- \/state
- $
- """,
- re.VERBOSE,
- )
-
- state = None
-
- # A map {(i1, i2) : (sha1, source)}:
- merges = {}
-
- # refnames that were found but not understood:
- unexpected = []
-
- for line in check_output([
- 'git', 'for-each-ref', 'refs/imerge/%s' % (name,)
- ]).splitlines():
- (sha1, type, refname) = line.split()
- m = merge_ref_re.match(refname)
- if m:
- if type != 'commit':
- raise Failure('Reference %r is not a commit!' % (refname,))
- i1, i2 = int(m.group('i1')), int(m.group('i2'))
- source = m.group('source')
- merges[i1, i2] = (sha1, source)
- continue
-
- m = state_ref_re.match(refname)
- if m:
- if type != 'blob':
- raise Failure('Reference %r is not a blob!' % (refname,))
- state = self.read_imerge_state_dict(name)
- continue
-
- unexpected.append(refname)
-
- if state is None:
- raise Failure(
- 'No state found; it should have been a blob reference at '
- '"refs/imerge/%s/state"' % (name,)
- )
-
- if unexpected:
- raise Failure(
- 'Unexpected reference(s) found in "refs/imerge/%s" namespace:\n %s\n'
- % (name, '\n '.join(unexpected),)
- )
-
- return (state, merges)
-
- def write_imerge_state_dict(self, name, state):
- state_string = json.dumps(state, sort_keys=True) + '\n'
-
- cmd = ['git', 'hash-object', '-t', 'blob', '-w', '--stdin']
- p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
- out = communicate(p, input=state_string)[0]
- retcode = p.poll()
- if retcode:
- raise CalledProcessError(retcode, cmd)
- sha1 = out.strip()
- check_call([
- 'git', 'update-ref',
- '-m', 'imerge %r: Record state' % (name,),
- 'refs/imerge/%s/state' % (name,),
- sha1,
- ])
-
- def is_ancestor(self, commit1, commit2):
- """Return True iff commit1 is an ancestor (or equal to) commit2."""
-
- if commit1 == commit2:
- return True
- else:
- return int(
- check_output([
- 'git', 'rev-list', '--count', '--ancestry-path',
- '%s..%s' % (commit1, commit2,),
- ]).strip()
- ) != 0
-
- def is_ff(self, refname, commit):
- """Would updating refname to commit be a fast-forward update?
-
- Return True iff refname is not currently set or it points to an
- ancestor of commit.
-
- """
-
- try:
- ref_oldval = self.get_commit_sha1(refname)
- except ValueError:
- # refname doesn't already exist; no problem.
- return True
- else:
- return self.is_ancestor(ref_oldval, commit)
-
- def automerge(self, commit1, commit2, msg=None):
- """Attempt an automatic merge of commit1 and commit2.
-
- Return the SHA1 of the resulting commit, or raise
- AutomaticMergeFailed on error. This must be called with a clean
- worktree."""
-
- call_silently(['git', 'checkout', '-f', commit1])
- cmd = ['git', '-c', 'rerere.enabled=false', 'merge']
- if msg is not None:
- cmd += ['-m', msg]
- cmd += [commit2]
- try:
- call_silently(cmd)
- except CalledProcessError:
- self.abort_merge()
- raise AutomaticMergeFailed(commit1, commit2)
- else:
- return self.get_commit_sha1('HEAD')
-
- def manualmerge(self, commit, msg):
- """Initiate a merge of commit into the current HEAD."""
-
- check_call(['git', 'merge', '--no-commit', '-m', msg, commit,])
-
- def require_clean_work_tree(self, action):
- """Verify that the current tree is clean.
-
- The code is a Python translation of the git-sh-setup(1) function
- of the same name."""
-
- process = subprocess.Popen(
- ['git', 'rev-parse', '--verify', 'HEAD'],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- )
- err = communicate(process)[1]
- retcode = process.poll()
- if retcode:
- raise UncleanWorkTreeError(err.rstrip())
-
- self.refresh_index()
-
- error = []
- if self.unstaged_changes():
- error.append('Cannot %s: You have unstaged changes.' % (action,))
-
- if self.uncommitted_changes():
- if not error:
- error.append('Cannot %s: Your index contains uncommitted changes.' % (action,))
- else:
- error.append('Additionally, your index contains uncommitted changes.')
-
- if error:
- raise UncleanWorkTreeError('\n'.join(error))
-
- def simple_merge_in_progress(self):
- """Return True iff a merge (of a single branch) is in progress."""
-
- try:
- with open(os.path.join(self.git_dir(), 'MERGE_HEAD')) as f:
- heads = [line.rstrip() for line in f]
- except IOError:
- return False
-
- return len(heads) == 1
-
- def commit_user_merge(self, edit_log_msg=None):
- """If a merge is in progress and ready to be committed, commit it.
-
- If a simple merge is in progress and any changes in the working
- tree are staged, commit the merge commit and return True.
- Otherwise, return False.
-
- """
-
- if not self.simple_merge_in_progress():
- return False
-
- # Check if all conflicts are resolved and everything in the
- # working tree is staged:
- self.refresh_index()
- if self.unstaged_changes():
- raise UncleanWorkTreeError(
- 'Cannot proceed: You have unstaged changes.'
- )
-
- # A merge is in progress, and either all changes have been staged
- # or no changes are necessary. Create a merge commit.
- cmd = ['git', 'commit', '--no-verify']
-
- if edit_log_msg is None:
- edit_log_msg = self.get_default_edit()
-
- if edit_log_msg:
- cmd += ['--edit']
- else:
- cmd += ['--no-edit']
-
- try:
- check_call(cmd)
- except CalledProcessError:
- raise Failure('Could not commit staged changes.')
-
- return True
-
- def create_commit_chain(self, base, path):
- """Point refname at the chain of commits indicated by path.
-
- path is a list [(commit, metadata), ...]. Create a series of
- commits corresponding to the entries in path. Each commit's tree
- is taken from the corresponding old commit, and each commit's
- metadata is taken from the corresponding metadata commit. Use base
- as the parent of the first commit, or make the first commit a root
- commit if base is None. Reuse existing commits from the list
- whenever possible.
-
- Return a commit object corresponding to the last commit in the
- chain.
-
- """
-
- reusing = True
- if base is None:
- if not path:
- raise ValueError('neither base nor path specified')
- parents = []
- else:
- parents = [base]
-
- for (commit, metadata) in path:
- if reusing:
- if commit == metadata and self.get_commit_parents(commit) == parents:
- # We can reuse this commit, too.
- parents = [commit]
- continue
- else:
- reusing = False
-
- # Create a commit, copying the old log message and author info
- # from the metadata commit:
- tree = self.get_tree(commit)
- new_commit = self.commit_tree(
- tree, parents,
- msg=self.get_log_message(metadata),
- metadata=self.get_author_info(metadata),
- )
- parents = [new_commit]
-
- [commit] = parents
- return commit
-
- def rev_parse(self, arg):
- return check_output(['git', 'rev-parse', '--verify', '--quiet', arg]).strip()
-
- def rev_list(self, *args):
- cmd = ['git', 'rev-list'] + list(args)
- return [
- l.strip()
- for l in check_output(cmd).splitlines()
- ]
-
- def rev_list_with_parents(self, *args):
- """Iterate over (commit, [parent,...])."""
-
- cmd = ['git', 'log', '--format=%H %P'] + list(args)
- for line in check_output(cmd).splitlines():
- commits = line.strip().split()
- yield (commits[0], commits[1:])
-
- def summarize_commit(self, commit):
- """Summarize `commit` to stdout."""
-
- check_call(['git', '--no-pager', 'log', '--no-walk', commit])
-
- def get_author_info(self, commit):
- """Return environment settings to set author metadata.
-
- Return a map {str : str}."""
-
- # We use newlines as separators here because msysgit has problems
- # with NUL characters; see
- #
- # https://github.com/mhagger/git-imerge/pull/71
- a = check_output([
- 'git', '--no-pager', 'log', '-n1',
- '--format=%an%n%ae%n%ai', commit
- ]).strip().splitlines()
-
- return {
- 'GIT_AUTHOR_NAME': env_encode(a[0]),
- 'GIT_AUTHOR_EMAIL': env_encode(a[1]),
- 'GIT_AUTHOR_DATE': env_encode(a[2]),
- }
-
- def get_log_message(self, commit):
- contents = check_output([
- 'git', 'cat-file', 'commit', commit,
- ]).splitlines(True)
- contents = contents[contents.index('\n') + 1:]
- if contents and contents[-1][-1:] != '\n':
- contents.append('\n')
- return ''.join(contents)
-
- def get_commit_parents(self, commit):
- """Return a list containing the parents of commit."""
-
- return check_output(
- ['git', '--no-pager', 'log', '--no-walk', '--pretty=format:%P', commit]
- ).strip().split()
-
- def get_tree(self, arg):
- return self.rev_parse('%s^{tree}' % (arg,))
-
- def update_ref(self, refname, value, msg, deref=True):
- if deref:
- opt = []
- else:
- opt = ['--no-deref']
-
- check_call(['git', 'update-ref'] + opt + ['-m', msg, refname, value])
-
- def delete_ref(self, refname, msg, deref=True):
- if deref:
- opt = []
- else:
- opt = ['--no-deref']
-
- check_call(['git', 'update-ref'] + opt + ['-m', msg, '-d', refname])
-
- def delete_imerge_refs(self, name):
- stdin = ''.join(
- 'delete %s\n' % (refname,)
- for refname in check_output([
- 'git', 'for-each-ref',
- '--format=%(refname)',
- 'refs/imerge/%s' % (name,)
- ]).splitlines()
- )
-
- process = subprocess.Popen(
- [
- 'git', 'update-ref',
- '-m', 'imerge: remove merge %r' % (name,),
- '--stdin',
- ],
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
- )
- out = communicate(process, input=stdin)[0]
- retcode = process.poll()
- if retcode:
- sys.stderr.write(
- 'Warning: error removing references:\n%s' % (out,)
- )
-
- def detach(self, msg):
- """Detach HEAD. msg is used for the reflog."""
-
- self.update_ref('HEAD', 'HEAD^0', msg, deref=False)
-
- def reset_hard(self, commit):
- check_call(['git', 'reset', '--hard', commit])
-
- def amend(self):
- check_call(['git', 'commit', '--amend'])
-
- def abort_merge(self):
- # We don't use "git merge --abort" here because it was
- # only added in git version 1.7.4.
- check_call(['git', 'reset', '--merge'])
-
- def compute_best_merge_base(self, tip1, tip2):
- try:
- merge_bases = check_output(['git', 'merge-base', '--all', tip1, tip2]).splitlines()
- except CalledProcessError:
- raise Failure('Cannot compute merge base for %r and %r' % (tip1, tip2))
- if not merge_bases:
- raise Failure('%r and %r do not have a common merge base' % (tip1, tip2))
- if len(merge_bases) == 1:
- return merge_bases[0]
-
- # There are multiple merge bases. The "best" one is the one that
- # is the "closest" to the tips, which we define to be the one with
- # the fewest non-merge commits in "merge_base..tip". (It can be
- # shown that the result is independent of which tip is used in the
- # computation.)
- best_base = best_count = None
- for merge_base in merge_bases:
- cmd = ['git', 'rev-list', '--no-merges', '--count', '%s..%s' % (merge_base, tip1)]
- count = int(check_output(cmd).strip())
- if best_base is None or count < best_count:
- best_base = merge_base
- best_count = count
-
- return best_base
-
- def linear_ancestry(self, commit1, commit2, first_parent):
- """Compute a linear ancestry between commit1 and commit2.
-
- Our goal is to find a linear series of commits connecting
- `commit1` and `commit2`. We do so as follows:
-
- * If all of the commits in
-
- git rev-list --ancestry-path commit1..commit2
-
- are on a linear chain, return that.
-
- * If there are multiple paths between `commit1` and `commit2` in
- that list of commits, then
-
- * If `first_parent` is not set, then raise an
- `NonlinearAncestryError` exception.
-
- * If `first_parent` is set, then, at each merge commit, follow
- the first parent that is in that list of commits.
-
- Return a list of SHA-1s in 'chronological' order.
-
- Raise NotFirstParentAncestorError if commit1 is not an ancestor of
- commit2.
-
- """
-
- oid1 = self.rev_parse(commit1)
- oid2 = self.rev_parse(commit2)
-
- parentage = {oid1 : []}
- for (commit, parents) in self.rev_list_with_parents(
- '--ancestry-path', '--topo-order', '%s..%s' % (oid1, oid2)
- ):
- parentage[commit] = parents
-
- commits = []
-
- commit = oid2
- while commit != oid1:
- parents = parentage.get(commit, [])
-
- # Only consider parents that are in the ancestry path:
- included_parents = [
- parent
- for parent in parents
- if parent in parentage
- ]
-
- if not included_parents:
- raise NotFirstParentAncestorError(commit1, commit2)
- elif len(included_parents) == 1 or first_parent:
- parent = included_parents[0]
- else:
- raise NonlinearAncestryError(commit1, commit2)
-
- commits.append(commit)
- commit = parent
-
- commits.reverse()
-
- return commits
-
- def get_boundaries(self, tip1, tip2, first_parent):
- """Get the boundaries of an incremental merge.
-
- Given the tips of two branches that should be merged, return
- (merge_base, commits1, commits2) describing the edges of the
- imerge. Raise Failure if there are any problems."""
-
- merge_base = self.compute_best_merge_base(tip1, tip2)
-
- commits1 = self.linear_ancestry(merge_base, tip1, first_parent)
- if not commits1:
- raise NothingToDoError(tip1, tip2)
-
- commits2 = self.linear_ancestry(merge_base, tip2, first_parent)
- if not commits2:
- raise NothingToDoError(tip2, tip1)
-
- return (merge_base, commits1, commits2)
-
- def get_head_refname(self, short=False):
- """Return the name of the reference that is currently checked out.
-
- If `short` is set, return it as a branch name. If HEAD is
- currently detached, return None."""
-
- cmd = ['git', 'symbolic-ref', '--quiet']
- if short:
- cmd += ['--short']
- cmd += ['HEAD']
- try:
- return check_output(cmd).strip()
- except CalledProcessError:
- return None
-
- def restore_head(self, refname, message):
- check_call(['git', 'symbolic-ref', '-m', message, 'HEAD', refname])
- check_call(['git', 'reset', '--hard'])
-
- def checkout(self, refname, quiet=False):
- cmd = ['git', 'checkout']
- if quiet:
- cmd += ['--quiet']
- if refname.startswith(GitRepository.BRANCH_PREFIX):
- target = refname[len(GitRepository.BRANCH_PREFIX):]
- else:
- target = '%s^0' % (refname,)
- cmd += [target]
- check_call(cmd)
-
- def commit_tree(self, tree, parents, msg, metadata=None):
- """Create a commit containing the specified tree.
-
- metadata can be author or committer information to be added to the
- environment, as str objects; e.g., {'GIT_AUTHOR_NAME' : 'me'}.
-
- Return the SHA-1 of the new commit object."""
-
- cmd = ['git', 'commit-tree', tree]
- for parent in parents:
- cmd += ['-p', parent]
-
- if metadata is not None:
- env = os.environ.copy()
- env.update(metadata)
- else:
- env = os.environ
-
- process = subprocess.Popen(
- cmd, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- )
- out = communicate(process, input=msg)[0]
- retcode = process.poll()
-
- if retcode:
- # We don't store the output in the CalledProcessError because
- # the "output" keyword parameter was not supported in Python
- # 2.6:
- raise CalledProcessError(retcode, cmd)
-
- return out.strip()
-
- def revert(self, commit):
- """Apply the inverse of commit^..commit to HEAD and commit."""
-
- cmd = ['git', 'revert', '--no-edit']
- if len(self.get_commit_parents(commit)) > 1:
- cmd += ['-m', '1']
- cmd += [commit]
- check_call(cmd)
-
- def reparent(self, commit, parent_sha1s, msg=None):
- """Create a new commit object like commit, but with the specified parents.
-
- commit is the SHA1 of an existing commit and parent_sha1s is a
- list of SHA1s. Create a new commit exactly like that one, except
- that it has the specified parent commits. Return the SHA1 of the
- resulting commit object, which is already stored in the object
- database but is not yet referenced by anything.
-
- If msg is set, then use it as the commit message for the new
- commit."""
-
- old_commit = check_output(['git', 'cat-file', 'commit', commit])
- separator = old_commit.index('\n\n')
- headers = old_commit[:separator + 1].splitlines(True)
- rest = old_commit[separator + 2:]
-
- new_commit = StringIO()
- for i in range(len(headers)):
- line = headers[i]
- if line.startswith('tree '):
- new_commit.write(line)
- for parent_sha1 in parent_sha1s:
- new_commit.write('parent %s\n' % (parent_sha1,))
- elif line.startswith('parent '):
- # Discard old parents:
- pass
- else:
- new_commit.write(line)
-
- new_commit.write('\n')
- if msg is None:
- new_commit.write(rest)
- else:
- new_commit.write(msg)
- if not msg.endswith('\n'):
- new_commit.write('\n')
-
- process = subprocess.Popen(
- ['git', 'hash-object', '-t', 'commit', '-w', '--stdin'],
- stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- )
- out = communicate(process, input=new_commit.getvalue())[0]
- retcode = process.poll()
- if retcode:
- raise Failure('Could not reparent commit %s' % (commit,))
- return out.strip()
-
- def temporary_head(self, message):
- """Return a context manager to manage a temporary HEAD.
-
- On entry, record the current HEAD state. On exit, restore it.
- message is used for the reflog.
-
- """
-
- return GitTemporaryHead(self, message)
-
-
-class MergeRecord(object):
- # Bits for the flags field:
-
- # There is a saved successful auto merge:
- SAVED_AUTO = 0x01
-
- # An auto merge (which may have been unsuccessful) has been done:
- NEW_AUTO = 0x02
-
- # There is a saved successful manual merge:
- SAVED_MANUAL = 0x04
-
- # A manual merge (which may have been unsuccessful) has been done:
- NEW_MANUAL = 0x08
-
- # A merge that is currently blocking the merge frontier:
- BLOCKED = 0x10
-
- # Some useful bit combinations:
- SAVED = SAVED_AUTO | SAVED_MANUAL
- NEW = NEW_AUTO | NEW_MANUAL
-
- AUTO = SAVED_AUTO | NEW_AUTO
- MANUAL = SAVED_MANUAL | NEW_MANUAL
-
- ALLOWED_INITIAL_FLAGS = [
- SAVED_AUTO,
- SAVED_MANUAL,
- NEW_AUTO,
- NEW_MANUAL,
- ]
-
- def __init__(self, sha1=None, flags=0):
- # The currently believed correct merge, or None if it is
- # unknown or the best attempt was unsuccessful.
- self.sha1 = sha1
-
- if self.sha1 is None:
- if flags != 0:
- raise ValueError('Initial flags (%s) for sha1=None should be 0' % (flags,))
- elif flags not in self.ALLOWED_INITIAL_FLAGS:
- raise ValueError('Initial flags (%s) is invalid' % (flags,))
-
- # See bits above.
- self.flags = flags
-
- def record_merge(self, sha1, source):
- """Record a merge at this position.
-
- source must be SAVED_AUTO, SAVED_MANUAL, NEW_AUTO, or NEW_MANUAL."""
-
- if source == self.SAVED_AUTO:
- # SAVED_AUTO is recorded in any case, but only used if it
- # is the only info available.
- if self.flags & (self.MANUAL | self.NEW) == 0:
- self.sha1 = sha1
- self.flags |= source
- elif source == self.NEW_AUTO:
- # NEW_AUTO is silently ignored if any MANUAL value is
- # already recorded.
- if self.flags & self.MANUAL == 0:
- self.sha1 = sha1
- self.flags |= source
- elif source == self.SAVED_MANUAL:
- # SAVED_MANUAL is recorded in any case, but only used if
- # no NEW_MANUAL is available.
- if self.flags & self.NEW_MANUAL == 0:
- self.sha1 = sha1
- self.flags |= source
- elif source == self.NEW_MANUAL:
- # NEW_MANUAL is always used, and also causes NEW_AUTO to
- # be forgotten if present.
- self.sha1 = sha1
- self.flags = (self.flags | source) & ~self.NEW_AUTO
- else:
- raise ValueError('Undefined source: %s' % (source,))
-
- def record_blocked(self, blocked):
- if blocked:
- self.flags |= self.BLOCKED
- else:
- self.flags &= ~self.BLOCKED
-
- def is_known(self):
- return self.sha1 is not None
-
- def is_blocked(self):
- return self.flags & self.BLOCKED != 0
-
- def is_manual(self):
- return self.flags & self.MANUAL != 0
-
- def save(self, git, name, i1, i2):
- """If this record has changed, save it."""
-
- def set_ref(source):
- git.update_ref(
- 'refs/imerge/%s/%s/%d-%d' % (name, source, i1, i2),
- self.sha1,
- 'imerge %r: Record %s merge' % (name, source,),
- )
-
- def clear_ref(source):
- git.delete_ref(
- 'refs/imerge/%s/%s/%d-%d' % (name, source, i1, i2),
- 'imerge %r: Remove obsolete %s merge' % (name, source,),
- )
-
- if self.flags & self.MANUAL:
- if self.flags & self.AUTO:
- # Any MANUAL obsoletes any AUTO:
- if self.flags & self.SAVED_AUTO:
- clear_ref('auto')
-
- self.flags &= ~self.AUTO
-
- if self.flags & self.NEW_MANUAL:
- # Convert NEW_MANUAL to SAVED_MANUAL.
- if self.sha1:
- set_ref('manual')
- self.flags |= self.SAVED_MANUAL
- elif self.flags & self.SAVED_MANUAL:
- # Delete any existing SAVED_MANUAL:
- clear_ref('manual')
- self.flags &= ~self.SAVED_MANUAL
- self.flags &= ~self.NEW_MANUAL
-
- elif self.flags & self.NEW_AUTO:
- # Convert NEW_AUTO to SAVED_AUTO.
- if self.sha1:
- set_ref('auto')
- self.flags |= self.SAVED_AUTO
- elif self.flags & self.SAVED_AUTO:
- # Delete any existing SAVED_AUTO:
- clear_ref('auto')
- self.flags &= ~self.SAVED_AUTO
- self.flags &= ~self.NEW_AUTO
-
-
-class UnexpectedMergeFailure(Exception):
- def __init__(self, msg, i1, i2):
- Exception.__init__(self, msg)
- self.i1, self.i2 = i1, i2
-
-
-class BlockCompleteError(Exception):
- pass
-
-
-class FrontierBlockedError(Exception):
- def __init__(self, msg, i1, i2):
- Exception.__init__(self, msg)
- self.i1 = i1
- self.i2 = i2
-
-
-class NotABlockingCommitError(Exception):
- pass
-
-
-def find_frontier_blocks(block):
- """Iterate over the frontier blocks for the specified block.
-
- Use bisection to find the blocks. Iterate over the blocks starting
- in the bottom left and ending at the top right. Record in block
- any blockers that we find.
-
- We make the following assumptions (using Python subscript
- notation):
-
- 0. All of the merges in block[1:,0] and block[0,1:] are
- already known. (This is an invariant of the Block class.)
-
- 1. If a direct merge can be done between block[i1-1,0] and
- block[0,i2-1], then all of the pairwise merges in
- block[1:i1, 1:i2] can also be done.
-
- 2. If a direct merge fails between block[i1-1,0] and
- block[0,i2-1], then all of the pairwise merges in
- block[i1-1:,i2-1:] would also fail.
-
- Under these assumptions, the merge frontier is a stepstair
- pattern going from the bottom-left to the top-right, and
- bisection can be used to find the transition between mergeable
- and conflicting in any row or column.
-
- Of course these assumptions are not rigorously true, so the
- MergeFrontier returned by this function is only an
- approximation of the real merge diagram. We check for and
- correct such inconsistencies later.
-
- """
-
- # Given that these diagrams typically have few blocks, check
- # the end of a range first to see if the whole range can be
- # determined, and fall back to bisection otherwise. We
- # determine the frontier block by block, starting in the lower
- # left.
-
- if block.len1 <= 1 or block.len2 <= 1 or block.is_blocked(1, 1):
- return
-
- if block.is_mergeable(block.len1 - 1, block.len2 - 1):
- # The whole block is mergable!
- yield block
- return
-
- if not block.is_mergeable(1, 1):
- # There are no mergeable blocks in block; therefore,
- # block[1,1] must itself be unmergeable. Record that
- # fact:
- block[1, 1].record_blocked(True)
- return
-
- # At this point, we know that there is at least one mergeable
- # commit in the first column. Find the height of the success
- # block in column 1:
- i1 = 1
- i2 = find_first_false(
- lambda i: block.is_mergeable(i1, i),
- 2, block.len2,
- )
-
- # Now we know that (1,i2-1) is mergeable but (1,i2) is not;
- # e.g., (i1, i2) is like 'A' (or maybe 'B') in the following
- # diagram (where '*' means mergeable, 'x' means not mergeable,
- # and '?' means indeterminate) and that the merge under 'A' is
- # not mergeable:
- #
- # i1
- #
- # 0123456
- # 0 *******
- # 1 **?????
- # i2 2 **?????
- # 3 **?????
- # 4 *Axxxxx
- # 5 *xxxxxx
- # B
-
- while True:
- if i2 == 1:
- break
-
- # At this point in the loop, we know that any blocks to
- # the left of 'A' have already been recorded, (i1, i2-1)
- # is mergeable but (i1, i2) is not; e.g., we are at a
- # place like 'A' in the following diagram:
- #
- # i1
- #
- # 0123456
- # 0 **|****
- # 1 **|*???
- # i2 2 **|*???
- # 3 **|Axxx
- # 4 --+xxxx
- # 5 *xxxxxx
- #
- # This implies that (i1, i2) is the first unmergeable
- # commit in a blocker block (though blocker blocks are not
- # recorded explicitly). It also implies that a mergeable
- # block has its last mergeable commit somewhere in row
- # i2-1; find its width.
- if (
- i1 == block.len1 - 1
- or block.is_mergeable(block.len1 - 1, i2 - 1)
- ):
- yield block[:block.len1, :i2]
- break
- else:
- i1 = find_first_false(
- lambda i: block.is_mergeable(i, i2 - 1),
- i1 + 1, block.len1 - 1,
- )
- yield block[:i1, :i2]
-
- # At this point in the loop, (i1-1, i2-1) is mergeable but
- # (i1, i2-1) is not; e.g., 'A' in the following diagram:
- #
- # i1
- #
- # 0123456
- # 0 **|*|**
- # 1 **|*|??
- # i2 2 --+-+xx
- # 3 **|xxAx
- # 4 --+xxxx
- # 5 *xxxxxx
- #
- # The block ending at (i1-1,i2-1) has just been recorded.
- # Now find the height of the conflict rectangle at column
- # i1 and fill it in:
- if i2 - 1 == 1 or not block.is_mergeable(i1, 1):
- break
- else:
- i2 = find_first_false(
- lambda i: block.is_mergeable(i1, i),
- 2, i2 - 1,
- )
-
-
-class MergeFrontier(object):
- """Represents the merge frontier within a Block.
-
- A MergeFrontier is represented by a list of SubBlocks, each of
- which is thought to be completely mergeable. The list is kept in
- normalized form:
-
- * Only non-empty blocks are retained
-
- * Blocks are sorted from bottom left to upper right
-
- * No redundant blocks
-
- """
-
- @staticmethod
- def map_known_frontier(block):
- """Return the MergeFrontier describing existing successful merges in block.
-
- The return value only includes the part that is fully outlined
- and whose outline consists of rectangles reaching back to
- (0,0).
-
- A blocked commit is *not* considered to be within the
- frontier, even if a merge is registered for it. Such merges
- must be explicitly unblocked."""
-
- # FIXME: This algorithm can take combinatorial time, for
- # example if there is a big block of merges that is a dead
- # end:
- #
- # +++++++
- # +?+++++
- # +?+++++
- # +?+++++
- # +?*++++
- #
- # The problem is that the algorithm will explore all of the
- # ways of getting to commit *, and the number of paths grows
- # like a binomial coefficient. The solution would be to
- # remember dead-ends and reject any curves that visit a point
- # to the right of a dead-end.
- #
- # For now we don't intend to allow a situation like this to be
- # created, so we ignore the problem.
-
- # A list (i1, i2, down) of points in the path so far. down is
- # True iff the attempted step following this one was
- # downwards.
- path = []
-
- def create_frontier(path):
- blocks = []
- for ((i1old, i2old, downold), (i1new, i2new, downnew)) in iter_neighbors(path):
- if downold is True and downnew is False:
- blocks.append(block[:i1new + 1, :i2new + 1])
- return MergeFrontier(block, blocks)
-
- # Loop invariants:
- #
- # * path is a valid path
- #
- # * (i1, i2) is in block but it not yet added to path
- #
- # * down is True if a step downwards from (i1, i2) has not yet
- # been attempted
- (i1, i2) = (block.len1 - 1, 0)
- down = True
- while True:
- if down:
- if i2 == block.len2 - 1:
- # Hit edge of block; can't move down:
- down = False
- elif (i1, i2 + 1) in block and not block.is_blocked(i1, i2 + 1):
- # Can move down
- path.append((i1, i2, True))
- i2 += 1
- else:
- # Can't move down.
- down = False
- else:
- if i1 == 0:
- # Success!
- path.append((i1, i2, False))
- return create_frontier(path)
- elif (i1 - 1, i2) in block and not block.is_blocked(i1 - 1, i2):
- # Can move left
- path.append((i1, i2, False))
- down = True
- i1 -= 1
- else:
- # There's no way to go forward; backtrack until we
- # find a place where we can still try going left:
- while True:
- try:
- (i1, i2, down) = path.pop()
- except IndexError:
- # This shouldn't happen because, in the
- # worst case, there is a valid path across
- # the top edge of the merge diagram.
- raise RuntimeError('Block is improperly formed!')
- if down:
- down = False
- break
-
- @staticmethod
- def compute_by_bisection(block):
- """Return a MergeFrontier instance for block.
-
- Compute the blocks making up the boundary using bisection. See
- find_frontier_blocks() for more information.
-
- """
-
- return MergeFrontier(block, list(find_frontier_blocks(block)))
-
- def __init__(self, block, blocks=None):
- self.block = block
- self.blocks = self._normalized_blocks(blocks or [])
-
- def __iter__(self):
- """Iterate over blocks from bottom left to upper right."""
-
- return iter(self.blocks)
-
- def __bool__(self):
- """Return True iff this frontier has no completed parts."""
-
- return bool(self.blocks)
-
- def __nonzero__(self):
- """Return True iff this frontier has no completed parts."""
-
- return bool(self.blocks)
-
- def is_complete(self):
- """Return True iff the frontier covers the whole block."""
-
- return (
- len(self.blocks) == 1
- and self.blocks[0].len1 == self.block.len1
- and self.blocks[0].len2 == self.block.len2
- )
-
- # Additional codes used in the 2D array returned from create_diagram()
- FRONTIER_WITHIN = 0x10
- FRONTIER_RIGHT_EDGE = 0x20
- FRONTIER_BOTTOM_EDGE = 0x40
- FRONTIER_MASK = 0x70
-
- @classmethod
- def default_formatter(cls, node, legend=None):
- def color(node, within):
- if within:
- return AnsiColor.B_GREEN + node + AnsiColor.END
- else:
- return AnsiColor.B_RED + node + AnsiColor.END
-
- if legend is None:
- legend = ['?', '*', '.', '#', '@', '-', '|', '+']
- merge = node & Block.MERGE_MASK
- within = merge == Block.MERGE_MANUAL or (node & cls.FRONTIER_WITHIN)
- skip = [Block.MERGE_MANUAL, Block.MERGE_BLOCKED, Block.MERGE_UNBLOCKED]
- if merge not in skip:
- vertex = (cls.FRONTIER_BOTTOM_EDGE | cls.FRONTIER_RIGHT_EDGE)
- edge_status = node & vertex
- if edge_status == vertex:
- return color(legend[-1], within)
- elif edge_status == cls.FRONTIER_RIGHT_EDGE:
- return color(legend[-2], within)
- elif edge_status == cls.FRONTIER_BOTTOM_EDGE:
- return color(legend[-3], within)
- return color(legend[merge], within)
-
- def create_diagram(self):
- """Generate a diagram of this frontier.
-
- The returned diagram is a nested list of integers forming a 2D array,
- representing the merge frontier embedded in the diagram of commits
- returned from Block.create_diagram().
-
- At each node in the returned diagram is an integer whose value is a
- bitwise-or of existing MERGE_* constant from Block.create_diagram()
- and zero or more of the FRONTIER_* constants defined in this class."""
-
- diagram = self.block.create_diagram()
-
- try:
- next_block = self.blocks[0]
- except IndexError:
- next_block = None
-
- diagram[0][-1] |= self.FRONTIER_BOTTOM_EDGE
- for i2 in range(1, self.block.len2):
- if next_block is None or i2 >= next_block.len2:
- diagram[0][i2] |= self.FRONTIER_RIGHT_EDGE
-
- prev_block = None
- for n in range(len(self.blocks)):
- block = self.blocks[n]
- try:
- next_block = self.blocks[n + 1]
- except IndexError:
- next_block = None
-
- for i1 in range(block.len1):
- for i2 in range(block.len2):
- v = self.FRONTIER_WITHIN
- if i1 == block.len1 - 1 and (
- next_block is None or i2 >= next_block.len2
- ):
- v |= self.FRONTIER_RIGHT_EDGE
- if i2 == block.len2 - 1 and (
- prev_block is None or i1 >= prev_block.len1
- ):
- v |= self.FRONTIER_BOTTOM_EDGE
- diagram[i1][i2] |= v
- prev_block = block
-
- try:
- prev_block = self.blocks[-1]
- except IndexError:
- prev_block = None
-
- for i1 in range(1, self.block.len1):
- if prev_block is None or i1 >= prev_block.len1:
- diagram[i1][0] |= self.FRONTIER_BOTTOM_EDGE
- diagram[-1][0] |= self.FRONTIER_RIGHT_EDGE
-
- return diagram
-
- def format_diagram(self, formatter=None, diagram=None):
- if formatter is None:
- formatter = self.default_formatter
- if diagram is None:
- diagram = self.create_diagram()
- return [
- [formatter(diagram[i1][i2]) for i2 in range(self.block.len2)]
- for i1 in range(self.block.len1)]
-
- def write(self, f):
- """Write this frontier to file-like object f."""
- diagram = self.format_diagram()
- for i2 in range(self.block.len2):
- for i1 in range(self.block.len1):
- f.write(diagram[i1][i2])
- f.write('\n')
-
- def write_html(self, f, name, cssfile='imerge.css', abbrev_sha1=7):
- class_map = {
- Block.MERGE_UNKNOWN: 'merge_unknown',
- Block.MERGE_MANUAL: 'merge_manual',
- Block.MERGE_AUTOMATIC: 'merge_automatic',
- Block.MERGE_BLOCKED: 'merge_blocked',
- Block.MERGE_UNBLOCKED: 'merge_unblocked',
- self.FRONTIER_WITHIN: 'frontier_within',
- self.FRONTIER_RIGHT_EDGE: 'frontier_right_edge',
- self.FRONTIER_BOTTOM_EDGE: 'frontier_bottom_edge',
- }
-
- def map_to_classes(i1, i2, node):
- merge = node & Block.MERGE_MASK
- ret = [class_map[merge]]
- for bit in [self.FRONTIER_WITHIN, self.FRONTIER_RIGHT_EDGE,
- self.FRONTIER_BOTTOM_EDGE]:
- if node & bit:
- ret.append(class_map[bit])
- if not (node & self.FRONTIER_WITHIN):
- ret.append('frontier_without')
- elif (node & Block.MERGE_MASK) == Block.MERGE_UNKNOWN:
- ret.append('merge_skipped')
- if i1 == 0 or i2 == 0:
- ret.append('merge_initial')
- if i1 == 0:
- ret.append('col_left')
- if i1 == self.block.len1 - 1:
- ret.append('col_right')
- if i2 == 0:
- ret.append('row_top')
- if i2 == self.block.len2 - 1:
- ret.append('row_bottom')
- return ret
-
- f.write("""\
-<html>
-<head>
-<title>git-imerge: %s</title>
-<link rel="stylesheet" href="%s" type="text/css" />
-</head>
-<body>
-<table id="imerge">
-""" % (name, cssfile))
-
- diagram = self.create_diagram()
-
- f.write(' <tr>\n')
- f.write(' <th class="indexes">&nbsp;</td>\n')
- for i1 in range(self.block.len1):
- f.write(' <th class="indexes">%d-*</td>\n' % (i1,))
- f.write(' </tr>\n')
-
- for i2 in range(self.block.len2):
- f.write(' <tr>\n')
- f.write(' <th class="indexes">*-%d</td>\n' % (i2,))
- for i1 in range(self.block.len1):
- classes = map_to_classes(i1, i2, diagram[i1][i2])
- record = self.block.get_value(i1, i2)
- sha1 = record.sha1 or ''
- td_id = record.sha1 and ' id="%s"' % (record.sha1) or ''
- td_class = classes and ' class="' + ' '.join(classes) + '"' or ''
- f.write(' <td%s%s>%.*s</td>\n' % (
- td_id, td_class, abbrev_sha1, sha1))
- f.write(' </tr>\n')
- f.write('</table>\n</body>\n</html>\n')
-
- @staticmethod
- def _normalized_blocks(blocks):
- """Return a normalized list of blocks from the argument.
-
- * Remove empty blocks.
-
- * Remove redundant blocks.
-
- * Sort the blocks according to their len1 members.
-
- """
-
- def contains(block1, block2):
- """Return true if block1 contains block2."""
-
- return block1.len1 >= block2.len1 and block1.len2 >= block2.len2
-
- blocks = sorted(blocks, key=lambda block: block.len1)
- ret = []
-
- for block in blocks:
- if block.len1 == 0 or block.len2 == 0:
- continue
- while True:
- if not ret:
- ret.append(block)
- break
-
- last = ret[-1]
- if contains(last, block):
- break
- elif contains(block, last):
- ret.pop()
- else:
- ret.append(block)
- break
-
- return ret
-
- def remove_failure(self, i1, i2):
- """Refine the merge frontier given that the specified merge fails."""
-
- newblocks = []
- shrunk_block = False
-
- for block in self.blocks:
- if i1 < block.len1 and i2 < block.len2:
- if i1 > 1:
- newblocks.append(block[:i1, :])
- if i2 > 1:
- newblocks.append(block[:, :i2])
- shrunk_block = True
- else:
- newblocks.append(block)
-
- if shrunk_block:
- self.blocks = self._normalized_blocks(newblocks)
-
- def partition(self, block):
- """Return two MergeFrontier instances partitioned by block.
-
- Return (frontier1, frontier2), where each frontier is limited
- to each side of the argument.
-
- block must be contained in this MergeFrontier and already be
- outlined."""
-
- # Remember that the new blocks have to include the outlined
- # edge of the partitioning block to satisfy the invariant that
- # the left and upper edge of a block has to be known.
-
- left = []
- right = []
- for b in self.blocks:
- if b.len1 == block.len1 and b.len2 == block.len2:
- # That's the block we're partitioning on; just skip it.
- pass
- elif b.len1 < block.len1 and b.len2 > block.len2:
- left.append(b[:, block.len2 - 1:])
- elif b.len1 > block.len1 and b.len2 < block.len2:
- right.append(b[block.len1 - 1:, :])
- else:
- raise ValueError(
- 'MergeFrontier partitioned with inappropriate block'
- )
- return (
- MergeFrontier(self.block[:block.len1, block.len2 - 1:], left),
- MergeFrontier(self.block[block.len1 - 1:, :block.len2], right),
- )
-
- def iter_blocker_blocks(self):
- """Iterate over the blocks on the far side of this frontier.
-
- This must only be called for an outlined frontier."""
-
- if not self:
- yield self.block
- return
-
- blockruns = []
- if self.blocks[0].len2 < self.block.len2:
- blockruns.append([self.block[0, :]])
- blockruns.append(self)
- if self.blocks[-1].len1 < self.block.len1:
- blockruns.append([self.block[:, 0]])
-
- for block1, block2 in iter_neighbors(itertools.chain(*blockruns)):
- yield self.block[block1.len1 - 1:block2.len1, block2.len2 - 1: block1.len2]
-
- def get_affected_blocker_block(self, i1, i2):
- """Return the blocker block that a successful merge (i1,i2) would unblock.
-
- If there is no such block, raise NotABlockingCommitError."""
-
- for block in self.iter_blocker_blocks():
- try:
- (block_i1, block_i2) = block.convert_original_indexes(i1, i2)
- except IndexError:
- pass
- else:
- if (block_i1, block_i2) == (1, 1):
- # That's the one we need to improve this block:
- return block
- else:
- # An index pair can only be in a single blocker
- # block, which we've already found:
- raise NotABlockingCommitError(
- 'Commit %d-%d was not blocking the frontier.'
- % self.block.get_original_indexes(i1, i2)
- )
- else:
- raise NotABlockingCommitError(
- 'Commit %d-%d was not on the frontier.'
- % self.block.get_original_indexes(i1, i2)
- )
-
- def auto_expand(self):
- """Try pushing out one of the blocks on this frontier.
-
- Raise BlockCompleteError if the whole block has already been
- solved. Raise FrontierBlockedError if the frontier is blocked
- everywhere. This method does *not* update self; if it returns
- successfully you should recompute the frontier from
- scratch."""
-
- blocks = list(self.iter_blocker_blocks())
- if not blocks:
- raise BlockCompleteError('The block is already complete')
-
- # Try blocks from left to right:
- blocks.sort(key=lambda block: block.get_original_indexes(0, 0))
-
- for block in blocks:
- if block.auto_expand_frontier():
- return
- else:
- # None of the blocks could be expanded. Suggest that the
- # caller do a manual merge of the commit that is blocking
- # the leftmost blocker block.
- i1, i2 = blocks[0].get_original_indexes(1, 1)
- raise FrontierBlockedError(
- 'Conflict; suggest manual merge of %d-%d' % (i1, i2),
- i1, i2
- )
-
-
-class NoManualMergeError(Exception):
- pass
-
-
-class ManualMergeUnusableError(Exception):
- def __init__(self, msg, commit):
- Exception.__init__(self, 'Commit %s is not usable; %s' % (commit, msg))
- self.commit = commit
-
-
-class CommitNotFoundError(Exception):
- def __init__(self, commit):
- Exception.__init__(
- self,
- 'Commit %s was not found among the known merge commits' % (commit,),
- )
- self.commit = commit
-
-
-class Block(object):
- """A rectangular range of commits, indexed by (i1,i2).
-
- The commits block[0,1:] and block[1:,0] are always all known.
- block[0,0] may or may not be known; it is usually unneeded (except
- maybe implicitly).
-
- Members:
-
- name -- the name of the imerge of which this block is part.
-
- len1, len2 -- the dimensions of the block.
-
- """
-
- def __init__(self, git, name, len1, len2):
- self.git = git
- self.name = name
- self.len1 = len1
- self.len2 = len2
-
- def get_merge_state(self):
- """Return the MergeState instance containing this Block."""
-
- raise NotImplementedError()
-
- def get_area(self):
- """Return the area of this block, ignoring the known edges."""
-
- return (self.len1 - 1) * (self.len2 - 1)
-
- def _check_indexes(self, i1, i2):
- if not (0 <= i1 < self.len1):
- raise IndexError('first index (%s) is out of range 0:%d' % (i1, self.len1,))
- if not (0 <= i2 < self.len2):
- raise IndexError('second index (%s) is out of range 0:%d' % (i2, self.len2,))
-
- def _normalize_indexes(self, index):
- """Return a pair of non-negative integers (i1, i2)."""
-
- try:
- (i1, i2) = index
- except TypeError:
- raise IndexError('Block indexing requires exactly two indexes')
-
- if i1 < 0:
- i1 += self.len1
- if i2 < 0:
- i2 += self.len2
-
- self._check_indexes(i1, i2)
- return (i1, i2)
-
- def get_original_indexes(self, i1, i2):
- """Return the original indexes corresponding to (i1,i2) in this block.
-
- This function supports negative indexes."""
-
- return self._normalize_indexes((i1, i2))
-
- def convert_original_indexes(self, i1, i2):
- """Return the indexes in this block corresponding to original indexes (i1,i2).
-
- raise IndexError if they are not within this block. This
- method does not support negative indices."""
-
- return (i1, i2)
-
- def _set_value(self, i1, i2, value):
- """Set the MergeRecord for integer indexes (i1, i2).
-
- i1 and i2 must be non-negative."""
-
- raise NotImplementedError()
-
- def get_value(self, i1, i2):
- """Return the MergeRecord for integer indexes (i1, i2).
-
- i1 and i2 must be non-negative."""
-
- raise NotImplementedError()
-
- def __getitem__(self, index):
- """Return the MergeRecord at (i1, i2) (requires two indexes).
-
- If i1 and i2 are integers but the merge is unknown, return
- None. If either index is a slice, return a SubBlock."""
-
- try:
- (i1, i2) = index
- except TypeError:
- raise IndexError('Block indexing requires exactly two indexes')
- if isinstance(i1, slice) or isinstance(i2, slice):
- return SubBlock(self, i1, i2)
- else:
- return self.get_value(*self._normalize_indexes((i1, i2)))
-
- def __contains__(self, index):
- return self[index].is_known()
-
- def is_blocked(self, i1, i2):
- """Return True iff the specified commit is blocked."""
-
- (i1, i2) = self._normalize_indexes((i1, i2))
- return self[i1, i2].is_blocked()
-
- def is_mergeable(self, i1, i2):
- """Determine whether (i1,i2) can be merged automatically.
-
- If we already have a merge record for (i1,i2), return True.
- Otherwise, attempt a merge (discarding the result)."""
-
- (i1, i2) = self._normalize_indexes((i1, i2))
- if (i1, i2) in self:
- return True
- else:
- sys.stderr.write(
- 'Attempting automerge of %d-%d...' % self.get_original_indexes(i1, i2)
- )
- try:
- self.git.automerge(self[i1, 0].sha1, self[0, i2].sha1)
- sys.stderr.write('success.\n')
- return True
- except AutomaticMergeFailed:
- sys.stderr.write('failure.\n')
- return False
-
- def auto_outline(self):
- """Complete the outline of this Block.
-
- raise UnexpectedMergeFailure if automerging fails."""
-
- # Check that all of the merges go through before recording any
- # of them permanently.
- merges = []
-
- def do_merge(i1, commit1, i2, commit2, msg='Autofilling %d-%d...', record=True):
- if (i1, i2) in self:
- return self[i1, i2].sha1
- (i1orig, i2orig) = self.get_original_indexes(i1, i2)
- sys.stderr.write(msg % (i1orig, i2orig))
- logmsg = 'imerge \'%s\': automatic merge %d-%d' % (self.name, i1orig, i2orig)
- try:
- merge = self.git.automerge(commit1, commit2, msg=logmsg)
- sys.stderr.write('success.\n')
- except AutomaticMergeFailed as e:
- sys.stderr.write('unexpected conflict. Backtracking...\n')
- raise UnexpectedMergeFailure(str(e), i1, i2)
- if record:
- merges.append((i1, i2, merge))
- return merge
-
- i2 = self.len2 - 1
- left = self[0, i2].sha1
- for i1 in range(1, self.len1 - 1):
- left = do_merge(i1, self[i1, 0].sha1, i2, left)
-
- i1 = self.len1 - 1
- above = self[i1, 0].sha1
- for i2 in range(1, self.len2 - 1):
- above = do_merge(i1, above, i2, self[0, i2].sha1)
-
- i1, i2 = self.len1 - 1, self.len2 - 1
- if i1 > 1 and i2 > 1:
- # We will compare two ways of doing the final "vertex" merge:
- # as a continuation of the bottom edge, or as a continuation
- # of the right edge. We only accept it if both approaches
- # succeed and give identical trees.
- vertex_v1 = do_merge(
- i1, self[i1, 0].sha1, i2, left,
- msg='Autofilling %d-%d (first way)...',
- record=False,
- )
- vertex_v2 = do_merge(
- i1, above, i2, self[0, i2].sha1,
- msg='Autofilling %d-%d (second way)...',
- record=False,
- )
- if self.git.get_tree(vertex_v1) == self.git.get_tree(vertex_v2):
- sys.stderr.write(
- 'The two ways of autofilling %d-%d agree.\n'
- % self.get_original_indexes(i1, i2)
- )
- # Everything is OK. Now reparent the actual vertex merge to
- # have above and left as its parents:
- merges.append(
- (i1, i2, self.git.reparent(vertex_v1, [above, left]))
- )
- else:
- sys.stderr.write(
- 'The two ways of autofilling %d-%d do not agree. Backtracking...\n'
- % self.get_original_indexes(i1, i2)
- )
- raise UnexpectedMergeFailure('Inconsistent vertex merges', i1, i2)
- else:
- do_merge(
- i1, above, i2, left,
- msg='Autofilling %d-%d...',
- )
-
- # Done! Now we can record the results:
- sys.stderr.write('Recording autofilled block %s.\n' % (self,))
- for (i1, i2, merge) in merges:
- self[i1, i2].record_merge(merge, MergeRecord.NEW_AUTO)
-
- def auto_fill_micromerge(self):
- """Try to fill the very first micromerge in this block.
-
- Return True iff the attempt was successful."""
-
- assert (1, 1) not in self
- if self.len1 <= 1 or self.len2 <= 1 or self.is_blocked(1, 1):
- return False
-
- i1, i2 = 1, 1
- (i1orig, i2orig) = self.get_original_indexes(i1, i2)
- sys.stderr.write('Attempting to merge %d-%d...' % (i1orig, i2orig))
- logmsg = 'imerge \'%s\': automatic merge %d-%d' % (self.name, i1orig, i2orig)
- try:
- merge = self.git.automerge(
- self[i1, i2 - 1].sha1,
- self[i1 - 1, i2].sha1,
- msg=logmsg,
- )
- sys.stderr.write('success.\n')
- except AutomaticMergeFailed:
- sys.stderr.write('conflict.\n')
- self[i1, i2].record_blocked(True)
- return False
- else:
- self[i1, i2].record_merge(merge, MergeRecord.NEW_AUTO)
- return True
-
- def auto_outline_frontier(self, merge_frontier=None):
- """Try to outline the merge frontier of this block.
-
- Return True iff some progress was made."""
-
- if merge_frontier is None:
- merge_frontier = MergeFrontier.compute_by_bisection(self)
-
- if not merge_frontier:
- # Nothing to do.
- return False
-
- best_block = max(merge_frontier, key=lambda block: block.get_original_indexes(0, 0))
-
- try:
- best_block.auto_outline()
- except UnexpectedMergeFailure as e:
- # One of the merges that we expected to succeed in
- # fact failed.
- merge_frontier.remove_failure(e.i1, e.i2)
- return self.auto_outline_frontier(merge_frontier)
- else:
- f1, f2 = merge_frontier.partition(best_block)
- if f1:
- f1.block.auto_outline_frontier(f1)
- if f2:
- f2.block.auto_outline_frontier(f2)
- return True
-
- def auto_expand_frontier(self):
- merge_state = self.get_merge_state()
- if merge_state.manual:
- return False
- elif merge_state.goal == 'full':
- return self.auto_fill_micromerge()
- else:
- return self.auto_outline_frontier()
-
- # The codes in the 2D array returned from create_diagram()
- MERGE_UNKNOWN = 0
- MERGE_MANUAL = 1
- MERGE_AUTOMATIC = 2
- MERGE_BLOCKED = 3
- MERGE_UNBLOCKED = 4
- MERGE_MASK = 7
-
- # A map {(is_known(), manual, is_blocked()) : integer constant}
- MergeState = {
- (False, False, False): MERGE_UNKNOWN,
- (False, False, True): MERGE_BLOCKED,
- (True, False, True): MERGE_UNBLOCKED,
- (True, True, True): MERGE_UNBLOCKED,
- (True, False, False): MERGE_AUTOMATIC,
- (True, True, False): MERGE_MANUAL,
- }
-
- def create_diagram(self):
- """Generate a diagram of this Block.
-
- The returned diagram, is a nested list of integers forming a 2D array,
- where the integer at diagram[i1][i2] is one of MERGE_UNKNOWN,
- MERGE_MANUAL, MERGE_AUTOMATIC, MERGE_BLOCKED, or MERGE_UNBLOCKED,
- representing the state of the commit at (i1, i2)."""
-
- diagram = [[None for i2 in range(self.len2)] for i1 in range(self.len1)]
-
- for i2 in range(self.len2):
- for i1 in range(self.len1):
- rec = self.get_value(i1, i2)
- c = self.MergeState[
- rec.is_known(), rec.is_manual(), rec.is_blocked()]
- diagram[i1][i2] = c
-
- return diagram
-
- def format_diagram(self, legend=None, diagram=None):
- if legend is None:
- legend = [
- AnsiColor.D_GRAY + '?' + AnsiColor.END,
- AnsiColor.B_GREEN + '*' + AnsiColor.END,
- AnsiColor.B_GREEN + '.' + AnsiColor.END,
- AnsiColor.B_RED + '#' + AnsiColor.END,
- AnsiColor.B_YELLOW + '@' + AnsiColor.END,
- ]
- if diagram is None:
- diagram = self.create_diagram()
- return [
- [legend[diagram[i1][i2]] for i2 in range(self.len2)]
- for i1 in range(self.len1)]
-
- def write(self, f, legend=None, sep='', linesep='\n'):
- diagram = self.format_diagram(legend)
- for i2 in range(self.len2):
- f.write(sep.join(diagram[i1][i2] for i1 in range(self.len1)) + linesep)
-
- def writeppm(self, f):
- f.write('P3\n')
- f.write('%d %d 255\n' % (self.len1, self.len2,))
- legend = ['127 127 0', '0 255 0', '0 127 0', '255 0 0', '127 0 0']
- self.write(f, legend, sep=' ')
-
-
-class SubBlock(Block):
- @staticmethod
- def _convert_to_slice(i, len):
- """Return (start, len) for the specified index.
-
- i may be an integer or a slice with step equal to 1."""
-
- if isinstance(i, int):
- if i < 0:
- i += len
- i = slice(i, i + 1)
- elif isinstance(i, slice):
- if i.step is not None and i.step != 1:
- raise ValueError('Index has a non-zero step size')
- else:
- raise ValueError('Index cannot be converted to a slice')
-
- (start, stop, step) = i.indices(len)
- return (start, stop - start)
-
- def __init__(self, block, slice1, slice2):
- (start1, len1) = self._convert_to_slice(slice1, block.len1)
- (start2, len2) = self._convert_to_slice(slice2, block.len2)
- Block.__init__(self, block.git, block.name, len1, len2)
- if isinstance(block, SubBlock):
- # Peel away one level of indirection:
- self._merge_state = block._merge_state
- self._start1 = start1 + block._start1
- self._start2 = start2 + block._start2
- else:
- assert(isinstance(block, MergeState))
- self._merge_state = block
- self._start1 = start1
- self._start2 = start2
-
- def get_merge_state(self):
- return self._merge_state
-
- def get_original_indexes(self, i1, i2):
- i1, i2 = self._normalize_indexes((i1, i2))
- return self._merge_state.get_original_indexes(
- i1 + self._start1,
- i2 + self._start2,
- )
-
- def convert_original_indexes(self, i1, i2):
- (i1, i2) = self._merge_state.convert_original_indexes(i1, i2)
- if not (
- self._start1 <= i1 < self._start1 + self.len1
- and self._start2 <= i2 < self._start2 + self.len2
- ):
- raise IndexError('Indexes are not within block')
- return (i1 - self._start1, i2 - self._start2)
-
- def _set_value(self, i1, i2, sha1, flags):
- self._check_indexes(i1, i2)
- self._merge_state._set_value(
- i1 + self._start1,
- i2 + self._start2,
- sha1, flags,
- )
-
- def get_value(self, i1, i2):
- self._check_indexes(i1, i2)
- return self._merge_state.get_value(i1 + self._start1, i2 + self._start2)
-
- def __str__(self):
- return '%s[%d:%d,%d:%d]' % (
- self._merge_state,
- self._start1, self._start1 + self.len1,
- self._start2, self._start2 + self.len2,
- )
-
-
-class MissingMergeFailure(Failure):
- def __init__(self, i1, i2):
- Failure.__init__(self, 'Merge %d-%d is not yet done' % (i1, i2))
- self.i1 = i1
- self.i2 = i2
-
-
-class MergeState(Block):
- SOURCE_TABLE = {
- 'auto': MergeRecord.SAVED_AUTO,
- 'manual': MergeRecord.SAVED_MANUAL,
- }
-
- @staticmethod
- def get_scratch_refname(name):
- return 'refs/heads/imerge/%s' % (name,)
-
- @staticmethod
- def _check_no_merges(git, commits):
- multiparent_commits = [
- commit
- for commit in commits
- if len(git.get_commit_parents(commit)) > 1
- ]
- if multiparent_commits:
- raise Failure(
- 'The following commits on the to-be-merged branch are merge commits:\n'
- ' %s\n'
- '--goal=\'rebase\' is not yet supported for branches that include merges.\n'
- % ('\n '.join(multiparent_commits),)
- )
-
- @staticmethod
- def initialize(
- git, name, merge_base,
- tip1, commits1,
- tip2, commits2,
- goal=DEFAULT_GOAL, goalopts=None,
- manual=False, branch=None,
- ):
- """Create and return a new MergeState object."""
-
- git.verify_imerge_name_available(name)
- if branch:
- git.check_branch_name_format(branch)
- else:
- branch = name
-
- if goal == 'rebase':
- MergeState._check_no_merges(git, commits2)
-
- return MergeState(
- git, name, merge_base,
- tip1, commits1,
- tip2, commits2,
- MergeRecord.NEW_MANUAL,
- goal=goal, goalopts=goalopts,
- manual=manual,
- branch=branch,
- )
-
- @staticmethod
- def read(git, name):
- (state, merges) = git.read_imerge_state(name)
-
- # Translate sources from strings into MergeRecord constants
- # SAVED_AUTO or SAVED_MANUAL:
- merges = dict((
- ((i1, i2), (sha1, MergeState.SOURCE_TABLE[source]))
- for ((i1, i2), (sha1, source)) in merges.items()
- ))
-
- blockers = state.get('blockers', [])
-
- # Find merge_base, commits1, and commits2:
- (merge_base, source) = merges.pop((0, 0))
- if source != MergeRecord.SAVED_MANUAL:
- raise Failure('Merge base should be manual!')
- commits1 = []
- for i1 in itertools.count(1):
- try:
- (sha1, source) = merges.pop((i1, 0))
- if source != MergeRecord.SAVED_MANUAL:
- raise Failure('Merge %d-0 should be manual!' % (i1,))
- commits1.append(sha1)
- except KeyError:
- break
-
- commits2 = []
- for i2 in itertools.count(1):
- try:
- (sha1, source) = merges.pop((0, i2))
- if source != MergeRecord.SAVED_MANUAL:
- raise Failure('Merge (0,%d) should be manual!' % (i2,))
- commits2.append(sha1)
- except KeyError:
- break
-
- tip1 = state.get('tip1', commits1[-1])
- tip2 = state.get('tip2', commits2[-1])
-
- goal = state['goal']
- if goal not in ALLOWED_GOALS:
- raise Failure('Goal %r, read from state, is not recognized.' % (goal,))
-
- goalopts = state['goalopts']
-
- manual = state['manual']
- branch = state.get('branch', name)
-
- state = MergeState(
- git, name, merge_base,
- tip1, commits1,
- tip2, commits2,
- MergeRecord.SAVED_MANUAL,
- goal=goal, goalopts=goalopts,
- manual=manual,
- branch=branch,
- )
-
- # Now write the rest of the merges to state:
- for ((i1, i2), (sha1, source)) in merges.items():
- if i1 == 0 and i2 >= state.len2:
- raise Failure('Merge 0-%d is missing!' % (state.len2,))
- if i1 >= state.len1 and i2 == 0:
- raise Failure('Merge %d-0 is missing!' % (state.len1,))
- if i1 >= state.len1 or i2 >= state.len2:
- raise Failure(
- 'Merge %d-%d is out of range [0:%d,0:%d]'
- % (i1, i2, state.len1, state.len2)
- )
- state[i1, i2].record_merge(sha1, source)
-
- # Record any blockers:
- for (i1, i2) in blockers:
- state[i1, i2].record_blocked(True)
-
- return state
-
- @staticmethod
- def remove(git, name):
- # If HEAD is the scratch refname, abort any in-progress
- # commits and detach HEAD:
- scratch_refname = MergeState.get_scratch_refname(name)
- if git.get_head_refname() == scratch_refname:
- try:
- git.abort_merge()
- except CalledProcessError:
- pass
- # Detach head so that we can delete scratch_refname:
- git.detach('Detach HEAD from %s' % (scratch_refname,))
-
- # Delete the scratch refname:
- git.delete_ref(
- scratch_refname, 'imerge %s: remove scratch reference' % (name,),
- )
-
- # Remove any references referring to intermediate merges:
- git.delete_imerge_refs(name)
-
- # If this merge was the default, unset the default:
- if git.get_default_imerge_name() == name:
- git.set_default_imerge_name(None)
-
- def __init__(
- self, git, name, merge_base,
- tip1, commits1,
- tip2, commits2,
- source,
- goal=DEFAULT_GOAL, goalopts=None,
- manual=False,
- branch=None,
- ):
- Block.__init__(self, git, name, len(commits1) + 1, len(commits2) + 1)
- self.tip1 = tip1
- self.tip2 = tip2
- self.goal = goal
- self.goalopts = goalopts
- self.manual = bool(manual)
- self.branch = branch or name
-
- # A simulated 2D array. Values are None or MergeRecord instances.
- self._data = [[None] * self.len2 for i1 in range(self.len1)]
-
- self.get_value(0, 0).record_merge(merge_base, source)
- for (i1, commit) in enumerate(commits1, 1):
- self.get_value(i1, 0).record_merge(commit, source)
- for (i2, commit) in enumerate(commits2, 1):
- self.get_value(0, i2).record_merge(commit, source)
-
- def get_merge_state(self):
- return self
-
- def set_goal(self, goal):
- if goal not in ALLOWED_GOALS:
- raise ValueError('%r is not an allowed goal' % (goal,))
-
- if goal == 'rebase':
- self._check_no_merges(
- self.git,
- [self[0, i2].sha1 for i2 in range(1, self.len2)],
- )
-
- self.goal = goal
-
- def _set_value(self, i1, i2, value):
- self._data[i1][i2] = value
-
- def get_value(self, i1, i2):
- value = self._data[i1][i2]
- # Missing values spring to life on first access:
- if value is None:
- value = MergeRecord()
- self._data[i1][i2] = value
- return value
-
- def __contains__(self, index):
- # Avoid creating new MergeRecord objects here.
- (i1, i2) = self._normalize_indexes(index)
- value = self._data[i1][i2]
- return (value is not None) and value.is_known()
-
- def auto_complete_frontier(self):
- """Complete the frontier using automerges.
-
- If progress is blocked before the frontier is complete, raise
- a FrontierBlockedError. Save the state as progress is
- made."""
-
- progress_made = False
- try:
- while True:
- frontier = MergeFrontier.map_known_frontier(self)
- frontier.auto_expand()
- self.save()
- progress_made = True
- except BlockCompleteError:
- return
- except FrontierBlockedError as e:
- self.save()
- if not progress_made:
- # Adjust the error message:
- raise FrontierBlockedError(
- 'No progress was possible; suggest manual merge of %d-%d'
- % (e.i1, e.i2),
- e.i1, e.i2,
- )
- else:
- raise
-
- def find_index(self, commit):
- """Return (i1,i2) for the specified commit.
-
- Raise CommitNotFoundError if it is not known."""
-
- for i2 in range(0, self.len2):
- for i1 in range(0, self.len1):
- if (i1, i2) in self:
- record = self[i1, i2]
- if record.sha1 == commit:
- return (i1, i2)
- raise CommitNotFoundError(commit)
-
- def request_user_merge(self, i1, i2):
- """Prepare the working tree for the user to do a manual merge.
-
- It is assumed that the merges above and to the left of (i1, i2)
- are already done."""
-
- above = self[i1, i2 - 1]
- left = self[i1 - 1, i2]
- if not above.is_known() or not left.is_known():
- raise RuntimeError('The parents of merge %d-%d are not ready' % (i1, i2))
- refname = MergeState.get_scratch_refname(self.name)
- self.git.update_ref(
- refname, above.sha1,
- 'imerge %r: Prepare merge %d-%d' % (self.name, i1, i2,),
- )
- self.git.checkout(refname)
- logmsg = 'imerge \'%s\': manual merge %d-%d' % (self.name, i1, i2)
- try:
- self.git.manualmerge(left.sha1, logmsg)
- except CalledProcessError:
- # We expect an error (otherwise we would have automerged!)
- pass
- sys.stderr.write(
- '\n'
- 'Original first commit:\n'
- )
- self.git.summarize_commit(self[i1, 0].sha1)
- sys.stderr.write(
- '\n'
- 'Original second commit:\n'
- )
- self.git.summarize_commit(self[0, i2].sha1)
- sys.stderr.write(
- '\n'
- 'There was a conflict merging commit %d-%d, shown above.\n'
- 'Please resolve the conflict, commit the result, then type\n'
- '\n'
- ' git-imerge continue\n'
- % (i1, i2)
- )
-
- def incorporate_manual_merge(self, commit):
- """Record commit as a manual merge of its parents.
-
- Return the indexes (i1,i2) where it was recorded. If the
- commit is not usable for some reason, raise
- ManualMergeUnusableError."""
-
- parents = self.git.get_commit_parents(commit)
- if len(parents) < 2:
- raise ManualMergeUnusableError('it is not a merge', commit)
- if len(parents) > 2:
- raise ManualMergeUnusableError('it is an octopus merge', commit)
- # Find the parents among our contents...
- try:
- (i1first, i2first) = self.find_index(parents[0])
- (i1second, i2second) = self.find_index(parents[1])
- except CommitNotFoundError:
- raise ManualMergeUnusableError(
- 'its parents are not known merge commits', commit,
- )
- swapped = False
- if i1first < i1second:
- # Swap parents to make the parent from above the first parent:
- (i1first, i2first, i1second, i2second) = (i1second, i2second, i1first, i2first)
- swapped = True
- if i1first != i1second + 1 or i2first != i2second - 1:
- raise ManualMergeUnusableError(
- 'it is not a pairwise merge of adjacent parents', commit,
- )
- if swapped:
- # Create a new merge with the parents in the conventional order:
- commit = self.git.reparent(commit, [parents[1], parents[0]])
-
- i1, i2 = i1first, i2second
- self[i1, i2].record_merge(commit, MergeRecord.NEW_MANUAL)
- return (i1, i2)
-
- def incorporate_user_merge(self, edit_log_msg=None):
- """If the user has done a merge for us, incorporate the results.
-
- If the scratch reference refs/heads/imerge/NAME exists and is
- checked out, first check if there are staged changes that can
- be committed. Then try to incorporate the current commit into
- this MergeState, delete the reference, and return (i1,i2)
- corresponding to the merge. If the scratch reference does not
- exist, raise NoManualMergeError(). If the scratch reference
- exists but cannot be used, raise a ManualMergeUnusableError.
- If there are unstaged changes in the working tree, emit an
- error message and raise UncleanWorkTreeError.
-
- """
-
- refname = MergeState.get_scratch_refname(self.name)
-
- try:
- commit = self.git.get_commit_sha1(refname)
- except ValueError:
- raise NoManualMergeError('Reference %s does not exist.' % (refname,))
-
- head_name = self.git.get_head_refname()
- if head_name is None:
- raise NoManualMergeError('HEAD is currently detached.')
- elif head_name != refname:
- # This should not usually happen. The scratch reference
- # exists, but it is not current. Perhaps the user gave up on
- # an attempted merge then switched to another branch. We want
- # to delete refname, but only if it doesn't contain any
- # content that we don't already know.
- try:
- self.find_index(commit)
- except CommitNotFoundError:
- # It points to a commit that we don't have in our records.
- raise Failure(
- 'The scratch reference, %(refname)s, already exists but is not\n'
- 'checked out. If it points to a merge commit that you would like\n'
- 'to use, please check it out using\n'
- '\n'
- ' git checkout %(refname)s\n'
- '\n'
- 'and then try to continue again. If it points to a commit that is\n'
- 'unneeded, then please delete the reference using\n'
- '\n'
- ' git update-ref -d %(refname)s\n'
- '\n'
- 'and then continue.'
- % dict(refname=refname)
- )
- else:
- # It points to a commit that is already recorded. We can
- # delete it without losing any information.
- self.git.delete_ref(
- refname,
- 'imerge %r: Remove obsolete scratch reference' % (self.name,),
- )
- sys.stderr.write(
- '%s did not point to a new merge; it has been deleted.\n'
- % (refname,)
- )
- raise NoManualMergeError(
- 'Reference %s was not checked out.' % (refname,)
- )
-
- # If we reach this point, then the scratch reference exists and is
- # checked out. Now check whether there is staged content that
- # can be committed:
- if self.git.commit_user_merge(edit_log_msg=edit_log_msg):
- commit = self.git.get_commit_sha1('HEAD')
-
- self.git.require_clean_work_tree('proceed')
-
- merge_frontier = MergeFrontier.map_known_frontier(self)
-
- # This might throw ManualMergeUnusableError:
- (i1, i2) = self.incorporate_manual_merge(commit)
-
- # Now detach head so that we can delete refname.
- self.git.detach('Detach HEAD from %s' % (refname,))
-
- self.git.delete_ref(
- refname, 'imerge %s: remove scratch reference' % (self.name,),
- )
-
- try:
- # This might throw NotABlockingCommitError:
- unblocked_block = merge_frontier.get_affected_blocker_block(i1, i2)
- unblocked_block[1, 1].record_blocked(False)
- sys.stderr.write(
- 'Merge has been recorded for merge %d-%d.\n'
- % unblocked_block.get_original_indexes(1, 1)
- )
- except NotABlockingCommitError:
- raise
- finally:
- self.save()
-
- def _set_refname(self, refname, commit, force=False):
- try:
- ref_oldval = self.git.get_commit_sha1(refname)
- except ValueError:
- # refname doesn't already exist; simply point it at commit:
- self.git.update_ref(refname, commit, 'imerge: recording final merge')
- self.git.checkout(refname, quiet=True)
- else:
- # refname already exists. This has two ramifications:
- # 1. HEAD might point at it
- # 2. We may only fast-forward it (unless force is set)
- head_refname = self.git.get_head_refname()
-
- if not force and not self.git.is_ancestor(ref_oldval, commit):
- raise Failure(
- '%s cannot be fast-forwarded to %s!' % (refname, commit)
- )
-
- if head_refname == refname:
- self.git.reset_hard(commit)
- else:
- self.git.update_ref(
- refname, commit, 'imerge: recording final merge',
- )
- self.git.checkout(refname, quiet=True)
-
- def simplify_to_full(self, refname, force=False):
- for i1 in range(1, self.len1):
- for i2 in range(1, self.len2):
- if not (i1, i2) in self:
- raise Failure(
- 'Cannot simplify to "full" because '
- 'merge %d-%d is not yet done'
- % (i1, i2)
- )
-
- self._set_refname(refname, self[-1, -1].sha1, force=force)
-
- def simplify_to_rebase_with_history(self, refname, force=False):
- i1 = self.len1 - 1
- for i2 in range(1, self.len2):
- if not (i1, i2) in self:
- raise Failure(
- 'Cannot simplify to rebase-with-history because '
- 'merge %d-%d is not yet done'
- % (i1, i2)
- )
-
- commit = self[i1, 0].sha1
- for i2 in range(1, self.len2):
- orig = self[0, i2].sha1
- tree = self.git.get_tree(self[i1, i2].sha1)
-
- # Create a commit, copying the old log message:
- msg = (
- self.git.get_log_message(orig).rstrip('\n')
- + '\n\n(rebased-with-history from commit %s)\n' % orig
- )
- commit = self.git.commit_tree(tree, [commit, orig], msg=msg)
-
- self._set_refname(refname, commit, force=force)
-
- def simplify_to_border(
- self, refname,
- with_history1=False, with_history2=False, force=False,
- ):
- i1 = self.len1 - 1
- for i2 in range(1, self.len2):
- if not (i1, i2) in self:
- raise Failure(
- 'Cannot simplify to border because '
- 'merge %d-%d is not yet done'
- % (i1, i2)
- )
-
- i2 = self.len2 - 1
- for i1 in range(1, self.len1):
- if not (i1, i2) in self:
- raise Failure(
- 'Cannot simplify to border because '
- 'merge %d-%d is not yet done'
- % (i1, i2)
- )
-
- i1 = self.len1 - 1
- commit = self[i1, 0].sha1
- for i2 in range(1, self.len2 - 1):
- orig = self[0, i2].sha1
- tree = self.git.get_tree(self[i1, i2].sha1)
-
- # Create a commit, copying the old log message:
- if with_history2:
- parents = [commit, orig]
- msg = (
- self.git.get_log_message(orig).rstrip('\n')
- + '\n\n(rebased-with-history from commit %s)\n' % (orig,)
- )
- else:
- parents = [commit]
- msg = (
- self.git.get_log_message(orig).rstrip('\n')
- + '\n\n(rebased from commit %s)\n' % (orig,)
- )
-
- commit = self.git.commit_tree(tree, parents, msg=msg)
- commit1 = commit
-
- i2 = self.len2 - 1
- commit = self[0, i2].sha1
- for i1 in range(1, self.len1 - 1):
- orig = self[i1, 0].sha1
- tree = self.git.get_tree(self[i1, i2].sha1)
-
- # Create a commit, copying the old log message:
- if with_history1:
- parents = [orig, commit]
- msg = (
- self.git.get_log_message(orig).rstrip('\n')
- + '\n\n(rebased-with-history from commit %s)\n' % (orig,)
- )
- else:
- parents = [commit]
- msg = (
- self.git.get_log_message(orig).rstrip('\n')
- + '\n\n(rebased from commit %s)\n' % (orig,)
- )
-
- commit = self.git.commit_tree(tree, parents, msg=msg)
- commit2 = commit
-
- # Construct the apex commit:
- tree = self.git.get_tree(self[-1, -1].sha1)
- msg = (
- 'Merge %s into %s (using imerge border)'
- % (self.tip2, self.tip1)
- )
-
- commit = self.git.commit_tree(tree, [commit1, commit2], msg=msg)
-
- # Update the reference:
- self._set_refname(refname, commit, force=force)
-
- def _simplify_to_path(self, refname, base, path, force=False):
- """Simplify based on path and set refname to the result.
-
- The base and path arguments are defined similarly to
- create_commit_chain(), except that instead of SHA-1s they may
- optionally represent commits via (i1, i2) tuples.
-
- """
-
- def to_sha1(arg):
- if type(arg) is tuple:
- commit_record = self[arg]
- if not commit_record.is_known():
- raise MissingMergeFailure(*arg)
- return commit_record.sha1
- else:
- return arg
-
- base_sha1 = to_sha1(base)
- path_sha1 = []
- for (commit, metadata) in path:
- commit_sha1 = to_sha1(commit)
- metadata_sha1 = to_sha1(metadata)
- path_sha1.append((commit_sha1, metadata_sha1))
-
- # A path simplification is allowed to discard history, as long
- # as the *pre-simplification* apex commit is a descendant of
- # the branch to be moved.
- if path:
- apex = path_sha1[-1][0]
- else:
- apex = base_sha1
-
- if not force and not self.git.is_ff(refname, apex):
- raise Failure(
- '%s cannot be updated to %s without discarding history.\n'
- 'Use --force if you are sure, or choose a different reference'
- % (refname, apex,)
- )
-
- # The update is OK, so here we can set force=True:
- self._set_refname(
- refname,
- self.git.create_commit_chain(base_sha1, path_sha1),
- force=True,
- )
-
- def simplify_to_rebase(self, refname, force=False):
- i1 = self.len1 - 1
- path = [
- ((i1, i2), (0, i2))
- for i2 in range(1, self.len2)
- ]
-
- try:
- self._simplify_to_path(refname, (i1, 0), path, force=force)
- except MissingMergeFailure as e:
- raise Failure(
- 'Cannot simplify to %s because merge %d-%d is not yet done'
- % (self.goal, e.i1, e.i2)
- )
-
- def simplify_to_drop(self, refname, force=False):
- try:
- base = self.goalopts['base']
- except KeyError:
- raise Failure('Goal "drop" was not initialized correctly')
-
- i2 = self.len2 - 1
- path = [
- ((i1, i2), (i1, 0))
- for i1 in range(1, self.len1)
- ]
-
- try:
- self._simplify_to_path(refname, base, path, force=force)
- except MissingMergeFailure as e:
- raise Failure(
- 'Cannot simplify to rebase because merge %d-%d is not yet done'
- % (e.i1, e.i2)
- )
-
- def simplify_to_revert(self, refname, force=False):
- self.simplify_to_rebase(refname, force=force)
-
- def simplify_to_merge(self, refname, force=False):
- if not (-1, -1) in self:
- raise Failure(
- 'Cannot simplify to merge because merge %d-%d is not yet done'
- % (self.len1 - 1, self.len2 - 1)
- )
- tree = self.git.get_tree(self[-1, -1].sha1)
- parents = [self[-1, 0].sha1, self[0, -1].sha1]
-
- # Create a preliminary commit with a generic commit message:
- sha1 = self.git.commit_tree(
- tree, parents,
- msg='Merge %s into %s (using imerge)' % (self.tip2, self.tip1),
- )
-
- self._set_refname(refname, sha1, force=force)
-
- # Now let the user edit the commit log message:
- self.git.amend()
-
- def simplify(self, refname, force=False):
- """Simplify this MergeState and save the result to refname.
-
- The merge must be complete before calling this method."""
-
- if self.goal == 'full':
- self.simplify_to_full(refname, force=force)
- elif self.goal == 'rebase':
- self.simplify_to_rebase(refname, force=force)
- elif self.goal == 'rebase-with-history':
- self.simplify_to_rebase_with_history(refname, force=force)
- elif self.goal == 'border':
- self.simplify_to_border(refname, force=force)
- elif self.goal == 'border-with-history':
- self.simplify_to_border(refname, with_history2=True, force=force)
- elif self.goal == 'border-with-history2':
- self.simplify_to_border(
- refname, with_history1=True, with_history2=True, force=force,
- )
- elif self.goal == 'drop':
- self.simplify_to_drop(refname, force=force)
- elif self.goal == 'revert':
- self.simplify_to_revert(refname, force=force)
- elif self.goal == 'merge':
- self.simplify_to_merge(refname, force=force)
- else:
- raise ValueError('Invalid value for goal (%r)' % (self.goal,))
-
- def save(self):
- """Write the current MergeState to the repository."""
-
- blockers = []
- for i2 in range(0, self.len2):
- for i1 in range(0, self.len1):
- record = self[i1, i2]
- if record.is_known():
- record.save(self.git, self.name, i1, i2)
- if record.is_blocked():
- blockers.append((i1, i2))
-
- state = dict(
- version='.'.join(str(i) for i in STATE_VERSION),
- blockers=blockers,
- tip1=self.tip1, tip2=self.tip2,
- goal=self.goal,
- goalopts=self.goalopts,
- manual=self.manual,
- branch=self.branch,
- )
- self.git.write_imerge_state_dict(self.name, state)
-
- def __str__(self):
- return 'MergeState(\'%s\', tip1=\'%s\', tip2=\'%s\', goal=\'%s\')' % (
- self.name, self.tip1, self.tip2, self.goal,
- )
-
-
-def choose_merge_name(git, name):
- names = list(git.iter_existing_imerge_names())
-
- # If a name was specified, try to use it and fail if not possible:
- if name is not None:
- if name not in names:
- raise Failure('There is no incremental merge called \'%s\'!' % (name,))
- if len(names) > 1:
- # Record this as the new default:
- git.set_default_imerge_name(name)
- return name
-
- # A name was not specified. Try to use the default name:
- default_name = git.get_default_imerge_name()
- if default_name:
- if git.check_imerge_exists(default_name):
- return default_name
- else:
- # There's no reason to keep the invalid default around:
- git.set_default_imerge_name(None)
- raise Failure(
- 'Warning: The default incremental merge \'%s\' has disappeared.\n'
- '(The setting imerge.default has been cleared.)\n'
- 'Please try again.'
- % (default_name,)
- )
-
- # If there is exactly one imerge, set it to be the default and use it.
- if len(names) == 1 and git.check_imerge_exists(names[0]):
- return names[0]
-
- raise Failure('Please select an incremental merge using --name')
-
-
-def read_merge_state(git, name=None):
- return MergeState.read(git, choose_merge_name(git, name))
-
-
-def cmd_list(parser, options):
- git = GitRepository()
- names = list(git.iter_existing_imerge_names())
- default_merge = git.get_default_imerge_name()
- if not default_merge and len(names) == 1:
- default_merge = names[0]
- for name in names:
- if name == default_merge:
- sys.stdout.write('* %s\n' % (name,))
- else:
- sys.stdout.write(' %s\n' % (name,))
-
-
-def cmd_init(parser, options):
- git = GitRepository()
- git.require_clean_work_tree('proceed')
-
- if not options.name:
- parser.error(
- 'Please specify the --name to be used for this incremental merge'
- )
- tip1 = git.get_head_refname(short=True) or 'HEAD'
- tip2 = options.tip2
- try:
- (merge_base, commits1, commits2) = git.get_boundaries(
- tip1, tip2, options.first_parent,
- )
- except NonlinearAncestryError as e:
- if options.first_parent:
- parser.error(str(e))
- else:
- parser.error('%s\nPerhaps use "--first-parent"?' % (e,))
-
- merge_state = MergeState.initialize(
- git, options.name, merge_base,
- tip1, commits1,
- tip2, commits2,
- goal=options.goal, manual=options.manual,
- branch=(options.branch or options.name),
- )
- merge_state.save()
- if len(list(git.iter_existing_imerge_names())) > 1:
- git.set_default_imerge_name(options.name)
-
-
-def cmd_start(parser, options):
- git = GitRepository()
- git.require_clean_work_tree('proceed')
-
- if not options.name:
- parser.error(
- 'Please specify the --name to be used for this incremental merge'
- )
- tip1 = git.get_head_refname(short=True) or 'HEAD'
- tip2 = options.tip2
-
- try:
- (merge_base, commits1, commits2) = git.get_boundaries(
- tip1, tip2, options.first_parent,
- )
- except NonlinearAncestryError as e:
- if options.first_parent:
- parser.error(str(e))
- else:
- parser.error('%s\nPerhaps use "--first-parent"?' % (e,))
-
- merge_state = MergeState.initialize(
- git, options.name, merge_base,
- tip1, commits1,
- tip2, commits2,
- goal=options.goal, manual=options.manual,
- branch=(options.branch or options.name),
- )
- merge_state.save()
- if len(list(git.iter_existing_imerge_names())) > 1:
- git.set_default_imerge_name(options.name)
-
- try:
- merge_state.auto_complete_frontier()
- except FrontierBlockedError as e:
- merge_state.request_user_merge(e.i1, e.i2)
- else:
- sys.stderr.write('Merge is complete!\n')
-
-
-def cmd_merge(parser, options):
- git = GitRepository()
- git.require_clean_work_tree('proceed')
-
- tip2 = options.tip2
-
- if options.name:
- name = options.name
- else:
- # By default, name the imerge after the branch being merged:
- name = tip2
- git.check_imerge_name_format(name)
-
- tip1 = git.get_head_refname(short=True)
- if tip1:
- if not options.branch:
- # See if we can store the result to the checked-out branch:
- try:
- git.check_branch_name_format(tip1)
- except InvalidBranchNameError:
- pass
- else:
- options.branch = tip1
- else:
- tip1 = 'HEAD'
-
- if not options.branch:
- if options.name:
- options.branch = options.name
- else:
- parser.error(
- 'HEAD is not a simple branch. '
- 'Please specify --branch for storing results.'
- )
-
- try:
- (merge_base, commits1, commits2) = git.get_boundaries(
- tip1, tip2, options.first_parent,
- )
- except NonlinearAncestryError as e:
- if options.first_parent:
- parser.error(str(e))
- else:
- parser.error('%s\nPerhaps use "--first-parent"?' % (e,))
- except NothingToDoError as e:
- sys.stdout.write('Already up-to-date.\n')
- sys.exit(0)
-
- merge_state = MergeState.initialize(
- git, name, merge_base,
- tip1, commits1,
- tip2, commits2,
- goal=options.goal, manual=options.manual,
- branch=options.branch,
- )
- merge_state.save()
- if len(list(git.iter_existing_imerge_names())) > 1:
- git.set_default_imerge_name(name)
-
- try:
- merge_state.auto_complete_frontier()
- except FrontierBlockedError as e:
- merge_state.request_user_merge(e.i1, e.i2)
- else:
- sys.stderr.write('Merge is complete!\n')
-
-
-def cmd_rebase(parser, options):
- git = GitRepository()
- git.require_clean_work_tree('proceed')
-
- tip1 = options.tip1
-
- tip2 = git.get_head_refname(short=True)
- if tip2:
- if not options.branch:
- # See if we can store the result to the current branch:
- try:
- git.check_branch_name_format(tip2)
- except InvalidBranchNameError:
- pass
- else:
- options.branch = tip2
- if not options.name:
- # By default, name the imerge after the branch being rebased:
- options.name = tip2
- else:
- tip2 = git.rev_parse('HEAD')
-
- if not options.name:
- parser.error(
- 'The checked-out branch could not be used as the imerge name.\n'
- 'Please use the --name option.'
- )
-
- if not options.branch:
- if options.name:
- options.branch = options.name
- else:
- parser.error(
- 'HEAD is not a simple branch. '
- 'Please specify --branch for storing results.'
- )
-
- try:
- (merge_base, commits1, commits2) = git.get_boundaries(
- tip1, tip2, options.first_parent,
- )
- except NonlinearAncestryError as e:
- if options.first_parent:
- parser.error(str(e))
- else:
- parser.error('%s\nPerhaps use "--first-parent"?' % (e,))
- except NothingToDoError as e:
- sys.stdout.write('Already up-to-date.\n')
- sys.exit(0)
-
- merge_state = MergeState.initialize(
- git, options.name, merge_base,
- tip1, commits1,
- tip2, commits2,
- goal=options.goal, manual=options.manual,
- branch=options.branch,
- )
- merge_state.save()
- if len(list(git.iter_existing_imerge_names())) > 1:
- git.set_default_imerge_name(options.name)
-
- try:
- merge_state.auto_complete_frontier()
- except FrontierBlockedError as e:
- merge_state.request_user_merge(e.i1, e.i2)
- else:
- sys.stderr.write('Merge is complete!\n')
-
-
-def cmd_drop(parser, options):
- git = GitRepository()
- git.require_clean_work_tree('proceed')
-
- m = re.match(r'^(?P<start>.*[^\.])(?P<sep>\.{2,})(?P<end>[^\.].*)$', options.range)
- if m:
- if m.group('sep') != '..':
- parser.error(
- 'Range must either be a single commit '
- 'or in the form "commit..commit"'
- )
- start = git.rev_parse(m.group('start'))
- end = git.rev_parse(m.group('end'))
- else:
- end = git.rev_parse(options.range)
- start = git.rev_parse('%s^' % (end,))
-
- try:
- to_drop = git.linear_ancestry(start, end, options.first_parent)
- except NonlinearAncestryError as e:
- if options.first_parent:
- parser.error(str(e))
- else:
- parser.error('%s\nPerhaps use "--first-parent"?' % (e,))
-
- # Suppose we want to drop commits 2 and 3 in the branch below.
- # Then we set up an imerge as follows:
- #
- # o - 0 - 1 - 2 - 3 - 4 - 5 - 6 ← tip1
- # |
- # 3⁻¹
- # |
- # 2⁻¹
- #
- # ↑
- # tip2
- #
- # We first use imerge to rebase tip1 onto tip2, then we simplify
- # by discarding the sequence (2, 3, 3⁻¹, 2⁻¹) (which together are
- # a NOOP). In this case, goalopts would have the following
- # contents:
- #
- # goalopts['base'] = rev_parse(commit1)
-
- tip1 = git.get_head_refname(short=True)
- if tip1:
- if not options.branch:
- # See if we can store the result to the current branch:
- try:
- git.check_branch_name_format(tip1)
- except InvalidBranchNameError:
- pass
- else:
- options.branch = tip1
- if not options.name:
- # By default, name the imerge after the branch being rebased:
- options.name = tip1
- else:
- tip1 = git.rev_parse('HEAD')
-
- if not options.name:
- parser.error(
- 'The checked-out branch could not be used as the imerge name.\n'
- 'Please use the --name option.'
- )
-
- if not options.branch:
- if options.name:
- options.branch = options.name
- else:
- parser.error(
- 'HEAD is not a simple branch. '
- 'Please specify --branch for storing results.'
- )
-
- # Create a branch based on end that contains the inverse of the
- # commits that we want to drop. This will be tip2:
-
- git.checkout(end)
- for commit in reversed(to_drop):
- git.revert(commit)
-
- tip2 = git.rev_parse('HEAD')
-
- try:
- (merge_base, commits1, commits2) = git.get_boundaries(
- tip1, tip2, options.first_parent,
- )
- except NonlinearAncestryError as e:
- if options.first_parent:
- parser.error(str(e))
- else:
- parser.error('%s\nPerhaps use "--first-parent"?' % (e,))
- except NothingToDoError as e:
- sys.stdout.write('Already up-to-date.\n')
- sys.exit(0)
-
- merge_state = MergeState.initialize(
- git, options.name, merge_base,
- tip1, commits1,
- tip2, commits2,
- goal='drop', goalopts={'base' : start},
- manual=options.manual,
- branch=options.branch,
- )
- merge_state.save()
- if len(list(git.iter_existing_imerge_names())) > 1:
- git.set_default_imerge_name(options.name)
-
- try:
- merge_state.auto_complete_frontier()
- except FrontierBlockedError as e:
- merge_state.request_user_merge(e.i1, e.i2)
- else:
- sys.stderr.write('Merge is complete!\n')
-
-
-def cmd_revert(parser, options):
- git = GitRepository()
- git.require_clean_work_tree('proceed')
-
- m = re.match(r'^(?P<start>.*[^\.])(?P<sep>\.{2,})(?P<end>[^\.].*)$', options.range)
- if m:
- if m.group('sep') != '..':
- parser.error(
- 'Range must either be a single commit '
- 'or in the form "commit..commit"'
- )
- start = git.rev_parse(m.group('start'))
- end = git.rev_parse(m.group('end'))
- else:
- end = git.rev_parse(options.range)
- start = git.rev_parse('%s^' % (end,))
-
- try:
- to_revert = git.linear_ancestry(start, end, options.first_parent)
- except NonlinearAncestryError as e:
- if options.first_parent:
- parser.error(str(e))
- else:
- parser.error('%s\nPerhaps use "--first-parent"?' % (e,))
-
- # Suppose we want to revert commits 2 and 3 in the branch below.
- # Then we set up an imerge as follows:
- #
- # o - 0 - 1 - 2 - 3 - 4 - 5 - 6 ← tip1
- # |
- # 3⁻¹
- # |
- # 2⁻¹
- #
- # ↑
- # tip2
- #
- # Then we use imerge to rebase tip2 onto tip1.
-
- tip1 = git.get_head_refname(short=True)
- if tip1:
- if not options.branch:
- # See if we can store the result to the current branch:
- try:
- git.check_branch_name_format(tip1)
- except InvalidBranchNameError:
- pass
- else:
- options.branch = tip1
- if not options.name:
- # By default, name the imerge after the branch being rebased:
- options.name = tip1
- else:
- tip1 = git.rev_parse('HEAD')
-
- if not options.name:
- parser.error(
- 'The checked-out branch could not be used as the imerge name.\n'
- 'Please use the --name option.'
- )
-
- if not options.branch:
- if options.name:
- options.branch = options.name
- else:
- parser.error(
- 'HEAD is not a simple branch. '
- 'Please specify --branch for storing results.'
- )
-
- # Create a branch based on end that contains the inverse of the
- # commits that we want to drop. This will be tip2:
-
- git.checkout(end)
- for commit in reversed(to_revert):
- git.revert(commit)
-
- tip2 = git.rev_parse('HEAD')
-
- try:
- (merge_base, commits1, commits2) = git.get_boundaries(
- tip1, tip2, options.first_parent,
- )
- except NonlinearAncestryError as e:
- if options.first_parent:
- parser.error(str(e))
- else:
- parser.error('%s\nPerhaps use "--first-parent"?' % (e,))
- except NothingToDoError as e:
- sys.stdout.write('Already up-to-date.\n')
- sys.exit(0)
-
- merge_state = MergeState.initialize(
- git, options.name, merge_base,
- tip1, commits1,
- tip2, commits2,
- goal='revert',
- manual=options.manual,
- branch=options.branch,
- )
- merge_state.save()
- if len(list(git.iter_existing_imerge_names())) > 1:
- git.set_default_imerge_name(options.name)
-
- try:
- merge_state.auto_complete_frontier()
- except FrontierBlockedError as e:
- merge_state.request_user_merge(e.i1, e.i2)
- else:
- sys.stderr.write('Merge is complete!\n')
-
-
-def cmd_remove(parser, options):
- git = GitRepository()
- MergeState.remove(git, choose_merge_name(git, options.name))
-
-
-def cmd_continue(parser, options):
- git = GitRepository()
- merge_state = read_merge_state(git, options.name)
- try:
- merge_state.incorporate_user_merge(edit_log_msg=options.edit)
- except NoManualMergeError:
- pass
- except NotABlockingCommitError as e:
- raise Failure(str(e))
- except ManualMergeUnusableError as e:
- raise Failure(str(e))
-
- try:
- merge_state.auto_complete_frontier()
- except FrontierBlockedError as e:
- merge_state.request_user_merge(e.i1, e.i2)
- else:
- sys.stderr.write('Merge is complete!\n')
-
-
-def cmd_record(parser, options):
- git = GitRepository()
- merge_state = read_merge_state(git, options.name)
- try:
- merge_state.incorporate_user_merge(edit_log_msg=options.edit)
- except NoManualMergeError as e:
- raise Failure(str(e))
- except NotABlockingCommitError:
- raise Failure(str(e))
- except ManualMergeUnusableError as e:
- raise Failure(str(e))
-
- try:
- merge_state.auto_complete_frontier()
- except FrontierBlockedError as e:
- pass
- else:
- sys.stderr.write('Merge is complete!\n')
-
-
-def cmd_autofill(parser, options):
- git = GitRepository()
- git.require_clean_work_tree('proceed')
- merge_state = read_merge_state(git, options.name)
- with git.temporary_head(message='imerge: restoring'):
- try:
- merge_state.auto_complete_frontier()
- except FrontierBlockedError as e:
- raise Failure(str(e))
-
-
-def cmd_simplify(parser, options):
- git = GitRepository()
- git.require_clean_work_tree('proceed')
- merge_state = read_merge_state(git, options.name)
- merge_frontier = MergeFrontier.map_known_frontier(merge_state)
- if not merge_frontier.is_complete():
- raise Failure('Merge %s is not yet complete!' % (merge_state.name,))
- refname = 'refs/heads/%s' % ((options.branch or merge_state.branch),)
- if options.goal is not None:
- merge_state.set_goal(options.goal)
- merge_state.save()
- merge_state.simplify(refname, force=options.force)
-
-
-def cmd_finish(parser, options):
- git = GitRepository()
- git.require_clean_work_tree('proceed')
- merge_state = read_merge_state(git, options.name)
- merge_frontier = MergeFrontier.map_known_frontier(merge_state)
- if not merge_frontier.is_complete():
- raise Failure('Merge %s is not yet complete!' % (merge_state.name,))
- refname = 'refs/heads/%s' % ((options.branch or merge_state.branch),)
- if options.goal is not None:
- merge_state.set_goal(options.goal)
- merge_state.save()
- merge_state.simplify(refname, force=options.force)
- MergeState.remove(git, merge_state.name)
-
-
-def cmd_diagram(parser, options):
- git = GitRepository()
- if not (options.commits or options.frontier):
- options.frontier = True
- if not (options.color or (options.color is None and sys.stdout.isatty())):
- AnsiColor.disable()
-
- merge_state = read_merge_state(git, options.name)
- if options.commits:
- merge_state.write(sys.stdout)
- sys.stdout.write('\n')
- if options.frontier:
- merge_frontier = MergeFrontier.map_known_frontier(merge_state)
- merge_frontier.write(sys.stdout)
- sys.stdout.write('\n')
- if options.html:
- merge_frontier = MergeFrontier.map_known_frontier(merge_state)
- html = open(options.html, 'w')
- merge_frontier.write_html(html, merge_state.name)
- html.close()
- sys.stdout.write(
- 'Key:\n'
- )
- if options.frontier:
- sys.stdout.write(
- ' |,-,+ = rectangles forming current merge frontier\n'
- )
- sys.stdout.write(
- ' * = merge done manually\n'
- ' . = merge done automatically\n'
- ' # = conflict that is currently blocking progress\n'
- ' @ = merge was blocked but has been resolved\n'
- ' ? = no merge recorded\n'
- '\n'
- )
-
-
-def reparent_recursively(git, start_commit, parents, end_commit):
- """Change the parents of start_commit and its descendants.
-
- Change start_commit to have the specified parents, and reparent
- all commits on the ancestry path between start_commit and
- end_commit accordingly. Return the replacement end_commit.
- start_commit, parents, and end_commit must all be resolved OIDs.
-
- """
-
- # A map {old_oid : new_oid} keeping track of which replacements
- # have to be made:
- replacements = {}
-
- # Reparent start_commit:
- replacements[start_commit] = git.reparent(start_commit, parents)
-
- for (commit, parents) in git.rev_list_with_parents(
- '--ancestry-path', '--topo-order', '--reverse',
- '%s..%s' % (start_commit, end_commit)
- ):
- parents = [replacements.get(p, p) for p in parents]
- replacements[commit] = git.reparent(commit, parents)
-
- try:
- return replacements[end_commit]
- except KeyError:
- raise ValueError(
- "%s is not an ancestor of %s" % (start_commit, end_commit),
- )
-
-
-def cmd_reparent(parser, options):
- git = GitRepository()
- try:
- commit = git.get_commit_sha1(options.commit)
- except ValueError:
- sys.exit('%s is not a valid commit', options.commit)
-
- try:
- head = git.get_commit_sha1('HEAD')
- except ValueError:
- sys.exit('HEAD is not a valid commit')
-
- try:
- parents = [git.get_commit_sha1(p) for p in options.parents]
- except ValueError as e:
- sys.exit(e.message)
-
- sys.stderr.write('Reparenting %s..HEAD\n' % (options.commit,))
-
- try:
- new_head = reparent_recursively(git, commit, parents, head)
- except ValueError as e:
- sys.exit(e.message)
-
- sys.stdout.write('%s\n' % (new_head,))
-
-
-def main(args):
- NAME_INIT_HELP = 'name to use for this incremental merge'
-
- def add_name_argument(subparser, help=None):
- if help is None:
- subcommand = subparser.prog.split()[1]
- help = 'name of incremental merge to {0}'.format(subcommand)
-
- subparser.add_argument(
- '--name', action='store', default=None, help=help,
- )
-
- def add_goal_argument(subparser, default=DEFAULT_GOAL):
- help = 'the goal of the incremental merge'
- if default is None:
- help = (
- 'the type of simplification to be made '
- '(default is the value provided to "init" or "start")'
- )
- subparser.add_argument(
- '--goal',
- action='store', default=default,
- choices=ALLOWED_GOALS,
- help=help,
- )
-
- def add_branch_argument(subparser):
- subcommand = subparser.prog.split()[1]
- help = 'the name of the branch to which the result will be stored'
- if subcommand in ['simplify', 'finish']:
- help = (
- 'the name of the branch to which to store the result '
- '(default is the value provided to "init" or "start" if any; '
- 'otherwise the name of the merge). '
- 'If BRANCH already exists then it must be able to be '
- 'fast-forwarded to the result unless the --force option is '
- 'specified.'
- )
- subparser.add_argument(
- '--branch',
- action='store', default=None,
- help=help,
- )
-
- def add_manual_argument(subparser):
- subparser.add_argument(
- '--manual',
- action='store_true', default=False,
- help=(
- 'ask the user to complete all merges manually, even when they '
- 'appear conflict-free. This option disables the usual bisection '
- 'algorithm and causes the full incremental merge diagram to be '
- 'completed.'
- ),
- )
-
- def add_first_parent_argument(subparser, default=None):
- subcommand = subparser.prog.split()[1]
- help = (
- 'handle only the first parent commits '
- '(this option is currently required if the history is nonlinear)'
- )
- if subcommand in ['merge', 'rebase']:
- help = argparse.SUPPRESS
- subparser.add_argument(
- '--first-parent', action='store_true', default=default, help=help,
- )
-
- def add_tip2_argument(subparser):
- subparser.add_argument(
- 'tip2', action='store', metavar='branch',
- help='the tip of the branch to be merged into HEAD',
- )
-
- parser = argparse.ArgumentParser(
- description=__doc__,
- formatter_class=argparse.RawDescriptionHelpFormatter,
- )
- subparsers = parser.add_subparsers(dest='subcommand', help='sub-command')
-
- subparser = subparsers.add_parser(
- 'start',
- help=(
- 'start a new incremental merge '
- '(equivalent to "init" followed by "continue")'
- ),
- )
- add_name_argument(subparser, help=NAME_INIT_HELP)
- add_goal_argument(subparser)
- add_branch_argument(subparser)
- add_manual_argument(subparser)
- add_first_parent_argument(subparser)
- add_tip2_argument(subparser)
-
- subparser = subparsers.add_parser(
- 'merge',
- help='start a simple merge via incremental merge',
- )
- add_name_argument(subparser, help=NAME_INIT_HELP)
- add_goal_argument(subparser, default='merge')
- add_branch_argument(subparser)
- add_manual_argument(subparser)
- add_first_parent_argument(subparser, default=True)
- add_tip2_argument(subparser)
-
- subparser = subparsers.add_parser(
- 'rebase',
- help='start a simple rebase via incremental merge',
- )
- add_name_argument(subparser, help=NAME_INIT_HELP)
- add_goal_argument(subparser, default='rebase')
- add_branch_argument(subparser)
- add_manual_argument(subparser)
- add_first_parent_argument(subparser, default=True)
- subparser.add_argument(
- 'tip1', action='store', metavar='branch',
- help=(
- 'the tip of the branch onto which the current branch should '
- 'be rebased'
- ),
- )
-
- subparser = subparsers.add_parser(
- 'drop',
- help='drop one or more commits via incremental merge',
- )
- add_name_argument(subparser, help=NAME_INIT_HELP)
- add_branch_argument(subparser)
- add_manual_argument(subparser)
- add_first_parent_argument(subparser, default=True)
- subparser.add_argument(
- 'range', action='store', metavar='[commit | commit..commit]',
- help=(
- 'the commit or range of commits that should be dropped'
- ),
- )
-
- subparser = subparsers.add_parser(
- 'revert',
- help='revert one or more commits via incremental merge',
- )
- add_name_argument(subparser, help=NAME_INIT_HELP)
- add_branch_argument(subparser)
- add_manual_argument(subparser)
- add_first_parent_argument(subparser, default=True)
- subparser.add_argument(
- 'range', action='store', metavar='[commit | commit..commit]',
- help=(
- 'the commit or range of commits that should be reverted'
- ),
- )
-
- subparser = subparsers.add_parser(
- 'continue',
- help=(
- 'record the merge at branch imerge/NAME '
- 'and start the next step of the merge '
- '(equivalent to "record" followed by "autofill" '
- 'and then sets up the working copy with the next '
- 'conflict that has to be resolved manually)'
- ),
- )
- add_name_argument(subparser)
- subparser.set_defaults(edit=None)
- subparser.add_argument(
- '--edit', '-e', dest='edit', action='store_true',
- help='commit staged changes with the --edit option',
- )
- subparser.add_argument(
- '--no-edit', dest='edit', action='store_false',
- help='commit staged changes with the --no-edit option',
- )
-
- subparser = subparsers.add_parser(
- 'finish',
- help=(
- 'simplify then remove a completed incremental merge '
- '(equivalent to "simplify" followed by "remove")'
- ),
- )
- add_name_argument(subparser)
- add_goal_argument(subparser, default=None)
- add_branch_argument(subparser)
- subparser.add_argument(
- '--force',
- action='store_true', default=False,
- help='allow the target branch to be updated in a non-fast-forward manner',
- )
-
- subparser = subparsers.add_parser(
- 'diagram',
- help='display a diagram of the current state of a merge',
- )
- add_name_argument(subparser)
- subparser.add_argument(
- '--commits', action='store_true', default=False,
- help='show the merges that have been made so far',
- )
- subparser.add_argument(
- '--frontier', action='store_true', default=False,
- help='show the current merge frontier',
- )
- subparser.add_argument(
- '--html', action='store', default=None,
- help='generate HTML diagram showing the current merge frontier',
- )
- subparser.add_argument(
- '--color', dest='color', action='store_true', default=None,
- help='draw diagram with colors',
- )
- subparser.add_argument(
- '--no-color', dest='color', action='store_false',
- help='draw diagram without colors',
- )
-
- subparser = subparsers.add_parser(
- 'list',
- help=(
- 'list the names of incremental merges that are currently in progress. '
- 'The active merge is shown with an asterisk next to it.'
- ),
- )
-
- subparser = subparsers.add_parser(
- 'init',
- help='initialize a new incremental merge',
- )
- add_name_argument(subparser, help=NAME_INIT_HELP)
- add_goal_argument(subparser)
- add_branch_argument(subparser)
- add_manual_argument(subparser)
- add_first_parent_argument(subparser)
- add_tip2_argument(subparser)
-
- subparser = subparsers.add_parser(
- 'record',
- help='record the merge at branch imerge/NAME',
- )
- # record:
- add_name_argument(
- subparser,
- help='name of merge to which the merge should be added',
- )
- subparser.set_defaults(edit=None)
- subparser.add_argument(
- '--edit', '-e', dest='edit', action='store_true',
- help='commit staged changes with the --edit option',
- )
- subparser.add_argument(
- '--no-edit', dest='edit', action='store_false',
- help='commit staged changes with the --no-edit option',
- )
-
- subparser = subparsers.add_parser(
- 'autofill',
- help='autofill non-conflicting merges',
- )
- add_name_argument(subparser)
-
- subparser = subparsers.add_parser(
- 'simplify',
- help=(
- 'simplify a completed incremental merge by discarding unneeded '
- 'intermediate merges and cleaning up the ancestry of the commits '
- 'that are retained'
- ),
- )
- add_name_argument(subparser)
- add_goal_argument(subparser, default=None)
- add_branch_argument(subparser)
- subparser.add_argument(
- '--force',
- action='store_true', default=False,
- help='allow the target branch to be updated in a non-fast-forward manner',
- )
-
- subparser = subparsers.add_parser(
- 'remove',
- help='irrevocably remove an incremental merge',
- )
- add_name_argument(subparser)
-
- subparser = subparsers.add_parser(
- 'reparent',
- help=(
- 'change the parents of the specified commit and propagate the '
- 'change to HEAD'
- ),
- )
- subparser.add_argument(
- '--commit', metavar='COMMIT', default='HEAD',
- help=(
- 'target commit to reparent. Create a new commit identical to '
- 'this one, but having the specified parents. Then create '
- 'new versions of all descendants of this commit all the way to '
- 'HEAD, incorporating the modified commit. Output the SHA-1 of '
- 'the replacement HEAD commit.'
- ),
- )
- subparser.add_argument(
- 'parents', nargs='*', metavar='PARENT',
- help='a list of commits',
- )
-
- options = parser.parse_args(args)
-
- # Set an environment variable GIT_IMERGE=1 while we are running.
- # This makes it possible for hook scripts etc. to know that they
- # are being run within git-imerge, and should perhaps behave
- # differently. In the future we might make the value more
- # informative, like GIT_IMERGE=[automerge|autofill|...].
- os.environ[str('GIT_IMERGE')] = str('1')
-
- if options.subcommand == 'list':
- cmd_list(parser, options)
- elif options.subcommand == 'init':
- cmd_init(parser, options)
- elif options.subcommand == 'start':
- cmd_start(parser, options)
- elif options.subcommand == 'merge':
- cmd_merge(parser, options)
- elif options.subcommand == 'rebase':
- cmd_rebase(parser, options)
- elif options.subcommand == 'drop':
- cmd_drop(parser, options)
- elif options.subcommand == 'revert':
- cmd_revert(parser, options)
- elif options.subcommand == 'remove':
- cmd_remove(parser, options)
- elif options.subcommand == 'continue':
- cmd_continue(parser, options)
- elif options.subcommand == 'record':
- cmd_record(parser, options)
- elif options.subcommand == 'autofill':
- cmd_autofill(parser, options)
- elif options.subcommand == 'simplify':
- cmd_simplify(parser, options)
- elif options.subcommand == 'finish':
- cmd_finish(parser, options)
- elif options.subcommand == 'diagram':
- cmd_diagram(parser, options)
- elif options.subcommand == 'reparent':
- cmd_reparent(parser, options)
- else:
- parser.error('Unrecognized subcommand')
-
-
-if __name__ == '__main__':
- try:
- main(sys.argv[1:])
- except Failure as e:
- sys.exit(str(e))
-
-
-# vim: set expandtab ft=python:
diff --git a/local/.bin/hornet/twitch b/local/.bin/hornet/twitch
deleted file mode 100755
index 5b691ab..0000000
--- a/local/.bin/hornet/twitch
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-
-if [ -z "${1}" ]; then
- ssh partofme killall vlc
- echo '*/buffer close twitch.#' >~/.weechat/weechat_fifo
-else
- ssh partofme sh -c "'nohup twitch ${1} > /dev/null 2>&1 &'"
- echo "irc.server.twitch */join #${1}" >~/.weechat/weechat_fifo
-fi
diff --git a/local/.bin/nopaste b/local/.bin/nopaste
deleted file mode 100755
index 573af2e..0000000
--- a/local/.bin/nopaste
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/env perl
-use strict;
-use warnings;
-use 5.020;
-
-use Carp;
-use File::Spec;
-use File::Temp;
-use POSIX 'strftime';
-
-if (@ARGV > 1) {
- croak "can only nopaste one file at a time";
-}
-
-my $date = strftime("%Y-%m-%d", localtime);
-my $template = "${date}-XXXXXXXX";
-my $suffix = @ARGV
- ? "-${\(File::Spec->splitdir($ARGV[0]))[-1]}"
- : undef;
-
-my $contents = do { local $/; <> };
-
-my $tmpfile = File::Temp->new(
- TEMPLATE => $template,
- SUFFIX => $suffix,
- UNLINK => 1,
- TMPDIR => 1,
-);
-my $tmpfilename = $tmpfile->filename;
-
-print $tmpfile $contents or croak "Can't write to $tmpfilename: $!";
-close $tmpfile or croak "Can't write to $tmpfilename: $!";
-chmod 0644 => $tmpfilename;
-
-system('scp', '-pq', $tmpfilename, "tozt.net:paste");
-
-say "https://paste.tozt.net/${\(File::Spec->splitdir($tmpfilename))[-1]}";
diff --git a/local/.bin/pick-music b/local/.bin/pick-music
deleted file mode 100755
index 8c406f7..0000000
--- a/local/.bin/pick-music
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-set -eu
-set -o pipefail
-
-lastfm-query sync doyster
-lastfm-query recommend "${1:-20}"
diff --git a/local/.bin/rand-music b/local/.bin/rand-music
deleted file mode 100755
index 6e9bccc..0000000
--- a/local/.bin/rand-music
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-set -eu
-set -o pipefail
-
-if [ "${1:-}" = '--old' ]; then
- extra_args="--exclude yearly --include all"
- shift
-else
- extra_args=
-fi
-
-lastfm-query sync doyster
-lastfm-query recommend --random --album $extra_args "${1:-20}"
diff --git a/local/.bin/ringtone b/local/.bin/ringtone
deleted file mode 100755
index 19dddb6..0000000
--- a/local/.bin/ringtone
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-set -eu
-set -o pipefail
-
-if [ $# -lt 3 ]; then
- echo "usage: ringtone <input> <output> <fake_video_file>"
- echo "note: fake_video_file is unused, but must be valid"
- echo " and at least the length of the output"
- exit 1
-fi
-
-mencoder \
- -ovc frameno \
- -oac mp3lame \
- -lameopts cbr:br=64 \
- -of rawaudio \
- -o "$2" \
- -audiofile "$1" \
- -endpos 256kb \
- "$3"
diff --git a/local/.bin/tozt/learn_spam b/local/.bin/tozt/learn_spam
deleted file mode 100755
index 75e500a..0000000
--- a/local/.bin/tozt/learn_spam
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/sh
-set -eu
-set -o pipefail
-
-MAILDIR=~/Maildir
-OPTS="--no-sync"
-SALEARN=/usr/bin/vendor_perl/sa-learn
-
-if [ "$1" = "-q" ]; then
- OPTS="$OPTS"
- OUTFILE="/dev/null"
-else
- OPTS="$OPTS --showdots"
- OUTFILE="/dev/stdout"
-fi
-
-if [ ! "$1" = "-q" ]; then
- echo "$(date): Learning ham..."
-fi
-nice find $MAILDIR -type f |
- perl -nl0e'chomp; ($ts) = /(\d{10})/; $now = time; print if m{/(new|cur)/} && !m{/(\.old|\.spam)/} && $ts > $now - 60*60*24*30' |
- xargs -n1000 -0 $SALEARN "$OPTS" --ham >$OUTFILE
-
-if [ ! "$1" = "-q" ]; then
- echo "$(date): Learning spam..."
-fi
-nice find $MAILDIR -type f |
- perl -nl0e'chomp; ($ts) = /(\d{10})/; $now = time; print if m{/(new|cur)/} && m{/\.spam/} && $ts > $now - 60*60*24*30' |
- xargs -n1000 -0 $SALEARN "$OPTS" --spam >$OUTFILE
-
-if [ ! "$1" = "-q" ]; then
- echo "$(date): Syncing..."
-fi
-nice $SALEARN --sync >$OUTFILE
-
-if [ ! "$1" = "-q" ]; then
- echo "$(date): Done!"
-fi
diff --git a/local/.bin/update-addressbook b/local/.bin/update-addressbook
deleted file mode 100755
index 4683e86..0000000
--- a/local/.bin/update-addressbook
+++ /dev/null
@@ -1,276 +0,0 @@
-#!/usr/bin/env perl
-use strict;
-use warnings;
-use 5.020;
-use feature 'signatures';
-no warnings 'experimental::signatures';
-
-use Config::INI::Reader;
-use Config::INI::Writer;
-use Email::Address;
-use File::Find;
-
-my @exclude_patterns = (
- # quoted-printable (needs special handling, punting for now)
- sub($address) { $address->format =~ /^=\?/ },
- # automated emails
- sub($address) { $address->address =~ /(mailer-daemon|noreply)/i },
-);
-
-# abook uses # for comments instead of ;
-package Abook::Reader {
- use base 'Config::INI::Reader';
-
- sub preprocess_line($self, $line) {
- ${$line} =~ s/\s+#.*$//g;
- }
-
- sub can_ignore($self, $line, $) {
- return $line =~ /\A\s*(?:#|$)/ ? 1 : 0;
- }
-}
-
-# abook is super finicky about its input format
-package Abook::Writer {
- use base 'Config::INI::Writer';
-
- sub write_handle($self, $input, $handle) {
- print $handle "# abook addressbook file\n\n";
- $self->SUPER::write_handle($input, $handle);
- }
-
- sub preprocess_input($self, $data) {
- my $ini_data = [
- format => {
- program => 'abook',
- version => '0.6.1',
- },
- ];
- my $i = 0;
- for my $name (sort { fc($a) cmp fc($b) } keys %$data) {
- my $person_data = $data->{$name}->as_hashref;
- delete $person_data->{name};
- push @$ini_data, (
- $i++ => [
- name => $name,
- %$person_data,
- ],
- );
- }
-
- $self->SUPER::preprocess_input($ini_data);
- }
-
- sub stringify_value_assignment($self, $name, $value) {
- return '' unless defined $value;
- return $name . '=' . $self->stringify_value($value) . "\n";
- }
-}
-
-package Person {
- sub from_hashref($class, $data) {
- return bless $data, $class;
- }
-
- sub from_email($class, $name, $addresses) {
- return $class->from_hashref({
- name => $name,
- email => join(',', map { $_->address } @$addresses),
- });
- }
-
- sub addresses($self) {
- return split(',', $self->{email} // '');
- }
-
- sub as_hashref($self) {
- return { %$self };
- }
-}
-
-sub existing_people($addressbook) {
- my $data = Abook::Reader->read_file($addressbook);
- delete $data->{format};
-
- my %people;
- for my $id (keys %$data) {
- my $person = $data->{$id};
- $people{$person->{name}} = Person->from_hashref($person);
- }
-
- return %people;
-}
-
-sub maildir_addresses($maildir) {
- my %addresses;
-
- find(sub() {
- open my $fh, '<', $_ or die "couldn't open $_: $!";
- while (<$fh>) {
- last if /^$/;
- next unless /^(?:From|Sender): /;
- for my $address (Email::Address->parse($_)) {
- $address = Email::Address->new(
- $address->name,
- $address->address,
- );
-
- my $name = $address->name;
- my $format = $address->format;
-
- next if $addresses{$name} &&
- grep { $format eq $_ } @{ $addresses{$name} };
- next if grep { $_->($address) } @exclude_patterns;
-
- push @{ $addresses{$name} ||= [] }, $address;
- }
- }
- close $fh;
- }, $maildir);
-
- return %addresses;
-}
-
-sub merge_addresses($old, $new) {
- my %reverse_old = map {
- map { $_->address => $_->name } @$_
- } values %$old;
- my @new_addresses = map {
- map { $_->address } @$_
- } values %$new;
-
- my %seen_address;
- my %reverse_ret;
- for my $new_address (@new_addresses) {
- next if $seen_address{$new_address}++;
-
- my @related_addresses = ($new_address);
- my @related_names;
- while (1) {
- my @new_related_names = map {
- my $cur_address = $_;
- (
- (grep {
- grep {
- fc($_->address) eq fc($cur_address)
- } @{ $new->{$_} }
- } keys %$new),
- (grep {
- grep {
- fc($_->address) eq fc($cur_address)
- } @{ $old->{$_} }
- } keys %$old),
- )
- } @related_addresses;
- @new_related_names = keys(
- %{ { map { $_ => 1 } @new_related_names } }
- );
-
- my @new_related_addresses = map {
- $_->address
- } map {
- (
- (exists $new->{$_}
- ? (@{ $new->{$_} })
- : ()),
- (exists $old->{$_}
- ? (@{ $old->{$_} })
- : ()),
- )
- } @new_related_names;
- @new_related_addresses = keys(
- %{ { map { $_ => 1 } @new_related_addresses } }
- );
-
- last if @related_names == @new_related_names
- && @related_addresses == @new_related_addresses;
-
- @related_addresses = @new_related_addresses;
- @related_names = @new_related_names;
- }
-
- my ($name) = grep { exists $old->{$_} } @related_names;
- $name = (sort @related_names)[0] unless defined $name;
-
- for my $related_address (@related_addresses) {
- $seen_address{$related_address}++;
- $reverse_ret{$related_address} = $name;
- }
- }
-
- %reverse_ret = (%reverse_old, %reverse_ret);
-
- my %ret;
- for my $address (keys %reverse_ret) {
- my $name = $reverse_ret{$address};
- push @{ $ret{$name} ||= [] }, Email::Address->new($name, $address);
- }
-
- for my $name (keys %ret) {
- my %seen_name;
- for my $address (@{ $ret{$name} }) {
- if (defined $seen_name{lc($address->address)}) {
- if (defined $reverse_old{$address->address}) {
- $seen_name{lc($address->address)} = $address;
- }
- }
- else {
- $seen_name{lc($address->address)} = $address;
- }
- }
- $ret{$name} = [ values %seen_name ];
- }
-
- return %ret;
-}
-
-sub merge_people($old, $new) {
- for my $name (keys %$new) {
- if (exists $old->{$name}) {
- my $old_person = $old->{$name};
- my @addresses = $old_person->addresses;
- for my $address ($new->{$name}->addresses) {
- push @addresses, $address
- unless grep { $_ eq $address } @addresses;
- }
-
- $old_person->{email} = join(',', @addresses);
- }
- else {
- $old->{$name} = $new->{$name};
- }
- }
-
- return %$old;
-}
-
-sub main($addressbook, $maildir) {
- die "usage: $0 ADDRESSBOOK MAILDIR" unless @_ == 2;
-
- my %existing_people = existing_people($addressbook);
- my %existing_addresses = map {
- my $name = $_;
- $name => [
- map {
- Email::Address->new($name, $_)
- } $existing_people{$name}->addresses
- ]
- } keys %existing_people;
-
- my %maildir = maildir_addresses($maildir);
-
- my %new_addresses = merge_addresses(\%existing_addresses, \%maildir);
- my %new_people = map {
- $_ => Person->from_email($_, $new_addresses{$_})
- } keys %new_addresses;
-
- my %new_abook = merge_people(\%existing_people, \%new_people);
-
- rename $addressbook => "$addressbook.bak"
- or die "couldn't rename $addressbook: $!";
-
- my $writer = Abook::Writer->new;
- $writer->write_file(\%new_abook, $addressbook);
-}
-
-main(@ARGV);
diff --git a/mpd/.config/mpd/mpd.conf b/mpd/.config/mpd/mpd.conf
deleted file mode 100644
index e30c2fc..0000000
--- a/mpd/.config/mpd/mpd.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-db_file "~/.cache/mpd/mpd.db"
-state_file "~/.cache/mpd/mpd.state"
-playlist_directory "~/.config/mpd/playlists"
-music_directory "~/media/audio/copy"
-bind_to_address "localhost"
diff --git a/mpd/.services/available/mpd/log/run b/mpd/.services/available/mpd/log/run
deleted file mode 100755
index 072bb30..0000000
--- a/mpd/.services/available/mpd/log/run
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-set -eu
-
-exec multilog t s16777215 "$HOME/.log/mpd"
diff --git a/mpd/.services/available/mpd/run b/mpd/.services/available/mpd/run
deleted file mode 100755
index 186baef..0000000
--- a/mpd/.services/available/mpd/run
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-exec mpd --no-daemon 2>&1
diff --git a/mpd/Makefile b/mpd/Makefile
deleted file mode 100644
index b251d30..0000000
--- a/mpd/Makefile
+++ /dev/null
@@ -1,6 +0,0 @@
-include ../Makefile.include
-
-install: $(HOME)/.services/enabled/mpd
- @mkdir -p $(HOME)/.log/mpd
- @mkdir -p $(HOME)/.cache/mpd
- @mkdir -p $(HOME)/.config/mpd/playlists
diff --git a/ncmpcpp/.config/ncmpcpp/bindings b/ncmpcpp/.config/ncmpcpp/bindings
deleted file mode 100644
index 20a0259..0000000
--- a/ncmpcpp/.config/ncmpcpp/bindings
+++ /dev/null
@@ -1,591 +0,0 @@
-##############################################################
-## This is the example bindings file. Copy it to ##
-## ~/.ncmpcpp/bindings or $XDG_CONFIG_HOME/ncmpcpp/bindings ##
-## and set up your preferences ##
-##############################################################
-##
-##### General rules #####
-##
-## 1) Because each action has runtime checks whether it's
-## ok to run it, a few actions can be bound to one key.
-## Actions will be bound in order given in configuration
-## file. When a key is pressed, first action in order
-## will test itself whether it's possible to run it. If
-## test succeeds, action is executed and other actions
-## bound to this key are ignored. If it doesn't, next
-## action in order tests itself etc.
-##
-## 2) It's possible to bind more that one action at once
-## to a key. It can be done using the following syntax:
-##
-## def_key "key"
-## action1
-## action2
-## ...
-##
-## This creates a chain of actions. When such chain is
-## executed, each action in chain is run until the end of
-## chain is reached or one of its actions fails to execute
-## due to its requirements not being met. If multiple actions
-## and/or chains are bound to the same key, they will be
-## consecutively run until one of them gets fully executed.
-##
-## 3) When ncmpcpp starts, bindings configuration file is
-## parsed and then ncmpcpp provides "missing pieces"
-## of default keybindings. If you want to disable some
-## bindings, there is a special action called 'dummy'
-## for that purpose. Eg. if you want to disable ability
-## to crop playlists, you need to put the following
-## into configuration file:
-##
-## def_key "C"
-## dummy
-##
-## After that ncmpcpp will not bind any default action
-## to this key.
-##
-## 4) To let you write simple macros, the following special
-## actions are provided:
-##
-## - push_character "character" - pushes given special
-## character into input queue, so it will be immediately
-## picked by ncmpcpp upon next call to readKey function.
-## Accepted values: mouse, up, down, page_up, page_down,
-## home, end, space, enter, insert, delete, left, right,
-## tab, ctrl-a, ctrl-b, ..., ctrl-z, ctrl-[, ctrl-\\,
-## ctrl-], ctrl-^, ctrl-_, f1, f2, ..., f12, backspace.
-## In addition, most of these names can be prefixed with
-## alt-/ctrl-/shift- to be recognized with the appropriate
-## modifier key(s).
-##
-## - push_characters "string" - pushes given string into
-## input queue.
-##
-## - require_runnable "action" - checks whether given action
-## is runnable and fails if it isn't. This is especially
-## useful when mixed with previous two functions. Consider
-## the following macro definition:
-##
-## def_key "key"
-## push_characters "custom_filter"
-## apply_filter
-##
-## If apply_filter can't be currently run, we end up with
-## sequence of characters in input queue which will be
-## treated just as we typed them. This may lead to unexpected
-## results (in this case 'c' will most likely clear current
-## playlist, 'u' will trigger database update, 's' will stop
-## playback etc.). To prevent such thing from happening, we
-## need to change above definition to this one:
-##
-## def_key "key"
-## require_runnable "apply_filter"
-## push_characters "custom_filter"
-## apply_filter
-##
-## Here, first we test whether apply_filter can be actually run
-## before we stuff characters into input queue, so if condition
-## is not met, whole chain is aborted and we're fine.
-##
-## - require_screen "screen" - checks whether given screen is
-## currently active. accepted values: browser, clock, help,
-## media_library, outputs, playlist, playlist_editor,
-## search_engine, tag_editor, visualizer, last_fm, lyrics,
-## selected_items_adder, server_info, song_info,
-## sort_playlist_dialog, tiny_tag_editor.
-##
-## - run_external_command "command" - runs given command using
-## system() function.
-##
-## 5) In addition to binding to a key, you can also bind actions
-## or chains of actions to a command. If it comes to commands,
-## syntax is very similar to defining keys. Here goes example
-## definition of a command:
-##
-## def_command "quit" [deferred]
-## stop
-## quit
-##
-## If you execute the above command (which can be done by
-## invoking action execute_command, typing 'quit' and pressing
-## enter), ncmpcpp will stop the player and then quit. Note the
-## presence of word 'deferred' enclosed in square brackets. It
-## tells ncmpcpp to wait for confirmation (ie. pressing enter)
-## after you typed quit. Instead of 'deferred', 'immediate'
-## could be used. Then ncmpcpp will not wait for confirmation
-## (enter) and will execute the command the moment it sees it.
-##
-## Note: while command chains are executed, internal environment
-## update (which includes current window refresh and mpd status
-## update) is not performed for performance reasons. However, it
-## may be desirable to do so in some situration. Therefore it's
-## possible to invoke by hand by performing 'update enviroment'
-## action.
-##
-## Note: There is a difference between:
-##
-## def_key "key"
-## action1
-##
-## def_key "key"
-## action2
-##
-## and
-##
-## def_key "key"
-## action1
-## action2
-##
-## First one binds two single actions to the same key whilst
-## second one defines a chain of actions. The behavior of
-## these two is different and is described in (1) and (2).
-##
-## Note: Function def_key accepts non-ascii characters.
-##
-##### List of unbound actions #####
-##
-## The following actions are not bound to any key/command:
-##
-## - set_volume
-##
-
-def_key "j"
- scroll_down
-
-def_key "k"
- scroll_up
-
-def_key "J"
- move_sort_order_down
-
-def_key "K"
- move_sort_order_up
-
-def_key ">"
- next
-
-def_key "<"
- previous
-
-def_key "up"
- volume_up
-
-def_key "down"
- volume_down
-
-def_key "backspace"
- stop
-
-def_key "space"
- pause
-
-def_key "left"
- seek_backward
-
-def_key "right"
- seek_forward
-
-def_key "p"
- previous_found_item
-
-def_key "n"
- next_found_item
-
-def_key "a"
- add_item_to_playlist
-
-def_key "d"
- delete_playlist_items
-
-def_key "g"
- move_home
-
-def_key "G"
- move_end
-
-#def_key "shift-up"
-# select_item
-# scroll_up
-#
-#def_key "down"
-# scroll_down
-#
-#def_key "shift-down"
-# select_item
-# scroll_down
-#
-#def_key "["
-# scroll_up_album
-#
-#def_key "]"
-# scroll_down_album
-#
-#def_key "{"
-# scroll_up_artist
-#
-#def_key "}"
-# scroll_down_artist
-#
-#def_key "page_up"
-# page_up
-#
-#def_key "page_down"
-# page_down
-#
-#def_key "home"
-# move_home
-#
-#def_key "end"
-# move_end
-#
-#def_key "insert"
-# select_item
-#
-#def_key "enter"
-# enter_directory
-#
-#def_key "enter"
-# toggle_output
-#
-#def_key "enter"
-# run_action
-#
-#def_key "enter"
-# play_item
-#
-#def_key "space"
-# add_item_to_playlist
-#
-#def_key "space"
-# toggle_lyrics_update_on_song_change
-#
-#def_key "space"
-# toggle_visualization_type
-#
-#def_key "delete"
-# delete_playlist_items
-#
-#def_key "delete"
-# delete_browser_items
-#
-#def_key "delete"
-# delete_stored_playlist
-#
-#def_key "right"
-# next_column
-#
-#def_key "right"
-# slave_screen
-#
-#def_key "right"
-# volume_up
-#
-#def_key "+"
-# volume_up
-#
-#def_key "left"
-# previous_column
-#
-#def_key "left"
-# master_screen
-#
-#def_key "left"
-# volume_down
-#
-#def_key "-"
-# volume_down
-#
-#def_key ":"
-# execute_command
-#
-#def_key "tab"
-# next_screen
-#
-#def_key "shift-tab"
-# previous_screen
-#
-#def_key "f1"
-# show_help
-#
-#def_key "1"
-# show_playlist
-#
-#def_key "2"
-# show_browser
-#
-#def_key "2"
-# change_browse_mode
-#
-#def_key "3"
-# show_search_engine
-#
-#def_key "3"
-# reset_search_engine
-#
-#def_key "4"
-# show_media_library
-#
-#def_key "4"
-# toggle_media_library_columns_mode
-#
-#def_key "5"
-# show_playlist_editor
-#
-#def_key "6"
-# show_tag_editor
-#
-#def_key "7"
-# show_outputs
-#
-#def_key "8"
-# show_visualizer
-#
-#def_key "="
-# show_clock
-#
-#def_key "@"
-# show_server_info
-#
-#def_key "s"
-# stop
-#
-#def_key "p"
-# pause
-#
-#def_key ">"
-# next
-#
-#def_key "<"
-# previous
-#
-#def_key "ctrl-h"
-# jump_to_parent_directory
-#
-#def_key "ctrl-h"
-# replay_song
-#
-#def_key "backspace"
-# jump_to_parent_directory
-#
-#def_key "backspace"
-# replay_song
-#
-#def_key "f"
-# seek_forward
-#
-#def_key "b"
-# seek_backward
-#
-#def_key "r"
-# toggle_repeat
-#
-#def_key "z"
-# toggle_random
-#
-#def_key "y"
-# save_tag_changes
-#
-#def_key "y"
-# start_searching
-#
-#def_key "y"
-# toggle_single
-#
-#def_key "R"
-# toggle_consume
-#
-#def_key "Y"
-# toggle_replay_gain_mode
-#
-#def_key "T"
-# toggle_add_mode
-#
-#def_key "|"
-# toggle_mouse
-#
-#def_key "#"
-# toggle_bitrate_visibility
-#
-#def_key "Z"
-# shuffle
-#
-#def_key "x"
-# toggle_crossfade
-#
-#def_key "X"
-# set_crossfade
-#
-#def_key "u"
-# update_database
-#
-#def_key "ctrl-s"
-# sort_playlist
-#
-#def_key "ctrl-s"
-# toggle_browser_sort_mode
-#
-#def_key "ctrl-s"
-# toggle_media_library_sort_mode
-#
-#def_key "ctrl-r"
-# reverse_playlist
-#
-#def_key "ctrl-f"
-# apply_filter
-#
-#def_key "ctrl-_"
-# select_found_items
-#
-#def_key "/"
-# find
-#
-#def_key "/"
-# find_item_forward
-#
-#def_key "?"
-# find
-#
-#def_key "?"
-# find_item_backward
-#
-#def_key "."
-# next_found_item
-#
-#def_key ","
-# previous_found_item
-#
-#def_key "w"
-# toggle_find_mode
-#
-#def_key "e"
-# edit_song
-#
-#def_key "e"
-# edit_library_tag
-#
-#def_key "e"
-# edit_library_album
-#
-#def_key "e"
-# edit_directory_name
-#
-#def_key "e"
-# edit_playlist_name
-#
-#def_key "e"
-# edit_lyrics
-#
-#def_key "i"
-# show_song_info
-#
-#def_key "I"
-# show_artist_info
-#
-#def_key "g"
-# jump_to_position_in_song
-#
-#def_key "l"
-# show_lyrics
-#
-#def_key "ctrl-v"
-# select_range
-#
-#def_key "v"
-# reverse_selection
-#
-#def_key "V"
-# remove_selection
-#
-#def_key "B"
-# select_album
-#
-#def_key "a"
-# add_selected_items
-#
-#def_key "c"
-# clear_playlist
-#
-#def_key "c"
-# clear_main_playlist
-#
-#def_key "C"
-# crop_playlist
-#
-#def_key "C"
-# crop_main_playlist
-#
-#def_key "m"
-# move_sort_order_up
-#
-#def_key "m"
-# move_selected_items_up
-#
-#def_key "n"
-# move_sort_order_down
-#
-#def_key "n"
-# move_selected_items_down
-#
-#def_key "M"
-# move_selected_items_to
-#
-#def_key "A"
-# add
-#
-#def_key "S"
-# save_playlist
-#
-#def_key "o"
-# jump_to_playing_song
-#
-#def_key "G"
-# jump_to_browser
-#
-#def_key "G"
-# jump_to_playlist_editor
-#
-#def_key "~"
-# jump_to_media_library
-#
-#def_key "E"
-# jump_to_tag_editor
-#
-#def_key "U"
-# toggle_playing_song_centering
-#
-#def_key "P"
-# toggle_display_mode
-#
-#def_key "\\"
-# toggle_interface
-#
-#def_key "!"
-# toggle_separators_between_albums
-#
-#def_key "L"
-# toggle_lyrics_fetcher
-#
-#def_key "F"
-# fetch_lyrics_in_background
-#
-#def_key "alt-l"
-# toggle_fetching_lyrics_in_background
-#
-#def_key "ctrl-l"
-# toggle_screen_lock
-#
-#def_key "`"
-# toggle_library_tag_type
-#
-#def_key "`"
-# refetch_lyrics
-#
-#def_key "`"
-# add_random_items
-#
-#def_key "ctrl-p"
-# set_selected_items_priority
-#
-#def_key "q"
-# quit
-#
diff --git a/packages.hornet b/packages.hornet
index 77a92be..12bcf19 100644
--- a/packages.hornet
+++ b/packages.hornet
@@ -1,4 +1,3 @@
-abook
alacritty
bash
compton
@@ -14,16 +13,13 @@ less
local
mbsync
mbsyncloop
-mpd
msmtp
mutt
-ncmpcpp
nethack
notmuch
readline
redshift
reply
-screen
sh
ssh
starship
diff --git a/packages.mail b/packages.mail
index e4cae92..9edbc1a 100644
--- a/packages.mail
+++ b/packages.mail
@@ -1,4 +1,3 @@
-abook
bash
crawl
fortune-mod
@@ -11,7 +10,6 @@ mutt
nethack
readline
reply
-screen
sh
ssh
starship
diff --git a/packages.mz-doy1 b/packages.mz-doy1
index 4f276e0..5bd0864 100644
--- a/packages.mz-doy1
+++ b/packages.mz-doy1
@@ -13,7 +13,6 @@ local
readline
redshift
reply
-screen
sh
starship
tig
diff --git a/packages.partofme b/packages.partofme
index e4cae92..9edbc1a 100644
--- a/packages.partofme
+++ b/packages.partofme
@@ -1,4 +1,3 @@
-abook
bash
crawl
fortune-mod
@@ -11,7 +10,6 @@ mutt
nethack
readline
reply
-screen
sh
ssh
starship
diff --git a/packages.root b/packages.root
index d049884..9b528c5 100644
--- a/packages.root
+++ b/packages.root
@@ -7,7 +7,6 @@ less
local
readline
reply
-screen
sh
ssh
starship
diff --git a/packages.tozt b/packages.tozt
index e4cae92..9edbc1a 100644
--- a/packages.tozt
+++ b/packages.tozt
@@ -1,4 +1,3 @@
-abook
bash
crawl
fortune-mod
@@ -11,7 +10,6 @@ mutt
nethack
readline
reply
-screen
sh
ssh
starship
diff --git a/screen/.screenrc b/screen/.screenrc
deleted file mode 100644
index 414b4c9..0000000
--- a/screen/.screenrc
+++ /dev/null
@@ -1,28 +0,0 @@
-# .screenrc, based on Eidolos' config at http://sartak.org/conf/
-startup_message off
-nethack on
-
-# bells in background windows make bells in the foreground window
-bell_msg "^G"
-
-# i turn visual bell on in terminal settings, don't do it here
-vbell off
-
-# avoid logging in a new user for every term (thanks toft)
-deflogin off
-
-# lines of scrollback!
-defscrollback 4096
-
-# quick esc timeout
-maptimeout 50
-
-# use the alternate screen feature (for full-screen curses apps, etc)
-altscreen on
-
-# some keybindings
-bind ^a windowlist -b
-bind ^g screen //group
-
-# i want utf8 in screen too
-defutf8 on