Example #1
0
def arg_type_git_remote_or_local(string):
    # See: https://stackoverflow.com/questions/9610131/how-to-check-the-validity-of-a-remote-git-repository-url
    try:
        launch(["git", "ls-remote", string],
               epfx="Cannot handle '%s' as a Git repository" % string)
    except:
        raise ArgumentTypeError("\n" + format_exc())

    return string
Example #2
0
def arg_type_git_ref_name_internal(ref, string):
    full_name = "refs/%ss/%s" % (ref, string)

    try:
        launch(["git", "check-ref-format", full_name],
               epfx="Incorrect %s name '%s' (%s)" % (ref, string, full_name))
    except:
        raise ArgumentTypeError("\n" + format_exc())

    return full_name
Example #3
0
    def git2(self, *cmd_args):
        cwd = getcwd()

        if cwd != self.path:
            chdir(self.path)

        self._stdout, self._stderr = launch((self._ctx.git_command, ) +
                                            cmd_args)
Example #4
0
    def __init__(self,
                 git_command="git",
                 cache_path=None,
                 from_cache=False,
                 **kw):
        super(GitContext, self).__init__(git_command=git_command,
                                         cache_path=cache_path,
                                         from_cache=from_cache,
                                         **kw)

        self._sha2commit = {}
        self._origin2cloned = {}

        # get version of git
        _stdout, _stderr = launch([self.git_command, "--version"],
                                  epfx="Cannot get version of git")

        _, _, version = _stdout.split(b" ")[:3]
        self._git_version = tuple(int(v) for v in version.split(b".")[:3])

        # walk cache_path and fill _cahce
        self._cache = cache = {}

        # refer the cache by absolute path
        cwd = getcwd()
        cache_path = self.cache_path
        if not cache_path.startswith(cwd):
            cache_path = join(cwd, cache_path)
            self.cache_path = cache_path

        backup_dir = join(cache_path, "backup")

        if cache_path:
            for root, _, files in walk(cache_path):
                if root.startswith(backup_dir):
                    continue

                for f in files:
                    # First 40 characters of the a file name must be the SHA1
                    # of the corresponding commit.
                    if not cache_file_re.match(f):
                        continue

                    key = b(f[:40].lower())
                    cached_file = join(root, f)

                    if key in cache:
                        print("Multiple entries for %s found in the "
                              "cache:\n    '%s'\n    '%s'" %
                              (u(key), cached_file, cache[key]))
                        continue

                    cache[key] = cached_file

        print("cache = " + str(self._cache).replace(",", ",\n   ").replace(
            "{", "{\n    ").replace("}", "\n}"))
Example #5
0
 def run(self):
     env = common.load_env(self.log)
     if env['game_dir'] is None:
         return
     ca_bundle_path = common.locate_ca_bundle(env['script_dir'])
     if ca_bundle_path is None:
         common.get_node(
             {
                 'src': 'https://curl.haxx.se/ca/cacert.pem',
                 'dest': 'cacert.pem'
             }, False, False, env['script_dir'], self.log)
         self.log.log_ts('CA Bundle updated', info=True)
     addons = common.list_addons(env, self.log)
     update_context = {'launch': True, 'error': False}
     common.update_addons(env, addons, self.log, False, update_context)
     if update_context['error']:
         pass
     elif update_context['launch']:
         common.launch(env['game_dir'], env['game_args'])
Example #6
0
    def launch(self, *cmd_args):
        cwd = getcwd()

        if cwd != self.path:
            chdir(self.path)

        out, err = launch(cmd_args)
        if out:
            self._out(out)
        if err:
            self._err(err)
Example #7
0
def arg_type_git(string):
    try:
        _stdout, _ = launch([string, "--version"],
                            epfx="Launch of '%s --version' failed" % string)
    except:
        raise ArgumentTypeError("\n" + format_exc())

    try:
        words = _stdout.split(b" ")
        if len(words) < 3:
            raise ValueError("Too few words %d, expected at least 3." %
                             len(words))
    except:
        raise ArgumentTypeError(
            "Cannot tokenize output\n%s\nunderlying error:\n%s" %
            (_stdout, format_exc()))

    if [b"git", b"version"] != words[0:2]:
        raise ArgumentTypeError(
            "Unexpected version string '%s', expected 'git version ...'" % (
                b" ".join(words[0:3])  # add third word
            ))

    return string
Example #8
0
def main():
    print("Git Interactive Cloner")

    init_cwd = getcwd()

    ap = ArgumentParser()
    ap.add_argument("source", type=arg_type_git_repository, nargs="?")
    ap.add_argument("-d", "--destination", type=arg_type_new_directory)
    ap.add_argument("-r", "--result-state", type=arg_type_output_file)
    ap.add_argument("-m",
                    "--main-stream",
                    type=arg_type_SHA1_lower,
                    metavar="SHA1",
                    help="""\
Set main stream by SHA1 of one of main stream commits. Only main stream commits
will be cloned. Other commits will be taken as is. A commit belongs to main
stream if it is a descendant of the given commit or both have at least one
common ancestor. Commonly, SHA1 corresponds to main stream initial commit.""")
    ap.add_argument("-b",
                    "--break",
                    type=arg_type_SHA1_lower,
                    action='append',
                    dest="breaks",
                    metavar="SHA1",
                    help="""\
Specify break points. A break point is set on the commit identified by SHA1. \
The process will be interrupted after the commit allowing a user to change it. \
The tool will recover original committer name, e-mail and date during the next \
launch.""")
    ap.add_argument("-s",
                    "--skip",
                    type=arg_type_SHA1_lower,
                    action='append',
                    dest="skips",
                    metavar="SHA1",
                    help="""\
Specify a commit to skip. Use multiple options to skip several commits. If a \
commit at break point is skipped then interruption will be made after \
previous non-skipped commit in the branch except for no commits are copied yet \
since either trunk or root.""")
    ap.add_argument("-H",
                    "--head",
                    type=arg_type_git_head_name,
                    action='append',
                    dest="refs",
                    metavar="name_of_head",
                    help="""\
Copy commits those are ancestors of selected heads only (including the
heads).""")
    ap.add_argument("-t",
                    "--tag",
                    type=arg_type_git_tag_name,
                    action='append',
                    dest="refs",
                    metavar="name_of_tag",
                    help="""\
Copy commits those are ancestors of selected tags only (including the tag).""")
    ap.add_argument("-i",
                    "--insert-before",
                    type=composite_type(arg_type_SHA1_lower,
                                        arg_type_input_file),
                    action='append',
                    nargs=2,
                    dest="insertions",
                    metavar=("SHA1", "COMMIT"),
                    help="""\
Insert COMMIT before the commit with SHA1. COMMIT must be defined by a path
of the patch file in 'git am' compatible format.""")
    ap.add_argument("-g",
                    "--git",
                    type=arg_type_git,
                    default="git",
                    metavar="path/to/alternative/git",
                    help="""Use explicit git executable.""")
    ap.add_argument("-l",
                    "--log",
                    type=arg_type_output_file,
                    default=LOG_STANDARD,
                    metavar="path/to/log.csv",
                    help="Log git`s standard output and errors to that file.")
    ap.add_argument(
        "-c",
        "--cache",
        type=arg_type_directory,
        metavar="path/to/cache",
        dest="cache_path",
        help="""Resolve conflicts or edit break point commits using
patches from the cache. A patch file name must start with SHA1 of corresponding
original commit."""

        # TODO: User modifications will be also preserved in the cache.
    )
    ap.add_argument(
        "--from-cache",
        action="store_true",
        help="""If a patch is found in the cache then the process will not
be interrupted on either a conflicts or a break point. All changes is taken
from that patch.""")

    args = ap.parse_args()

    ctx = None
    if isfile(STATE_FILE_NAME):
        try:
            ctx = load_context(STATE_FILE_NAME)
        except:
            print("Incorrect state file")
            print_exc(file=sys.stdout)

    cloned_source = None

    if ctx is None:
        git_cmd = args.git
        source = args.source

        if source is None:
            print("No source repository path was given.")
            ap.print_help(sys.stdout)
            return

        try:
            local = arg_type_git_local(source)
        except ArgumentTypeError:
            remote = source
            # Source points to a remote repository. It must be cloned first
            # because git.Repo cannot work with a remote repository.
            cloned_source = join(init_cwd, ".gic-cloned-source")
            try:
                cloned_source = arg_type_new_directory(cloned_source)
            except ArgumentTypeError:
                raise RuntimeError(
                    "Cannot clone source repository into local "
                    "temporal directory '%s', underlying error:\n%s" %
                    (cloned_source, format_exc()))

            print("Cloning source repository into local temporal directory "
                  "'%s'" % cloned_source)

            # delete existing copy
            if isdir(cloned_source):
                rmtree(cloned_source)

            try:
                launch([git_cmd, "clone", remote, cloned_source],
                       epfx="Cloning has failed")
            except:
                sys.stderr.write("\n" + format_exc())
                rmtree(cloned_source)
                exit(1)

            # create all branches in temporal copy
            tmp_repo = Repo(cloned_source)

            chdir(cloned_source)

            for ref in list(tmp_repo.references):
                if not ref.path.startswith("refs/remotes/origin/"):
                    continue

                # cut out prefix "origin/"
                branch = ref.name[7:]

                if branch == "HEAD" or branch == "master":
                    continue

                try:
                    launch(
                        [git_cmd, "branch", branch, ref.name],
                        epfx="Cannot create tracking branch '%s' in temporal"
                        " copy of origin repository" % branch)
                except:
                    chdir(init_cwd)
                    rmtree(cloned_source)
                    sys.stderr.write("\n" + format_exc())
                    exit(1)

            chdir(init_cwd)

            srcRepoPath = cloned_source
        else:
            # Source points to a local repository.
            srcRepoPath = local

        log = args.log

        # overwrite log
        if log is not LOG_STANDARD and isfile(log):
            unlink(log)

        ctx = GitContext(src_repo_path=srcRepoPath,
                         git_command=git_cmd,
                         cache_path=args.cache_path,
                         from_cache=args.from_cache,
                         log=log)

        switch_context(ctx)
    else:
        srcRepoPath = ctx.src_repo_path

    print("Building graph of repository: " + srcRepoPath)

    repo = Repo(srcRepoPath)
    sha2commit = ctx._sha2commit
    GICCommitDesc.build_git_graph(repo,
                                  sha2commit,
                                  skip_remotes=True,
                                  skip_stashes=True,
                                  refs=args.refs)

    print("Total commits: %d" % len(sha2commit))

    if ctx.current_action < 0:
        destination = args.destination
        if destination is None:
            print("No destination specified. Dry run.")
            return

        dstRepoPath = destination

        ms = args.main_stream
        if ms:
            ms_bits = sha2commit[ms].roots
        else:
            ms_bits = 0

        print("The repository will be cloned to: " + dstRepoPath)

        # Planing
        plan(repo,
             sha2commit,
             dstRepoPath,
             breaks=args.breaks,
             skips=args.skips,
             main_stream_bits=ms_bits,
             insertions=args.insertions)

        # remove temporal clone of the source repository
        if cloned_source:
            RemoveDirectory(path=cloned_source)
    else:
        print("The context was loaded. Continuing...")

        ctx.restore_cloned()

    ctx.do()

    # save results
    if getcwd() != init_cwd:
        chdir(init_cwd)

    if ctx.finished:
        if isfile(STATE_FILE_NAME):
            unlink(STATE_FILE_NAME)
    else:
        pythonize(ctx, STATE_FILE_NAME + ".tmp")

        if isfile(STATE_FILE_NAME):
            unlink(STATE_FILE_NAME)
        rename(STATE_FILE_NAME + ".tmp", STATE_FILE_NAME)

    rs = args.result_state
    if rs:
        pythonize(ctx, rs)
Example #9
0
    def __call__(self):
        ctx = self._ctx
        cache = ctx._cache
        sha = self.commit_sha

        if sha not in cache:
            return

        patch_file_name = cache[sha]

        print("Applying changes from " + patch_file_name)

        # analyze the patch and prepare working directory to patching
        p = open(patch_file_name, "rb")
        liter = iter(list(p.readlines()))
        p.close()

        msg = b""
        for l in liter:
            minfo = re_commit_message.match(l)
            if minfo:
                msg += minfo.group(3)
                break
        else:
            raise ValueError("Incorrect patch file: no commit message.")

        msg_lines = []
        for l in liter:
            if l.startswith(b"diff --git ") or l.startswith(b"---"):
                msg += b"".join(msg_lines[:-1])
                break
            msg_lines.append(l)
        else:
            raise ValueError(
                "Incorrect patch file: commit message terminator is not found."
            )

        changed_files = set()
        created_files = set()
        deleted_files = set()

        for l in liter:
            if l.startswith(b"--- a/"):
                file_name = l[6:].strip(b"\n\r")
                l = next(liter)
                if l.startswith(b"--- /dev/null"):
                    deleted_files.add(file_name)
                else:
                    changed_files.add(file_name)
            elif l.startswith(b"--- /dev/null"):
                l = next(liter)
                created_files.add(l[6:].strip(b"\n\r"))

        # for  n in ["changed_files", "created_files", "deleted_files"]:
        #    print(n + " = " + str(locals()[n])
        #        .replace("set([", "set([\n    ")
        #        .replace("'])", "'\n])")
        #        .replace("', '", "',\n    '")
        #    )

        s2c = ctx._sha2commit

        c = s2c[sha]

        if changed_files or deleted_files:
            p = c.parents[0]
            p_cloned_sha = p.cloned_sha

            for cf in chain(changed_files, deleted_files):
                self.git("checkout", p_cloned_sha, cf)

        for f in created_files:
            if isfile(f):
                self.launch("rm", f)

        # actual patching
        try:
            out, err = launch(["patch", "-p", "1", "-i", patch_file_name],
                              epfx="Failed to apply changes from '%s'" %
                              patch_file_name)
        except LaunchFailed as e:
            out, err = e._stdout, e._stderr
            Interrupt(reason=str(e))

        if out:
            self._out(out)
        if err:
            self._err(err)

        # apply commit message
        merge_msg_path = join(self.path, ".git", "MERGE_MSG")
        if isfile(merge_msg_path):
            # a merge is in progress
            msg_f = open(merge_msg_path, "wb")
            msg_f.write(msg)
            msg_f.close()
        else:
            self.git("commit", "--only", "--amend", "-m", msg)

        self._changed_files = changed_files
        self._deleted_files = deleted_files
        self._created_files = created_files