diff options
Diffstat (limited to 'git-p4.py')
| -rwxr-xr-x | git-p4.py | 169 | 
1 files changed, 133 insertions, 36 deletions
| @@ -43,6 +43,9 @@ verbose = False  # Only labels/tags matching this will be imported/exported  defaultLabelRegexp = r'[a-zA-Z0-9_\-.]+$' +# Grab changes in blocks of this many revisions, unless otherwise requested +defaultBlockSize = 512 +  def p4_build_cmd(cmd):      """Build a suitable p4 command line. @@ -131,13 +134,11 @@ def read_pipe(c, ignore_error=False):          sys.stderr.write('Reading pipe: %s\n' % str(c))      expand = isinstance(c,basestring) -    p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand) -    pipe = p.stdout -    val = pipe.read() -    if p.wait() and not ignore_error: -        die('Command failed: %s' % str(c)) - -    return val +    p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=expand) +    (out, err) = p.communicate() +    if p.returncode != 0 and not ignore_error: +        die('Command failed: %s\nError: %s' % (str(c), err)) +    return out  def p4_read_pipe(c, ignore_error=False):      real_cmd = p4_build_cmd(c) @@ -249,6 +250,10 @@ def p4_reopen(type, f):  def p4_move(src, dest):      p4_system(["move", "-k", wildcard_encode(src), wildcard_encode(dest)]) +def p4_last_change(): +    results = p4CmdList(["changes", "-m", "1"]) +    return int(results[0]['change']) +  def p4_describe(change):      """Make sure it returns a valid result by checking for         the presence of field "time".  Return a dict of the @@ -368,7 +373,7 @@ def getP4OpenedType(file):      # Returns the perforce file type for the given file.      result = p4_read_pipe(["opened", wildcard_encode(file)]) -    match = re.match(".*\((.+)\)\r?$", result) +    match = re.match(".*\((.+)\)( \*exclusive\*)?\r?$", result)      if match:          return match.group(1)      else: @@ -502,12 +507,14 @@ def p4Cmd(cmd):  def p4Where(depotPath):      if not depotPath.endswith("/"):          depotPath += "/" -    depotPath = depotPath + "..." -    outputList = p4CmdList(["where", depotPath]) +    depotPathLong = depotPath + "..." +    outputList = p4CmdList(["where", depotPathLong])      output = None      for entry in outputList:          if "depotFile" in entry: -            if entry["depotFile"] == depotPath: +            # Search for the base client side depot path, as long as it starts with the branch's P4 path. +            # The base path always ends with "/...". +            if entry["depotFile"].find(depotPath) == 0 and entry["depotFile"][-4:] == "/...":                  output = entry                  break          elif "data" in entry: @@ -740,17 +747,77 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent  def originP4BranchesExist():          return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master") -def p4ChangesForPaths(depotPaths, changeRange): + +def p4ParseNumericChangeRange(parts): +    changeStart = int(parts[0][1:]) +    if parts[1] == '#head': +        changeEnd = p4_last_change() +    else: +        changeEnd = int(parts[1]) + +    return (changeStart, changeEnd) + +def chooseBlockSize(blockSize): +    if blockSize: +        return blockSize +    else: +        return defaultBlockSize + +def p4ChangesForPaths(depotPaths, changeRange, requestedBlockSize):      assert depotPaths -    cmd = ['changes'] -    for p in depotPaths: -        cmd += ["%s...%s" % (p, changeRange)] -    output = p4_read_pipe_lines(cmd) +    # Parse the change range into start and end. Try to find integer +    # revision ranges as these can be broken up into blocks to avoid +    # hitting server-side limits (maxrows, maxscanresults). But if +    # that doesn't work, fall back to using the raw revision specifier +    # strings, without using block mode. + +    if changeRange is None or changeRange == '': +        changeStart = 1 +        changeEnd = p4_last_change() +        block_size = chooseBlockSize(requestedBlockSize) +    else: +        parts = changeRange.split(',') +        assert len(parts) == 2 +        try: +            (changeStart, changeEnd) = p4ParseNumericChangeRange(parts) +            block_size = chooseBlockSize(requestedBlockSize) +        except: +            changeStart = parts[0][1:] +            changeEnd = parts[1] +            if requestedBlockSize: +                die("cannot use --changes-block-size with non-numeric revisions") +            block_size = None + +    # Accumulate change numbers in a dictionary to avoid duplicates      changes = {} -    for line in output: -        changeNum = int(line.split(" ")[1]) -        changes[changeNum] = True + +    for p in depotPaths: +        # Retrieve changes a block at a time, to prevent running +        # into a MaxResults/MaxScanRows error from the server. + +        while True: +            cmd = ['changes'] + +            if block_size: +                end = min(changeEnd, changeStart + block_size) +                revisionRange = "%d,%d" % (changeStart, end) +            else: +                revisionRange = "%s,%s" % (changeStart, changeEnd) + +            cmd += ["%s...@%s" % (p, revisionRange)] + +            for line in p4_read_pipe_lines(cmd): +                changeNum = int(line.split(" ")[1]) +                changes[changeNum] = True + +            if not block_size: +                break + +            if end >= changeEnd: +                break + +            changeStart = end + 1      changelist = changes.keys()      changelist.sort() @@ -1220,7 +1287,7 @@ class P4Submit(Command, P4UserMap):              editor = os.environ.get("P4EDITOR")          else:              editor = read_pipe("git var GIT_EDITOR").strip() -        system([editor, template_file]) +        system(["sh", "-c", ('%s "$@"' % editor), editor, template_file])          # If the file was not saved, prompt to see if this patch should          # be skipped.  But skip this verification step if configured so. @@ -1627,7 +1694,10 @@ class P4Submit(Command, P4UserMap):          if self.useClientSpec:              self.clientSpecDirs = getClientSpec() -        if self.useClientSpec: +        # Check for the existance of P4 branches +        branchesDetected = (len(p4BranchesInGit().keys()) > 1) + +        if self.useClientSpec and not branchesDetected:              # all files are relative to the client spec              self.clientPath = getClientRoot()          else: @@ -1878,10 +1948,14 @@ class View(object):              if "unmap" in res:                  # it will list all of them, but only one not unmap-ped                  continue +            if gitConfigBool("core.ignorecase"): +                res['depotFile'] = res['depotFile'].lower()              self.client_spec_path_cache[res['depotFile']] = self.convert_client_path(res["clientFile"])          # not found files or unmap files set to ""          for depotFile in fileArgs: +            if gitConfigBool("core.ignorecase"): +                depotFile = depotFile.lower()              if depotFile not in self.client_spec_path_cache:                  self.client_spec_path_cache[depotFile] = "" @@ -1890,6 +1964,9 @@ class View(object):             depot file should live.  Returns "" if the file should             not be mapped in the client.""" +        if gitConfigBool("core.ignorecase"): +            depot_path = depot_path.lower() +          if depot_path in self.client_spec_path_cache:              return self.client_spec_path_cache[depot_path] @@ -1911,7 +1988,10 @@ class P4Sync(Command, P4UserMap):                  optparse.make_option("--import-labels", dest="importLabels", action="store_true"),                  optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",                                       help="Import into refs/heads/ , not refs/remotes"), -                optparse.make_option("--max-changes", dest="maxChanges"), +                optparse.make_option("--max-changes", dest="maxChanges", +                                     help="Maximum number of changes to import"), +                optparse.make_option("--changes-block-size", dest="changes_block_size", type="int", +                                     help="Internal block size to use when iteratively calling p4 changes"),                  optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',                                       help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),                  optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true', @@ -1940,6 +2020,7 @@ class P4Sync(Command, P4UserMap):          self.syncWithOrigin = True          self.importIntoRemotes = True          self.maxChanges = "" +        self.changes_block_size = None          self.keepRepoPath = False          self.depotPaths = None          self.p4BranchesInGit = [] @@ -2110,10 +2191,17 @@ class P4Sync(Command, P4UserMap):              # them back too.  This is not needed to the cygwin windows version,              # just the native "NT" type.              # -            text = p4_read_pipe(['print', '-q', '-o', '-', file['depotFile']]) -            if p4_version_string().find("/NT") >= 0: -                text = text.replace("\r\n", "\n") -            contents = [ text ] +            try: +                text = p4_read_pipe(['print', '-q', '-o', '-', '%s@%s' % (file['depotFile'], file['change'])]) +            except Exception as e: +                if 'Translation of file content failed' in str(e): +                    type_base = 'binary' +                else: +                    raise e +            else: +                if p4_version_string().find('/NT') >= 0: +                    text = text.replace('\r\n', '\n') +                contents = [ text ]          if type_base == "apple":              # Apple filetype files will be streamed as a concatenation of @@ -2246,8 +2334,11 @@ class P4Sync(Command, P4UserMap):          else:              return "%s <a@b>" % userid -    # Stream a p4 tag      def streamTag(self, gitStream, labelName, labelDetails, commit, epoch): +        """ Stream a p4 tag. +        commit is either a git commit, or a fast-import mark, ":<p4commit>" +        """ +          if verbose:              print "writing tag %s for commit %s" % (labelName, commit)          gitStream.write("tag %s\n" % labelName) @@ -2298,7 +2389,7 @@ class P4Sync(Command, P4UserMap):              self.clientSpecDirs.update_client_spec_path_cache(files)          self.gitStream.write("commit %s\n" % branch) -#        gitStream.write("mark :%s\n" % details["change"]) +        self.gitStream.write("mark :%s\n" % details["change"])          self.committedChanges.add(int(details["change"]))          committer = ""          if author not in self.users: @@ -2417,13 +2508,19 @@ class P4Sync(Command, P4UserMap):              if change.has_key('change'):                  # find the corresponding git commit; take the oldest commit                  changelist = int(change['change']) -                gitCommit = read_pipe(["git", "rev-list", "--max-count=1", -                     "--reverse", ":/\[git-p4:.*change = %d\]" % changelist]) -                if len(gitCommit) == 0: -                    print "could not find git commit for changelist %d" % changelist -                else: -                    gitCommit = gitCommit.strip() +                if changelist in self.committedChanges: +                    gitCommit = ":%d" % changelist       # use a fast-import mark                      commitFound = True +                else: +                    gitCommit = read_pipe(["git", "rev-list", "--max-count=1", +                        "--reverse", ":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True) +                    if len(gitCommit) == 0: +                        print "importing label %s: could not find git commit for changelist %d" % (name, changelist) +                    else: +                        commitFound = True +                        gitCommit = gitCommit.strip() + +                if commitFound:                      # Convert from p4 time format                      try:                          tmwhen = time.strptime(labelDetails['Update'], "%Y/%m/%d %H:%M:%S") @@ -2586,7 +2683,7 @@ class P4Sync(Command, P4UserMap):          branchPrefix = self.depotPaths[0] + branch + "/"          range = "@1,%s" % maxChange          #print "prefix" + branchPrefix -        changes = p4ChangesForPaths([branchPrefix], range) +        changes = p4ChangesForPaths([branchPrefix], range, self.changes_block_size)          if len(changes) <= 0:              return False          firstChange = changes[0] @@ -3002,7 +3099,7 @@ class P4Sync(Command, P4UserMap):                  if self.verbose:                      print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),                                                                self.changeRange) -                changes = p4ChangesForPaths(self.depotPaths, self.changeRange) +                changes = p4ChangesForPaths(self.depotPaths, self.changeRange, self.changes_block_size)                  if len(self.maxChanges) > 0:                      changes = changes[:min(int(self.maxChanges), len(changes))] | 
